From 7d1c22c78d6e765a2ae04ac9e5a4511c637b3fa2 Mon Sep 17 00:00:00 2001 From: Lucas Vogel Date: Tue, 29 Sep 2026 15:39:58 -0400 Subject: [PATCH 1/2] test: xunit v3 4.0.1 on Microsoft.Testing.Platform Closes the migration deferred on 2026-09-10, and supersedes Dependabot's PR #32, which bumped the versions without it and failed the build. xunit.v3 4.0 drops VSTest on the .NET 10 SDK. The error points at a property that cannot fix it: TestingPlatformDotnetTestSupport is the pre-.NET-10 bridge that redirects the VSTest target into InvokeTestingPlatform, and MTP 2.4.0 removed that escape hatch. The error is raised from inside the bridge's own _MTPBeforeVSTest target, guarded on the SDK major version and a variable named _SupportsGlobalJsonTestRunner, so setting the property changes nothing. The SDK says the same thing: `dotnet test --help` on 10.0.401 directs you to global.json. So the opt-in is global.json, and three packages come out rather than up: Microsoft.NET.Test.Sdk the VSTest targets and test host xunit.runner.visualstudio the VSTest adapter coverlet.collector a VSTest data collector The first two cannot work under MTP. The third would be worse than absent: the collect flag would still be accepted and quietly produce nothing. No replacement is added, because nothing in this repository has ever asked for a coverage number. Test projects gain Exe, which Microsoft.NET.Test.Sdk used to supply implicitly. It is in Directory.Build.props conditioned on the project name, because IsTestProject is set by the test SDK targets long after .props is evaluated and a condition on it would silently never match. The filter rewrite the deferred-work entry predicted turned out to be unnecessary. xunit.v3 4.x ships --filter accepting VSTest syntax, so both CI steps, CONTRIBUTING and the corpus-audit instructions are unchanged and still correct. One thing improves for free: a run that executes no tests now exits 8 instead of passing. Measured, not assumed -- a filter matching nothing exits 8 while the real suite exits 0. A filter typo used to be a silent green. Verified on a clean tree: locked-mode restore, Release build, both CI test steps run verbatim, the CLI smoke test, and a test assembly executed directly. 363 tests: 362 pass, 1 skip (the corpus audit). That is the pre-migration count of 362 plus the one performance test the old filter excluded. Also corrects src/Docmd.Cli/packages.lock.json, which still recorded Ooxml.Md.Core at 0.2.3 after the 0.2.4 release. --locked-mode does not catch this: it validates package versions, not the project-reference versions in the same file. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018wZEgtuzaZPswykiGEBxbz --- .github/dependabot.yml | 5 +- CHANGELOG.md | 33 ++++ CONTRIBUTING.md | 26 ++++ Directory.Build.props | 14 ++ Directory.Packages.props | 20 ++- docs/deferred-work.md | 52 +++++-- global.json | 3 + src/Docmd.Cli/packages.lock.json | 2 +- .../Docmd.Word.Tests/Docmd.Word.Tests.csproj | 3 - tests/Docmd.Word.Tests/packages.lock.json | 142 +++++++----------- .../Ooxml.Md.Core.Tests.csproj | 3 - tests/Ooxml.Md.Core.Tests/packages.lock.json | 138 +++++++---------- 12 files changed, 238 insertions(+), 203 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index aa1787b..31d0807 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -29,11 +29,12 @@ updates: - "PhoenixmlDb.*" # Test tooling churns often and rarely interestingly. One PR, not six. + # Microsoft.NET.Test.Sdk and coverlet.collector were dropped with the move to + # Microsoft.Testing.Platform; the MTP packages themselves arrive transitively through + # xunit.v3, so "xunit*" is the whole surface Dependabot can see here. test-tooling: patterns: - "xunit*" - - "Microsoft.NET.Test.Sdk" - - "coverlet*" ignore: # FluentAssertions 7.x and 8.x moved to the paid Xceed licence. docmd's core is diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dfcfac..d3a058c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,39 @@ stylesheet writes against is not stable until `1.0`. ## [Unreleased] +Test infrastructure only. **No product change**: nothing under `src/` was touched, and no shipped +package reference moved. + +### Changed + +- **xunit.v3 3.2.2 → 4.0.1**, which runs on Microsoft.Testing.Platform and drops VSTest. The + test platform moved with it; `dotnet test docmd.slnx` and the existing `--filter` expressions + are unchanged. + + The opt-in is `"test": { "runner": "Microsoft.Testing.Platform" }` in `global.json`. The + property the error message points at, `TestingPlatformDotnetTestSupport`, is the pre-.NET-10 + bridge and cannot help: MTP 2.4.0 raises the error from inside that bridge's own target, + guarded on the SDK major version alone. + + Three VSTest-only packages were removed rather than bumped, because under MTP they do nothing: + `Microsoft.NET.Test.Sdk`, `xunit.runner.visualstudio` and `coverlet.collector`. Test projects + gained `Exe`, which `Microsoft.NET.Test.Sdk` used to supply. + + Verified: 363 tests — 362 pass, 1 skip (the corpus audit) — which is the pre-migration count + of 362 plus the one performance test the old filter had excluded; `--locked-mode` restore + clean; the performance gate still runs in isolation. + + A run that executes no tests now exits 8 rather than passing, so a filter that matches nothing + fails CI instead of going green. Full reasoning in + [`docs/deferred-work.md`](docs/deferred-work.md). + +### Fixed + +- `src/Docmd.Cli/packages.lock.json` recorded `Ooxml.Md.Core` at `0.2.3` after the 0.2.4 release. + Regenerating the lock files corrected it. Worth knowing that `--locked-mode` does not catch + this: it validates package versions, not the project-reference versions in the same file, so + that field goes stale on every release and nothing complains. + ## [0.2.4] — 2026-09-29 Dependencies. **No behaviour change**: output is byte-identical across the 13-document corpus. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1969ef4..14774cf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,6 +57,32 @@ deliberately not: `fixtures/real/sample.docx` is a genuine third-party-written d `word-noise` carries Word's own mess — `w:proofErr`, rsids, bookmarks between runs, words split mid-word at spell-check boundaries. Those two find bugs hand-written XML never does. +### The test platform + +Tests run on **Microsoft.Testing.Platform**, not VSTest. `dotnet test docmd.slnx` is unchanged, +and so is `--filter "FullyQualifiedName~Something"` — xunit.v3 4.x accepts VSTest filter syntax. +Three things are worth knowing: + +- **Each test project is an executable.** `OutputType` is `Exe`, set centrally in + `Directory.Build.props` for any project whose name ends in `.Tests`, and you can run one + without `dotnet test`: + + ```console + $ ./tests/Docmd.Word.Tests/bin/Release/net10.0/Docmd.Word.Tests + $ ./tests/Docmd.Word.Tests/bin/Release/net10.0/Docmd.Word.Tests -class Docmd.Word.Tests.TransformScalingTests + ``` + + Run that way it is xunit's own in-process console runner, whose options are single-dash and + different (`-class`, `-method`, `-trait`; `--help` lists them). The MTP options that + `dotnet test` accepts, `--filter` included, are rejected there with `unknown option`. +- **A run that executes no tests fails**, with exit code 8. Under VSTest a filter that matched + nothing was a silent pass, which is the vacuous pass above with CI's name on it. +- **A VSTest filter cannot be combined** with xunit's own `--filter-class` / `--filter-method` / + `--filter-query` forms. Pick one style per invocation. + +The opt-in is `"test": { "runner": "Microsoft.Testing.Platform" }` in `global.json`. Without it +the SDK runs the VSTest command, which on .NET 10 fails the build rather than falling back. + ### Running the corpus audit Against your own documents: diff --git a/Directory.Build.props b/Directory.Build.props index 71f7966..5916770 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -37,6 +37,20 @@ https://github.com/phoenixmldb/docmd true + + + Exe + + diff --git a/Directory.Packages.props b/Directory.Packages.props index 9352051..28f87df 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,10 +7,22 @@ - - - - + + diff --git a/docs/deferred-work.md b/docs/deferred-work.md index e244f03..a93314a 100644 --- a/docs/deferred-work.md +++ b/docs/deferred-work.md @@ -60,11 +60,12 @@ already dead, while the anchor text survives. Recorded so the corpus audit can r - `docmd audit`, `-r`/`--recursive`, `--review`, `--style-map`, `--strict`, `--report` and `--revisions` all currently fail cleanly as not-yet-supported. -## xunit v3 4.0 needs a test-platform migration, not a version bump +## xunit v3 4.0 needed a test-platform migration, not a version bump -**Status:** open, deliberately deferred 2026-09-10. Pinned at xunit.v3 3.2.2. +**Status:** closed 2026-09-29. On xunit.v3 4.0.1, running on Microsoft.Testing.Platform 2.4.0. -xunit.v3 4.0.0 drops VSTest support on the .NET 10 SDK and requires Microsoft.Testing.Platform: +xunit.v3 4.0 drops VSTest on the .NET 10 SDK. The error names a property that sounds like the +fix and is not: ``` error : Testing with VSTest target is no longer supported by Microsoft.Testing.Platform @@ -72,19 +73,38 @@ on .NET 10 SDK and later. If you use dotnet test, you should opt-in to the new d test experience. ``` -Setting `TestingPlatformDotnetTestSupport` is not sufficient on its own; the `dotnet test` CLI -needs its own opt-in as well. Beyond that, MTP changes filter syntax, and `--filter -"FullyQualifiedName~X"` appears in `.github/workflows/ci.yml` (which runs the performance gate as -its own step), in `CONTRIBUTING.md` twice, and in the corpus-audit instructions. All of them -change together or none do. - -Deferred because it is orthogonal to shipping, and a hurried test-infrastructure migration -immediately before a first release is a poor trade: the thing that tells you whether the release -is sound is the last thing to rebuild in a hurry. Nothing about 3.2.2 is broken. - -**Closes with:** `dotnet.config` selecting the MTP runner, the property above, the filter -expressions rewritten in all four places, and a CI run proving the performance gate still runs -in isolation. +`TestingPlatformDotnetTestSupport=true` is the pre-.NET-10 bridge: it redirects the VSTest +target into `InvokeTestingPlatform`. MTP 2.4.0 removed that escape hatch, and the removal is +what produces the error above. `Microsoft.Testing.Platform.MSBuild.targets` raises it from +`_MTPBeforeVSTest`, guarded on nothing but the SDK major version and a variable named +`_SupportsGlobalJsonTestRunner` — so the property cannot suppress it, and the refusal is +conditioned on a *better* opt-in being available. The SDK agrees: `dotnet test --help` on +10.0.401 says to opt in "via global.json". + +**What it actually took**, which was less than this entry predicted: + +- `global.json` gains `"test": { "runner": "Microsoft.Testing.Platform" }`. Not `dotnet.config`, + which this entry guessed at; that is a different mechanism and not what this SDK reads. +- Three packages removed rather than bumped, all VSTest-only and inert under MTP: + `Microsoft.NET.Test.Sdk`, `xunit.runner.visualstudio`, `coverlet.collector`. Reasoning is in + `Directory.Packages.props`. Coverage, if anyone ever wants a number, is `coverlet.mtp` or + `Microsoft.Testing.Extensions.CodeCoverage`; nothing here has asked for one. +- `Exe` for test projects, which `Microsoft.NET.Test.Sdk` used to supply + implicitly. It is in `Directory.Build.props` conditioned on the project name, because + `IsTestProject` is set long after `.props` is evaluated and a condition on it never matches. +- **The filter rewrite was unnecessary.** This entry expected `--filter + "FullyQualifiedName~X"` to change in four places. xunit.v3 4.x ships `--filter` accepting + VSTest syntax, so CI, `CONTRIBUTING.md` and the corpus-audit instructions are unchanged and + still correct. The restriction is that a VSTest filter cannot be combined with xunit's own + `--filter-class` / `--filter-query` forms, which nothing here does. + +**What we gained rather than paid for:** a run that executes no tests now exits 8 instead of +passing. A filter typo used to be a silent green — measured, not assumed: +`--filter "FullyQualifiedName~NoSuchTestNameAtAll"` exits 8, and the real suite exits 0. + +Verified: 363 tests (362 pass, 1 skip — the corpus audit), matching the pre-migration count of +362 plus the one performance test the old filter excluded; `--locked-mode` restore clean; the +performance gate still runs alone. ## Formats diff --git a/global.json b/global.json index 01cb658..a0703e8 100644 --- a/global.json +++ b/global.json @@ -2,5 +2,8 @@ "sdk": { "version": "10.0.400", "rollForward": "latestFeature" + }, + "test": { + "runner": "Microsoft.Testing.Platform" } } diff --git a/src/Docmd.Cli/packages.lock.json b/src/Docmd.Cli/packages.lock.json index 0ee553b..208dfae 100644 --- a/src/Docmd.Cli/packages.lock.json +++ b/src/Docmd.Cli/packages.lock.json @@ -61,7 +61,7 @@ "docmd.word": { "type": "Project", "dependencies": { - "Ooxml.Md.Core": "[0.2.3, )", + "Ooxml.Md.Core": "[0.2.4, )", "PhoenixmlDb.Xslt": "[2.4.1, )" } }, diff --git a/tests/Docmd.Word.Tests/Docmd.Word.Tests.csproj b/tests/Docmd.Word.Tests/Docmd.Word.Tests.csproj index ec56fa5..1947013 100644 --- a/tests/Docmd.Word.Tests/Docmd.Word.Tests.csproj +++ b/tests/Docmd.Word.Tests/Docmd.Word.Tests.csproj @@ -7,10 +7,7 @@ false - - -