Skip to content

test(orleans): one Orleans test base, not two — the cluster becomes a DECLARATION - #2963

Merged
rbuergi merged 3 commits into
mainfrom
test/retire-orleans-testbase
Sep 1, 2026
Merged

rbuergi merged 3 commits into
mainfrom
test/retire-orleans-testbase

Conversation

@rbuergi

@rbuergi rbuergi commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Facility 3 of the decentralise-tests programme: eliminate OrleansTestBase and OrleansSharedTestBase.

🧱 Stacks on #2961 (the IMeshBootstrap seam), which is open and green. Its two commits ride along in this diff until it merges; GitHub drops them from the diff automatically once it does. The seam is not duplicated here.

What they actually were

Not two designs — 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 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 OrleansMeshTestBase that 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.

🚨 One thing in the plan does not reproduce

DecentralisedTests.md step 3 says "convert the 23 Orleans subclasses to Bootstrap => MeshBootstrap.Orleans(…)", which reads as make them MonolithMeshTestBase subclasses. They cannot be, for two independent reasons:

  1. A cluster is deployed asynchronously, in InitializeAsync. ConfigureMeshBase — where Bootstrap.Bootstrap(builder) is called — runs in the constructor, before any cluster exists.
  2. 18 of these suites assert on Cluster or SiloServices — 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:

survey measured
suites on the two bases 24 23
cases they carry 47 50
cases in the assembly 192 207

OrleansSharedTestBase reaches 27 cases, not 24; the OrleansTestBase<T> row's twelfth "class" is the non-generic OrleansTestBase declaration itself — a base, not a suite.

Verdicts: MeshWeaver.Hosting.Orleans.Test 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).

The pool now leases on the cluster SHAPE

OrleansMeshPool keyed 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 whole OrleansClusterShape (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.cs carries 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 at main, 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:

  1. this PR — OrleansMeshTestBase lands; core stops using the old names.
  2. MeshWeaver.Plugins PR — its 10 OrleansTestBase<T> suites and AiOrleansSharedTestBase move over.
  3. a two-line PR here — the bridge file goes.

A guard test (NoSuiteInThisRepo_DerivesFromTheRetiredBases) fails if any suite in this repo derives from them again, so the bridge cannot quietly become permanent. InitialSilosCount is kept on the bridge alone because Plugins' OrleansPostgresLifecycleTest overrides 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.

rbuergi and others added 3 commits September 1, 2026 13:09
…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>
Copilot AI lite review requested due to automatic review settings September 1, 2026 11:44
@rbuergi
rbuergi enabled auto-merge September 1, 2026 11:44
@rbuergi
rbuergi disabled auto-merge September 1, 2026 11:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 OrleansClusterShape instead of fixture type alone.
  • Moves/uses the IMeshBootstrap seam 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.

Comment on lines +117 to +123
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.");

Comment on lines 135 to +136
public class MyOrleansTest(SharedOrleansFixture fixture, ITestOutputHelper output)
: OrleansTestBase(output), IAsyncLifetime
: OrleansMeshTestBase(output), IAsyncLifetime
@rbuergi

rbuergi commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

⛔ Auto-merge is deliberately DISARMED until Systemorph/MeshWeaver.Plugins#1091 merges

Building MeshWeaver.Plugins@main against this branch — the check that actually matters, since its CI checks this repo out at main, unpinned — found that the compat bridge is necessary but not sufficient. Two Plugins suites declare private helpers whose names the new base grows:

Plugins file member against this branch
OrleansHarnessPartitionPublicReadTest SiloServices CS0108
OrleansMeshChangeFeedTest ClientMesh CS0108

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 Cluster.SiloServices() and Fixture.ClientMesh verbatim). It is deliberately compatible with both cores — measured Build succeeded. against main (b54fb5f) and against this branch — so it can land first and leave no red window in either repo.

Order: Plugins#1091 → this PR. Re-arm with gh pr merge 2963 --auto once #1091 is merged.

Everything else in MeshWeaver.Plugins@main builds green against this branch

MeshWeaver.AI.Orleans.TestBase, MeshWeaver.AI.Orleans.Test (with #1091), MeshWeaver.Hosting.PostgreSql.Test, MeshWeaver.Hosting.Grpc.Test, MeshWeaver.Markdown.Collaboration.Orleans.Test, MeshWeaver.Markdown.Export.Test — all six, Build succeeded.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

802 tests  +9   798 ✅ +9   3m 24s ⏱️ +17s
  7 suites ±0     4 💤 ±0 
  7 files   ±0     0 ❌ ±0 

Results for commit 7dad9ea. ± Comparison against base commit b54fb5f.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

    8 files  ±0      8 suites  ±0   4m 8s ⏱️ ±0s
1 506 tests +1  1 505 ✅ +1  1 💤 ±0  0 ❌ ±0 
2 216 runs  +1  2 215 ✅ +1  1 💤 ±0  0 ❌ ±0 

Results for commit 7dad9ea. ± Comparison against base commit b54fb5f.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

1 105 tests   - 33   1 105 ✅  - 33   4m 2s ⏱️ +10s
    7 suites ± 0       0 💤 ± 0 
    7 files   ± 0       0 ❌ ± 0 

Results for commit 7dad9ea. ± Comparison against base commit b54fb5f.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

3 084 tests  +7   2 892 ✅ +7   5m 33s ⏱️ - 1m 10s
    8 suites ±0     192 💤 ±0 
    8 files   ±0       0 ❌ ±0 

Results for commit 7dad9ea. ± Comparison against base commit b54fb5f.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

1 169 tests  ±0   1 169 ✅ ±0   4m 35s ⏱️ -16s
    7 suites ±0       0 💤 ±0 
    7 files   ±0       0 ❌ ±0 

Results for commit 7dad9ea. ± Comparison against base commit b54fb5f.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

1 190 tests  +35   1 190 ✅ +35   9m 22s ⏱️ + 1m 12s
    7 suites ± 0       0 💤 ± 0 
    7 files   ± 0       0 ❌ ± 0 

Results for commit 7dad9ea. ± Comparison against base commit b54fb5f.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Test Results

   44 files  ± 0     44 suites  ±0   31m 6s ⏱️ +13s
8 856 tests +19  8 659 ✅ +19  197 💤 ±0  0 ❌ ±0 
9 566 runs  +19  9 369 ✅ +19  197 💤 ±0  0 ❌ ±0 

Results for commit 7dad9ea. ± Comparison against base commit b54fb5f.

@rbuergi
rbuergi added this pull request to the merge queue Sep 1, 2026
@rbuergi
rbuergi removed this pull request from the merge queue due to a manual request Sep 1, 2026
@rbuergi
rbuergi added this pull request to the merge queue Sep 1, 2026
@rbuergi
rbuergi merged commit d36920c into main Sep 1, 2026
26 checks passed
@rbuergi
rbuergi deleted the test/retire-orleans-testbase branch September 13, 2026 10:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants