diff --git a/src/Adapter/MSTest.Engine/Engine/TestExecutionContext.cs b/src/Adapter/MSTest.Engine/Engine/TestExecutionContext.cs index 8ea46d3754..8c1d818ef6 100644 --- a/src/Adapter/MSTest.Engine/Engine/TestExecutionContext.cs +++ b/src/Adapter/MSTest.Engine/Engine/TestExecutionContext.cs @@ -17,17 +17,15 @@ internal sealed class TestExecutionContext : ITestExecutionContext private readonly PlatformTestNode _platformTestNode; private readonly ITrxReportCapability? _trxReportCapability; private readonly SessionUid _sessionUid; - private readonly Func _publishDataAsync; private readonly CancellationToken _originalCancellationToken; public TestExecutionContext(IConfiguration configuration, TestNode testNode, PlatformTestNode platformTestNode, - ITrxReportCapability? trxReportCapability, SessionUid sessionUid, Func publishDataAsync, CancellationToken cancellationToken) + ITrxReportCapability? trxReportCapability, SessionUid sessionUid, CancellationToken cancellationToken) { Configuration = configuration; _platformTestNode = platformTestNode; _trxReportCapability = trxReportCapability; _sessionUid = sessionUid; - _publishDataAsync = publishDataAsync; TestInfo = new TestInfo(testNode); _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); _originalCancellationToken = cancellationToken; @@ -76,8 +74,11 @@ OperationCanceledException canceledException } } - public async Task AddTestAttachmentAsync(FileInfo file, string displayName, string? description = null) - => await _publishDataAsync(new TestNodeFileArtifact(_sessionUid, _platformTestNode, file, displayName, description)); + public Task AddTestAttachmentAsync(FileInfo file, string displayName, string? description = null) + { + _platformTestNode.Properties.Add(new FileArtifactProperty(_sessionUid, file, displayName, description)); + return Task.CompletedTask; + } private static void AddTrxExceptionInformation(PropertyBag propertyBag, Exception? exception) { diff --git a/src/Adapter/MSTest.Engine/Engine/ThreadPoolTestNodeRunner.cs b/src/Adapter/MSTest.Engine/Engine/ThreadPoolTestNodeRunner.cs index 75db5071dc..2268dfc741 100644 --- a/src/Adapter/MSTest.Engine/Engine/ThreadPoolTestNodeRunner.cs +++ b/src/Adapter/MSTest.Engine/Engine/ThreadPoolTestNodeRunner.cs @@ -215,7 +215,7 @@ private async Task InvokeTestNodeAndPublishResultAsync(TestNode testNode platformTestNode.Properties.Add(new TrxFullyQualifiedTypeNameProperty(platformTestNode.Uid.Value[..platformTestNode.Uid.Value.LastIndexOf('.')])); } - TestExecutionContext testExecutionContext = new(_configuration, testNode, platformTestNode, _trxReportCapability, _sessionUid, _publishDataAsync, _cancellationToken); + TestExecutionContext testExecutionContext = new(_configuration, testNode, platformTestNode, _trxReportCapability, _sessionUid, _cancellationToken); try { // If we're already enqueued we cancel the test before the start diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxDataConsumer.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxDataConsumer.cs index 6cf1b4627d..65efcfbdcb 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxDataConsumer.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxDataConsumer.cs @@ -40,7 +40,6 @@ internal sealed class TrxReportGenerator : private readonly TrxTestApplicationLifecycleCallbacks? _trxTestApplicationLifecycleCallbacks; private readonly ILogger _logger; private readonly List _tests = []; - private readonly Dictionary> _artifactsByTestNode = new(); private readonly Dictionary> _artifactsByExtension = new(); private readonly bool _isEnabled; @@ -84,7 +83,6 @@ public TrxReportGenerator( public Type[] DataTypesConsumed { get; } = [ typeof(TestNodeUpdateMessage), - typeof(TestNodeFileArtifact), typeof(SessionFileArtifact) ]; @@ -140,18 +138,7 @@ public Task ConsumeAsync(IDataProducer dataProducer, IData value, CancellationTo } break; - case TestNodeFileArtifact testNodeFileArtifact: - if (!_artifactsByTestNode.TryGetValue(testNodeFileArtifact.TestNode.Uid, out List? nodeFileArtifacts)) - { - nodeFileArtifacts = [testNodeFileArtifact]; - _artifactsByTestNode[testNodeFileArtifact.TestNode.Uid] = nodeFileArtifacts; - } - else - { - nodeFileArtifacts.Add(testNodeFileArtifact); - } - break; case SessionFileArtifact fileArtifact: if (!_artifactsByExtension.TryGetValue(dataProducer, out List? sessionFileArtifacts)) { @@ -235,7 +222,7 @@ public async Task OnTestSessionFinishingAsync(SessionUid sessionUid, Cancellatio int exitCode = _testApplicationProcessExitCode.GetProcessExitCode(); TrxReportEngine trxReportGeneratorEngine = new(_testApplicationModuleInfo, _environment, _commandLineOptionsService, _configuration, - _clock, _tests.ToArray(), _failedTestsCount, _passedTestsCount, _notExecutedTestsCount, _timeoutTestsCount, _artifactsByExtension, _artifactsByTestNode, + _clock, _tests.ToArray(), _failedTestsCount, _passedTestsCount, _notExecutedTestsCount, _timeoutTestsCount, _artifactsByExtension, _adapterSupportTrxCapability, _testFramework, _testStartTime.Value, exitCode, cancellationToken); (string reportFileName, string? warning) = await trxReportGeneratorEngine.GenerateReportAsync(); if (warning is not null) diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxProcessLifetimeHandler.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxProcessLifetimeHandler.cs index 935619ba20..903579a21a 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxProcessLifetimeHandler.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxProcessLifetimeHandler.cs @@ -164,7 +164,6 @@ public async Task OnTestHostProcessExitedAsync(ITestHostProcessInformation testH TrxReportEngine trxReportGeneratorEngine = new(_testApplicationModuleInfo, _environment, _commandLineOptions, _configuration, _clock, [], 0, 0, 0, 0, artifacts, - new Dictionary>(), adapterSupportTrxCapability: null, new TestAdapterInfo(_testAdapterInformationRequest!.TestAdapterId, _testAdapterInformationRequest.TestAdapterVersion), _startTime, @@ -201,7 +200,6 @@ await _messageBus.PublishAsync( TrxReportEngine trxReportGeneratorEngine = new(_testApplicationModuleInfo, _environment, _commandLineOptions, _configuration, _clock, [], 0, 0, 0, 0, artifacts, - new Dictionary>(), false, new TestAdapterInfo(_testAdapterInformationRequest!.TestAdapterId, _testAdapterInformationRequest.TestAdapterVersion), _startTime, diff --git a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs index 2c7187e22b..f7ddbb944f 100644 --- a/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs +++ b/src/Platform/Microsoft.Testing.Extensions.TrxReport/TrxReportEngine.cs @@ -87,7 +87,6 @@ internal sealed partial class TrxReportEngine private readonly int _notExecutedTestsCount; private readonly int _timeoutTestsCount; private readonly Dictionary> _artifactsByExtension; - private readonly Dictionary> _artifactsByTestNode; private readonly bool? _adapterSupportTrxCapability; private readonly ITestFramework _testFrameworkAdapter; private readonly DateTimeOffset _testStartTime; @@ -97,7 +96,7 @@ internal sealed partial class TrxReportEngine private readonly IFileSystem _fileSystem; private readonly bool _isCopyingFileAllowed; - public TrxReportEngine(ITestApplicationModuleInfo testApplicationModuleInfo, IEnvironment environment, ICommandLineOptions commandLineOptionsService, IConfiguration configuration, IClock clock, TestNodeUpdateMessage[] testNodeUpdatedMessages, int failedTestsCount, int passedTestsCount, int notExecutedTestsCount, int timeoutTestsCount, Dictionary> artifactsByExtension, Dictionary> artifactsByTestNode, bool? adapterSupportTrxCapability, ITestFramework testFrameworkAdapter, DateTimeOffset testStartTime, int exitCode, CancellationToken cancellationToken) + public TrxReportEngine(ITestApplicationModuleInfo testApplicationModuleInfo, IEnvironment environment, ICommandLineOptions commandLineOptionsService, IConfiguration configuration, IClock clock, TestNodeUpdateMessage[] testNodeUpdatedMessages, int failedTestsCount, int passedTestsCount, int notExecutedTestsCount, int timeoutTestsCount, Dictionary> artifactsByExtension, bool? adapterSupportTrxCapability, ITestFramework testFrameworkAdapter, DateTimeOffset testStartTime, int exitCode, CancellationToken cancellationToken) : this( new SystemFileSystem(), testApplicationModuleInfo, @@ -111,7 +110,6 @@ public TrxReportEngine(ITestApplicationModuleInfo testApplicationModuleInfo, IEn notExecutedTestsCount, timeoutTestsCount, artifactsByExtension, - artifactsByTestNode, adapterSupportTrxCapability, testFrameworkAdapter, testStartTime, @@ -120,7 +118,7 @@ public TrxReportEngine(ITestApplicationModuleInfo testApplicationModuleInfo, IEn { } - public TrxReportEngine(IFileSystem fileSystem, ITestApplicationModuleInfo testApplicationModuleInfo, IEnvironment environment, ICommandLineOptions commandLineOptionsService, IConfiguration configuration, IClock clock, TestNodeUpdateMessage[] testNodeUpdatedMessages, int failedTestsCount, int passedTestsCount, int notExecutedTestsCount, int timeoutTestsCount, Dictionary> artifactsByExtension, Dictionary> artifactsByTestNode, bool? adapterSupportTrxCapability, ITestFramework testFrameworkAdapter, DateTimeOffset testStartTime, int exitCode, CancellationToken cancellationToken, bool isCopyingFileAllowed = true) + public TrxReportEngine(IFileSystem fileSystem, ITestApplicationModuleInfo testApplicationModuleInfo, IEnvironment environment, ICommandLineOptions commandLineOptionsService, IConfiguration configuration, IClock clock, TestNodeUpdateMessage[] testNodeUpdatedMessages, int failedTestsCount, int passedTestsCount, int notExecutedTestsCount, int timeoutTestsCount, Dictionary> artifactsByExtension, bool? adapterSupportTrxCapability, ITestFramework testFrameworkAdapter, DateTimeOffset testStartTime, int exitCode, CancellationToken cancellationToken, bool isCopyingFileAllowed = true) { _testApplicationModuleInfo = testApplicationModuleInfo; _environment = environment; @@ -133,7 +131,6 @@ public TrxReportEngine(IFileSystem fileSystem, ITestApplicationModuleInfo testAp _notExecutedTestsCount = notExecutedTestsCount; _timeoutTestsCount = timeoutTestsCount; _artifactsByExtension = artifactsByExtension; - _artifactsByTestNode = artifactsByTestNode; _adapterSupportTrxCapability = adapterSupportTrxCapability; _testFrameworkAdapter = testFrameworkAdapter; _testStartTime = testStartTime; @@ -521,17 +518,17 @@ private void AddResults(string testAppModule, XElement testRun, out XElement tes unitTestResult.Add(output); } - if (_artifactsByTestNode.TryGetValue(testNode.Uid, out List? fileArtifacts)) + XElement? resultFiles = null; + foreach (FileArtifactProperty testFileArtifact in testNode.Properties.OfType()) { - var resultFiles = new XElement("ResultFiles"); - - foreach (SessionFileArtifact fileArtifact in fileArtifacts) - { - resultFiles.Add(new XElement( - "ResultFile", - new XAttribute("path", fileArtifact.FileInfo.FullName))); - } + resultFiles ??= new XElement("ResultFiles"); + resultFiles.Add(new XElement( + "ResultFile", + new XAttribute("path", testFileArtifact.FileInfo.FullName))); + } + if (resultFiles is not null) + { unitTestResult.Add(resultFiles); } diff --git a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/FrameworkHandlerAdapter.cs b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/FrameworkHandlerAdapter.cs index 5fec1ec86f..8e2286fc53 100644 --- a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/FrameworkHandlerAdapter.cs +++ b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/FrameworkHandlerAdapter.cs @@ -97,7 +97,7 @@ public void RecordAttachments(IList attachmentSets) { _logger.LogTrace($"{nameof(FrameworkHandlerAdapter)}.RecordAttachments"); _frameworkHandle?.RecordAttachments(attachmentSets); - PublishAttachmentsAsync(attachmentSets).Await(); + PublishTestSessionAttachmentsAsync(attachmentSets).Await(); } /// @@ -126,12 +126,10 @@ public void RecordResult(TestResult testResult) _frameworkHandle?.RecordResult(testResult); // Publish node state change to Microsoft Testing Platform - var testNode = testResult.ToTestNode(_isTrxEnabled, _clientInfo); + var testNode = testResult.ToTestNode(_isTrxEnabled, _clientInfo, _session.SessionUid); var testNodeChange = new TestNodeUpdateMessage(_session.SessionUid, testNode); _messageBus.PublishAsync(_adapterExtensionBase, testNodeChange).Await(); - - PublishAttachmentsAsync(testResult.Attachments, testNode).Await(); } /// @@ -158,7 +156,7 @@ public void RecordStart(TestCase testCase) public void SendMessage(TestMessageLevel testMessageLevel, string message) => _comboMessageLogger.SendMessage(testMessageLevel, message); - private async Task PublishAttachmentsAsync(IEnumerable attachments, TestNode? testNode = null) + private async Task PublishTestSessionAttachmentsAsync(IEnumerable attachments) { foreach (AttachmentSet attachmentSet in attachments) { @@ -169,9 +167,7 @@ private async Task PublishAttachmentsAsync(IEnumerable attachment throw new FormatException($"Test adapter {_adapterExtensionBase.DisplayName} only supports file attachments."); } - SessionFileArtifact fileArtifact = testNode is null - ? new SessionFileArtifact(_session.SessionUid, new(attachment.Uri.LocalPath), attachmentSet.DisplayName, attachment.Description) - : new TestNodeFileArtifact(_session.SessionUid, testNode, new(attachment.Uri.LocalPath), attachmentSet.DisplayName, attachment.Description); + var fileArtifact = new SessionFileArtifact(_session.SessionUid, new(attachment.Uri.LocalPath), attachmentSet.DisplayName, attachment.Description); await _messageBus.PublishAsync(_adapterExtensionBase, fileArtifact); } } diff --git a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/ObjectModelConverters.cs b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/ObjectModelConverters.cs index a4442df12e..1e7d56b294 100644 --- a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/ObjectModelConverters.cs +++ b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/ObjectModel/ObjectModelConverters.cs @@ -127,7 +127,7 @@ private static void CopyVSTestProperties(IEnumerable testPropertie /// /// Converts a VSTest to a Microsoft Testing Platform . /// - public static TestNode ToTestNode(this TestResult testResult, bool isTrxEnabled, IClientInfo client) + public static TestNode ToTestNode(this TestResult testResult, bool isTrxEnabled, IClientInfo client, SessionUid sessionUid) { var testNode = testResult.TestCase.ToTestNode(isTrxEnabled, client, testResult.DisplayName); CopyVSTestProperties(testResult.Properties, testNode, testResult.TestCase, testResult.GetPropertyValue, isTrxEnabled, client); @@ -179,6 +179,14 @@ public static TestNode ToTestNode(this TestResult testResult, bool isTrxEnabled, } } + foreach (AttachmentSet attachmentSet in testResult.Attachments) + { + foreach (UriDataAttachment attachment in attachmentSet.Attachments) + { + testNode.Properties.Add(new FileArtifactProperty(sessionUid, new FileInfo(attachment.Uri.LocalPath), attachmentSet.DisplayName, attachment.Description)); + } + } + if (standardErrorMessages.Count > 0) { testNode.Properties.Add(new StandardErrorProperty(string.Join(Environment.NewLine, standardErrorMessages))); diff --git a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/VSTestBridgedTestFrameworkBase.cs b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/VSTestBridgedTestFrameworkBase.cs index 8f45834add..827cd6ad8a 100644 --- a/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/VSTestBridgedTestFrameworkBase.cs +++ b/src/Platform/Microsoft.Testing.Extensions.VSTestBridge/VSTestBridgedTestFrameworkBase.cs @@ -52,7 +52,6 @@ protected VSTestBridgedTestFrameworkBase(IServiceProvider serviceProvider, ITest [ typeof(TestNodeUpdateMessage), typeof(SessionFileArtifact), - typeof(TestNodeFileArtifact) ]; /// diff --git a/src/Platform/Microsoft.Testing.Platform/Messages/FileArtifacts.cs b/src/Platform/Microsoft.Testing.Platform/Messages/FileArtifacts.cs index c29dc9f6db..0dd5337ad6 100644 --- a/src/Platform/Microsoft.Testing.Platform/Messages/FileArtifacts.cs +++ b/src/Platform/Microsoft.Testing.Platform/Messages/FileArtifacts.cs @@ -125,6 +125,7 @@ public override string ToString() /// /// Represents a test node file artifact. /// +[Obsolete("This API is obsolete and will be removed in v2. Instead of publishing this to MessageBus, add FileArtifactProperty property to the TestNode when publishing TestNodeUpdateMessage with a test result", error: true)] public class TestNodeFileArtifact : SessionFileArtifact { /// diff --git a/src/Platform/Microsoft.Testing.Platform/Messages/TestNodeProperties.cs b/src/Platform/Microsoft.Testing.Platform/Messages/TestNodeProperties.cs index 5f2ce18fc0..970ad2caa5 100644 --- a/src/Platform/Microsoft.Testing.Platform/Messages/TestNodeProperties.cs +++ b/src/Platform/Microsoft.Testing.Platform/Messages/TestNodeProperties.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 Microsoft.Testing.Platform.TestHost; + namespace Microsoft.Testing.Platform.Extensions.Messages; /// @@ -372,6 +374,15 @@ public record StandardOutputProperty(string StandardOutput) : IProperty; [Experimental("TPEXP", UrlFormat = "https://aka.ms/testingplatform/diagnostics#{0}")] public record StandardErrorProperty(string StandardError) : IProperty; +/// +/// Property that represents multiple artifacts/attachments to associate with a test node. +/// +/// The session UID. +/// The file information. +/// The display name. +/// The description. +public record FileArtifactProperty(SessionUid SessionUid, FileInfo FileInfo, string DisplayName, string? Description = null) : IProperty; + internal sealed record SerializableKeyValuePairStringProperty(string Key, string Value) : KeyValuePairStringProperty(Key, Value); internal sealed record SerializableNamedKeyValuePairsStringProperty(string Name, KeyValuePair[] Pairs) : IProperty diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/BrowserOutputDevice.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/BrowserOutputDevice.cs index fd0ac01e30..b2a3213f34 100644 --- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/BrowserOutputDevice.cs +++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/BrowserOutputDevice.cs @@ -98,7 +98,6 @@ public async Task InitializeAsync() [ typeof(TestNodeUpdateMessage), typeof(SessionFileArtifact), - typeof(TestNodeFileArtifact), typeof(FileArtifact), ]; @@ -369,13 +368,10 @@ public Task ConsumeAsync(IDataProducer dataProducer, IData value, CancellationTo break; } - break; - - case TestNodeFileArtifact: - { - // TODO - } - + // TODO: + // foreach (FileArtifactProperty testFileArtifact in testNodeStateChanged.TestNode.Properties.OfType()) + // { + // } break; case SessionFileArtifact: diff --git a/src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs b/src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs index d9b2166ea9..3b175c931f 100644 --- a/src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs +++ b/src/Platform/Microsoft.Testing.Platform/OutputDevice/TerminalOutputDevice.cs @@ -171,7 +171,6 @@ private string GetShortArchitecture(string runtimeIdentifier) [ typeof(TestNodeUpdateMessage), typeof(SessionFileArtifact), - typeof(TestNodeFileArtifact), typeof(FileArtifact), ]; @@ -405,6 +404,18 @@ public Task ConsumeAsync(IDataProducer dataProducer, IData value, CancellationTo string? standardOutput = testNodeStateChanged.TestNode.Properties.SingleOrDefault()?.StandardOutput; string? standardError = testNodeStateChanged.TestNode.Properties.SingleOrDefault()?.StandardError; + foreach (FileArtifactProperty artifact in testNodeStateChanged.TestNode.Properties.OfType()) + { + bool isOutOfProcessArtifact = _firstCallTo_OnSessionStartingAsync; + _terminalTestReporter.ArtifactAdded( + isOutOfProcessArtifact, + _assemblyName, + _targetFramework, + _shortArchitecture, + testNodeStateChanged.TestNode.DisplayName, + artifact.FileInfo.FullName); + } + switch (testNodeStateChanged.TestNode.Properties.SingleOrDefault()) { case InProgressTestNodeStateProperty: @@ -536,20 +547,6 @@ public Task ConsumeAsync(IDataProducer dataProducer, IData value, CancellationTo break; - case TestNodeFileArtifact artifact: - { - bool isOutOfProcessArtifact = _firstCallTo_OnSessionStartingAsync; - _terminalTestReporter.ArtifactAdded( - isOutOfProcessArtifact, - _assemblyName, - _targetFramework, - _shortArchitecture, - artifact.TestNode.DisplayName, - artifact.FileInfo.FullName); - } - - break; - case SessionFileArtifact artifact: { bool isOutOfProcessArtifact = _firstCallTo_OnSessionStartingAsync; diff --git a/src/Platform/Microsoft.Testing.Platform/PublicAPI/PublicAPI.Unshipped.txt b/src/Platform/Microsoft.Testing.Platform/PublicAPI/PublicAPI.Unshipped.txt index 7dc5c58110..bcf20aa22c 100644 --- a/src/Platform/Microsoft.Testing.Platform/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/Platform/Microsoft.Testing.Platform/PublicAPI/PublicAPI.Unshipped.txt @@ -1 +1,22 @@ #nullable enable +Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty +Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty.Deconstruct(out Microsoft.Testing.Platform.TestHost.SessionUid SessionUid, out System.IO.FileInfo! FileInfo, out string! DisplayName, out string? Description) -> void +Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty.Description.get -> string? +Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty.Description.init -> void +Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty.DisplayName.get -> string! +Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty.DisplayName.init -> void +Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty.FileInfo.get -> System.IO.FileInfo! +Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty.FileInfo.init -> void +Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty.SessionUid.get -> Microsoft.Testing.Platform.TestHost.SessionUid +Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty.SessionUid.init -> void +Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty.FileArtifactProperty(Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty! original) -> void +Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty.FileArtifactProperty(Microsoft.Testing.Platform.TestHost.SessionUid SessionUid, System.IO.FileInfo! FileInfo, string! DisplayName, string? Description = null) -> void +override Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty.Equals(object? obj) -> bool +override Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty.GetHashCode() -> int +override Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty.ToString() -> string! +static Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty.operator !=(Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty? left, Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty? right) -> bool +static Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty.operator ==(Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty? left, Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty? right) -> bool +virtual Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty.$() -> Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty! +virtual Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty.EqualityContract.get -> System.Type! +virtual Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty.Equals(Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty? other) -> bool +virtual Microsoft.Testing.Platform.Extensions.Messages.FileArtifactProperty.PrintMembers(System.Text.StringBuilder! builder) -> bool diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/DotnetTestDataConsumer.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/DotnetTestDataConsumer.cs index 559fca9f4b..ab24a9bf02 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/DotnetTestDataConsumer.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/DotnetTest/IPC/DotnetTestDataConsumer.cs @@ -25,7 +25,6 @@ public DotnetTestDataConsumer(DotnetTestConnection dotnetTestConnection, IEnviro { typeof(TestNodeUpdateMessage), typeof(SessionFileArtifact), - typeof(TestNodeFileArtifact), typeof(FileArtifact), typeof(TestRequestExecutionTimeInfo), }; @@ -114,28 +113,30 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella break; } - break; - - case TestNodeFileArtifact testNodeFileArtifact: - FileArtifactMessages fileArtifactMessages = new( - ExecutionId, - DotnetTestConnection.InstanceId, - new[] - { + foreach (FileArtifactProperty artifact in testNodeUpdateMessage.Properties.OfType()) + { + FileArtifactMessages testFileArtifactMessages = new( + ExecutionId, + DotnetTestConnection.InstanceId, + new[] + { new FileArtifactMessage( - testNodeFileArtifact.FileInfo.FullName, - testNodeFileArtifact.DisplayName, - testNodeFileArtifact.Description ?? string.Empty, - testNodeFileArtifact.TestNode.Uid.Value, - testNodeFileArtifact.TestNode.DisplayName, - testNodeFileArtifact.SessionUid.Value), - }); + artifact.FileInfo.FullName, + artifact.DisplayName, + artifact.Description ?? string.Empty, + testNodeUpdateMessage.TestNode.Uid.Value, + testNodeUpdateMessage.TestNode.DisplayName, + artifact.SessionUid.Value), + }); + + await _dotnetTestConnection.SendMessageAsync(testFileArtifactMessages); + break; + } - await _dotnetTestConnection.SendMessageAsync(fileArtifactMessages); break; case SessionFileArtifact sessionFileArtifact: - fileArtifactMessages = new( + var fileArtifactMessages = new FileArtifactMessages( ExecutionId, DotnetTestConnection.InstanceId, new[] diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.cs index f829b100fd..bc9ce6f2ce 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.cs @@ -138,6 +138,7 @@ public Json(Dictionary? serializers = null, Dictionary new KeyValuePair(x.Key, x.Value)))); } + int attachmentIndex = 0; foreach (IProperty property in message.Properties) { if (property is SerializableKeyValuePairStringProperty keyValuePairProperty) @@ -289,6 +290,15 @@ public Json(Dictionary? serializers = null, Dictionary @@ -223,10 +222,6 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella PopulateTestNodeStatistics(update); break; - case TestNodeFileArtifact testNodeFileArtifact: - Artifacts.Add(new Artifact(testNodeFileArtifact.FileInfo.FullName, dataProducer.Uid, FileType, testNodeFileArtifact.DisplayName, testNodeFileArtifact.Description)); - break; - case SessionFileArtifact sessionFileArtifact: Artifacts.Add(new Artifact(sessionFileArtifact.FileInfo.FullName, dataProducer.Uid, FileType, sessionFileArtifact.DisplayName, sessionFileArtifact.Description)); break; diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.cs index a9ad5812b3..b52caea24a 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.cs @@ -199,6 +199,8 @@ static SerializerUtilities() #endif } + int attachmentIndex = 0; + foreach (IProperty property in n.Properties) { if (property is SerializableKeyValuePairStringProperty keyValuePairProperty) @@ -363,6 +365,15 @@ static SerializerUtilities() properties["time.duration-ms"] = timingProperty.GlobalTiming.Duration.TotalMilliseconds; continue; } + + if (property is FileArtifactProperty artifact) + { + properties[$"attachments.{attachmentIndex}.uri"] = artifact.FileInfo.FullName; + properties[$"attachments.{attachmentIndex}.display-name"] = artifact.DisplayName; + properties[$"attachments.{attachmentIndex}.description"] = artifact.Description; + attachmentIndex++; + continue; + } } if (!properties.ContainsKey("node-type")) diff --git a/src/Platform/Microsoft.Testing.Platform/TestHostControllers/PassiveNodeDataConsumer.cs b/src/Platform/Microsoft.Testing.Platform/TestHostControllers/PassiveNodeDataConsumer.cs index 6bad942616..9e4c2e5be8 100644 --- a/src/Platform/Microsoft.Testing.Platform/TestHostControllers/PassiveNodeDataConsumer.cs +++ b/src/Platform/Microsoft.Testing.Platform/TestHostControllers/PassiveNodeDataConsumer.cs @@ -17,7 +17,7 @@ public PassiveNodeDataConsumer(PassiveNode? passiveNode) => _passiveNode = passiveNode; public Type[] DataTypesConsumed - => [typeof(SessionFileArtifact), typeof(TestNodeFileArtifact), typeof(FileArtifact)]; + => [typeof(SessionFileArtifact), typeof(FileArtifact)]; public string Uid => nameof(PassiveNodeDataConsumer); @@ -43,13 +43,6 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella switch (value) { - case TestNodeFileArtifact testNodeFileArtifact: - { - RunTestAttachment runTestAttachment = new(testNodeFileArtifact.FileInfo.FullName, dataProducer.Uid, FileType, testNodeFileArtifact.DisplayName, testNodeFileArtifact.Description); - await _passiveNode.SendAttachmentsAsync(new TestsAttachments([runTestAttachment]), cancellationToken); - break; - } - case SessionFileArtifact sessionFileArtifact: { RunTestAttachment runTestAttachment = new(sessionFileArtifact.FileInfo.FullName, dataProducer.Uid, FileType, sessionFileArtifact.DisplayName, sessionFileArtifact.Description); diff --git a/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/TrxTests.cs b/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/TrxTests.cs index 5929e75def..a9352bee8a 100644 --- a/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/TrxTests.cs +++ b/test/UnitTests/Microsoft.Testing.Extensions.UnitTests/TrxTests.cs @@ -27,7 +27,6 @@ public class TrxTests private readonly Mock _testFrameworkMock = new(); private readonly Mock _testApplicationModuleInfoMock = new(); private readonly Mock _fileSystem = new(); - private readonly Dictionary> _artifactsByTestNode = new(); private readonly Dictionary> _artifactsByExtension = new(); [TestMethod] @@ -381,9 +380,8 @@ public async Task TrxReportEngine_GenerateReportAsync_WithArtifactsByTestNode_Tr { // Arrange using MemoryFileStream memoryStream = new(); - _artifactsByTestNode.Add("test()", [new(new SessionUid("1"), new FileInfo("fileName"), "TestMethod", "description")]); TrxReportEngine trxReportEngine = GenerateTrxReportEngine(1, 0, - new(new PassedTestNodeStateProperty()), memoryStream); + new(new PassedTestNodeStateProperty(), new FileArtifactProperty(new SessionUid("1"), new FileInfo("fileName"), "TestMethod", "description")), memoryStream); // Act (string fileName, string? warning) = await trxReportEngine.GenerateReportAsync(); @@ -462,7 +460,7 @@ public async Task TrxReportEngine_GenerateReportAsync_FileAlreadyExists_WillRetr _ = _testApplicationModuleInfoMock.Setup(_ => _.GetCurrentTestApplicationFullPath()).Returns("TestAppPath"); TrxReportEngine trxReportEngine = new(_fileSystem.Object, _testApplicationModuleInfoMock.Object, _environmentMock.Object, _commandLineOptionsMock.Object, _configurationMock.Object, _clockMock.Object, [], 0, 0, 0, 0, - _artifactsByExtension, _artifactsByTestNode, true, _testFrameworkMock.Object, DateTime.UtcNow, 0, CancellationToken.None, + _artifactsByExtension, true, _testFrameworkMock.Object, DateTime.UtcNow, 0, CancellationToken.None, isCopyingFileAllowed: false); // Act @@ -510,7 +508,7 @@ private TrxReportEngine GenerateTrxReportEngine(int passedTestsCount, int failed return new TrxReportEngine(_fileSystem.Object, _testApplicationModuleInfoMock.Object, _environmentMock.Object, _commandLineOptionsMock.Object, _configurationMock.Object, _clockMock.Object, testNodeUpdatedMessages, failedTestsCount, passedTestsCount, notExecutedTestsCount, timeoutTestsCount, - _artifactsByExtension, _artifactsByTestNode, adapterSupportTrxCapability, _testFrameworkMock.Object, testStartTime, 0, cancellationToken, + _artifactsByExtension, adapterSupportTrxCapability, _testFrameworkMock.Object, testStartTime, 0, cancellationToken, isCopyingFileAllowed: false); } diff --git a/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/ObjectModelConvertersTests.cs b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/ObjectModelConvertersTests.cs index 9e0d20d5d9..f570e7e686 100644 --- a/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/ObjectModelConvertersTests.cs +++ b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/ObjectModelConvertersTests.cs @@ -48,7 +48,7 @@ public void ToTestNode_WhenTestResultHasCodeFilePath_SetsTestFileLocationPropert { CodeFilePath = "FilePath", }); - var testNode = testResult.ToTestNode(false, TestClient); + var testNode = testResult.ToTestNode(false, TestClient, new SessionUid("SessionUid")); Assert.AreEqual("FilePath", testNode.Properties.Single().FilePath); } @@ -61,7 +61,7 @@ public void ToTestNode_WhenTestResultOutcomeIsFailed_TestNodePropertiesContainFa ErrorMessage = "SomeErrorMessage", ErrorStackTrace = "SomeStackTrace", }; - var testNode = testResult.ToTestNode(false, TestClient); + var testNode = testResult.ToTestNode(false, TestClient, new SessionUid("SessionUid")); FailedTestNodeStateProperty[] failedTestNodeStateProperties = testNode.Properties.OfType().ToArray(); Assert.AreEqual(1, failedTestNodeStateProperties.Length); @@ -77,7 +77,7 @@ public void ToTestNode_WhenTestResultHasMSTestDiscovererTestCategoryTestProperty var testCategoryProperty = TestProperty.Register("MSTestDiscoverer.TestCategory", "Label", typeof(string[]), TestPropertyAttributes.None, typeof(TestCase)); testResult.SetPropertyValue(testCategoryProperty, ["category1"]); - var testNode = testResult.ToTestNode(false, VSTestClient); + var testNode = testResult.ToTestNode(false, VSTestClient, new SessionUid("SessionUid")); SerializableNamedKeyValuePairsStringProperty[] errorTestNodeStateProperties = testNode.Properties.OfType().ToArray(); Assert.AreEqual(1, errorTestNodeStateProperties.Length); @@ -93,7 +93,7 @@ public void ToTestNode_WhenTestResultHasMSTestDiscovererTestCategoryTestProperty var testCategoryProperty = TestProperty.Register("MSTestDiscoverer.TestCategory", "Label", typeof(string[]), TestPropertyAttributes.None, typeof(TestCase)); testResult.SetPropertyValue(testCategoryProperty, ["category1"]); - var testNode = testResult.ToTestNode(true, VSTestClient); + var testNode = testResult.ToTestNode(true, VSTestClient, new SessionUid("SessionUid")); TrxCategoriesProperty[] trxCategoriesProperty = testNode.Properties.OfType().ToArray(); Assert.AreEqual(1, trxCategoriesProperty.Length); @@ -108,7 +108,7 @@ public void ToTestNode_WhenTestResultHasTestCaseHierarchyTestProperty_TestNodePr var testCaseHierarchy = TestProperty.Register("TestCase.Hierarchy", "Label", typeof(string[]), TestPropertyAttributes.None, typeof(TestCase)); testResult.SetPropertyValue(testCaseHierarchy, ["assembly", "class", "category", "test"]); - var testNode = testResult.ToTestNode(false, VSTestClient); + var testNode = testResult.ToTestNode(false, VSTestClient, new SessionUid("SessionUid")); SerializableNamedArrayStringProperty[] trxCategoriesProperty = testNode.Properties.OfType().ToArray(); Assert.AreEqual(1, trxCategoriesProperty.Length); @@ -127,7 +127,7 @@ public void ToTestNode_WhenTestResultHasOriginalExecutorUriProperty_TestNodeProp testResult.SetPropertyValue(originalExecutorUriProperty, new Uri("https://vs.com/")); - var testNode = testResult.ToTestNode(false, VSTestClient); + var testNode = testResult.ToTestNode(false, VSTestClient, new SessionUid("SessionUid")); SerializableKeyValuePairStringProperty[] serializableKeyValuePairStringProperty = testNode.Properties.OfType().ToArray(); Assert.AreEqual(3, serializableKeyValuePairStringProperty.Length); @@ -140,7 +140,7 @@ public void ToTestNode_WhenTestResultHasFullyQualifiedTypeAndTrxEnabled_TestNode { TestResult testResult = new(new TestCase("assembly.class.test", new("executor://uri", UriKind.Absolute), "source.cs")); - var testNode = testResult.ToTestNode(true, TestClient); + var testNode = testResult.ToTestNode(true, TestClient, new SessionUid("SessionUid")); Assert.AreEqual(testNode.Properties.OfType()?.Length, 1); Assert.AreEqual("assembly.class", testNode.Properties.Single().FullyQualifiedTypeName); @@ -151,7 +151,7 @@ public void ToTestNode_WhenTestResultHasNoFullyQualifiedTypeAndTrxEnabled_Throws { TestResult testResult = new(new TestCase("test", new("executor://uri", UriKind.Absolute), "source.cs")); - string errorMessage = Assert.ThrowsException(() => testResult.ToTestNode(true, TestClient)).Message; + string errorMessage = Assert.ThrowsException(() => testResult.ToTestNode(true, TestClient, new SessionUid("SessionUid"))).Message; Assert.IsTrue(errorMessage.Contains("Unable to parse fully qualified type name from test case: ")); } @@ -169,7 +169,7 @@ public void ToTestNode_FromTestResult_TestNodePropertiesContainCorrectTimingProp EndTime = endTime, Duration = duration, }; - var testNode = testResult.ToTestNode(false, TestClient); + var testNode = testResult.ToTestNode(false, TestClient, new SessionUid("SessionUid")); var testResultTimingProperty = new TimingProperty(new(startTime, endTime, duration), []); Assert.AreEqual(testNode.Properties.OfType()[0], testResultTimingProperty); @@ -183,7 +183,7 @@ public void ToTestNode_WhenTestResultOutcomeIsNotFoundWithoutSetErrorMessage_Tes Outcome = TestOutcome.NotFound, ErrorStackTrace = "SomeStackTrace", }; - var testNode = testResult.ToTestNode(false, TestClient); + var testNode = testResult.ToTestNode(false, TestClient, new SessionUid("SessionUid")); ErrorTestNodeStateProperty[] errorTestNodeStateProperties = testNode.Properties.OfType().ToArray(); Assert.AreEqual(1, errorTestNodeStateProperties.Length); @@ -199,7 +199,7 @@ public void ToTestNode_WhenTestResultOutcomeIsSkipped_TestNodePropertiesContainS { Outcome = TestOutcome.Skipped, }; - var testNode = testResult.ToTestNode(false, TestClient); + var testNode = testResult.ToTestNode(false, TestClient, new SessionUid("SessionUid")); SkippedTestNodeStateProperty[] skipTestNodeStateProperties = testNode.Properties.OfType().ToArray(); Assert.AreEqual(1, skipTestNodeStateProperties.Length); @@ -212,7 +212,7 @@ public void ToTestNode_WhenTestResultOutcomeIsNone_TestNodePropertiesContainSkip { Outcome = TestOutcome.None, }; - var testNode = testResult.ToTestNode(false, TestClient); + var testNode = testResult.ToTestNode(false, TestClient, new SessionUid("SessionUid")); SkippedTestNodeStateProperty[] skipTestNodeStateProperties = testNode.Properties.OfType().ToArray(); Assert.AreEqual(1, skipTestNodeStateProperties.Length); @@ -225,7 +225,7 @@ public void ToTestNode_WhenTestResultOutcomeIsPassed_TestNodePropertiesContainPa { Outcome = TestOutcome.Passed, }; - var testNode = testResult.ToTestNode(false, TestClient); + var testNode = testResult.ToTestNode(false, TestClient, new SessionUid("SessionUid")); PassedTestNodeStateProperty[] passedTestNodeStateProperties = testNode.Properties.OfType().ToArray(); Assert.AreEqual(1, passedTestNodeStateProperties.Length); @@ -239,7 +239,7 @@ public void ToTestNode_WhenTestResultHasUidAndDisplayNameWithWellKnownClient_Tes DisplayName = "TestName", }; - var testNode = testResult.ToTestNode(false, VSTestClient); + var testNode = testResult.ToTestNode(false, VSTestClient, new SessionUid("SessionUid")); SerializableKeyValuePairStringProperty[] errorTestNodeStateProperties = testNode.Properties.OfType().ToArray(); Assert.AreEqual(2, errorTestNodeStateProperties.Length, "Expected 2 SerializableKeyValuePairStringProperty"); @@ -257,7 +257,7 @@ public void ToTestNode_WhenTestResultHasTraits_TestNodePropertiesContainIt() Traits = { new Trait("key", "value") }, }; - var testNode = testResult.ToTestNode(false, VSTestClient); + var testNode = testResult.ToTestNode(false, VSTestClient, new SessionUid("SessionUid")); SerializableNamedKeyValuePairsStringProperty[] errorTestNodeStateProperties = testNode.Properties.OfType().ToArray(); Assert.AreEqual(1, errorTestNodeStateProperties.Length); @@ -280,7 +280,7 @@ public void ToTestNode_WhenTestResultHasMultipleStandardOutputMessages_TestNodeP }, }; - var testNode = testResult.ToTestNode(false, VSTestClient); + var testNode = testResult.ToTestNode(false, VSTestClient, new SessionUid("SessionUid")); StandardOutputProperty[] standardOutputProperties = testNode.Properties.OfType().ToArray(); Assert.IsTrue(standardOutputProperties.Length == 1); @@ -300,7 +300,7 @@ public void ToTestNode_WhenTestResultHasMultipleStandardErrorMessages_TestNodePr }, }; - var testNode = testResult.ToTestNode(false, VSTestClient); + var testNode = testResult.ToTestNode(false, VSTestClient, new SessionUid("SessionUid")); StandardErrorProperty[] standardErrorProperties = testNode.Properties.OfType().ToArray(); Assert.IsTrue(standardErrorProperties.Length == 1);