From b00f2438d70eb3482311021075d886e75d6d3839 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 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. Add FileArtifactCopyDataConsumer, a new IDataConsumer in the core platform that subscribes to TestNodeUpdateMessage. When it sees FileArtifactProperty, it copies the file to the results directory. This benefits all downstream consumers. Update TRX report engine to write just the filename in ResultFile path elements, since the file is now in the results directory. Also: replace unbounded while(true) with bounded for loop when resolving duplicate file names in TRX artifact copy. Fixes #6782 d --- .../TrxReportEngine.cs | 14 +- .../Hosts/TestHostBuilder.cs | 3 + .../Resources/PlatformResources.resx | 6 + .../Resources/xlf/PlatformResources.cs.xlf | 10 ++ .../Resources/xlf/PlatformResources.de.xlf | 10 ++ .../Resources/xlf/PlatformResources.es.xlf | 10 ++ .../Resources/xlf/PlatformResources.fr.xlf | 10 ++ .../Resources/xlf/PlatformResources.it.xlf | 10 ++ .../Resources/xlf/PlatformResources.ja.xlf | 10 ++ .../Resources/xlf/PlatformResources.ko.xlf | 10 ++ .../Resources/xlf/PlatformResources.pl.xlf | 10 ++ .../Resources/xlf/PlatformResources.pt-BR.xlf | 10 ++ .../Resources/xlf/PlatformResources.ru.xlf | 10 ++ .../Resources/xlf/PlatformResources.tr.xlf | 10 ++ .../xlf/PlatformResources.zh-Hans.xlf | 10 ++ .../xlf/PlatformResources.zh-Hant.xlf | 10 ++ .../TestHost/FileArtifactCopyDataConsumer.cs | 80 +++++++++ .../TrxTests.cs | 4 +- .../FileArtifactCopyDataConsumerTests.cs | 155 ++++++++++++++++++ 19 files changed, 379 insertions(+), 13 deletions(-) create mode 100644 src/Platform/Microsoft.Testing.Platform/TestHost/FileArtifactCopyDataConsumer.cs create mode 100644 test/UnitTests/Microsoft.Testing.Platform.UnitTests/TestHost/FileArtifactCopyDataConsumerTests.cs diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs index 7ffe805ea8..94a0a8b16b 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs @@ -352,19 +352,11 @@ private async Task CopyArtifactIntoTrxDirectoryAndReturnHrefValueAsync(F 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); @@ -536,7 +528,7 @@ private void AddResults(string testAppModule, XElement testRun, out XElement tes resultFiles ??= new XElement("ResultFiles"); resultFiles.Add(new XElement( "ResultFile", - new XAttribute("path", testFileArtifact.FileInfo.FullName))); + new XAttribute("path", testFileArtifact.FileInfo.Name))); } if (resultFiles is not null) diff --git a/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs b/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs index beb17eeeea..3c3f7d7d68 100644 --- a/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs +++ b/src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs @@ -720,6 +720,9 @@ private static async Task BuildTestFrameworkAsync(TestFrameworkB await RegisterAsServiceOrConsumerOrBothAsync(testFrameworkBuilderData.TestExecutionRequestInvoker, serviceProvider, dataConsumersBuilder).ConfigureAwait(false); await RegisterAsServiceOrConsumerOrBothAsync(testFrameworkBuilderData.TestExecutionFilterFactory, serviceProvider, dataConsumersBuilder).ConfigureAwait(false); + // Register consumer that copies per-test file artifacts to the results directory. + dataConsumersBuilder.Add(new FileArtifactCopyDataConsumer(serviceProvider.GetConfiguration())); + // Create the test framework adapter ITestFrameworkCapabilities testFrameworkCapabilities = serviceProvider.GetTestFrameworkCapabilities(); ITestFramework testFramework = testFrameworkBuilderData.TestFrameworkManager.TestFrameworkFactory(testFrameworkCapabilities, serviceProvider); diff --git a/src/Platform/Microsoft.Testing.Platform/Resources/PlatformResources.resx b/src/Platform/Microsoft.Testing.Platform/Resources/PlatformResources.resx index 5236750662..f8cf35d536 100644 --- a/src/Platform/Microsoft.Testing.Platform/Resources/PlatformResources.resx +++ b/src/Platform/Microsoft.Testing.Platform/Resources/PlatformResources.resx @@ -746,4 +746,10 @@ Takes one argument as string in the format <value>[h|m|s] where 'value' is Waiting for debugger to attach (TESTINGPLATFORM_WAIT_ATTACH_DEBUGGER=1) is not supported in browser environments. + + File artifact copy + + + Copies per-test file artifacts to the test results directory. + \ No newline at end of file diff --git a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.cs.xlf b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.cs.xlf index 85fc8d4bcf..694b5bf8af 100644 --- a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.cs.xlf +++ b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.cs.xlf @@ -296,6 +296,16 @@ selhalo s {0} upozorněním(i). + + Copies per-test file artifacts to the test results directory. + Copies per-test file artifacts to the test results directory. + + + + File artifact copy + File artifact copy + + Finished test session. Testovací relace byla dokončena. diff --git a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.de.xlf b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.de.xlf index 158af32848..68995fcf50 100644 --- a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.de.xlf +++ b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.de.xlf @@ -296,6 +296,16 @@ fehlerhaft mit {0} Warnung(en) + + Copies per-test file artifacts to the test results directory. + Copies per-test file artifacts to the test results directory. + + + + File artifact copy + File artifact copy + + Finished test session. Die Testsitzung wurde beendet. diff --git a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.es.xlf b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.es.xlf index 18f14a67fb..19b2fb0ce7 100644 --- a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.es.xlf +++ b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.es.xlf @@ -296,6 +296,16 @@ error con {0} advertencias + + Copies per-test file artifacts to the test results directory. + Copies per-test file artifacts to the test results directory. + + + + File artifact copy + File artifact copy + + Finished test session. Finalizó la sesión de prueba. diff --git a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.fr.xlf b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.fr.xlf index 291f760766..0791282570 100644 --- a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.fr.xlf +++ b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.fr.xlf @@ -296,6 +296,16 @@ a échoué avec {0} avertissement(s) + + Copies per-test file artifacts to the test results directory. + Copies per-test file artifacts to the test results directory. + + + + File artifact copy + File artifact copy + + Finished test session. Session de test terminée. diff --git a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.it.xlf b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.it.xlf index 4995771b47..332096d7d7 100644 --- a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.it.xlf +++ b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.it.xlf @@ -296,6 +296,16 @@ non riuscito con {0} avvisi + + Copies per-test file artifacts to the test results directory. + Copies per-test file artifacts to the test results directory. + + + + File artifact copy + File artifact copy + + Finished test session. Sessione di test completata. diff --git a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ja.xlf b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ja.xlf index 953110e668..58d0dbaf3b 100644 --- a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ja.xlf +++ b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ja.xlf @@ -296,6 +296,16 @@ {0} 件の警告付きで失敗しました + + Copies per-test file artifacts to the test results directory. + Copies per-test file artifacts to the test results directory. + + + + File artifact copy + File artifact copy + + Finished test session. テスト セッションを終了しました。 diff --git a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ko.xlf b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ko.xlf index f8fe5c3ed4..08b28bff56 100644 --- a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ko.xlf +++ b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ko.xlf @@ -296,6 +296,16 @@ {0} 경고와 함께 실패 + + Copies per-test file artifacts to the test results directory. + Copies per-test file artifacts to the test results directory. + + + + File artifact copy + File artifact copy + + Finished test session. 테스트 세션을 마쳤습니다. diff --git a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pl.xlf b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pl.xlf index 1256812f80..960fd3018b 100644 --- a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pl.xlf +++ b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pl.xlf @@ -296,6 +296,16 @@ zakończono niepowodzeniem, z ostrzeżeniami w liczbie: {0} + + Copies per-test file artifacts to the test results directory. + Copies per-test file artifacts to the test results directory. + + + + File artifact copy + File artifact copy + + Finished test session. Zakończono sesję testą. diff --git a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pt-BR.xlf b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pt-BR.xlf index da56e750e5..002731c247 100644 --- a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pt-BR.xlf +++ b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.pt-BR.xlf @@ -296,6 +296,16 @@ falhou com {0} aviso(s) + + Copies per-test file artifacts to the test results directory. + Copies per-test file artifacts to the test results directory. + + + + File artifact copy + File artifact copy + + Finished test session. Sessão de teste concluída. diff --git a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ru.xlf b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ru.xlf index 749658de47..70b35c815a 100644 --- a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ru.xlf +++ b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.ru.xlf @@ -296,6 +296,16 @@ сбой с предупреждениями ({0}) + + Copies per-test file artifacts to the test results directory. + Copies per-test file artifacts to the test results directory. + + + + File artifact copy + File artifact copy + + Finished test session. Тестовый сеанс завершен. diff --git a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.tr.xlf b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.tr.xlf index beecd40ddd..0d4c614d76 100644 --- a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.tr.xlf +++ b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.tr.xlf @@ -296,6 +296,16 @@ {0} uyarıyla başarısız oldu + + Copies per-test file artifacts to the test results directory. + Copies per-test file artifacts to the test results directory. + + + + File artifact copy + File artifact copy + + Finished test session. Test oturumu bitti. diff --git a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hans.xlf b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hans.xlf index 633a4dd6ff..e5a32ec872 100644 --- a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hans.xlf +++ b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hans.xlf @@ -296,6 +296,16 @@ 失败,出现 {0} 警告 + + Copies per-test file artifacts to the test results directory. + Copies per-test file artifacts to the test results directory. + + + + File artifact copy + File artifact copy + + Finished test session. 已完成测试会话。 diff --git a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hant.xlf b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hant.xlf index 533e4a732f..f9d1b246df 100644 --- a/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hant.xlf +++ b/src/Platform/Microsoft.Testing.Platform/Resources/xlf/PlatformResources.zh-Hant.xlf @@ -296,6 +296,16 @@ 失敗,有 {0} 個警告 + + Copies per-test file artifacts to the test results directory. + Copies per-test file artifacts to the test results directory. + + + + File artifact copy + File artifact copy + + Finished test session. 已完成測試會話。 diff --git a/src/Platform/Microsoft.Testing.Platform/TestHost/FileArtifactCopyDataConsumer.cs b/src/Platform/Microsoft.Testing.Platform/TestHost/FileArtifactCopyDataConsumer.cs new file mode 100644 index 0000000000..f6f63f2c7b --- /dev/null +++ b/src/Platform/Microsoft.Testing.Platform/TestHost/FileArtifactCopyDataConsumer.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Testing.Platform.Configurations; +using Microsoft.Testing.Platform.Extensions; +using Microsoft.Testing.Platform.Extensions.Messages; +using Microsoft.Testing.Platform.Helpers; +using Microsoft.Testing.Platform.Resources; + +namespace Microsoft.Testing.Platform.TestHost; + +/// +/// Copies per-test file artifacts to the test results directory so that the +/// results directory is self-contained. +/// +internal sealed class FileArtifactCopyDataConsumer : IDataConsumer +{ + private readonly IConfiguration _configuration; + + public FileArtifactCopyDataConsumer(IConfiguration configuration) + => _configuration = configuration; + + public Type[] DataTypesConsumed => [typeof(TestNodeUpdateMessage)]; + + public string Uid => nameof(FileArtifactCopyDataConsumer); + + public string Version => AppVersion.DefaultSemVer; + + public string DisplayName => PlatformResources.FileArtifactCopyDataConsumerDisplayName; + + public string Description => PlatformResources.FileArtifactCopyDataConsumerDescription; + + public Task IsEnabledAsync() => Task.FromResult(true); + + public Task ConsumeAsync(IDataProducer dataProducer, IData value, CancellationToken cancellationToken) + { + if (value is not TestNodeUpdateMessage message) + { + return Task.CompletedTask; + } + + FileArtifactProperty[] artifacts = message.TestNode.Properties.OfType(); + if (artifacts.Length == 0) + { + return Task.CompletedTask; + } + + string resultsDirectory = _configuration.GetTestResultDirectory(); + + foreach (FileArtifactProperty artifact in artifacts) + { + CopyFileToResultsDirectory(artifact.FileInfo, resultsDirectory); + } + + return Task.CompletedTask; + } + + private static void CopyFileToResultsDirectory(FileInfo file, string resultsDirectory) + { + // If the file is already under the results directory, skip. + string fullResultsDirectory = Path.GetFullPath(resultsDirectory); + if (file.FullName.StartsWith(fullResultsDirectory, StringComparison.OrdinalIgnoreCase)) + { + return; + } + + if (!Directory.Exists(resultsDirectory)) + { + Directory.CreateDirectory(resultsDirectory); + } + + string destination = Path.Combine(resultsDirectory, file.Name); + for (int nameCounter = 1; File.Exists(destination) && nameCounter <= 10; nameCounter++) + { + destination = Path.Combine(resultsDirectory, $"{Path.GetFileNameWithoutExtension(file.Name)}_{nameCounter}{Path.GetExtension(file.Name)}"); + } + + File.Copy(file.FullName, destination, overwrite: false); + } +} diff --git a/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/TrxTests.cs b/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/TrxTests.cs index 4769158d12..1a0af13863 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] diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/TestHost/FileArtifactCopyDataConsumerTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/TestHost/FileArtifactCopyDataConsumerTests.cs new file mode 100644 index 0000000000..77b9059c54 --- /dev/null +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/TestHost/FileArtifactCopyDataConsumerTests.cs @@ -0,0 +1,155 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Testing.Platform.Configurations; +using Microsoft.Testing.Platform.Extensions; +using Microsoft.Testing.Platform.Extensions.Messages; +using Microsoft.Testing.Platform.TestHost; + +using Moq; + +namespace Microsoft.Testing.Platform.UnitTests; + +[TestClass] +public sealed class FileArtifactCopyDataConsumerTests : IDisposable +{ + private readonly string _resultsDirectory; + private readonly string _sourceDirectory; + private readonly FileArtifactCopyDataConsumer _consumer; + + public FileArtifactCopyDataConsumerTests() + { + _resultsDirectory = Path.Combine(Path.GetTempPath(), $"TestResults_{Guid.NewGuid():N}"); + _sourceDirectory = Path.Combine(Path.GetTempPath(), $"TestSource_{Guid.NewGuid():N}"); + + Directory.CreateDirectory(_resultsDirectory); + Directory.CreateDirectory(_sourceDirectory); + + Mock configMock = new(); + configMock.Setup(c => c[PlatformConfigurationConstants.PlatformResultDirectory]).Returns(_resultsDirectory); + + _consumer = new FileArtifactCopyDataConsumer(configMock.Object); + } + + public void Dispose() + { + if (Directory.Exists(_resultsDirectory)) + { + Directory.Delete(_resultsDirectory, recursive: true); + } + + if (Directory.Exists(_sourceDirectory)) + { + Directory.Delete(_sourceDirectory, recursive: true); + } + } + + [TestMethod] + public async Task IsEnabledAsync_ReturnsTrue() + { + bool isEnabled = await _consumer.IsEnabledAsync(); + + Assert.IsTrue(isEnabled); + } + + [TestMethod] + public async Task ConsumeAsync_WithNoFileArtifacts_DoesNotCopyAnything() + { + TestNodeUpdateMessage message = CreateMessage(new PropertyBag(new PassedTestNodeStateProperty())); + + await _consumer.ConsumeAsync(new DummyProducer(), message, CancellationToken.None); + + Assert.AreEqual(0, Directory.GetFiles(_resultsDirectory).Length); + } + + [TestMethod] + public async Task ConsumeAsync_WithFileArtifact_CopiesFileToResultsDirectory() + { + string sourceFile = CreateSourceFile("TestOutput.txt", "test content"); + TestNodeUpdateMessage message = CreateMessage(new PropertyBag( + new PassedTestNodeStateProperty(), + new FileArtifactProperty(new FileInfo(sourceFile), "TestOutput.txt"))); + + await _consumer.ConsumeAsync(new DummyProducer(), message, CancellationToken.None); + + string expectedDestination = Path.Combine(_resultsDirectory, "TestOutput.txt"); + Assert.IsTrue(File.Exists(expectedDestination)); + Assert.AreEqual("test content", File.ReadAllText(expectedDestination)); + } + + [TestMethod] + public async Task ConsumeAsync_WithFileAlreadyInResultsDirectory_DoesNotCopyAgain() + { + string fileInResults = Path.Combine(_resultsDirectory, "AlreadyHere.txt"); + File.WriteAllText(fileInResults, "already here"); + + TestNodeUpdateMessage message = CreateMessage(new PropertyBag( + new PassedTestNodeStateProperty(), + new FileArtifactProperty(new FileInfo(fileInResults), "AlreadyHere.txt"))); + + await _consumer.ConsumeAsync(new DummyProducer(), message, CancellationToken.None); + + Assert.AreEqual(1, Directory.GetFiles(_resultsDirectory).Length); + Assert.AreEqual("already here", File.ReadAllText(fileInResults)); + } + + [TestMethod] + public async Task ConsumeAsync_WithNonTestNodeUpdateMessage_DoesNothing() + { + Mock dataMock = new(); + + await _consumer.ConsumeAsync(new DummyProducer(), dataMock.Object, CancellationToken.None); + + Assert.AreEqual(0, Directory.GetFiles(_resultsDirectory).Length); + } + + [TestMethod] + public async Task ConsumeAsync_WithDuplicateFileName_AppendsCounter() + { + // Pre-create a file in the results directory with the same name. + File.WriteAllText(Path.Combine(_resultsDirectory, "Duplicate.txt"), "original"); + + string sourceFile = CreateSourceFile("Duplicate.txt", "new content"); + TestNodeUpdateMessage message = CreateMessage(new PropertyBag( + new PassedTestNodeStateProperty(), + new FileArtifactProperty(new FileInfo(sourceFile), "Duplicate.txt"))); + + await _consumer.ConsumeAsync(new DummyProducer(), message, CancellationToken.None); + + string expectedDestination = Path.Combine(_resultsDirectory, "Duplicate_1.txt"); + Assert.IsTrue(File.Exists(expectedDestination)); + Assert.AreEqual("new content", File.ReadAllText(expectedDestination)); + } + + private string CreateSourceFile(string name, string content) + { + string path = Path.Combine(_sourceDirectory, name); + File.WriteAllText(path, content); + return path; + } + + private static TestNodeUpdateMessage CreateMessage(PropertyBag properties) + => new( + default, + new TestNode + { + Uid = new TestNodeUid("test-id"), + DisplayName = "TestMethod", + Properties = properties, + }); + + private sealed class DummyProducer : IDataProducer + { + public Type[] DataTypesProduced => [typeof(TestNodeUpdateMessage)]; + + public string Uid => nameof(DummyProducer); + + public string Version => "1.0.0"; + + public string DisplayName => string.Empty; + + public string Description => string.Empty; + + public Task IsEnabledAsync() => Task.FromResult(true); + } +}