Migrate dotnet test to Microsoft.Testing.Platform for xunit.v3 4.0 - #325
Conversation
xunit.v3 4.0.0 pulls in Microsoft.Testing.Platform.MSBuild, and the .NET 10 SDK refuses to run it under the VSTest 'dotnet test' target: 'Testing with VSTest target is no longer supported by Microsoft.Testing.Platform on .NET 10 SDK and later.' This is what's failing CI on the nuget-deps dependabot PRs (#323, #324), which propose this same xunit.v3 bump. Add global.json to opt dotnet test into native MTP mode (the SDK's recommended path over the legacy TestingPlatformDotnetTestSupport VSTest bridge, which Microsoft says MTP v2 drops for .NET 10). Drop xunit.runner.visualstudio, the now-unneeded VSTest adapter. Swap coverlet.collector for coverlet.MTP, since the VSTest coverlet data collector is silently ignored under MTP (MTP0001); update the CI unit-test step from --collect:"XPlat Code Coverage" to --coverlet --coverlet-output-format cobertura, which still lands Cobertura XML at ./coverage for the existing Codecov upload step. Also applies the other package bumps #323/#324 propose (AwesomeAssertions, Microsoft.CodeAnalysis.CSharp, Microsoft.NET.Test.Sdk, xunit.analyzers) so those PRs become no-ops once this merges. Verified locally: dotnet test passes 310/310 and produces Cobertura output at ./coverage; csharpier check, dotnet format style, the offline codegen-drift regenerate, and editorconfig-checker are all clean.
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 10 included reviews per hour; 5 remain after this review. 📝 WalkthroughWalkthroughThe test configuration now selects Microsoft.Testing.Platform, uses ChangesMicrosoft.Testing.Platform test migration
Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk:⚪ Minimal · up to This PR updates the test runner and coverage configuration for xunit.v3 4.0 and reports passing tests and validation checks; no actionable merge-blocking risk remains beyond normal review and CI checks. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Full details: Docstring CoverageExplanation No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0 files. (2 skipped: 2 unsupported.) ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
PR Summary by QodoMigrate .NET 10 tests to native Microsoft Testing Platform
AI Description
Diagram
High-Level Assessment
Files changed (4) |
Code Review by Qodo
1. |
Uh oh!
There was an error while loading. Please reload this page.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@## develop #325 +/- ##
===========================================
- Coverage 87.43% 87.06% -0.37%
===========================================
Files 11 8 -3 Lines 1973 835 -1138 Branches 243 139 -104 ===========================================
- Hits 1725 727 -998 + Misses 166 65 -101 + Partials 82 43 -39 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🟢 Approval recommended
The changes consistently migrate local/CI test execution to MTP and update dependencies without introducing any confirmed functional regressions (only a minor comment clarification was noted).
Pull request overview
Migrates the repo’s test execution and code coverage pipeline to Microsoft.Testing.Platform (MTP) to keep dotnet test working under the .NET 10 SDK after upgrading to xunit.v3 4.0, and aligns CI/package references with that new runner model.
Changes:
- Added
global.jsonto optdotnet testinto Microsoft.Testing.Platform mode. - Updated test project + central package versions: dropped
xunit.runner.visualstudio, replacedcoverlet.collectorwithcoverlet.MTP, and bumped related test/tooling packages. - Updated CI unit test invocation to use coverlet.MTP arguments and keep Cobertura output under
./coveragefor Codecov upload.
File summaries
| File | Description |
|---|---|
LanguageTagsTests/LanguageTagsTests.csproj | Removes VSTest adapter usage and switches the test project to coverlet.MTP. |
global.json | Configures dotnet test to run using Microsoft.Testing.Platform. |
Directory.Packages.props | Centralizes the dependency updates (xUnit v3 4.0, coverlet.MTP, analyzers, etc.). |
.github/workflows/validate-task.yml | Updates CI to run tests/coverage using the MTP + coverlet.MTP flow and upload results to Codecov. |
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Uh oh!
There was an error while loading. Please reload this page.
Copilot: the coverlet.MTP comment attributed compile-time-closure inclusion to PrivateAssets; that's IncludeAssets (specifically not excluding "compile"), PrivateAssets only controls exposure to downstream consumers. Corrected, and noted this test project has none. Qodo: the new MTP comment in validate-task.yml wrapped narrower than the file's prevailing width. Reflowed to match.
There was a problem hiding this comment.
🟢 Approval recommended
The changes consistently migrate tests/coverage to MTP across project and CI configuration, with no remaining references to the removed VSTest adapter/collector in the reviewed code paths.
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Lite
Uh oh!
There was an error while loading. Please reload this page.
…Chain (#940) * Adopt the fleet LF line-ending default and renormalize the tree The hub moved the fleet default from CRLF-with-LF-exceptions to LF-with-a-CRLF-exception-for-bat-and-cmd. `.editorconfig` now declares `[*] end_of_line = lf` and `.gitattributes` declares `* text=auto eol=lf`, so git enforces the ending on checkout and renormalize rather than leaving it to the editor. The per-type LF pins those two files carried (`*.sh`, the husky hook, `uv.lock`, Dockerfiles, workflow YAML) are redundant under the new default and are dropped. `git add --renormalize .` converts the 160 tracked CRLF files to LF. Only `.editorconfig`, `.editorconfig-checker.json` and `.gitattributes` carry content changes; every other file in this commit is line-endings only, as `git diff --ignore-cr-at-eol` shows. The six binary files are untouched. * Re-vendor the carried instruction set from the hub Carries `CLAUDE.md`, the file that imports `AGENTS.md` into a Claude Code session, which this repo never had. Re-vendors the verbatim sections the audit reported stale: `AGENTS.md` "Context and Delegation Discipline" and "Where the Rules Live", and `GOVERNANCE.md` "Durable Knowledge and Self-Improvement", "Repository Boundaries and Write Safety", "Operational Repositories", "Hub-Hosted Tooling", "Verification Discipline", "PR Review Etiquette", "Workflow YAML Conventions" and "Repository Details". The repo-specific intro paragraphs, "Devcontainer" and "Repository Layout" are intent-fidelity and stay this repo's own. "Running the Linters Locally" is promoted from `###` to `##`, matching the hub's own heading level so the `AGENTS.md` rules table resolves, and takes the hub's current content: the per-tool Docker one-liners are superseded by the hub-hosted `scripts/docker_lint.py` wrapper. The PowerShell block goes with them, since this repo tracks no `.ps1` file and the wrapper discovers its own targets. `CODESTYLE.md` takes the hub's current local-gate and Shell wording, and `AGENTS.md` now names the `shell-codestyle` Skill alongside the other two. The PlexCleaner .NET and Python subsections are unchanged. `AUDIT.md` is rewritten against hub-hosted configuration: `repo-config/` and `spec/secrets.json` are no longer carried, so the self-audit reaches `repo-config/configure.sh check` and `spec/audit.py` from a hub checkout rather than diffing local payloads that are about to be deleted. `.github/copilot-instructions.md` takes the hub's current runbook, which routes to `scripts/pr_review.py` in place of the hand-rolled GraphQL. The "Disproved Claims" section keeps its shape and rules and carries no entry: this repo had none of its own, and the hub's are proofs about the hub's tree. * Delete the retired repo-config and spec carries `spec/divergences.json` gives both trees a `retire` disposition: the branch rulesets, the repository settings and the secrets declaration are hub-hosted ground truth reached from a hub checkout, not payloads a repo carries. A carried copy can only restate the hub's computation or drift from it between resyncs. The inbound sweep re-points the two "Repository Layout"/"Configuration Layout" bullets that described the directories, and drops the `host-tools.json` note's aside citing the carried `spec/secrets.json` as precedent for omitting a `$schema` pointer. `AUDIT.md` was already rewritten against the hub-hosted payloads in the previous commit. The `HISTORY.md` mention stays: it records what a past release shipped and was true then. * Carry the .github/skills tree from the hub `spec/files.json` declares `.github/skills` a `verbatim-tree` with `prune`, applying to every fleet repo, so the 37 files are carried whole rather than adapted. `scripts/carry.py apply` wrote them and reports the source and target digests equal. `.github/copilot-instructions.md` already points a review at `.github/skills/code-review/SKILL.md`; until now that path resolved to nothing here. * Migrate the test project to Microsoft.Testing.Platform The .NET 10 SDK refuses to run a VSTest target for a project that pulls in `Microsoft.Testing.Platform.MSBuild`, which xunit.v3 4.0.0 does. That is what has been failing the `Run unit tests` and `Build Docker image` jobs on Dependabot's nuget-deps bumps (#937 to `develop`, #938 to `main`), each re-rolled once with the same result, so the group has not landed since xunit.v3 went to 4.0.0. ptr727/ProjectTemplate#1088 catalogs the same break across the fleet and recommends the coverlet.MTP route, already round-tripped in ptr727/LanguageTags#325, and this is that migration. - `global.json` opts `dotnet test` into Microsoft.Testing.Platform. The hub cannot supply this file, since it lives in each consumer's own root. - `xunit.runner.visualstudio` is dropped, being the VSTest adapter. - `coverlet.collector` becomes `coverlet.MTP`, whose self-registration codegen needs the package on the test project's compile-time closure, so the `PrivateAssets`/`IncludeAssets` restriction the VSTest data collector carried is not re-applied. - The CI unit-test step becomes `dotnet test --coverlet --coverlet-output-format cobertura --results-directory ./coverage`, which still lands Cobertura XML where the existing Codecov step reads it. - `Docker/Dockerfile` copies `global.json` into the builder, without which `Docker/Build.sh`'s own `dotnet test` falls back to VSTest and fails the image build the same way. The rest of the group's bumps ride along, and `ptr727.LanguageTags` and `ptr727.Utilities` go to their current versions rather than the ones the stale Dependabot PR names. `dotnet outdated` reports nothing outdated, and `.config/dotnet-tools.json` is already at the current csharpier 1.3.0, husky 0.9.1 and dotnet-outdated-tool 4.8.1. Verified locally on the .NET 10.0.400 SDK: 239 of 239 tests pass under MTP, Cobertura XML is produced, and `csharpier check` and `dotnet format style --verify-no-changes` are clean. * Adopt the hub-hosted reusable workflow chain `spec/divergences.json` gives `build-release-task.yml`, `build-docker-task.yml` and `get-version-task.yml` a `retire` disposition, and the audit reported `publish-release.yml` missing its `plan` and `validate` jobs and `merge-bot-pull-request.yml` missing `merge-bot`. Both findings are the same migration, which the hub's `docs/reusable-workflows.md` tracks with PlexCleaner as an open checkbox on stages 1, 3 and 4. - `merge-bot-pull-request.yml` becomes the caller stub. The hub task is a superset of the two jobs carried here, and this repo passes no `rules` or `delete-branch` input: it has no tracker branch outside the built-in pairs, and `delete_branch_on_merge` is off fleet-wide. - `publish-release.yml` gains `plan` (the hub's `publish-plan-task`), which replaces the `ref_name in (main, develop)` job guard and now fails a dispatch from any other branch rather than skipping silently, and `validate`, which is the gate the retired local `build-release-task` used to run inside itself. `publish` calls the hub's `build-release-task`, and `publish-docker-readme` calls the hub's task for the Docker Hub overview, which the retired local Docker task used to push as a step. - `test-pull-request.yml` keeps its `push`-on-every-branch trigger and its aggregator, and its `smoke-build` now calls the hub release task. Both callers move to `permissions: {}` with a per-job `contents: read` grant, in place of the repository default of `write`. - `build-executable-task.yml` is deleted with them. It is not a hub-hosted file, but the hub's `dotnet-publish` default builds the same seven runtimes with the same MSBuild properties and archives the same `PlexCleaner.7z`, so no hook is needed. The `docker-prepare` default likewise emits this repo's exact tags, `./Docker/Dockerfile` context and cache repository. `validate-task.yml` stays carried: the hub's own unit-test step is the VSTest invocation Microsoft.Testing.Platform rejects (ptr727/ProjectTemplate#1088). `WORKFLOW.md` is reconciled against the new job graph, including all three flow diagrams, D0.4 for the pin contract, the D4.1 dispatch-guard change, D4.6 moving the validate gate to the caller, D9.5 naming a carried copy of a hub task a defect, and a new S15 trace for a hub-pin bump. `ARCHITECTURE.md`, `GOVERNANCE.md` "Repository Layout", `OPERATIONS.md` and `PlexCleaner.slnx` follow the same sweep. Verified locally: `actionlint` clean over the four workflows, markdownlint, cspell, ruff, ruff format, mypy and editorconfig-checker clean. * Exclude build output from the EditorConfig check The hub's `.editorconfig-checker.json` excludes the Python cache directories and nothing else, because editorconfig-checker normally scopes itself with `git ls-files` and never sees ignored build output. That fails in a git worktree, which is the layout `repo-worktree` mandates for every task: `.git` is a pointer file there, so `git` is unusable inside the linter's container and it falls back to walking the filesystem. It then reports 25 end-of-line errors against `.artifacts/**` `.pdb`, `.cache`, and the MTP-generated `SelfRegisteredExtensions.cs` after any local build. `.editorconfig-checker.json` is `intent` fidelity in `spec/files.json`, so this repo adds the three patterns its own `.gitignore` already lists. Reproduced before the change and clean after, in this worktree. * Select the test project with --project in the Docker build The Docker image build failed on this branch with "Specifying a project for 'dotnet test' should be via '--project'". Under Microsoft.Testing.Platform, which `global.json` now opts into, no 10.x SDK honors a positional project path, and the two SDKs this repo meets differ only in how loudly they say so. The 10.0.400 SDK the host pins ignores the path and resolves from the current directory, which is why this passed locally. The newer `dotnet-sdk-10.0` apt package the builder stage installs from `ubuntu:rolling` fails outright. The quiet arm is the worse one in the builder, since `/Builder` carries neither `PlexCleaner.slnx` nor a root project for that fallback to find, so `--project` is required there rather than merely tidier. Verified by rebuilding the builder stage with `--no-cache-filter builder`: 239 of 239 tests pass inside the container. * Answer the first review round Three findings from the review, each verified before acting on it. `createschema` now writes `PlexCleaner.schema.json` with LF and a trailing newline, so a regeneration is byte-identical to the tracked file instead of a whole-file working-tree diff against the repo's new LF default. The line ending is set on that one write rather than on `ConfigFileJsonContext`, which was the first attempt. That context also writes the user's own `PlexCleaner.json`, through `WriteDefaultsToFile`, `OpenAndUpgrade`'s schema upgrade, and the `RegisterInvalidFiles` ignore-list update, so flipping it there would silently rewrite a Windows user's settings file to LF on the next run that upgrades it. `PlexCleaner.defaults.json` is not a beneficiary either way: its comments are hand-restored and its `UseSystem`/`AutoUpdate` values are platform-dependent, so it cannot be regenerated in place at all. The sidecar, tool-info and process-result contexts keep CRLF, unchanged. Verified: `createschema` leaves the tree clean, and `defaultsettings` against a scratch path still writes CRLF. `publish-release.yml`'s comment above the `plan` job said a feature-branch dispatch resolves `publish == false`. The hub task exits with an `::error::` instead, which this branch's own D4.1 already states. `RegressionTests/README.md` said Python source is CRLF, which this branch made untrue. * Deserialize idet theory data to the type xUnit asked for `FfMpegIdetInfoSerializer.Deserialize` called `JsonSerializer.Deserialize<FfMpegIdetInfoSerializer>`, deserializing into the serializer class rather than into `FfMpegIdetInfo`, and fell back to a bare `new object()` on null. Both arms return something the `Parse_Idet_Parse_Test` theory's `FfMpegIdetInfo` parameter cannot accept, so any execution path that round-trips theory data through the registered serializer fails at the parameter bind rather than in the serializer. Proved by reflection against the built test assembly: with the old code `Deserialize(typeof(FfMpegIdetInfo), json)` returns `PlexCleanerTests.FfMpegIdetInfoSerializer`, and with the new code it returns `PlexCleaner.FfMpegIdetInfo`. The whole suite is unaffected either way, 239 of 239 before and after, because the in-process run hands theory data to the test directly rather than through the serializer, which is why this has sat latent. Pre-existing rather than introduced here, but the file is in the test project this branch migrates to Microsoft.Testing.Platform, the execution host that decides whether the round-trip happens at all, and the fix is one line. Raised by Copilot as a body-only finding on all three review rounds.
xunit.v3 4.0.0 pulls in Microsoft.Testing.Platform.MSBuild, and the .NET 10 SDK refuses to run it under the VSTest
dotnet testtarget:Testing with VSTest target is no longer supported by Microsoft.Testing.Platform on .NET 10 SDK and later.This is what's failing CI on the nuget-deps dependabot PRs (#323, #324), which propose this same xunit.v3 bump.Adds
global.jsonto optdotnet testinto native MTP mode (the SDK's recommended path over the legacyTestingPlatformDotnetTestSupportVSTest bridge, which Microsoft says MTP v2 drops for .NET 10). Dropsxunit.runner.visualstudio, the now-unneeded VSTest adapter. Swapscoverlet.collectorforcoverlet.MTP, since the VSTest coverlet data collector is silently ignored under MTP (MTP0001); updates the CI unit-test step from--collect:"XPlat Code Coverage"to--coverlet --coverlet-output-format cobertura, which still lands Cobertura XML at./coveragefor the existing Codecov upload step. Also applies the other package bumps #323/#324 propose (AwesomeAssertions, Microsoft.CodeAnalysis.CSharp, Microsoft.NET.Test.Sdk, xunit.analyzers) so those PRs become no-ops once this merges.Verified locally:
dotnet testpasses 310/310 and produces Cobertura output at./coverage; csharpier check,dotnet format style, the offline codegen-drift regenerate, and editorconfig-checker are all clean. Reviewed with the local-strict-review skill (adversarial pass, full diff + consumer files) before opening: no findings.Summary by CodeRabbit
Tests
Chores