Skip to content

chore(deps): WolverineFx 6, last 9.x packages, xunit v3 + xUnit1051 adoption - #55

Merged
ChrisonSimtian merged 3 commits into
mainfrom
chore/wolverine-6-and-xunit-v3
Jul 27, 2026
Merged

chore(deps): WolverineFx 6, last 9.x packages, xunit v3 + xUnit1051 adoption#55
ChrisonSimtian merged 3 commits into
mainfrom
chore/wolverine-6-and-xunit-v3

Conversation

@ChrisonSimtian

@ChrisonSimtian ChrisonSimtian commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Finishes the dependency sweep — every remaining major — plus the xunit v3 migration. Two commits, each self-consistent and independently buildable.

1. WolverineFx 6 + the last 9.x packages (880fd06)

  • WolverineFx + WolverineFx.Postgresql 5.18.06.22.0
  • Microsoft.Extensions.ServiceDiscovery 9.3.110.8.0
  • Microsoft.Extensions.Http.Resilience 9.4.010.8.0

Wolverine 6 compiled with zero source changes and the entire unit suite stayed green — while the fleet was completely broken. Two separate breaking changes, both runtime-only:

a) Core no longer ships the runtime Roslyn compiler (GH-2876). Handler code is generated at runtime under TypeLoadMode.Dynamic, so startup died:

Wolverine is running in TypeLoadMode.Dynamic … but no IAssemblyGenerator (Roslyn) is registered.
Core WolverineFx no longer ships the runtime compiler.

Added WolverineFx.RuntimeCompilation to the two hosts that call UseWolverine (ARD + ZDF agents); it self-registers. The alternative — pre-generated code + TypeLoadMode.Static — trades a build step and a regeneration discipline for faster cold start. Reasonable for the container images later, but not something to fold into a dependency bump.

b) ServiceLocationPolicy now defaults to NotAllowed (5.x was AllowedButWarn), so codegen refused to build a handler needing container resolution. CrawlShowHandler needs it twice over: IEnumerable<IBroadcasterCrawler> is an opaque lambda registration, and IEpisodeRepository's graph reaches EF Core's own DbContextOptions factory, which we don't control.

Set to AllowedButWarn — restoring exactly the 5.x behaviour. Deliberately not Allowed, so Wolverine keeps warning and the nudge to make these inline-constructible stays visible rather than being silenced.

Verified by running it, not by compiling it

Because neither failure was visible to the compiler or the test suite:

  • Migrator applied all migrations against Postgres 18.3
  • ZDF agent started → scheduler dispatched CrawlShowCommand over the durable Postgres transport → handler received it → crawled the real ZDF API → Crawled 18 episode(s) for 'heute-show' on zdf
  • 18 rows in Episodes; Wolverine created its 8 message-store tables
  • No errors in the agent log

2. xunit v3 migration (228be5b)

All five test projects: xunit 2.9.3 → xunit.v3 3.2.2. The xunit.runner.visualstudio 3.1.5 and Test.Sdk 18.8.1 from the last PR already support v3, so no runner change was needed.

What the migration forced:

  • IAsyncLifetime now returns ValueTask (was Task) and extends IAsyncDisposable — so PostgresFixture and the three repository fixtures needed new signatures.
  • Test projects are executables in v3 — added <OutputType>Exe</OutputType>.
  • TngTech.ArchUnitNET.xUnit had to go. It still depends on xunit.assert 2.x, which cannot coexist with v3, and 0.13.3 is the latest release. Swapped for core TngTech.ArchUnitNET plus a local ArchRuleAssert.Check() that evaluates the rule and fails through Assert.Fail, listing every violation.

The architecture tests were checked for false-passing, not assumed green

Replacing an assertion mechanism can silently turn 8 passing tests into 8 no-ops, so I inverted one rule — asserting Infrastructure does not depend on Domain, which it plainly does — and confirmed a proper failure:

Architecture rule violated: Types that reside in namespace with full name matching
"Krautwatch\.Infrastructure" should not depend on any types that reside in namespace
with full name matching "Krautwatch\.Domain" because Domain is the core of the hexagon…

Both the rule description and its because reason survive. Reverting restored 8/8. The layer rules still genuinely enforce.

3. Adopt xUnit1051 (c00725c)

xunit v3 ships analyzer rule xUnit1051 — calls accepting a CancellationToken should pass
TestContext.Current.CancellationToken so the runner can cancel tests. It does not exist in v2, so
commit 2 introduced 258 warning instances across 129 call sites. Adopted rather than suppressed:
it earns its place in Live.Tests, where a single test spends 6–16 minutes downloading from a CDN
and previously could not be cancelled at all.

Build is back to 0 warnings / 0 errors.

"Append an argument" turns out to be wrong more often than right — three distinct cases:

Case Fix
Token slot omitted append the token
Slot already filled with default replace it — appending produced 106 CS1501 errors
Other optional params before the token (FindShowAsync(query, client = "ard", ct = default)) named argument, or it lands in the client slot

NSubstitute setups became Arg.Any<CancellationToken>(), matching the convention the verification
calls in this suite already used. Binding a setup to one specific token would silently stop matching
once the handler forwards a different one — and a substitute that stops matching returns null rather
than failing loudly.

Verification calls moved from default to the test's own token, e.g.
Received(1).AddAsync(Arg.Any<DownloadJob>(), TestContext.Current.CancellationToken). That is a
slightly stronger assertion than before: it now proves the token is actually propagated from the
test through the handler to the repository.

Framework calls that could not be classified automatically were done by hand — FluentValidation
ValidateAsync, HttpContent.ReadAsStreamAsync, Stream.WriteAsync, and EF's FindAsync, whose
cancellable overload takes key values as an object[], so the call shape changes rather than just
gaining an argument.

Testing

Result
./build.sh Test 125 tests green, Live correctly excluded, 0 warnings / 0 errors
./build.sh TestLive 16/16 green against real ARD/ZDF — trait filtering works under v3 in both directions
Fleet ZDF agent crawled real ZDF over the durable bus, 18 episodes persisted
Commit 880fd06 alone builds clean (0 warnings) — the split is genuinely bisectable

Notes

  • A live test flaked once and was confirmed transient. Downloads_a_full_Extra3_episode_from_ARD failed with a SocketError after 1m54s mid-download from ARD's CDN; re-runs passed 16/16 twice. Real full download against a live CDN, unrelated to these changes.
  • Live suite wall-clock varies a lot — 6m to 16m across runs today, entirely CDN throughput. Something to bear in mind if it is ever put on a timeout.
  • Commit 2's message was amended to remove an incorrect "0 warnings" claim: I had grepped for warning CS and missed the xUnit1051 analyzer warnings, which CI surfaced. It now states the warnings introduced and points at commit 3.
  • tests/Domain.Tests is in the solution but contains zero test files — pre-existing, and why it never appears in test output. Either give it tests or drop it.
  • Microsoft.Extensions.{ServiceDiscovery,Http.Resilience} needed no source changes despite the 9.x → 10.x jump.

🤖 Generated with Claude Code

ChrisonSimtian and others added 3 commits July 27, 2026 17:37
Completes the version sweep with the runtime majors held back from the previous
PRs:

- WolverineFx + WolverineFx.Postgresql  5.18.0 -> 6.22.0
- Microsoft.Extensions.ServiceDiscovery 9.3.1  -> 10.8.0
- Microsoft.Extensions.Http.Resilience  9.4.0  -> 10.8.0

The Extensions pair were the last packages still on 9.x while everything else had
moved to 10.x.

Wolverine 6 compiled with zero source changes and the whole unit suite stayed
green — but the fleet was completely broken, in two separate ways that only appear
at runtime. Both were found by running an agent against a real Postgres and
watching a crawl actually happen, not by building.

1. Core no longer ships the runtime Roslyn compiler (GH-2876). Handler code is
   generated at runtime under TypeLoadMode.Dynamic, so startup died with
   "no IAssemblyGenerator (Roslyn) is registered". Added the
   WolverineFx.RuntimeCompilation package to the two hosts that call UseWolverine
   (the ARD and ZDF agents); it self-registers. The alternative is pre-generated
   code plus TypeLoadMode.Static, which trades a build step and a regeneration
   discipline for faster cold start — worth considering for the container images
   later, but not something to fold into a dependency bump.

2. ServiceLocationPolicy now defaults to NotAllowed (5.x was AllowedButWarn), so
   codegen refused to build a handler that needs container resolution.
   CrawlShowHandler needs it twice over: IEnumerable<IBroadcasterCrawler> is
   registered through an opaque lambda factory, and IEpisodeRepository's graph
   reaches EF Core's own DbContextOptions factory, which we do not control. Set
   ServiceLocationPolicy.AllowedButWarn to restore exactly the 5.x behaviour —
   deliberately not Allowed, so Wolverine keeps warning and the nudge to make
   these inline-constructible stays visible.

Verified end to end rather than by compilation:
- Migrator applied all migrations against Postgres 18.3.
- The ZDF agent started, the scheduler dispatched CrawlShowCommand over the
  durable Postgres transport, the handler received it, crawled the real ZDF API
  and reported "Crawled 18 episode(s) for 'heute-show' on zdf".
- 18 rows landed in Episodes; Wolverine created its 8 message-store tables.
- No errors in the agent log.
- ./build.sh Test green (Infra 67 / App 50 / Arch 8), 0 warnings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Moves all five test projects from xunit 2.9.3 to xunit.v3 3.2.2. The
xunit.runner.visualstudio 3.1.5 and Microsoft.NET.Test.Sdk 18.8.1 bumped in the
previous PR already support v3, so no runner change was needed.

Source changes the migration forced:

- xunit v3's IAsyncLifetime returns ValueTask (was Task) and extends
  IAsyncDisposable, so PostgresFixture and the three repository fixtures that
  adopted it during the SQLite removal needed new signatures.
- Test projects are executables in v3 — added <OutputType>Exe</OutputType>.
- TngTech.ArchUnitNET.xUnit had to go: it still depends on xunit.assert 2.x, which
  cannot coexist with v3, and 0.13.3 is the latest release. Swapped it for the core
  TngTech.ArchUnitNET package plus a local ArchRuleAssert.Check() that evaluates the
  rule and fails through Xunit's Assert.Fail, listing every violation.

Because that replaced the assertion mechanism, the architecture tests were checked
for false-passing rather than assumed green: inverting one rule (asserting
Infrastructure does NOT depend on Domain, which it plainly does) produced a proper
failure carrying both the rule description and its "because" reason, and reverting
restored 8/8. The layer rules still genuinely enforce.

Verified:
- ./build.sh Test     -> 125 tests, Live correctly EXCLUDED
- ./build.sh TestLive -> all 16 Live tests discovered, so the Category trait filter
                         works under v3 in both directions

Note: v3 ships analyzer rule xUnit1051 (calls accepting a CancellationToken should
pass TestContext.Current.CancellationToken), which does not exist in v2, so this
commit introduces 258 warning instances across 129 call sites. They are adopted in
the follow-up commit rather than suppressed.

Noted while migrating, not addressed here: tests/Domain.Tests is referenced by the
solution but contains zero test files, which is why it never appears in any test
output. Either give it tests or drop it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
xunit v3 ships analyzer rule xUnit1051: calls that accept a CancellationToken
should pass TestContext.Current.CancellationToken so the runner can cancel tests.
The rule does not exist in v2, so the migration introduced 258 warning instances
across 129 call sites. Adopted rather than suppressed — it earns its place in
Live.Tests, where a single test can spend six minutes downloading from a CDN and
previously could not be cancelled at all.

The build is back to 0 warnings / 0 errors.

Three distinct cases, because "append an argument" is wrong more often than not:

- Token slot omitted -> append the token.
- Token slot already filled with `default` -> replace it. NSubstitute *setups*
  (`GetByIdAsync("ep-1", default).Returns(...)`) become
  `Arg.Any<CancellationToken>()`, matching the convention the verification calls in
  this suite already used. Binding a setup to one specific token would silently stop
  matching once the handler forwards a different one, and a substitute that stops
  matching returns null rather than failing loudly.
- Methods with other optional parameters before the token
  (`FindShowAsync(query, client = "ard", ct = default)`) need a NAMED argument, since
  positional insertion lands in the wrong slot.

Verification calls moved from `default` to the test's own token, e.g.
`Received(1).AddAsync(Arg.Any<DownloadJob>(), TestContext.Current.CancellationToken)`.
That is a slightly stronger assertion than before: it now proves the token is
actually propagated from the test through the handler to the repository.

Framework calls the automation could not classify were done by hand: FluentValidation
ValidateAsync, HttpContent.ReadAsStreamAsync, Stream.WriteAsync, and EF's FindAsync
(whose cancellable overload takes the key values as an object[], so the call shape
changes rather than just gaining an argument).

Verified:
- Build: 0 warnings, 0 errors (was 258 xUnit1051 instances)
- ./build.sh Test: 125 green — which also confirms the NSubstitute rewrites still
  match, since a broken setup would return null and fail the assertions
- ./build.sh TestLive: real ARD/ZDF network suite green

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ChrisonSimtian
ChrisonSimtian force-pushed the chore/wolverine-6-and-xunit-v3 branch from 228be5b to c00725c Compare July 27, 2026 07:28
@ChrisonSimtian ChrisonSimtian changed the title chore(deps): WolverineFx 6, last 9.x packages, and xunit v3 migration chore(deps): WolverineFx 6, last 9.x packages, xunit v3 + xUnit1051 adoption Jul 27, 2026
@ChrisonSimtian
ChrisonSimtian merged commit 4de40e8 into main Jul 27, 2026
1 check passed
@ChrisonSimtian
ChrisonSimtian deleted the chore/wolverine-6-and-xunit-v3 branch July 27, 2026 07:33
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.

1 participant