Skip to content

Marten#4472: Modular composite configuration smoke + behavior tests across satellite assemblies#4495

Merged
jeremydmiller merged 1 commit into
masterfrom
feat/4472-modular-config-tests
May 19, 2026
Merged

Marten#4472: Modular composite configuration smoke + behavior tests across satellite assemblies#4495
jeremydmiller merged 1 commit into
masterfrom
feat/4472-modular-config-tests

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

Summary

Closes #4472.

Pre-9.0 audit gap: the monolithic services.AddMarten(opts => { ... }) case is well-tested, but the multi-assembly modular case — projection types declared in satellite assemblies (each carrying [assembly: JasperFxAssembly]), each contributing its own IConfigureMarten that references its local projections, composed into a main host via DI — was never explicitly validated against the post-#276 (SG-only dispatch) + post-#4461 (zero-runtime-codegen) contracts.

This PR adds the regression-gate fixture, the supporting Nuke + CI wiring, and a new docs page.

Three new isolated test projects

Project Role
src/ModularConfigTests.SatelliteA/ Order aggregate (Guid Id), OrderPlaced/OrderShipped events, partial OrderProjection : SingleStreamProjection<Order, Guid>, OrdersConfig : IConfigureMarten. [assembly: JasperFxAssembly]. SG analyzer wired locally.
src/ModularConfigTests.SatelliteB/ Daily aggregate (string Id), DailyOpened/DailyClosed events, partial DailyProjection : MultiStreamProjection<Daily, string> with date-keyed identity, ReportingConfig : IAsyncConfigureMarten. Same JasperFxAssembly + SG analyzer wiring.
src/ModularConfigTests/ Test harness — headline smoke test plus four pin tests, one per locked-in design contract.

The satellites are deliberately isolated from CoreTests / DaemonTests / EventSourcingTests. Each test fixture builds a fresh host with a unique per-test schema name.

Locked-in design contracts (pinned by the four tests)

  1. OrderingTests — registration-order = invocation-order; last-registered IConfigureMarten wins on scalar setter contention.
  2. LastWinsTests — duplicate event-type registration is idempotent (no throw); last-registered scalar setter wins.
  3. AddMartenTimingTestsIConfigureMarten registered AFTER services.AddMarten(...) still applies. The StoreOptions factory resolves IEnumerable<IConfigureMarten> at store-build time from the final DI snapshot.
  4. AsyncComposeTestsIConfigureMarten (sync) + IAsyncConfigureMarten (async) compose; both contributions land on the final StoreOptions.

Critical finding surfaced during the work — filed upstream as #4494

A bare services.AddSingleton<IAsyncConfigureMarten, MyAsyncConfig>() registration adds the implementation to DI but never invokes its Configure() method. The hosted service that drains async configs (AsyncConfigureMartenApplication) is only registered as a side effect of services.ConfigureMartenWithServices<T>(). The chip's smoke-test snippet used the bare AddSingleton; the fixture uses the correct extension API and the docs page calls the asymmetry out explicitly. UX gap tracked at #4494.

Wired into the build / CI

  • src/Marten.slnx registers the three new projects under /Testing/.
  • build/build.cs gains a TestModularConfig Nuke target, added to the Test aggregate's .DependsOn(...) chain after TestEventSourcing.
  • All four main CI workflows get a test-modular-config step:
    • on-push-do-ci-build-pg15-jsonnet-eventsourcing.yml (after test-event-sourcing)
    • on-push-do-ci-build-pgLatest-systemtextjson-eventsourcing.yml (after test-event-sourcing)
    • on-push-do-ci-build-pg15-jsonnet.yml (after test-document-db)
    • on-push-do-ci-build-pgLatest-systemtextjson.yml (after test-document-db)
  • .nuke/build.schema.json regenerated to include the new target. Also drops the stale TestCodeGen entry that was orphaned from build.cs — auto-regenerated cleanup unrelated to this PR's scope but cheaper to include than to leave drifted.

Docs

docs/configuration/composite-configuration.md (new) covers:

Page added to the VitePress sidebar under Configuration.

Test plan

  • ./build.sh test-modular-config succeeds locally
  • All 5 tests pass on net9.0 and net10.0 (1 smoke + 4 pin tests)
  • dotnet build src/Marten.slnx -c Release clean
  • markdownlint clean on the new docs page

Out of scope

  • NuGet-package-distribution scenario (satellite as .nupkg consumed by downstream) — explicit follow-up if the smoke test surfaces concerns.
  • TypeLoadMode.Static variant of the smoke test — the chip's smoke test uses TypeLoadMode.Auto; static-mode could be a follow-up.
  • Refactoring IConfigureMarten / IAsyncConfigureMarten themselves.

🤖 Generated with Claude Code

Pre-9.0 audit gap: monolithic services.AddMarten(opts => { ... }) is
well-tested, but the multi-assembly modular case — projection types
declared in satellite assemblies (each carrying [assembly: JasperFxAssembly]),
each contributing its own IConfigureMarten that references its local
projections, composed into a main host via DI — was never explicitly
validated against the post-#276 (SG-only dispatch) + post-#4461
(zero-runtime-codegen) contracts. This PR is the regression gate.

Adds three new top-level test projects (deliberately isolated from
CoreTests / DaemonTests / EventSourcingTests):

- src/ModularConfigTests.SatelliteA/
    Order aggregate (Guid Id), OrderPlaced / OrderShipped events,
    partial OrderProjection : SingleStreamProjection<Order, Guid>,
    OrdersConfig : IConfigureMarten. [assembly: JasperFxAssembly].
    JasperFx.Events.SourceGenerator wired as analyzer-only
    PackageReference so [GeneratedEvolver] emits at compile time.

- src/ModularConfigTests.SatelliteB/
    Daily aggregate (string Id), DailyOpened / DailyClosed events,
    partial DailyProjection : MultiStreamProjection<Daily, string>
    with date-keyed identity, ReportingConfig : IAsyncConfigureMarten.
    Same JasperFxAssembly + SG analyzer wiring as SatelliteA.

- src/ModularConfigTests/
    SmokeTest: end-to-end host composition exercising both
    satellites' projections through inline dispatch. Pins:
      1. OrderingTests — registration-order = invocation-order;
         last-registered IConfigureMarten wins on scalar setter.
      2. LastWinsTests — duplicate event-type registration is
         idempotent; last-registered scalar setter wins.
      3. AddMartenTimingTests — IConfigureMarten registered AFTER
         services.AddMarten still applies (order-independent).
      4. AsyncComposeTests — IConfigureMarten + IAsyncConfigureMarten
         compose; both contributions land on final StoreOptions.

Critical finding surfaced during the work: a bare
services.AddSingleton<IAsyncConfigureMarten, T>() registration adds
the implementation to DI but never invokes its Configure() method.
The hosted service that drains async configs
(AsyncConfigureMartenApplication) is only registered by
services.ConfigureMartenWithServices<T>(). The chip's smoke-test
snippet used the bare AddSingleton; the fixture uses the
correct extension and the docs page calls the asymmetry out
explicitly. Filed upstream as a UX gap for follow-up.

Wired:
- src/Marten.slnx registers the three new projects under /Testing/.
- build/build.cs gains a TestModularConfig Nuke target, added to
  the Test aggregate's .DependsOn(...) chain after TestEventSourcing.
- All four main CI workflows
  (pg15-jsonnet[-eventsourcing], pgLatest-systemtextjson[-eventsourcing])
  get a test-modular-config step adjacent to the existing
  test-event-sourcing / test-document-db steps.
- docs/configuration/composite-configuration.md documents the pattern,
  the four locked-in design contracts, the
  [assembly: JasperFxAssembly] marker rationale, the SG-analyzer
  satellite requirement, and the IConfigureMarten vs
  IAsyncConfigureMarten registration asymmetry. Page added to the
  VitePress sidebar.
- .nuke/build.schema.json regenerated to include the new target
  (also drops the stale TestCodeGen entry that was orphaned from
  build.cs — auto-regenerated cleanup).

Verified: smoke + 4 pin tests pass 5/5 on both net9.0 and net10.0.
./build.sh test-modular-config succeeds.

Closes #4472.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit 66c3c33 into master May 19, 2026
6 checks passed
@jeremydmiller jeremydmiller deleted the feat/4472-modular-config-tests branch May 19, 2026 15:11
@jeremydmiller jeremydmiller mentioned this pull request May 19, 2026
38 tasks
jeremydmiller added a commit that referenced this pull request May 19, 2026
Master merged #4495 (ModularConfigTests) after I branched, so the
agent pass didn't see this fixture's `opts.GeneratedCodeMode =
TypeLoadMode.Auto;` line. Build failed on CI for both NET10 + NET9
matrix legs. Removing the now-deleted property reference + the
matching `using JasperFx.CodeGeneration` import.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jeremydmiller added a commit that referenced this pull request May 19, 2026
Master merged #4495 (ModularConfigTests) after I branched, so the
agent pass didn't see this fixture's `opts.GeneratedCodeMode =
TypeLoadMode.Auto;` line. Build failed on CI for both NET10 + NET9
matrix legs. Removing the now-deleted property reference + the
matching `using JasperFx.CodeGeneration` import.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jeremydmiller added a commit that referenced this pull request May 19, 2026
* Eliminate all remaining JasperFx.CodeGeneration usages from Marten

PR #4461 removed the JasperFx.RuntimeCompiler PackageReference and
retired the major codegen surfaces. This is the long-tail cleanup of
the leftover `using JasperFx.CodeGeneration` imports, the now-orphaned
`GenerateCode(...)` interface methods + override bodies, the vestigial
ICodeFileCollection plumbing, the [Obsolete] StoreOptions
codegen-config properties, and the dead MartenSnapshot fingerprint
feature that existed to invalidate codegen artifacts that no longer
exist.

What changed (112 files, +60 / -3387):

- 8 stale `using JasperFx.CodeGeneration` imports dropped (no actual
  type usage post-#4461).
- 3 dead interface methods removed: `IIdGeneration.GenerateCode`,
  `ISelectableColumn.GenerateCode`, plus the
  `UpsertArgument.GenerateCodeTo*` / `GenerateBulkWriterCodeAsync`
  virtuals (+ `MetadataColumn.setMemberFromReader`). Cascade-deleted
  the override bodies across 8 identity strategies, 9 upsert-argument
  derivatives, 13 metadata columns, plus DataColumn / IdColumn /
  Events/Schema/VersionColumn. Each type itself is preserved as a
  data carrier / config marker.
- 5 whole-file deletions: `Internal/CodeGeneration/MartenSnapshot.cs`,
  `MartenSnapshotInputs.cs`, `FrameCollectionExtensions.cs`,
  `StoreOptions.GeneratesCode.cs`, `Events/EventGraph.GeneratesCode.cs`.
  Removed the `MartenSnapshot.VerifyAtBoot` / `PersistFingerprint`
  calls from `DocumentStore.cs` and the two CoreTests covering them.
- ICodeFileCollection / ICodeFile implementations dropped from
  `DocumentStore`, `EventGraph`, and `StoreOptions` (carried implicitly
  through deleting the GeneratesCode partials). Removed 2
  `services.AddSingleton<ICodeFileCollection>(...)` DI registrations
  from `MartenServiceCollectionExtensions.cs`.
- StoreOptions scrubbed: `GeneratedCodeMode`, `SourceCodeWritingEnabled`,
  `GeneratedCodeOutputPath`, `AllowRuntimeCodeGeneration`,
  `SetApplicationProject`, `CreateGenerationRules`, `BuildFiles`,
  `_generatedCodeMode`, `PreferJasperFxMessage`. Test-side cleanup
  deleted dozens of `opts.GeneratedCodeMode = TypeLoadMode.X` lines.
  `ApplicationAssembly` kept — `AutoRegister` and
  `TryUseSourceGeneratedDiscovery` legitimately need it as a scan
  hint.
- `Directory.Packages.props` orphan `JasperFx.RuntimeCompiler`
  PackageVersion line removed. Comments referencing RuntimeCompiler
  scrubbed in `SecondaryStoreProxyFactory.cs`,
  `CompiledQueryHandlerRegistry.cs`,
  `DocumentStore.CompiledQueryCollection.cs`,
  `Internal/ProviderGraph.cs`.

Verification:
- `src/Marten/` is now free of `JasperFx.CodeGeneration` imports —
  `grep -rn "JasperFx\.CodeGeneration" src/Marten/` returns empty.
- Full solution builds clean (0 errors).
- CoreTests 411/412 (1 pre-existing skip), DocumentDbTests 987/988
  (1 pre-existing skip), LinqTests 1257/1258 (1 pre-existing skip).

Doc updates and follow-up issues for the recovered Docker content
(now relevant to Wolverine / JasperFx rather than Marten) ship in
follow-up commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Docs: codegen removal in Marten 9.0 + recover Docker content for sibling repos

The companion to the code cleanup. Codegen-removal-related doc pages
are slimmed to a brief "removed in 9.0, delete your Internal/Generated
folder, codegen write no longer needed for Marten" note; the rest of
the touched files are snippet refreshes from `mdsnippets` propagating
the source-side scrubs.

- docs/devops/devops.md — the application-Dockerfile section dropped
  the `RUN dotnet run -- codegen write` step + added a Marten-9.0 tip
  pointing at the new reality. The recovered Docker content (the
  pre-9.0 multi-stage Dockerfile that ran codegen-write in the build
  stage) is captured in the follow-up issues filed against the
  Wolverine and JasperFx repos for re-publication in their docs.
- docs/configuration/cli.md — the Codegen warning block reframed:
  the `codegen` family is still exposed by the shared JasperFx CLI
  (for tools like Wolverine that ship their own codegen), but
  `dotnet run -- codegen write` is no longer necessary for Marten.
- docs/configuration/aot-publishing.md — corrected the "kept as
  [Obsolete] no-ops" claim to "deleted entirely" for the codegen-
  config properties.
- docs/configuration/optimized_artifact_workflow.md — same correction;
  the `GeneratedCodeMode` line was dropped from the sample
  bootstrapping. Snippet wrappers replaced with inline code blocks
  since the two snippets (`sample_simplest_possible_setup`,
  `sample_using_optimized_artifact_workflow`) no longer have a source
  region.
- docs/migration-guide.md — the Runtime code generation removed
  section says the codegen-config knobs are deleted (not [Obsolete]);
  the Obsolete API sweep section drops the `GeneratedCodeMode`
  guidance.
- docs/schema/index.md — pulled the stale warning about
  `opts.GeneratedCodeMode = TypeLoadMode.Auto` requiring a manual
  `Internal/` delete; not applicable post-9.0.
- All other docs/**.md changes are `mdsnippets` refreshes propagated
  from the source-side scrubs in the previous commit (Startup.cs
  samples, hostbuilder snippets, ValueType / EventSourcing samples,
  etc.).

Verified: `mdsnippets` clean, `markdownlint --disable MD009` clean
across changed files, `cspell --config ./docs/cSpell.json` clean,
`vitepress build docs` exits 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix ModularConfigTests after rebase: drop opts.GeneratedCodeMode

Master merged #4495 (ModularConfigTests) after I branched, so the
agent pass didn't see this fixture's `opts.GeneratedCodeMode =
TypeLoadMode.Auto;` line. Build failed on CI for both NET10 + NET9
matrix legs. Removing the now-deleted property reference + the
matching `using JasperFx.CodeGeneration` import.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

Pre-9.0: Test composite Marten configuration spread across assemblies via IConfigureMarten

1 participant