From 4c0e53928f46286aa80f0d5dd0907ba33ba49173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Sun, 22 Feb 2026 12:22:25 +0100 Subject: [PATCH] Copy per-test result files to results directory in TRX reports When using MTP, files added via TestContext.AddResultFile were not copied to the results directory. The TRX file referenced them with absolute paths, making the results directory non-self-contained and causing warnings in Azure DevOps builds. Changes: - Copy per-test FileArtifactProperty files into the results directory under {runDeploymentRoot}/In/{executionId}/{MachineName}/ - Use relative paths in TRX elements instead of absolute paths - Replace unbounded while(true) loops with bounded for loops (max 10 attempts) when resolving duplicate file names, in both the new and existing artifact copy methods Fixes #6782 --- .../TrxReportEngine.cs | 53 +++++++++++++------ .../TrxTests.cs | 4 +- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs index 7ffe805ea8..403fbc2006 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs @@ -169,7 +169,7 @@ public TrxReportEngine(IFileSystem fileSystem, ITestApplicationModuleInfo testAp isFileNameExplicitlyProvided = false; } - AddResults(testAppModule, testRun, out XElement testDefinitions, out XElement testEntries, out string uncategorizedTestId, out bool hasFailedTests); + (XElement testDefinitions, XElement testEntries, string uncategorizedTestId, bool hasFailedTests) = await AddResultsAsync(testAppModule, testRun, runDeploymentRoot).ConfigureAwait(false); testRun.Add(testDefinitions); testRun.Add(testEntries); AddTestLists(testRun, uncategorizedTestId); @@ -346,25 +346,40 @@ private async Task AddResultSummaryAsync(XElement testRun, string resultSummaryO await AddArtifactsToCollectionAsync(_artifactsByExtension, collectorDataEntries, runDeploymentRoot).ConfigureAwait(false); } + private async Task CopyTestResultFileAndReturnRelativePathAsync(FileInfo artifact, string runDeploymentRoot, string executionId) + { + string testResultDirectory = Path.Combine(_configuration.GetTestResultDirectory(), runDeploymentRoot, "In", executionId, _environment.MachineName); + if (!Directory.Exists(testResultDirectory)) + { + Directory.CreateDirectory(testResultDirectory); + } + + string fileName = artifact.Name; + + string destination = Path.Combine(testResultDirectory, fileName); + + // If the file already exists, append a number to the end of the file name + for (int nameCounter = 1; File.Exists(destination) && nameCounter <= 10; nameCounter++) + { + destination = Path.Combine(testResultDirectory, $"{Path.GetFileNameWithoutExtension(fileName)}_{nameCounter}{Path.GetExtension(fileName)}"); + } + + await CopyFileAsync(artifact, new FileInfo(destination)).ConfigureAwait(false); + + return Path.Combine(_environment.MachineName, Path.GetFileName(destination)); + } + private async Task CopyArtifactIntoTrxDirectoryAndReturnHrefValueAsync(FileInfo artifact, string runDeploymentRoot) { string artifactDirectory = CreateOrGetTrxArtifactDirectory(runDeploymentRoot); string fileName = artifact.Name; string destination = Path.Combine(artifactDirectory, fileName); - int nameCounter = 0; // If the file already exists, append a number to the end of the file name - while (true) + for (int nameCounter = 1; File.Exists(destination) && nameCounter <= 10; nameCounter++) { - if (File.Exists(destination)) - { - nameCounter++; - destination = Path.Combine(artifactDirectory, $"{Path.GetFileNameWithoutExtension(fileName)}_{nameCounter}{Path.GetExtension(fileName)}"); - continue; - } - - break; + destination = Path.Combine(artifactDirectory, $"{Path.GetFileNameWithoutExtension(fileName)}_{nameCounter}{Path.GetExtension(fileName)}"); } await CopyFileAsync(artifact, new FileInfo(destination)).ConfigureAwait(false); @@ -412,17 +427,17 @@ private static void AddTestLists(XElement testRun, string uncategorizedTestId) testRun.Add(testLists); } - private void AddResults(string testAppModule, XElement testRun, out XElement testDefinitions, out XElement testEntries, out string uncategorizedTestId, out bool hasFailedTests) + private async Task<(XElement TestDefinitions, XElement TestEntries, string UncategorizedTestId, bool HasFailedTests)> AddResultsAsync(string testAppModule, XElement testRun, string runDeploymentRoot) { var results = new XElement("Results"); // Duplicate test ids are not allowed inside the TestDefinitions element. - testDefinitions = new XElement("TestDefinitions"); + var testDefinitions = new XElement("TestDefinitions"); var uniqueTestDefinitionTestIds = new HashSet(); - testEntries = new XElement("TestEntries"); - uncategorizedTestId = "8C84FA94-04C1-424b-9868-57A2D4851A1D"; - hasFailedTests = false; + var testEntries = new XElement("TestEntries"); + string uncategorizedTestId = "8C84FA94-04C1-424b-9868-57A2D4851A1D"; + bool hasFailedTests = false; foreach (TestNodeUpdateMessage nodeMessage in _testNodeUpdatedMessages) { TestNode testNode = nodeMessage.TestNode; @@ -534,9 +549,11 @@ private void AddResults(string testAppModule, XElement testRun, out XElement tes foreach (FileArtifactProperty testFileArtifact in testNode.Properties.OfType()) { resultFiles ??= new XElement("ResultFiles"); + string relativePath = await CopyTestResultFileAndReturnRelativePathAsync( + testFileArtifact.FileInfo, runDeploymentRoot, executionId).ConfigureAwait(false); resultFiles.Add(new XElement( "ResultFile", - new XAttribute("path", testFileArtifact.FileInfo.FullName))); + new XAttribute("path", relativePath))); } if (resultFiles is not null) @@ -666,6 +683,8 @@ private void AddResults(string testAppModule, XElement testRun, out XElement tes } testRun.Add(results); + + return (testDefinitions, testEntries, uncategorizedTestId, hasFailedTests); } private static string AddTestSettings(XElement testRun, string testRunName) diff --git a/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/TrxTests.cs b/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/TrxTests.cs index 4769158d12..c61273278c 100644 --- a/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/TrxTests.cs +++ b/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/TrxTests.cs @@ -473,11 +473,11 @@ public async Task TrxReportEngine_GenerateReportAsync_WithArtifactsByTestNode_Tr string trxContentsPattern = @" - + "; - Assert.IsTrue(Regex.IsMatch(trxContent, trxContentsPattern)); + Assert.IsTrue(Regex.IsMatch(trxContent, trxContentsPattern), trxContent); } [TestMethod]