Repository navigation
test(orleans): one Orleans test base, not two — the cluster becomes a DECLARATION - #2963
Conversation
…o core The measurement behind eliminating MonolithMeshTestBase and OrleansTestBase, plus the one mechanical step that step 1 of the plan asks for. Doc/Architecture/DecentralisedTests: core's 44 test projects and 6,841 case declarations classified per the maintainer's buckets (plugin / substrate / framework primitive / not-a-mesh-thing, plus "harness" which the measurement forced); the four knobs 463 subclasses reduce to (ConfigureMesh 285, ShareMeshAcrossTests 74, ConfigureClient 53, SetupAccessRightsAsync 34 — and 131 that override nothing at all); the container-reach histogram (1,145 sites, eleven types covering 70%); what the two mw-plugin-test lanes can and cannot express today; and the honest floor — 535 cases (7.8%) that cannot run inside a booted mesh by construction, with FIVE missing facilities that make the migratable count zero until they exist. The seam: MeshBootstrap.cs moves from MeshWeaver.Plugins/src (where the fork that received it is never referenced by any real suite — every consumer's Exists() arm resolves to core's copy) into core's TestBase, where 439 of the 463 inheriting classes actually are. MonolithMeshTestBase.ConfigureMeshBase routes through Bootstrap.Bootstrap(builder) behind a protected virtual that defaults to MonolithBootstrap.Instance, which applies exactly the two builder calls it inlined before. No behaviour change; all 22 core consumers untouched. Verified: TestBase builds warnings-clean; MeshBootstrapTest 9/9 green; MeshWeaver.Persistence.Test 1137/1137 green (81 of its classes derive from the changed base); MeshWeaver.Documentation.Test 150/150 green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… DECLARATION
`OrleansTestBase<T>` and `OrleansSharedTestBase` were not two designs. They were the
same machinery written twice: both deployed a `TestCluster` through
`OrleansTestCluster.DeployAsync`, both created a client hub with a seeded
`AccessContext` and a routing-stream registration, both tracked those hubs and joined
their teardown before the silos went down. What actually differed was three VALUES —
which silo configurator, how many silos, and whether the cluster is leased or
dedicated — and values do not need a second base class. Measured: 23 subclasses, 20 of
which overrode nothing at all.
Both are replaced by ONE `OrleansMeshTestBase`, which says which mesh it wants through
the same `IMeshBootstrap` seam `MonolithMeshTestBase` uses, so the declaration reads
identically on both sides:
protected override IMeshBootstrap Bootstrap => MeshBootstrap.Orleans(o => o.WithSilos(2));
protected override Type SiloConfiguratorType => typeof(MySiloConfigurator);
`OrleansBootstrap.Applicator` is armed here, from a `[ModuleInitializer]`. That hook is
the whole reason `MeshWeaver.Hosting.Monolith.TestBase` can define the seam without
ever referencing Orleans — 23 in-process suites would otherwise drag a cluster's worth
of packages to boot a mesh that has no silos.
🚨 What the plan said and what is actually true. "Convert the subclasses to
`Bootstrap => MeshBootstrap.Orleans(…)`" reads as *make them `MonolithMeshTestBase`
subclasses*. They cannot be: a cluster is deployed ASYNCHRONOUSLY, in
`InitializeAsync`, whereas `ConfigureMeshBase` — where `Bootstrap.Bootstrap(builder)`
is called — runs in the CONSTRUCTOR, before any cluster exists; and 18 of these suites
assert on `Cluster` or `SiloServices`, surfaces a base that must not reference Orleans
cannot expose. The seam unifies the DECLARATION, not the boot. DecentralisedTests.md
records this, along with the re-measured counts (23 suites / 50 cases, not 24 / 47; the
assembly executes 207 cases, not 192 — counting `[Fact]` declarations undercounts).
The pool now leases on the whole `OrleansClusterShape` rather than the fixture TYPE.
Keying on the type was sound only while "a different cluster" and "a different fixture
subclass" were the same statement; once a suite can DESCRIBE its cluster they are not,
and a lease across two shapes hands a suite a cluster built by someone else's silo
configurator — a failure that surfaces as a missing registration two files away.
The two old names survive ONLY as a bridge (`OrleansTestBaseCompat.cs`, carrying no
implementation): MeshWeaver.Plugins derives from them in 11 places and its CI checks
this repository out at main, unpinned, so deleting them here would red that trunk
before its own conversion could land. A guard test fails if any suite in THIS repo
derives from them again, so the bridge cannot quietly become permanent.
Verified by running, not by reading: 207 passed / 0 failed before, 215 passed / 0
failed after (207 + 8 new seam cases), 30 s → 26 s.
MeshWeaver.Documentation.Test 150/150 (the disposal-join ratchet and the doc guards).
No What's New entry: pure-internal test infrastructure, no user-visible effect.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR consolidates the Orleans testing infrastructure by replacing OrleansTestBase<T> and OrleansSharedTestBase with a single OrleansMeshTestBase, aligning cluster configuration with the new IMeshBootstrap “declaration” seam, and updating docs/tests accordingly.
Changes:
- Introduces
OrleansMeshTestBase(single Orleans base) and updates Orleans test suites to derive from it. - Extends the Orleans cluster fixture/pool to lease by an explicit
OrleansClusterShapeinstead of fixture type alone. - Moves/uses the
IMeshBootstrapseam for declaring Orleans cluster topology (e.g., silo count) and updates architecture/testing documentation.
Reviewed changes
Copilot reviewed 43 out of 43 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/MeshWeaver.Hosting.Orleans.TestBase/SharedOrleansFixture.cs | Adds shape-driven construction and PoolKey so fixtures can be pooled by cluster shape. |
| test/MeshWeaver.Hosting.Orleans.TestBase/README.md | Updates Orleans testbase documentation to the new single-base model and seam usage. |
| test/MeshWeaver.Hosting.Orleans.TestBase/OrleansTestMeshExtensions.cs | Updates comments to reference OrleansMeshTestBase. |
| test/MeshWeaver.Hosting.Orleans.TestBase/OrleansTestConfigurators.cs | Moves client/silo configurators out of the retired base into a dedicated file. |
| test/MeshWeaver.Hosting.Orleans.TestBase/OrleansTestBaseCompat.cs | Adds temporary compatibility bridge types for cross-repo consumers (Plugins). |
| test/MeshWeaver.Hosting.Orleans.TestBase/OrleansTestBase.cs | Removes the dedicated-cluster Orleans base implementation (superseded). |
| test/MeshWeaver.Hosting.Orleans.TestBase/OrleansSharedTestBase.cs | Removes the pooled/shared Orleans base implementation (superseded). |
| test/MeshWeaver.Hosting.Orleans.TestBase/OrleansMeshTestBase.cs | Adds the new unified Orleans test base and module-initializer applicator registration. |
| test/MeshWeaver.Hosting.Orleans.TestBase/OrleansMeshPool.cs | Changes pool keying from fixture type to full cluster shape. |
| test/MeshWeaver.Hosting.Orleans.TestBase/MeshWeaver.Hosting.Orleans.TestBase.csproj | Updates package description and adds reference to the monolith testbase seam. |
| test/MeshWeaver.Hosting.Orleans.Test/StreamRoutedSilenceGateTest.cs | Migrates suite to OrleansMeshTestBase. |
| test/MeshWeaver.Hosting.Orleans.Test/RoutingQuiescenceClusterTest.cs | Migrates suite to OrleansMeshTestBase and sets SiloConfiguratorType. |
| test/MeshWeaver.Hosting.Orleans.Test/RoutingGrainTurnIsolationTest.cs | Migrates suite to OrleansMeshTestBase and sets SiloConfiguratorType. |
| test/MeshWeaver.Hosting.Orleans.Test/PartitionRootActivationTest.cs | Migrates suite to OrleansMeshTestBase and sets SiloConfiguratorType. |
| test/MeshWeaver.Hosting.Orleans.Test/OrleansUnresolvableNodeNoWedgeTest.cs | Migrates suite to OrleansMeshTestBase. |
| test/MeshWeaver.Hosting.Orleans.Test/OrleansUnresolvableAreaNoWedgeTest.cs | Migrates suite to OrleansMeshTestBase. |
| test/MeshWeaver.Hosting.Orleans.Test/OrleansThreeNodePropagationTest.cs | Migrates suite to OrleansMeshTestBase. |
| test/MeshWeaver.Hosting.Orleans.Test/OrleansSubscribeRequestNotFoundSurfaceTest.cs | Migrates suite to OrleansMeshTestBase. |
| test/MeshWeaver.Hosting.Orleans.Test/OrleansStaticRepoImportTest.cs | Migrates suite to OrleansMeshTestBase and uses SiloServices() helper. |
| test/MeshWeaver.Hosting.Orleans.Test/OrleansMeshTest.cs | Migrates suite to OrleansMeshTestBase. |
| test/MeshWeaver.Hosting.Orleans.Test/OrleansKernelProgressTest.cs | Migrates suite to OrleansMeshTestBase. |
| test/MeshWeaver.Hosting.Orleans.Test/OrleansIdleReactivationNoWedgeTest.cs | Migrates suite to OrleansMeshTestBase. |
| test/MeshWeaver.Hosting.Orleans.Test/OrleansGetDataRequestPropagationTest.cs | Migrates suite to OrleansMeshTestBase. |
| test/MeshWeaver.Hosting.Orleans.Test/OrleansEventSubscriptionTimerTest.cs | Migrates suite to OrleansMeshTestBase. |
| test/MeshWeaver.Hosting.Orleans.Test/OrleansDynamicCompilationTest.cs | Migrates suites to OrleansMeshTestBase and uses MeshBootstrap.Orleans(...WithSilos(2)) for 2-silo suite. |
| test/MeshWeaver.Hosting.Orleans.Test/OrleansContentImportSyncTest.cs | Migrates suite to OrleansMeshTestBase and sets SiloConfiguratorType. |
| test/MeshWeaver.Hosting.Orleans.Test/OrleansCompileActivityAccessTest.cs | Migrates suite to OrleansMeshTestBase and sets SiloConfiguratorType. |
| test/MeshWeaver.Hosting.Orleans.Test/OrleansBrokenNodeTypeAccessTest.cs | Migrates suite to OrleansMeshTestBase and keeps client config override. |
| test/MeshWeaver.Hosting.Orleans.Test/OrleansBootstrapSeamTest.cs | Adds tests pinning the seam/applicator and the pool keying behavior. |
| test/MeshWeaver.Hosting.Orleans.Test/OrleansAssemblyStoreTest.cs | Migrates suite to OrleansMeshTestBase and declares 2-silo bootstrap. |
| test/MeshWeaver.Hosting.Orleans.Test/OrleansApiTokenTest.cs | Migrates suite to OrleansMeshTestBase. |
| test/MeshWeaver.Hosting.Orleans.Test/GrainActivationCompletesFastTest.cs | Migrates suite to OrleansMeshTestBase and sets SiloConfiguratorType. |
| test/MeshWeaver.Hosting.Orleans.Test/DeadSubscriberEvictionTest.cs | Migrates suite to OrleansMeshTestBase. |
| test/MeshWeaver.Hosting.Monolith.TestBase/MonolithMeshTestBase.cs | Routes mesh setup through Bootstrap.Bootstrap(builder) and introduces Bootstrap virtual. |
| test/MeshWeaver.Hosting.Monolith.TestBase/MeshBootstrap.cs | Adds IMeshBootstrap + MeshBootstrap fluent API and Orleans bootstrap description types. |
| test/MeshWeaver.Hosting.Monolith.Test/MeshBootstrapTest.cs | Adds tests pinning the bootstrap fluent API behavior without requiring a cluster. |
| test/MeshWeaver.Documentation.Test/HubDisposalJoinRatchetGuard.cs | Updates guard docs/comments to the new base name. |
| src/MeshWeaver.Documentation/Data/Architecture/WritingTests.md | Updates guidance to reference OrleansMeshTestBase and the new declaration approach. |
| src/MeshWeaver.Documentation/Data/Architecture/TestStateIsolation.md | Updates an example to reference the new base name (but needs further adjustment). |
| src/MeshWeaver.Documentation/Data/Architecture/OrleansTestRoutingPattern.md | Updates routing-pattern doc references from old base to new base. |
| src/MeshWeaver.Documentation/Data/Architecture/DurableStreamsViaMeshNodes.md | Updates doc reference to OrleansMeshTestBase callers. |
| src/MeshWeaver.Documentation/Data/Architecture/DecentralisedTests.md | Adds/updates the decentralisation plan doc reflecting measured counts and the seam approach. |
| .claude/skills/testing/SKILL.md | Updates testing skill guidance to reflect OrleansMeshTestBase as the single Orleans base. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| private OrleansBootstrapOptions BootstrapOptions => Bootstrap is OrleansBootstrap orleans | ||
| ? orleans.Options | ||
| : throw new InvalidOperationException( | ||
| $"{GetType().Name} is an {nameof(OrleansMeshTestBase)}, so its Bootstrap must be a " | ||
| + $"MeshBootstrap.Orleans(...); it is '{Bootstrap.Name}'. A suite that wants an " | ||
| + $"in-process mesh derives from {nameof(MonolithMeshTestBase)} instead."); | ||
|
|
| public class MyOrleansTest(SharedOrleansFixture fixture, ITestOutputHelper output) | ||
| : OrleansTestBase(output), IAsyncLifetime | ||
| : OrleansMeshTestBase(output), IAsyncLifetime |
⛔ Auto-merge is deliberately DISARMED until Systemorph/MeshWeaver.Plugins#1091 mergesBuilding MeshWeaver.Plugins@main against this branch — the check that actually matters, since its CI checks this repo out at
CS0108 is a warning that repo compiles as an error, so merging this first reds that trunk — and that is the run which publishes its module bundles. Systemorph/MeshWeaver.Plugins#1091 removes both helpers (they re-derive Order: Plugins#1091 → this PR. Re-arm with Everything else in MeshWeaver.Plugins@main builds green against this branch
|
Facility 3 of the decentralise-tests programme: eliminate
OrleansTestBaseandOrleansSharedTestBase.What they actually were
Not two designs — the same machinery written twice. Both deployed a
TestClusterthroughOrleansTestCluster.DeployAsync; both created a client hub with a seededAccessContextand a routing-stream registration; both tracked those hubs and joined their teardown before the silos went down. What differed was three values — which silo configurator, how many silos, leased or dedicated — and values do not need a second base class. Measured: 23 subclasses, 20 of which overrode nothing at all.Both are gone, replaced by one
OrleansMeshTestBasethat says which mesh it wants through the sameIMeshBootstrapseamMonolithMeshTestBaseuses, so the declaration reads identically on both sides:OrleansBootstrap.Applicatoris armed here from a[ModuleInitializer]. That hook is the whole reasonMeshWeaver.Hosting.Monolith.TestBasecan define the seam without ever referencing Orleans — 23 in-process suites would otherwise drag a cluster's worth of packages to boot a mesh that has no silos.🚨 One thing in the plan does not reproduce
DecentralisedTests.mdstep 3 says "convert the 23 Orleans subclasses toBootstrap => MeshBootstrap.Orleans(…)", which reads as make themMonolithMeshTestBasesubclasses. They cannot be, for two independent reasons:InitializeAsync.ConfigureMeshBase— whereBootstrap.Bootstrap(builder)is called — runs in the constructor, before any cluster exists.ClusterorSiloServices— surfaces a base that must not reference Orleans cannot expose.So the seam unifies the declaration, not the boot. The doc is updated to say this rather than leaving the next reader to rediscover it.
Numbers, re-measured by running
The survey's figures were counted from
[Fact]declarations. Running the assembly says otherwise, and the doc is corrected:OrleansSharedTestBasereaches 27 cases, not 24; theOrleansTestBase<T>row's twelfth "class" is the non-genericOrleansTestBasedeclaration itself — a base, not a suite.Verdicts:
MeshWeaver.Hosting.Orleans.Test207 passed / 0 failed before → 215 passed / 0 failed after (207 + 8 new seam cases), 30 s → 26 s.MeshWeaver.Documentation.Test150/150 (the disposal-join ratchet and the doc guards).The pool now leases on the cluster SHAPE
OrleansMeshPoolkeyed on the fixture type. That was sound only while "a different cluster" and "a different fixture subclass" were the same statement. Once a suite can describe its cluster they are not, and a lease across two shapes hands a suite a cluster built by someone else's silo configurator — which surfaces as a missing registration two files from the cause. The key is now the wholeOrleansClusterShape(silo configurator, client configurator, silo count, shared store, fixture type), with a test pinning that any difference makes two clusters non-interchangeable.Why the two names still exist
OrleansTestBaseCompat.cscarries no implementation — three declarations forwarding to the new base. It exists because MeshWeaver.Plugins derives from them in 11 places and its CI checks this repository out atmain, unpinned (.github/workflows/ci.yml), so deleting them here reds that trunk the moment this merges, before its own conversion can land. The order with no red window:OrleansMeshTestBaselands; core stops using the old names.OrleansTestBase<T>suites andAiOrleansSharedTestBasemove over.A guard test (
NoSuiteInThisRepo_DerivesFromTheRetiredBases) fails if any suite in this repo derives from them again, so the bridge cannot quietly become permanent.InitialSilosCountis kept on the bridge alone because Plugins'OrleansPostgresLifecycleTestoverrides it.Grep, this repo, excluding the bridge and the guard that names them: zero derivations.
No What's New entry: pure-internal test infrastructure, no user-visible effect.