Pin the application assembly in the CoreTests harness (GH-3423)#3429
Merged
Conversation
Closes the rest of #3423. The remaining 19 failures were not an ordering or fixture problem at all: Wolverine was resolving the wrong application assembly. When nothing pins it, WolverineOptions.establishApplicationAssembly falls back to determineCallingAssembly(), a stack walk that takes the first non-System/Microsoft frame outside Wolverine. Under xUnit that frame is not reliably the test assembly — instrumenting a failing run showed ApplicationAssembly resolving to "xunit.execution.dotnet". Conventional discovery then scans xUnit, finds no handlers, and HandlerGraph.CanHandle returns false. The IndeterminateRoutesException the tests reported was only the downstream symptom; the real failure was swallowed and logged at WolverineRuntime.cs:315. Which frame the walk lands on depends on what ran before, which is why these tests passed alone and in the full assembly but failed as a filtered subset. Pin ApplicationAssembly explicitly in DefaultApp and in the two ClaimCheck harnesses that build their own hosts inside an async InitializeAsync. This is what Wolverine's own docs prescribe for test harnesses (see the ApplicationAssembly / RememberedApplicationAssembly doc comments) and it makes discovery deterministic regardless of call stack. Also fixes two races in the connection-budget tests added in #3422, both mine. Each compared counters sampled at different instants while the sweeper kept advancing them in the background: one waited on the snapshot count and then asserted on fetch counts (the budget probe runs at the top of a pass, before the per-database walk, so the Nth probe can land while a store has only been fetched N-1 times), and one asserted an exact probes == snapshots equality across two samples. Both now assert the invariant that holds at any instant. 25 consecutive runs green. Persistence subset: 20 failures -> 0 (178/178, stable over 5 runs). Full CoreTests assembly: 1953/1953. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Closes #3423. Follows #3427 (the fixture-ownership half, already merged).
The actual root cause
It was never an ordering or fixture problem. Wolverine was resolving the wrong application assembly.
When nothing pins it,
establishApplicationAssemblyfalls back todetermineCallingAssembly()(WolverineOptions.Assemblies.cs:66) — a stack walk that finds the outermostWolverineframe and takes the first assembly outward that isn'tSystem*/Microsoft*. Under xUnit, that frame is not reliably the test assembly.I instrumented a failing run rather than keep guessing. Ground truth:
Conventional discovery then scans xUnit, finds no handlers, and
HandlerGraph.CanHandlereturns false.WolverineRuntime.findInvokerfalls through to routing and throwsIndeterminateRoutesException— which is what the tests reported, and which is purely a downstream symptom. The real failure was swallowed and logged atWolverineRuntime.cs:315("Failed to create a message handler for {MessageType}"), which is why this was so opaque.Which frame the walk lands on depends on what ran before it. Hence: passes alone, passes in the full assembly, fails as a filtered subset.
The fix
Pin
ApplicationAssemblyexplicitly inDefaultAppand in the two ClaimCheck harnesses that build their own hosts inside an asyncInitializeAsync. This is exactly what Wolverine's own doc comments prescribe for test harnesses:Discovery is now deterministic regardless of call stack or test ordering.
Also: two races in my own #3422 tests
Both compared counters sampled at different instants while the sweeper kept advancing them in the background — my bug, not the product's:
probes == snapshotsequality across two samples; a pass completing in between made it 3 vs 2.Both now assert the invariant that holds at any instant rather than a moment-in-time equality. 25 consecutive runs green (was failing ~1 in 3).
Verification
CoreTests.PersistencesubsetCoreTestsassemblyconnection_budgettests, 25xWorth noting separately
determineCallingAssembly()is a best-effort heuristic and is documented as such, withApplicationAssemblyas the escape hatch — so pinning it in the harness is the sanctioned fix, not a workaround. But it's worth being aware that any host built from an async context can silently get the wrong application assembly and then quietly discover zero handlers. The swallowing atWolverineRuntime.cs:315makes that failure mode much harder to diagnose than it needs to be. Happy to open a separate issue if you think either deserves attention.🤖 Generated with Claude Code