Repository navigation
Use a fallback class name in TRX reports for nodes without type info - #12466
Amaury Levé (Evangelink) wants to merge 1 commit into
Conversation
TrxReportEngine.GetClassAndMethodName threw InvalidOperationException when a final test node had neither TrxFullyQualifiedTypeNameProperty nor TestMethodIdentifierProperty. Test frameworks that only report a state (and timing) could therefore not produce a TRX report at all. The TRX schema requires a class name, so report "DefaultClassName" in that case. This is the same last-resort name the VSTest TRX logger uses (Converter.GetTestClassName). The method name keeps falling back to the node display name. Nodes that have either property are unaffected, and validation of the other properties (for example duplicate singleton properties) is unchanged. No public API change. Document the required (final state), recommended (type name or method identifier) and optional TRX properties, including the fallback and the duplicate-property failure, on ITrxReportCapability and in the Abstractions package readme. Related to #12436 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This is something we could also do on the xUnit.net side, though it's a little trickier because this is not necessarily TRX specific. Should we always publish one of these (with a synthetic name) even outside the scope of TRX? |
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues were identified for the change enabling TRX reporting of test results without type information.
No open issues 🎉
What changed in this PR
This PR makes Microsoft.Testing.Platform’s TRX reporter accept final test results without type information, addressing the source-less test scenario in #12436. It preserves existing identity precedence and leaves broader reporting-failure handling out of scope.
Changes:
- Uses
DefaultClassNameinstead of throwing when both identity properties are absent, retaining the display-name fallback for the method. - Updates API and package documentation to explain required, recommended, and optional properties.
- Adds four test cases covering the fallback through report generation and streaming, plus unchanged identity precedence.
Confidence at a glance
🟡 TRX behavior, documentation, and tests — execution not independently verified
Test-value evidence: For revision a4eef71cb38972a0b0a9219652b02c5fd48bb640, the PR reports two fallback tests failing with production files restored from e92578e3a4c076150c8b4174fbe04bd5b9df91d6, then all four new cases passing with the fix. The two identity-precedence cases reportedly pass in both states. Tests were not rerun during this review; packed-package acceptance tests and Linux/macOS validation remain unperformed.
| File | Description |
|---|---|
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/TrxTests.cs |
Tests fallback output and existing identity precedence. |
test/UnitTests/Microsoft.Testing.Extensions.UnitTests/TrxConsumerBehaviorTests.cs |
Tests the fallback through the streaming report lifecycle. |
src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.Results.cs |
Replaces the missing-identity exception with the fallback. |
src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs |
Defines the shared fallback class-name constant. |
src/Platform/Microsoft.Testing.Extensions.TrxReport.Abstractions/PACKAGE.md |
Summarizes property requirements and fallback behavior. |
src/Platform/Microsoft.Testing.Extensions.TrxReport.Abstractions/ITrxReportCapability.cs |
Updates XML documentation for optional identity metadata. |
Review effort: Balanced
|
Preliminary test-triage analysis — Azure Pipelines build 1680059 Triggered by the failed leg Failing tests on the Linux Release leg (CTRF, retry attempts 1 and 2 both failed):
Not counted: the Windows App Model Diagnostics failures ( Next step: check whether the NativeAOT version mismatch also appears on main or other PRs, and re-run the leg to see if
|
Pull request
Summary
Related to #12436 ("Which properties are required for Microsoft.Testing.Extensions.TrxReport?"). This PR does not close it; see "Out of scope" below.
Problem. A
TestNodethat has a final state but neitherTrxFullyQualifiedTypeNamePropertynorTestMethodIdentifierPropertymakesTrxReportEngine.GetClassAndMethodNamethrowInvalidOperationException("The test framework '...' with UID '...' does not support the 'ITrxReportCapability' leading to missing or incomplete TRX reports"). That is the shape reported by frameworks without source or type information (the issue notes xUnit.net supports test cases that don't come from source code at all). With the released 2.5.3 package the exception escapes report generation: the test host process crashes, the test application exits with code 7 and the TRX contains no test results (reproduced below).Change. When neither property is present, the class name is now the documented fallback
DefaultClassName, and the method name is the node's display name (already the behavior of the caller,testMethodName ?? testResultDisplayName).TrxReportEngine.cs/TrxReportEngine.Results.cs: newprivate const string FallbackClassName = "DefaultClassName".GetClassAndMethodNamereturns(FallbackClassName, null)instead of throwing when there is no method identifier (and noTrxFullyQualifiedTypeNameProperty). The method no longer uses instance state, so it becamestatic, and the now-unused resourcesusingwas removed. TheTrxReportFrameworkDoesNotSupportTrxReportCapabilityresource is still used byTrxDataConsumerfor the "capability not supported" warning, so there are no.resx/.xlfchanges.TrxFullyQualifiedTypeNamePropertystill wins, then namespace + type name from the method identifier); the exceptions for a missing final state and for duplicate singleton properties.ITrxReportCapabilityXML documentation and theMicrosoft.Testing.Extensions.TrxReport.Abstractionspackage readme (PACKAGE.md) now say which properties are required (the final state), recommended (TrxFullyQualifiedTypeNamePropertyorTestMethodIdentifierProperty, with this fallback), optional (everything else), and that duplicates of a singleton property make report generation fail. This extends the documentation added in Document required and optional TRX report metadata #12458 and removes its now-stale sentence "Omitting both identity properties causes report generation to fail."PublicAPI.*.txt, resources andglobal.jsonare untouched. No changelog entry:docs/Changelog-Platform.mdis updated in bulk at release time and recent TRX fix PRs don't touch it.Out of scope (why this is "Related to", not "Fixes"). The issue discussion also describes (1) a second crash: repeated results with the same UID that mix an explicit
TrxTestDefinitionNamewith an omitted one whose display-name fallback differs, and (2) a proposal to report genuine contract violations as controlled reporting failures (preserve recoverable results, mark the run unsuccessful, record the affected IDs inRunInfo, nonzero exit code). This PR implements the first part of the stated preference ("support source-less tests through a documented fallback") only.Design and decision record
This is a bug fix, not a new feature, so there is no separate design document; the rationale is here.
TestMethod/@classNameisuse="required"in the TRX schema (vstst.xsd, vendored copy; the XSD ships with Visual Studio and is not in this repo), so the report needs some class name, but a test node is not obliged to have one. The engine already accepts a missing method identifier wheneverTrxFullyQualifiedTypeNamePropertyis present (the method name falls back to the display name, which is how NUnit is reported today), so "neither property" was the only remaining hard failure. Failing the whole report, with exit code 7 and an empty TRX, for a node that has a valid final state is disproportionate.NOTEabove that display-name fallback ended with "However, this is very wrong and we probably should fail if TestMethodIdentifierProperty isn't present." This PR deliberately does the opposite for source-less nodes (the method-name fallback itself is unchanged), so I replaced that sentence with the new decision instead of leaving a comment that contradicts the code. Easy to restore if you would rather keep the original remark.DefaultClassName. It is the last-resort class name of the VSTest TRX logger:Converter.GetTestClassNamestarts withvar className = "DefaultClassName";(microsoft/vstest@0f5ac692,Converter.cs#L593-L595). TRX consumers that already handle VSTest output see the same placeholder. There was no existing constant or convention insrc/Platformto reuse.TrxFullyQualifiedTypeNameProperty, which keeps precedence (the issue author says they will do that framework-side).DefaultClassNameclass; each is still its own test definition with its own test id. An empty fully qualified type name, or a method identifier with an empty type name, can still produce an empty class name (pre-existing and unchanged).Demonstration
Real composed product, no mocks: a minimal Microsoft Testing Platform host (source at the end of this section) whose test framework reports only a final state and timing, the shape from the issue, plus the real
Microsoft.Testing.Extensions.TrxReportassemblies. The same host source was built twice in a scratch project outside the repo (nothing of it is committed): once against the released package, once against this branch's build output.Before: released
Microsoft.Testing.Extensions.TrxReport2.5.3 from nuget.org (the shipped bits users have, not this PR's merge base):(
host exit codeis$LASTEXITCODEof the launched executable, i.e. the parent test application; the child test host process is the one that crashed with-532462766.) The generated TRX hasResultSummary outcome="Failed", all counters 0, noUnitTest/UnitTestResultentries, and a singleRunInfoerror "Test host process pid: ... crashed.".After: this branch's Debug build outputs (net10.0; content identical to
a4eef71cb38972a0b0a9219652b02c5fd48bb640), same input:Nodes that do carry type information are unchanged. With
E2E_SHAPE=all(default) the host publishes three nodes; the run exits 0 with 3 passed,ResultSummaryCompleted, and everyUnitTestResultPassedwith duration00:00:02.0000000:TestMethod/@classNameTestMethod/@namebareDefaultClassNamebare display namefqtnTrxFullyQualifiedTypeNameProperty("Fqn.Namespace.FqnType")Fqn.Namespace.FqnTypefqtn display nameidentTestMethodIdentifierProperty("asm", "My.Namespace", "MyType", "MyMethod", ...)My.Namespace.MyTypeMyMethodProvenance. "Before" used the assemblies from the 2.5.3 package (product version
2.5.3+0b1d8b5f7b2186835e2cf26292f3478f7cca087e), referenced with<PackageReference Include="Microsoft.Testing.Extensions.TrxReport" Version="2.5.3" />. "After" referenced this branch's Debug net10.0 outputs byHintPath(Microsoft.Testing.Platform,Microsoft.Testing.Extensions.TrxReport.Abstractions,Microsoft.Testing.Extensions.TrxReport); the SHA-256 of the scratch copies equals the build output:Microsoft.Testing.Extensions.TrxReport.dllAEDE41299CA9FFD6F196A795C6FA2E7A14F55FB243AB27B8FEA9FC0CB274B2F2Microsoft.Testing.Extensions.TrxReport.Abstractions.dllD8B0AE9D1FA455CBC0E835F62261CF8579E36A745D89312C030796708CA5968EMicrosoft.Testing.Platform.dllD7FEAB8A28909CA3B1F25CDE6832B2ED3D00E49D99FFDA912FDB2012DC4E5972Scratch host source (
Program.cs)Validation and regression proof
Environment: Windows, PowerShell. The repo-pinned SDK
12.0.100-alpha.1.26556.115was installed byeng\build.ps1into the git-ignored repo-local.dotnetfolder (the machine's global SDK is 11.0.100). Source state: this branch ata4eef71cb38972a0b0a9219652b02c5fd48bb640. The binaries were built before committing, from a working tree whose content is identical to the commit (file hashes and timestamps checked; the worktree is clean at the commit).Build (warnings are errors in this repo):
Result:
Build succeeded.with0 Warning(s)and0 Error(s), exit code 0. This buildsMicrosoft.Testing.Extensions.TrxReportandMicrosoft.Testing.Extensions.TrxReport.Abstractions(netstandard2.0, net10.0, net11.0) and the unit-test project (net462, net472, net10.0, net11.0).-installWindowsSdkwas not used.Tests (the test app is an MTP executable; run directly with the repo-local runtime):
The 11 skips on net10.0/net11.0 are pre-existing Linux-only link/merge tests (
Merge_Linux*,MergeTool_Linux*,TemporarySibling_Linux*,TrxMerge_DanglingDestinationLink,TrxMerge_ExistingDestinationLink) reported asNotExecutedon Windows; none relate to this change.Red/green. On the same tree, the two production files (
TrxReportEngine.cs,TrxReportEngine.Results.cs) were restored toorigin/main(e92578e3a4c076150c8b4174fbe04bd5b9df91d6) with the new tests kept, rebuilt (0 warnings, 0 errors) and the 4 new cases run on net10.0. Red:Green: with the fix restored and rebuilt, the same 4 cases give
total: 4, failed: 0, succeeded: 4, skipped: 0(table above).Pitfall found while doing this: right after restoring the fixed files, MSBuild's incremental compile skipped them (the restored files kept old timestamps), so a first "green" run still executed the old code and reproduced the throw. I touched the files, rebuilt (a real 1m58s recompile), confirmed the assembly timestamp, and re-ran; every green number above comes from fresh binaries.
Static checks:
git diff --checkis clean;git diff --stat origin/main..HEADis 6 files, 114 insertions, 7 deletions (3 C# files and onePACKAGE.mdundersrc/Platform/, 2 test files); noPublicAPI.*.txt,.resx/.xlf,global.jsonor.lock.ymlfile is touched.Test-value evidence
Tested revision: this branch; counterfactual: the same tree with the two production files restored to
origin/main.TrxTests.TrxReportEngine_GenerateReportAsync_WithOnlyStateAndTiming_UsesFallbackClassNameAndDisplayNameAsMethodNameCompleted,classNameDefaultClassName,name= display name, duration00:00:03.0000000InvalidOperationExceptionfromGetClassAndMethodName(Results.cs:284)TrxConsumerBehaviorTests.Session_NodeWithOnlyStateAndTimingStreamsAndReportsFallbackClassNameDefaultClassName, display-name method, duration00:00:02.0000000,Completed, no output-device warning, one publicationTrxTests.TrxReportEngine_GenerateReportAsync_WithTestMethodIdentifier_ClassNameIsFullyQualifiedTypeNameElseNamespaceAndType(2DataRows)TrxFullyQualifiedTypeNamePropertywins; otherwise namespace + type name from the method identifier; method nameMyMethodRed:
total: 4, failed: 2, succeeded: 2(the two state-only tests). Green:total: 4, failed: 0, succeeded: 4. Existing tests of the unchanged failure paths (duplicate singleton properties:TrxPropertyProjectionTests.Extract_DuplicateSingleton_RejectsAmbiguousReportMetadataandTrxTestResultExtractorTests.Extract_DuplicateSingletonProperty_Throws; non-final state:TrxTestResultExtractorTests.Extract_InProgressState_ThrowsUnreachableException) are untouched and pass in the "All TRX-related test classes" runs.Not validated / evidence gaps: CI has not run (draft). Linux and macOS were not run. The acceptance/integration test projects were not run (they need packed packages); the demonstration above runs the real host against build output, not packed packages. Only the TRX-related classes of the unit-test project were run, not the whole project. The second crash and controlled-failure reporting described under "Out of scope" are not covered.