diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs index 831e83767d..85dfe942d0 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs @@ -360,9 +360,9 @@ private void AddResultSummary(XElement testRun, string resultSummaryOutcome, str AddArtifactsToCollection(_artifactsByExtension, collectorDataEntries, runDeploymentRoot); } - private string CopyArtifactIntoTrxDirectoryAndReturnHrefValue(FileInfo artifact, string runDeploymentRoot) + private string CopyArtifactIntoTrxDirectoryAndReturnHrefValue(FileInfo artifact, string runDeploymentRoot, string? relativeResultsDirectory = null) { - string artifactDirectory = CreateOrGetTrxArtifactDirectory(runDeploymentRoot); + string artifactDirectory = CreateOrGetTrxArtifactDirectory(runDeploymentRoot, relativeResultsDirectory); string fileName = artifact.Name; string destination = Path.Combine(artifactDirectory, fileName); @@ -386,9 +386,11 @@ private string CopyArtifactIntoTrxDirectoryAndReturnHrefValue(FileInfo artifact, return Path.Combine(_environment.MachineName, Path.GetFileName(destination)); } - private string CreateOrGetTrxArtifactDirectory(string runDeploymentRoot) + private string CreateOrGetTrxArtifactDirectory(string runDeploymentRoot, string? relativeResultsDirectory = null) { - string directoryName = Path.Combine(_configuration.GetTestResultDirectory(), runDeploymentRoot, "In", _environment.MachineName); + string directoryName = relativeResultsDirectory is null + ? Path.Combine(_configuration.GetTestResultDirectory(), runDeploymentRoot, "In", _environment.MachineName) + : Path.Combine(_configuration.GetTestResultDirectory(), runDeploymentRoot, "In", relativeResultsDirectory, _environment.MachineName); if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); @@ -580,7 +582,7 @@ private SummaryCounts AddResults(TestNodeUpdateMessage[] testNodeUpdateMessages, { resultFiles ??= new XElement("ResultFiles"); - string href = CopyArtifactIntoTrxDirectoryAndReturnHrefValue(testFileArtifact.FileInfo, runDeploymentRoot); + string href = CopyArtifactIntoTrxDirectoryAndReturnHrefValue(testFileArtifact.FileInfo, runDeploymentRoot, executionId); resultFiles.Add(new XElement( "ResultFile", new XAttribute("path", href))); diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/TrxTests.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/TrxTests.cs index 956bd745bf..fe377cc261 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/TrxTests.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/TrxTests.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Xml.Linq; + namespace Microsoft.Testing.Platform.Acceptance.IntegrationTests; [TestClass] @@ -36,6 +38,38 @@ public async Task Trx_WhenReportTrxIsSpecified_TrxReportIsGeneratedInDefaultLoca await AssertTrxReportWasGeneratedAsync(testHostResult, trxPathPattern, 1); } + [DynamicData(nameof(TargetFrameworks.AllForDynamicData), typeof(TargetFrameworks))] + [TestMethod] + public async Task Trx_WhenReportTrxAndResultsDirectoryAreSpecifiedWithArtifact_ArtifactIsCopiedUnderRelativeResultsDirectory(string tfm) + { + string fileName = Guid.NewGuid().ToString("N"); + string testResultsPath = Path.Combine(AssetFixture.TargetAssetPath, Guid.NewGuid().ToString("N")); + var testHost = TestInfrastructure.TestHost.LocateFrom(AssetFixture.TargetAssetPath, TestAssetFixture.AssetName, tfm); + TestHostResult testHostResult = await testHost.ExecuteAsync( + $"--report-trx --report-trx-filename {fileName}.trx --results-directory \"{testResultsPath}\"", + new() { ["WITH_ARTIFACT"] = "1" }, + cancellationToken: TestContext.CancellationToken); + + testHostResult.AssertExitCodeIs(ExitCode.Success); + + string[] trxFiles = Directory.GetFiles(testResultsPath, $"{fileName}.trx", SearchOption.AllDirectories); + Assert.HasCount(1, trxFiles, $"Expected exactly one trx file but found {trxFiles.Length}: {string.Join(", ", trxFiles)}"); + + var trxDocument = XDocument.Parse(File.ReadAllText(trxFiles[0])); + XNamespace ns = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010"; + XElement unitTestResult = trxDocument.Descendants(ns + "UnitTestResult").Single(); + string relativeResultsDirectory = unitTestResult.Attribute("relativeResultsDirectory")!.Value; + string resultFilePath = unitTestResult.Descendants(ns + "ResultFile").Single().Attribute("path")!.Value; + string runDeploymentRoot = trxDocument.Descendants(ns + "Deployment").Single().Attribute("runDeploymentRoot")!.Value; + string normalizedResultFilePath = resultFilePath.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar); + + string copiedArtifactPath = Path.Combine(testResultsPath, runDeploymentRoot, "In", relativeResultsDirectory, normalizedResultFilePath); + Assert.IsTrue(File.Exists(copiedArtifactPath), $"Expected copied artifact at '{copiedArtifactPath}' but it was not found."); + + string legacyArtifactPath = Path.Combine(testResultsPath, runDeploymentRoot, "In", normalizedResultFilePath); + Assert.IsFalse(File.Exists(legacyArtifactPath), $"Artifact was copied to legacy path '{legacyArtifactPath}'."); + } + [DynamicData(nameof(TargetFrameworks.NetForDynamicData), typeof(TargetFrameworks))] [TestMethod] public async Task Trx_WhenTestHostCrash_ErrorIsDisplayedInsideTheTrx(string tfm) @@ -252,8 +286,16 @@ public async Task ExecuteRequestAsync(ExecuteRequestContext context) } var testMethodIdentifier = new TestMethodIdentifierProperty(string.Empty, string.Empty, "DummyClassName", "Test", 0, Array.Empty(), string.Empty); + PropertyBag properties = new(PassedTestNodeStateProperty.CachedInstance, testMethodIdentifier); + if (Environment.GetEnvironmentVariable("WITH_ARTIFACT") == "1") + { + string artifactPath = Path.Combine(Directory.GetCurrentDirectory(), "test-artifact.txt"); + File.WriteAllText(artifactPath, "artifact"); + properties.Add(new FileArtifactProperty(new FileInfo(artifactPath), "TestMethod", "description")); + } + await context.MessageBus.PublishAsync(this, new TestNodeUpdateMessage(context.Request.Session.SessionUid, - new TestNode() { Uid = "0", DisplayName = "Test", Properties = new(PassedTestNodeStateProperty.CachedInstance, testMethodIdentifier) })); + new TestNode() { Uid = "0", DisplayName = "Test", Properties = properties })); context.Complete(); } } diff --git a/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/TrxTests.cs b/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/TrxTests.cs index 491a882efe..4315016f4a 100644 --- a/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/TrxTests.cs +++ b/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/TrxTests.cs @@ -469,6 +469,8 @@ public async Task TrxReportEngine_GenerateReportAsync_WithArtifactsByTestNode_Tr Assert.IsNotNull(memoryStream.TrxContent); XDocument xml = memoryStream.TrxContent; AssertTrxOutcome(xml, "Completed"); + string relativeResultsDirectory = xml.Descendants().Single(x => x.Name.LocalName == "UnitTestResult").Attribute("relativeResultsDirectory")!.Value; + string expectedDestinationSuffix = Path.Combine("_MachineName_0001-01-01_00_00_00.0000000", "In", relativeResultsDirectory, "MachineName", "fileName"); string trxContent = xml.ToString(); string trxContentsPattern = @" @@ -478,6 +480,13 @@ public async Task TrxReportEngine_GenerateReportAsync_WithArtifactsByTestNode_Tr "; Assert.IsTrue(Regex.IsMatch(trxContent, trxContentsPattern)); + _fileSystem.Verify( + x => x.CopyFile( + It.Is(source => source.EndsWith("fileName", StringComparison.Ordinal)), + It.Is(destination => destination.EndsWith( + expectedDestinationSuffix, + StringComparison.Ordinal))), + Times.Once); } [TestMethod]