First-class Microsoft.Testing.Platform (MTP) support for `dotnet test`
Maintainer thường phản hồi trong vòng 5 ngày
@dennisdoomen đang làm issue này rồi.
Từ ngày 12/10/2026.
Đánh giá
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức phù hợp với người mới
- 35/100
- Loại issue
- Tính năng
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- csharp
- Lĩnh vực
- build-system, cli, testing
Hướng nghiên cứu
Bắt đầu với src/Fallout.Build/FalloutBuild.Statics.cs và src/Fallout.Common/Tools/DotNet/DotNet.json, sau đó theo dõi ArgumentAttribute và ToolOptions.GetListArguments() để hiểu các đối số được tạo ra. Xem lại tài liệu tham chiếu MTP CLI được liên kết trong issue và định nghĩa Test hiện có. Hoàn tất khi các thiết lập kiểm thử MTP hiển thị các tùy chọn được yêu cầu, các đối số mở rộng được chuyển tiếp an toàn sau '--' và các kiểm thử do MTP lưu trữ không còn thất bại trong quá trình khởi tạo FalloutBuild.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Summary
.NET 10 dropped VSTest support for test projects that use the Microsoft.Testing.Platform (MTP) runner (xunit.v3, and MSTest/NUnit once they support MTP). Opting in via global.json's "test": {"runner": "Microsoft.Testing.Platform"} hits two bugs in Fallout and is missing a feature. Found while migrating a real repo (an Azure Functions service and its build-automation library, both xunit v2 → xunit.v3) on Fallout.Common/Fallout.Build 10.4.0.
1. FalloutBuild.GetBuildAssemblyFile() throws under MTP
src/Fallout.Build/FalloutBuild.Statics.cs checks the entry assembly's name against a hardcoded allowlist (testhost, ReSharperTestRunner) to recognize a test host, and asserts if it doesn't match. This runs in FalloutBuild's static constructor, so it fires the moment anything touches a static member of FalloutBuild — including FalloutBuild.RootDirectory, which some generated settings classes read in a field initializer.
Under classic VSTest, the entry assembly is testhost, which the allowlist covers. Under MTP, the test assembly itself is the entry assembly (MTP self-hosts it), so its name matches nothing on the list and the assert throws TypeInitializationException — from unit tests that never reference FalloutBuild directly, only a settings class that happens to depend on it.
Suggested fix: drop the name check. If no type in the entry assembly derives from FalloutBuild, that already proves it isn't a build assembly. The name check adds nothing and can never list every current and future test host.
2. No typed support for MTP's dotnet test CLI in DotNetTestSettings
MTP's dotnet test is a separate, non-overlapping CLI surface from classic VSTest. DotNetTestSettings only generates the VSTest surface — Loggers (--logger), RunSettings, BlameMode, DataCollector, TestAdapterPath — none of which exist under MTP. MTP instead has its own flags with no VSTest equivalent: --coverage / --coverage-output(-format) (from Microsoft.Testing.Extensions.CodeCoverage), --report-trx / --report-trx-filename (from Microsoft.Testing.Extensions.TrxReport), --minimum-expected-tests, --diagnostic-output-directory, --treenode-filter, --zero-tests-policy.
Suggested fix: a dedicated MTP settings class, generated from a new TestMtp.json (or an "mtp": true variant of the existing Test definition):
{
"help": "Runs tests using the Microsoft.Testing.Platform (MTP) native runner, opted into via global.json's 'test.runner'. VSTest and MTP are separate, non-overlapping dotnet test CLI surfaces (see https://learn.microsoft.com/dotnet/core/tools/dotnet-test-mtp), so this is a distinct task from Test rather than an extension of it.",
"postfix": "TestMtp",
"commonPropertySets": ["restore", "restore-runtime"],
"definiteArgument": "test",
"settingsClass": {
"properties": [
{ "name": "ProjectFile", "type": "string", "format": "--project {value}", "help": "Path to the test project. Mutually exclusive with SolutionFile and TestModules." },
{ "name": "SolutionFile", "type": "string", "format": "--solution {value}", "help": "Path to the test solution. Mutually exclusive with ProjectFile and TestModules." },
{ "name": "TestModules", "type": "string", "format": "--test-modules {value}", "help": "Glob selecting already-built test module assemblies. Mutually exclusive with ProjectFile and SolutionFile." },
{ "name": "Configuration", "type": "string", "format": "--configuration {value}" },
{ "name": "Framework", "type": "string", "format": "--framework {value}" },
{ "name": "Filter", "type": "string", "format": "--filter {value}" },
{ "name": "Verbosity", "type": "DotNetVerbosity", "format": "--verbosity {value}" },
{ "name": "NoBuild", "type": "bool", "format": "--no-build" },
{ "name": "NoRestore", "type": "bool", "format": "--no-restore" },
{ "name": "ResultsDirectory", "type": "string", "format": "--results-directory {value}" },
{ "name": "MinimumExpectedTests", "type": "int?", "format": "--minimum-expected-tests {value}" },
{ "name": "DiagnosticOutputDirectory", "type": "string", "format": "--diagnostic-output-directory {value}" },
{ "name": "TreenodeFilter", "type": "string", "format": "--treenode-filter {value}" },
{ "name": "ZeroTestsPolicy", "type": "ZeroTestsPolicy", "format": "--zero-tests-policy {value}" },
{ "name": "Coverage", "type": "bool", "format": "--coverage", "help": "Requires the Microsoft.Testing.Extensions.CodeCoverage package." },
{ "name": "CoverageOutputFormat", "type": "string", "format": "--coverage-output-format {value}" },
{ "name": "CoverageOutput", "type": "string", "format": "--coverage-output {value}" },
{ "name": "ReportTrx", "type": "bool", "format": "--report-trx", "help": "Requires the Microsoft.Testing.Extensions.TrxReport package." },
{ "name": "ReportTrxFileName", "type": "string", "format": "--report-trx-filename {value}" },
{ "name": "ExtensionArguments", "type": "List<string>", "format": "{value}", "prefix": "--", "position": -1, "help": "Raw arguments forwarded verbatim to registered MTP extensions, placed after a literal '--' as Microsoft's dotnet test docs recommend, to avoid argument-binding ambiguity with recognized options." }
]
}
}
Plus the properties that already exist on both surfaces: Configuration, Framework, Filter, Verbosity, NoBuild, NoRestore, ResultsDirectory.
What this buys consumers
DotNetTestMtp(s => s
.SetProjectFile(Solution.MyProject_Specs)
.SetConfiguration(Configuration.Debug)
.SetFilter("Category!=Integration")
.EnableReportTrx()
.SetReportTrxFileName(ArtifactsDirectory / "UnitTestResults.trx")
.EnableCoverage()
.SetCoverageOutputFormat("cobertura")
.SetCoverageOutput(ArtifactsDirectory / "UnitTests.cobertura.xml"));
A typed, IntelliSense-able API matching the Set/Enable/Disable conventions the generator already produces for every other tool — instead of raw-string AddProcessAdditionalArguments("--project", ..., "--coverage", ...).
3. Safer forwarding of extension arguments
Microsoft's docs recommend putting MTP-extension arguments after a literal --, since a recognized option between an unrecognized option and its value can change how the leftover tokens bind. RunSettings already does something similar ("position": -1) but is VSTest-runsettings-specific. The generic ProcessAdditionalArguments escape hatch doesn't guarantee -- placement either, even though it's already appended last in every generated argument list.
"prefix": "--" in the JSON above is a new schema field for exactly this case: when set and the list is non-empty, ToolOptions.GetArgument should emit that one literal token before the per-value tokens, keeping each forwarded token its own argv entry. Two existing list mechanisms come close but aren't right: a Separator-joined list glues all values into one token (wrong — a forwarded path can contain spaces), and a plain repeated-format list (like Loggers) repeats the whole format, literal text included, once per value instead of once total.
4. PublishCodeCoverage silently accepts an unsupported glob (secondary, not MTP-specific)
AzurePipelines.Instance.PublishCodeCoverage(tool, summaryFile, reportDirectory) accepts a glob in summaryFile, but Azure Pipelines' ##vso[codecoverage.publish] command has never supported wildcards. It builds fine and fails only at pipeline run time, with a cryptic "file does not exist" error. A guard clause rejecting */? (or at least an XML-doc callout) would catch this at build time instead.
Notes
- See #19 for the parallel investigation, including confirmation that SDK 10.0.302+ (this repo's pinned version) restores the positional project path under MTP.
- Ngôn ngữ chính
- C#
- Star
- 169
- Fork
- 21
- Merge trung bình
- 3 ngày 7 giờ
- Pull request đã merge (30 ngày)
- 9
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của Fallout-build/Fallout
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
Fallout-build/Fallout#708 ·
Maintainer thường phản hồi trong vòng 5 ngày
-
.\build.ps1 in its current state isn't capable for :add-package commandCó thể đã có người làm @ITaluone đã nhận 36 ngày trước. Đang mởbug needs-triage
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
Fallout-build/Fallout#670 ·
Maintainer thường phản hồi trong vòng 5 ngày
-
PR #640 causes very annoying .NET SDK not foundCó thể đã có người làm @ITaluone đã nhận 1 ngày trước. Đang mởbug needs-triage
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 86/100
Fallout-build/Fallout#669 · 3 bình luận ·
Maintainer thường phản hồi trong vòng 5 ngày
-
CD enhancement
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Fallout-build/Fallout#649 ·
Maintainer thường phản hồi trong vòng 5 ngày
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Fallout-build/Fallout#630 · 1 bình luận · 1 reaction ·
Maintainer thường phản hồi trong vòng 5 ngày
Tất cả issue của Fallout-build/Fallout
Issue tương tự
-
enhancement needs triage
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
elastic/docs-builder#4495 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
ASP.NET Core container quickstart: `docker-run: debug` task sample is missing its closing braceĐang mở
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 88/100
microsoft/vscode-docs#10499 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
-
🌶️ hot chocolate
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
ChilliCream/graphql-platform#10567 ·
Maintainer thường phản hồi trong vòng 1 ngày