Skip to content

chore: migrate tests to xUnit.net v3, cover both shipped TFMs, repo maintenance - #7

Merged
StuartMeeks merged 1 commit into
mainfrom
chore/repo-maintenance-xunit-v3
Aug 19, 2026
Merged

chore: migrate tests to xUnit.net v3, cover both shipped TFMs, repo maintenance#7
StuartMeeks merged 1 commit into
mainfrom
chore/repo-maintenance-xunit-v3

Conversation

@StuartMeeks

Copy link
Copy Markdown
Owner

xunit → 4.0.0

There is no xunit 4.0.0 — the xunit package tops out at 2.9.3. Version 4.0.0 is xunit.v3, so this is a v2 → v3 migration:

  • xunit 2.9.3 → xunit.v3 4.0.0; v3 test projects are self-executing console apps, hence OutputType=Exe.
  • v3 4.0.0 runs on Microsoft.Testing.Platform, and MTP dropped VSTest-bridge support on the .NET 10 SDK. dotnet test opts into the MTP runner via a root global.json; TestingPlatformDotnetTestSupport is the old path and now hard-errors.
  • Dropped Microsoft.NET.Test.Sdk and xunit.runner.visualstudio — both VSTest-only. (xunit.runner.visualstudio 4.0.0 is still the v2 adapter, not a v3 upgrade path.)
  • One code fix: xUnit2033 in GradientTests.cs, surfaced by v3's analyzers and promoted to an error by TreatWarningsAsErrors.

Both shipped TFMs are now tested

The library ships lib/net8.0 and lib/net10.0, but the suite ran on net10.0 only — lib/net8.0 was compiled, never executed. The test project now multi-targets net8.0;net10.0, so the 40 tests run once per TFM against their own dependency graph (80 executions). Confirmed the net8.0 leg binds to the net8.0 asset, not a re-resolved net10 one:

runtimeTarget: .NETCoreApp,Version=v8.0
  NextIteration.SpectreConsole.Splash/0.3.0   ← TFM attribute: .NETCoreApp,Version=v8.0

CI needed the matching runtime — this was not optional. Building an older TFM needs only reference assemblies, but running it needs that runtime installed, so the previous 10.0.x-only setup failed the net8.0 leg outright:

Error output: You must install or update .NET to run this application.
Framework: 'Microsoft.NETCore.App', version '8.0.0' (x64)
Test run summary: Failed!   error: 1

The build job now installs 8.0.x alongside 10.0.x. The build still uses the newest SDK, as global.json carries no sdk pin. The publish job stays on 10.0.x — it executes no test code.

⚠️ Contributor impact: running the full suite locally now requires the .NET 8 runtime alongside the .NET 10 SDK (sudo apt-get install -y dotnet-runtime-8.0). Otherwise use dotnet test --framework net10.0.

Bugs fixed along the way

  • PackageOutputPath broke non-Windows builds. With GeneratePackageOnBuild=true, the hardcoded C:\nuget-local\ made every Linux/macOS/CI build write into a literal directory named C:\nuget-local\ under src/. Reproduced, then guarded the property to Windows so the local dev feed still works there.
  • The publish job could not check out the repo. Its permissions block listed only id-token: write, and GitHub sets every unlisted scope to none — leaving actions/checkout without contents: read. Worth checking whether the v0.3.0 tag publish actually succeeded.

Cleanup

  • Removed Microsoft.SourceLink.GitHub — the .NET SDK has bundled SourceLink since .NET 8. Verified rather than assumed: packed both ways and diffed. Identical .nuspec repository metadata, and .snupkg PDBs still carry raw.githubusercontent source URLs, so consumer step-through debugging is unaffected.
  • Bumped actions/checkout v6→v7, setup-dotnet v5→v6, upload-artifact v6→v7, download-artifact v7→v8. Read each release note first — all routine dependency/runtime updates, no input changes affecting this workflow. Dropped the obsolete FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 transitional flag.
  • Deduped Nullable/Authors already set by Directory.Build.props.

Verification

Full restore → build → test → pack in Release, matching CI:

build    0 Warning(s)  0 Error(s)
test     net8.0|x64  passed (2s 041ms)
         net10.0|x64 passed (1s 823ms)
         Passed!  total: 80  failed: 0  succeeded: 80  skipped: 0
pack     lib/net8.0 + lib/net10.0, repository metadata intact

No library code, public API, or shipped package contents changed.

🤖 Generated with Claude Code

…aintenance

Test framework
- xunit 2.9.3 -> xunit.v3 4.0.0 (there is no `xunit` 4.0.0; v3 ships as
  `xunit.v3`). v3 test projects are self-executing console apps, so the test
  project is now OutputType=Exe.
- v3 4.0.0 runs on Microsoft.Testing.Platform, and MTP dropped VSTest-bridge
  support on the .NET 10 SDK, so `dotnet test` opts into the MTP runner via a
  root global.json. `TestingPlatformDotnetTestSupport` is the old path and now
  hard-errors.
- Dropped Microsoft.NET.Test.Sdk and xunit.runner.visualstudio: both are
  VSTest-only. (xunit.runner.visualstudio 4.0.0 is still the *v2* adapter, not
  a v3 upgrade path.)
- Fixed xUnit2033 in GradientTests, surfaced by v3's analyzers and promoted to
  an error by TreatWarningsAsErrors.

Test coverage
- Test project multi-targets net8.0;net10.0, mirroring the library. lib/net8.0
  was previously compiled but never executed; the suite now runs once per TFM
  against its own dependency graph (80 executions).
- CI build job installs the 8.0.x runtime alongside 10.0.x. Building an older
  TFM needs only reference assemblies, but running it needs that runtime, so
  the previous 10.0.x-only setup would fail the net8.0 leg outright. The build
  still uses the newest SDK, as global.json carries no `sdk` pin. The publish
  job stays on 10.0.x since it executes no test code.

Fixes
- PackageOutputPath is guarded to Windows. With GeneratePackageOnBuild=true the
  hardcoded C:\nuget-local\ made every non-Windows build create a literal
  `C:\nuget-local\` directory under src/.
- Publish job gains `contents: read`. An explicit `permissions` block sets
  every unlisted scope to `none`, leaving actions/checkout unable to clone.

Cleanup
- Removed Microsoft.SourceLink.GitHub; the .NET SDK has bundled SourceLink
  since .NET 8. Verified pack output is unchanged either way: identical
  .nuspec repository metadata and .snupkg PDBs still carrying source URLs.
- Bumped actions/checkout v6->v7, setup-dotnet v5->v6, upload-artifact v6->v7,
  download-artifact v7->v8 (all routine dependency/runtime updates; no input
  changes affecting this workflow) and dropped the now-obsolete
  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 transitional flag.
- Deduped Nullable/Authors already set by Directory.Build.props.

No library code, public API, or shipped package contents changed.

Verified: restore/build/test/pack in Release matching CI - 0 warnings,
0 errors, 80/80 tests passing across net8.0 and net10.0, both lib/ assets
packed with repository metadata intact.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@StuartMeeks
StuartMeeks merged commit f9a4f8e into main Aug 19, 2026
1 check passed
@StuartMeeks
StuartMeeks deleted the chore/repo-maintenance-xunit-v3 branch August 19, 2026 15:31
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