build: move test suites from the VSTest bridge to Microsoft Testing Platform - #589
Conversation
…latform Follow-up to #577, which moved everything to xunit v3 but deliberately stayed on the VSTest bridge to keep that migration reviewable. All nine test projects drop Microsoft.NET.Test.Sdk and xunit.runner.visualstudio and run as their own MTP executables: no testhost indirection, faster startup, and xunit v3's own reporting. Four things the issue didn't anticipate: 1. OutputType=Exe now has to be explicit. Microsoft.NET.Test.Sdk was setting it; without it, xunit.v3's targets fail the build outright ("xUnit.net v3 test projects must be executable"). 2. Microsoft.Testing.Extensions.CodeCoverage is pinned to 18.0.6, not the latest 18.9.0. xunit.v3 3.2.2 is built against Microsoft.Testing. Platform 1.x -- its packages are literally named xunit.v3.core.mtp-v1. CodeCoverage 18.1.0+ moved to MTP 2.x, and the mixed graph dies at startup on every single test run with TypeLoadException: Could not load type 'Microsoft.Testing.Platform.Extensions.TestHost.IDataConsumer' from assembly 'Microsoft.Testing.Platform, Version=2.3.0.0' 18.0.6 is the last release on MTP 1.x. The constraint is recorded in Directory.Packages.props with the symptom, so the next person to bump it knows what they're looking at. 3. TestingPlatformShowTestsFailure is required, and its absence is a trap. By default MTP prints only error run failed: Tests failed: '.../TestResults/EventStoreTests_net9.0_x64.log' and sends the test name, assertion and stack trace to that file. CI never publishes it, so a red build carries no indication of what broke -- strictly worse than the VSTest bridge, which printed failures inline. Caught by deliberately failing a test and reading the CI log. With the property set, failures print as EventExtensionsTests.cs(32): error test failed: EventStoreTests.EventExtensionsTests.deliberately_failing_probe: Shouldly.ShouldAssertException : "actual" should be "expected" 4. The MTP properties belong in Directory.Build.props rather than per project: TestingPlatformDotnetTestSupport changes how `dotnet test` invokes a project, so a solution where only some projects answered to MTP would leave `dotnet test` on the .slnx in a mixed mode. build/Build.cs needed no changes at all. All seven DotNetTest calls work as-is under MTP, including TestCodegenFSharp's .SetFramework("net9.0"). Verified `./build.sh test` end to end (exit 0), every count matching the current baseline on every target framework: CoreTests 480 (479 + 1 skipped) CodegenTests 419 CommandLineTests 295 EventTests 648 EventStoreTests 72 Aspire.Tests 51 (net10.0) CodegenTests.FSharp 1 SourceGenerator.Tests 19 Events.SourceGenerator.Tests 26 Also verified failures still fail the build, which a silent runner swap could quietly break: a deliberately failing test produced "Failed: 1, Passed: 72" and Nuke surfaced it as ProcessException, 'dotnet' exited with code 1. CodegenTests and CommandLineTests, the two suites carrying [assembly: CollectionBehavior(CollectionPerAssembly)], pass unchanged. Closes #581 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CommandFactory prints "Searching '<assembly>' for commands" until its
static _hasAppliedExtensions latch flips. Being static, it flips once per
process, so exactly one test in the assembly picks up that banner as a
prefix on its captured console output -- whichever test happens to run
first.
execute_single_command_synchronously and its async sibling both asserted
their trimmed output with an exact ShouldBe, so whichever one drew the
short straw failed:
theOutput.ToString().Trim()
should be
"Big is True, Number is 7"
but was
"Searching 'JasperFx, Version=2.36.2.0, ...' for commands
Big is True, Number is 7"
This turned CI red on #587 (which passed on re-run, same commit) and
again on #589. It is not an MTP problem -- #587 was still on the VSTest
bridge -- but MTP orders tests differently, which is why it resurfaced
immediately after the runner change.
The banner is always a prefix, since discovery runs before the command
does, so anchoring to the end of the output keeps the assertion exactly
as strong while making it order-independent.
This is the same class of bug as the AnsiConsole binding already
documented in this constructor, which #577 fixed for the same reason.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
f9e4fc5 to
f52fb06
Compare
|
Rebased onto 1. MTP was hiding the failure — fixedThe CI failure was undiagnosable, and that was this PR's fault. All the log said was: MTP sends the test name, assertion, and stack trace to that file instead of stdout, and CI never publishes it — so a red build carried no indication of what broke. That is strictly worse than the VSTest bridge, which printed failures inline, and it would have made this migration a bad trade on its own. Reproduced locally by deliberately failing a test, then fixed with Thanks to that property, if the flake below ever recurs the log will name it instead of pointing at a file nobody can read. 2. The actual failure was a pre-existing order-dependent test
This is not an MTP problem. It hit #587 first, which was still on the VSTest bridge, and that PR went green on a re-run of the same commit. MTP simply orders tests differently, which is why it resurfaced immediately after the runner change. Both assertions are now Happy to split that commit out into its own PR if you'd rather keep this one purely about the runner — it's a pre-existing bug on Verification after the rebase
CoreTests is 480 rather than the 475 in the original description because #587 added 4 tests and #588 turned a commented-out |
Closes #581
Follow-up to #577, which moved everything to xunit v3 but deliberately stayed on the VSTest bridge to keep that migration reviewable.
All nine test projects now drop
Microsoft.NET.Test.Sdkandxunit.runner.visualstudioand run as their own MTP executables — notesthostindirection, faster startup, xunit v3's own reporting.Three things the issue didn't anticipate
1.
OutputType=Exehas to become explicit.Microsoft.NET.Test.Sdkwas setting it. Remove that package and xunit.v3's targets fail the build outright:2.
Microsoft.Testing.Extensions.CodeCoverageis pinned to 18.0.6, not the latest 18.9.0. This is the interesting one. xunit.v3 3.2.2 is built against Microsoft.Testing.Platform 1.x — its packages are literally namedxunit.v3.core.mtp-v1. CodeCoverage 18.1.0 and later moved to MTP 2.x. NuGet unifies the platform assembly up to 2.x whileMicrosoft.Testing.Platform.MSBuild 1.9.1stays behind, and every single test run dies at startup:18.0.6is the last release on MTP 1.x. The constraint and the symptom are both recorded inDirectory.Packages.propsso whoever bumps it next knows what they're looking at — the fix is to move forward only once xunit.v3 ships anmtp-v2build.3. The MTP properties belong in
Directory.Build.props, not per project.TestingPlatformDotnetTestSupportchanges howdotnet testinvokes a project; a solution where only some projects answered to MTP would leavedotnet teston the.slnxin a mixed mode. Both properties are inert in projects that don't reference xunit.v3.build/Build.cs needed no changes
The issue flagged Nuke's
DotNetTestas a blocker. All seven calls work as-is under MTP — they only useSetProjectFile/SetConfiguration/EnableNoBuild/EnableNoRestore, none of which change shape.TestCodegenFSharp's.SetFramework("net9.0")works too:The VSTest-only arguments the issue worried about (
--collect,--logger,--filter,--settings) aren't used anywhere in this repo.Verification
./build.sh testend to end, exit 0. Every count matches the baseline recorded in #581, on every target framework:Failures still fail the build. A runner swap can silently break exit-code propagation and turn CI permanently green, so I checked it directly by adding a deliberately failing test:
CollectionBehaviorstill honored. CodegenTests and CommandLineTests — the two suites carrying[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]— pass unchanged. The attribute is an xunit-level concern, independent of the runner. Note that MTP runs the two target frameworks concurrently, which is safe here: they are separate processes with separate per-TFM output directories, so neither the process-wide statics nor the on-disk assembly compilation collide../build.sh smoke-test-commandsalso still passes.One note on scope
The issue asked to swap
coverlet.collector→Microsoft.Testing.Extensions.CodeCoverage, which is what this does. Worth flagging: no build target actually asks for coverage — there is no--collectanywhere inbuild/Build.csor the workflow, socoverlet.collectorwas never invoked either. This is capability parity, not something in use. Say the word if you'd rather just drop the collector entirely.🤖 Generated with Claude Code