chore(deps): WolverineFx 6, last 9.x packages, xunit v3 + xUnit1051 adoption - #55
Merged
Merged
Conversation
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
force-pushed
the
chore/wolverine-6-and-xunit-v3
branch
from
July 27, 2026 07:28
228be5b to
c00725c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.Postgresql5.18.0→ 6.22.0Microsoft.Extensions.ServiceDiscovery9.3.1→ 10.8.0Microsoft.Extensions.Http.Resilience9.4.0→ 10.8.0Wolverine 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:Added
WolverineFx.RuntimeCompilationto the two hosts that callUseWolverine(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)
ServiceLocationPolicynow defaults toNotAllowed(5.x wasAllowedButWarn), so codegen refused to build a handler needing container resolution.CrawlShowHandlerneeds it twice over:IEnumerable<IBroadcasterCrawler>is an opaque lambda registration, andIEpisodeRepository's graph reaches EF Core's ownDbContextOptionsfactory, which we don't control.Set to
AllowedButWarn— restoring exactly the 5.x behaviour. Deliberately notAllowed, 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:
CrawlShowCommandover the durable Postgres transport → handler received it → crawled the real ZDF API →Crawled 18 episode(s) for 'heute-show' on zdfEpisodes; Wolverine created its 8 message-store tables2. xunit v3 migration (
228be5b)All five test projects:
xunit2.9.3 →xunit.v33.2.2. Thexunit.runner.visualstudio3.1.5 andTest.Sdk18.8.1 from the last PR already support v3, so no runner change was needed.What the migration forced:
IAsyncLifetimenow returnsValueTask(wasTask) and extendsIAsyncDisposable— soPostgresFixtureand the three repository fixtures needed new signatures.<OutputType>Exe</OutputType>.TngTech.ArchUnitNET.xUnithad to go. It still depends onxunit.assert2.x, which cannot coexist with v3, and 0.13.3 is the latest release. Swapped for coreTngTech.ArchUnitNETplus a localArchRuleAssert.Check()that evaluates the rule and fails throughAssert.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:
Both the rule description and its
becausereason survive. Reverting restored 8/8. The layer rules still genuinely enforce.3. Adopt xUnit1051 (
c00725c)xunit v3 ships analyzer rule xUnit1051 — calls accepting a
CancellationTokenshould passTestContext.Current.CancellationTokenso the runner can cancel tests. It does not exist in v2, socommit 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 CDNand 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:
defaultCS1501errorsFindShowAsync(query, client = "ard", ct = default))clientslotNSubstitute setups became
Arg.Any<CancellationToken>(), matching the convention the verificationcalls 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
nullratherthan failing loudly.
Verification calls moved from
defaultto the test's own token, e.g.Received(1).AddAsync(Arg.Any<DownloadJob>(), TestContext.Current.CancellationToken). That is aslightly 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'sFindAsync, whosecancellable overload takes key values as an
object[], so the call shape changes rather than justgaining an argument.
Testing
./build.sh Test./build.sh TestLive880fd06aloneNotes
Downloads_a_full_Extra3_episode_from_ARDfailed with aSocketErrorafter 1m54s mid-download from ARD's CDN; re-runs passed 16/16 twice. Real full download against a live CDN, unrelated to these changes.warning CSand missed thexUnit1051analyzer warnings, which CI surfaced. It now states the warnings introduced and points at commit 3.tests/Domain.Testsis 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