diff --git a/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.targets b/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.targets index 8f9d5c4c20..7329157c46 100644 --- a/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.targets +++ b/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.targets @@ -52,6 +52,8 @@ Copyright (c) .NET Foundation. All rights reserved. VSTestBlameHangDumpType="$(VSTestBlameHangDumpType)" VSTestBlameHangTimeout="$(VSTestBlameHangTimeout)" VSTestTraceDataCollectorDirectoryPath="$(TraceDataCollectorDirectoryPath)" + VSTestArtifactsProcessingMode="$(VSTestArtifactsProcessingMode)" + VSTestSessionCorrelationId="$(VSTestSessionCorrelationId)" VSTestNoLogo="$(VSTestNoLogo)" Condition="'$(IsTestProject)' == 'true'" /> diff --git a/src/Microsoft.TestPlatform.Build/PublicAPI/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.Build/PublicAPI/PublicAPI.Shipped.txt index fcde72eb43..9cd80864ae 100644 --- a/src/Microsoft.TestPlatform.Build/PublicAPI/PublicAPI.Shipped.txt +++ b/src/Microsoft.TestPlatform.Build/PublicAPI/PublicAPI.Shipped.txt @@ -42,6 +42,10 @@ Microsoft.TestPlatform.Build.Tasks.VSTestTask.VSTestLogger.get -> string[] Microsoft.TestPlatform.Build.Tasks.VSTestTask.VSTestLogger.set -> void Microsoft.TestPlatform.Build.Tasks.VSTestTask.VSTestNoLogo.get -> string Microsoft.TestPlatform.Build.Tasks.VSTestTask.VSTestNoLogo.set -> void +Microsoft.TestPlatform.Build.Tasks.VSTestTask.VSTestArtifactsProcessingMode.get -> string +Microsoft.TestPlatform.Build.Tasks.VSTestTask.VSTestArtifactsProcessingMode.set -> void +Microsoft.TestPlatform.Build.Tasks.VSTestTask.VSTestSessionCorrelationId.get -> string +Microsoft.TestPlatform.Build.Tasks.VSTestTask.VSTestSessionCorrelationId.set -> void Microsoft.TestPlatform.Build.Tasks.VSTestTask.VSTestPlatform.get -> string Microsoft.TestPlatform.Build.Tasks.VSTestTask.VSTestPlatform.set -> void Microsoft.TestPlatform.Build.Tasks.VSTestTask.VSTestResultsDirectory.get -> string @@ -63,4 +67,4 @@ override Microsoft.TestPlatform.Build.Tasks.VSTestLogsTask.Execute() -> bool override Microsoft.TestPlatform.Build.Tasks.VSTestTask.Execute() -> bool static Microsoft.TestPlatform.Build.Trace.Tracing.Trace(string message) -> void static Microsoft.TestPlatform.Build.Trace.Tracing.traceEnabled -> bool -static Microsoft.TestPlatform.Build.Utils.ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(string arg) -> string \ No newline at end of file +static Microsoft.TestPlatform.Build.Utils.ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(string arg) -> string diff --git a/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask.cs b/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask.cs index e21730652d..384e03aa42 100644 --- a/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask.cs +++ b/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask.cs @@ -161,6 +161,18 @@ public string VSTestNoLogo set; } + public string VSTestArtifactsProcessingMode + { + get; + set; + } + + public string VSTestSessionCorrelationId + { + get; + set; + } + public override bool Execute() { var traceEnabledValue = Environment.GetEnvironmentVariable("VSTEST_BUILD_TRACE"); @@ -416,6 +428,16 @@ private List AddArgs() allArgs.Add("--nologo"); } + if (!string.IsNullOrEmpty(VSTestArtifactsProcessingMode) && VSTestArtifactsProcessingMode.Equals("collect", StringComparison.OrdinalIgnoreCase)) + { + allArgs.Add("--artifactsProcessingMode-collect"); + } + + if (!string.IsNullOrEmpty(VSTestSessionCorrelationId)) + { + allArgs.Add("--testSessionCorrelationId:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(VSTestSessionCorrelationId)); + } + return allArgs; } } diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/IArtifactProcessingManager.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/IArtifactProcessingManager.cs new file mode 100644 index 0000000000..b9724b5dd0 --- /dev/null +++ b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/IArtifactProcessingManager.cs @@ -0,0 +1,14 @@ +// 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.Threading.Tasks; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + +internal interface IArtifactProcessingManager +{ + void CollectArtifacts(TestRunCompleteEventArgs testRunCompleteEventArgs, string runSettingsXml); + Task PostProcessArtifactsAsync(); +} diff --git a/src/Microsoft.TestPlatform.CoreUtilities/FeatureFlag/FeatureFlag.cs b/src/Microsoft.TestPlatform.CoreUtilities/FeatureFlag/FeatureFlag.cs new file mode 100644 index 0000000000..fa6529acc5 --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/FeatureFlag/FeatureFlag.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#if !NETSTANDARD1_0 + +using System; +using System.Collections.Generic; + +namespace Microsoft.VisualStudio.TestPlatform.Utilities; + +internal partial class FeatureFlag : IFeatureFlag +{ + private static readonly Dictionary FeatureFlags = new(); + + private const string Prefix = "VSTEST_FEATURE_"; + + public static IFeatureFlag Instance => new FeatureFlag(); + + static FeatureFlag() + { + FeatureFlags.Add(ARTIFACTS_POSTPROCESSING, false); + FeatureFlags.Add(ARTIFACTS_POSTPROCESSING_SDK_KEEP_OLD_UX, false); + } + + // Added for artifact porst-processing, it enable/disable the post processing. + // Added in 17.2-preview 7.0-preview + public static string ARTIFACTS_POSTPROCESSING = Prefix + "ARTIFACTS_POSTPROCESSING"; + + // Added for artifact porst-processing, it will show old output for dotnet sdk scenario. + // It can be useful if we need to restore old UX in case users are parsing the console output. + // Added in 17.2-preview 7.0-preview + public static string ARTIFACTS_POSTPROCESSING_SDK_KEEP_OLD_UX = Prefix + "ARTIFACTS_POSTPROCESSING_SDK_KEEP_OLD_UX"; + + // For now we're checking env var. + // We could add it also to some section inside the runsettings. + public bool IsEnabled(string featureName) => + int.TryParse(Environment.GetEnvironmentVariable(featureName), out int enabled) ? + enabled == 1 : + FeatureFlags.TryGetValue(featureName, out bool isEnabled) && isEnabled; +} + +#endif \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CoreUtilities/FeatureFlag/IFeatureFlag.cs b/src/Microsoft.TestPlatform.CoreUtilities/FeatureFlag/IFeatureFlag.cs new file mode 100644 index 0000000000..625b565595 --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/FeatureFlag/IFeatureFlag.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.Utilities; + +internal interface IFeatureFlag +{ + bool IsEnabled(string featureName); +} diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/FileHelper.cs b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/FileHelper.cs index fb36c75a3d..df80f75b85 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/FileHelper.cs +++ b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/FileHelper.cs @@ -140,6 +140,11 @@ public void Delete(string path) { File.Delete(path); } + + public void DeleteDirectory(string directoryPath, bool recursive) + { + Directory.Delete(directoryPath, recursive); + } } #endif diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IFileHelper.cs b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IFileHelper.cs index 7457f340d7..4c4892e0a1 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IFileHelper.cs +++ b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/Interfaces/IFileHelper.cs @@ -131,6 +131,14 @@ public interface IFileHelper /// void DeleteEmptyDirectroy(string directoryPath); + /// + /// Helper for deleting a directory. + /// + /// + /// The directory path. + /// + void DeleteDirectory(string directoryPath, bool recursive); + #if !NETSTANDARD1_0 /// /// Gets all files in directory based on search pattern diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Properties/AssemblyInfo.cs b/src/Microsoft.TestPlatform.CoreUtilities/Properties/AssemblyInfo.cs index 634857fa93..3be03ffb62 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Properties/AssemblyInfo.cs +++ b/src/Microsoft.TestPlatform.CoreUtilities/Properties/AssemblyInfo.cs @@ -28,6 +28,8 @@ [assembly: InternalsVisibleTo("Microsoft.TestPlatform.CrossPlatEngine.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.TestPlatform.TestHostProvider.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.CrossPlatEngine, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("vstest.console.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] #if !NETSTANDARD1_0 // The following GUID is for the ID of the typelib if this project is exposed to COM diff --git a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/PublicAPI.Shipped.txt index 4854efbf2f..12a6594564 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/PublicAPI.Shipped.txt +++ b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/PublicAPI.Shipped.txt @@ -70,6 +70,7 @@ Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces.IFileHelper Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces.IFileHelper.CopyFile(string sourcePath, string destinationPath) -> void Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces.IFileHelper.Delete(string path) -> void Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces.IFileHelper.DeleteEmptyDirectroy(string directoryPath) -> void +Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces.IFileHelper.DeleteDirectory(string directoryPath, bool recursive) -> void Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces.IFileHelper.DirectoryExists(string path) -> bool Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces.IFileHelper.Exists(string path) -> bool Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces.IFileHelper.GetCurrentDirectory() -> string diff --git a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net/PublicAPI.Shipped.txt index 9c98276d02..d94451d668 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net/PublicAPI.Shipped.txt +++ b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net/PublicAPI.Shipped.txt @@ -56,6 +56,7 @@ Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.FileHelper.CopyFile(string Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.FileHelper.CreateDirectory(string path) -> System.IO.DirectoryInfo Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.FileHelper.Delete(string path) -> void Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.FileHelper.DeleteEmptyDirectroy(string dirPath) -> void +Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.FileHelper.DeleteDirectory(string directoryPath, bool recursive) -> void Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.FileHelper.DirectoryExists(string path) -> bool Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.FileHelper.EnumerateFiles(string directory, System.IO.SearchOption searchOption, params string[] endsWithSearchPatterns) -> System.Collections.Generic.IEnumerable Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.FileHelper.Exists(string path) -> bool diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/PostProcessing/ArtifactProcessingManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/PostProcessing/ArtifactProcessingManager.cs new file mode 100644 index 0000000000..e6fb3c7729 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/PostProcessing/ArtifactProcessingManager.cs @@ -0,0 +1,230 @@ +// 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; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.VisualStudio.TestPlatform.Common; +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing; +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestRunAttachmentsProcessing; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; +using Microsoft.VisualStudio.TestPlatform.Utilities; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.ArtifactProcessing; + +internal class ArtifactProcessingManager : IArtifactProcessingManager +{ + private const string RunsettingsFileName = "runsettings.xml"; + private const string ExecutionCompleteFileName = "executionComplete.json"; + + private readonly string _testSessionCorrelationId; + private readonly IFileHelper _fileHelper; + private readonly ITestRunAttachmentsProcessingManager _testRunAttachmentsProcessingManager; + private readonly string _testSessionProcessArtifactFolder; + private readonly string _processArtifactFolder; + private readonly IDataSerializer _dataSerialized; + private readonly ITestRunAttachmentsProcessingEventsHandler _testRunAttachmentsProcessingEventsHandler; + private readonly IFeatureFlag _featureFlag; + + public ArtifactProcessingManager(string testSessionCorrelationId) : + this(testSessionCorrelationId, + new FileHelper(), + new TestRunAttachmentsProcessingManager(TestPlatformEventSource.Instance, new DataCollectorAttachmentsProcessorsFactory()), + JsonDataSerializer.Instance, + new PostProcessingTestRunAttachmentsProcessingEventsHandler(ConsoleOutput.Instance), + FeatureFlag.Instance) + { } + + public ArtifactProcessingManager(string testSessionCorrelationId, + IFileHelper fileHelper, + ITestRunAttachmentsProcessingManager testRunAttachmentsProcessingManager, + IDataSerializer dataSerialized, + ITestRunAttachmentsProcessingEventsHandler testRunAttachmentsProcessingEventsHandler, + IFeatureFlag featureFlag) + { + // We don't validate for null, it's expected, we'll have testSessionCorrelationId only in case of .NET SDK run. + if (testSessionCorrelationId is not null) + { + _testSessionCorrelationId = testSessionCorrelationId; + _processArtifactFolder = Path.Combine(Path.GetTempPath(), _testSessionCorrelationId); + _testSessionProcessArtifactFolder = Path.Combine(_processArtifactFolder, $"{Process.GetCurrentProcess().Id}_{Guid.NewGuid()}"); + } + _fileHelper = fileHelper; + _testRunAttachmentsProcessingManager = testRunAttachmentsProcessingManager; + _dataSerialized = dataSerialized; + _testRunAttachmentsProcessingEventsHandler = testRunAttachmentsProcessingEventsHandler; + _featureFlag = featureFlag; + } + + public void CollectArtifacts(TestRunCompleteEventArgs testRunCompleteEventArgs, string runSettingsXml) + { + if (!_featureFlag.IsEnabled(FeatureFlag.ARTIFACTS_POSTPROCESSING)) + { + EqtTrace.Verbose("ArtifactProcessingManager.CollectArtifacts: Feature disabled"); + return; + } + + if (string.IsNullOrEmpty(_testSessionCorrelationId)) + { + EqtTrace.Verbose("ArtifactProcessingManager.CollectArtifacts: null testSessionCorrelationId"); + return; + } + + try + { + // We need to save in case of attachements, we'll show these at the end on console. + if (testRunCompleteEventArgs?.AttachmentSets.Count > 0) + { + EqtTrace.Verbose($"ArtifactProcessingManager.CollectArtifacts: Saving data collectors artifacts for post process into {_processArtifactFolder}"); + Stopwatch watch = Stopwatch.StartNew(); + _fileHelper.CreateDirectory(_testSessionProcessArtifactFolder); + EqtTrace.Verbose($"ArtifactProcessingManager.CollectArtifacts: Persist runsettings \n{runSettingsXml}"); + _fileHelper.WriteAllTextToFile(Path.Combine(_testSessionProcessArtifactFolder, RunsettingsFileName), runSettingsXml); + var serializedExecutionComplete = _dataSerialized.SerializePayload(MessageType.ExecutionComplete, testRunCompleteEventArgs); + EqtTrace.Verbose($"ArtifactProcessingManager.CollectArtifacts: Persist ExecutionComplete message \n{serializedExecutionComplete}"); + _fileHelper.WriteAllTextToFile(Path.Combine(_testSessionProcessArtifactFolder, ExecutionCompleteFileName), serializedExecutionComplete); + EqtTrace.Verbose($"ArtifactProcessingManager.CollectArtifacts: Artifacts saved in {watch.Elapsed}"); + } + } + catch (Exception e) + { + EqtTrace.Error("ArtifactProcessingManager.CollectArtifacts: Exception during artifact post processing: " + e); + } + } + + public async Task PostProcessArtifactsAsync() + { + if (!_featureFlag.IsEnabled(FeatureFlag.ARTIFACTS_POSTPROCESSING)) + { + EqtTrace.Verbose("ArtifactProcessingManager.PostProcessArtifacts: Feature disabled"); + return; + } + + // This is not expected, anyway we prefer avoid exception for post processing + if (string.IsNullOrEmpty(_testSessionCorrelationId)) + { + EqtTrace.Error("ArtifactProcessingManager.PostProcessArtifacts: Unexpected null testSessionCorrelationId"); + return; + } + + if (!_fileHelper.DirectoryExists(_processArtifactFolder)) + { + EqtTrace.Verbose("ArtifactProcessingManager.PostProcessArtifacts: There are no artifacts to postprocess"); + return; + } + + var testArtifacts = LoadTestArtifacts(); + if (testArtifacts?.Length > 0) + { + try + { + await DataCollectorsAttachmentsPostProcessing(testArtifacts); + } + finally + { + try + { + _fileHelper.DeleteDirectory(_processArtifactFolder, true); + EqtTrace.Verbose($"ArtifactProcessingManager.PostProcessArtifacts: Directory '{_processArtifactFolder}' removed."); + } + catch (Exception ex) + { + EqtTrace.Error($"ArtifactProcessingManager.PostProcessArtifacts: Unable to removed directory the '{_processArtifactFolder}'.\n{ex}"); + } + } + } + else + { + EqtTrace.Warning($"ArtifactProcessingManager.PostProcessArtifacts: There are no artifacts to postprocess also if the artifact directory '{_processArtifactFolder}' exits"); + } + } + + // We don't put everything inside a try/catch, we prefer get the exceptions because + // we don't want partial results, it could confuse the user, better have a "failure". + private async Task DataCollectorsAttachmentsPostProcessing(TestArtifacts[] testArtifacts) + { + // We take the biggest runsettings in size, it should be the one with more configuration. + // In future we can think to merge...but it's not easy for custom config, we could break something. + string runsettingsFile = testArtifacts + .SelectMany(x => x.Artifacts.Where(x => x.Type == ArtifactType.Runsettings)) + .Select(x => new FileInfo(x.FileName)) + .OrderByDescending(x => x.Length) + .FirstOrDefault()? + .FullName; + + string runsettingsXml = null; + if (runsettingsFile is not null) + { + using var artifactStream = _fileHelper.GetStream(runsettingsFile, FileMode.Open, FileAccess.Read); + using var streamReader = new StreamReader(artifactStream); + runsettingsXml = await streamReader.ReadToEndAsync(); + EqtTrace.Verbose($"ArtifactProcessingManager.MergeDataCollectorAttachments: Chosen runsettings \n{runsettingsXml}"); + } + else + { + EqtTrace.Verbose($"ArtifactProcessingManager.MergeDataCollectorAttachments: Null runsettings"); + } + + HashSet invokedDataCollectors = new(); + List attachments = new(); + foreach (var artifact in testArtifacts + .SelectMany(x => x.Artifacts) + .Where(x => x.Type == ArtifactType.ExecutionComplete)) + { + using var artifactStream = _fileHelper.GetStream(artifact.FileName, FileMode.Open, FileAccess.Read); + using var streamReader = new StreamReader(artifactStream); + string executionCompleteMessage = await streamReader.ReadToEndAsync(); + EqtTrace.Verbose($"ArtifactProcessingManager.MergeDataCollectorAttachments: ExecutionComplete message \n{executionCompleteMessage}"); + TestRunCompleteEventArgs eventArgs = _dataSerialized.DeserializePayload(_dataSerialized.DeserializeMessage(executionCompleteMessage)); + foreach (var invokedDataCollector in eventArgs?.InvokedDataCollectors) + { + invokedDataCollectors.Add(invokedDataCollector); + } + foreach (var attachmentSet in eventArgs?.AttachmentSets) + { + attachments.Add(attachmentSet); + } + } + + await _testRunAttachmentsProcessingManager.ProcessTestRunAttachmentsAsync(runsettingsXml, + new RequestData() + { + IsTelemetryOptedIn = IsTelemetryOptedIn(), + ProtocolConfig = ObjectModel.Constants.DefaultProtocolConfig + }, + attachments, + invokedDataCollectors, + _testRunAttachmentsProcessingEventsHandler, + CancellationToken.None); + } + + + private TestArtifacts[] LoadTestArtifacts() => _fileHelper.GetFiles(_processArtifactFolder, "*.*", SearchOption.AllDirectories) + .Select(file => new { TestSessionId = Path.GetFileName(Path.GetDirectoryName(file)), Artifact = file }) + .GroupBy(grp => grp.TestSessionId) + .Select(testSessionArtifact => new TestArtifacts(testSessionArtifact.Key, testSessionArtifact.Select(x => ParseArtifact(x.Artifact)).Where(x => x is not null).ToArray())) + .ToArray(); + + private static Artifact ParseArtifact(string fileName) => + Path.GetFileName(fileName) switch + { + RunsettingsFileName => new Artifact(fileName, ArtifactType.ExecutionComplete), + ExecutionCompleteFileName => new Artifact(fileName, ArtifactType.Runsettings), + _ => null + }; + + private static bool IsTelemetryOptedIn() => Environment.GetEnvironmentVariable("VSTEST_TELEMETRY_OPTEDIN")?.Equals("1", StringComparison.Ordinal) == true; +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/PostProcessing/PostProcessingTestRunAttachmentsProcessingEventsHandler.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/PostProcessing/PostProcessingTestRunAttachmentsProcessingEventsHandler.cs new file mode 100644 index 0000000000..99c8b01250 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/PostProcessing/PostProcessingTestRunAttachmentsProcessingEventsHandler.cs @@ -0,0 +1,75 @@ +// 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; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using Microsoft.VisualStudio.TestPlatform.Utilities; + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.ArtifactProcessing; + +using CommandLineResources = Resources.Resources; + +internal class PostProcessingTestRunAttachmentsProcessingEventsHandler : ITestRunAttachmentsProcessingEventsHandler +{ + private readonly IOutput _consoleOutput; + private readonly ConcurrentBag _attachmentsSet = new(); + + public PostProcessingTestRunAttachmentsProcessingEventsHandler(IOutput consoleOutput) + { + _consoleOutput = consoleOutput ?? throw new ArgumentNullException(nameof(consoleOutput)); + } + + public void HandleLogMessage(TestMessageLevel level, string message) + { } + + public void HandleRawMessage(string rawMessage) + { } + + public void HandleTestRunAttachmentsProcessingProgress(TestRunAttachmentsProcessingProgressEventArgs attachmentsProcessingProgressEventArgs) + { } + + public void HandleProcessedAttachmentsChunk(IEnumerable attachments) + { + if (attachments is null) + { + EqtTrace.Warning($"PostProcessingTestRunAttachmentsProcessingEventsHandler.HandleProcessedAttachmentsChunk: Unexpected null attachments"); + return; + } + + foreach (var attachment in attachments) + { + _attachmentsSet.Add(attachment); + } + } + + public void HandleTestRunAttachmentsProcessingComplete(TestRunAttachmentsProcessingCompleteEventArgs attachmentsProcessingCompleteEventArgs, IEnumerable lastChunk) + { + foreach (var attachment in lastChunk ?? Enumerable.Empty()) + { + _attachmentsSet.Add(attachment); + } + + if (!_attachmentsSet.IsEmpty) + { + // Make an empty line + _consoleOutput.WriteLine("", OutputLevel.Information); + } + + _consoleOutput.Information(false, ConsoleColor.Gray, CommandLineResources.AttachmentsBanner); + foreach (var attachmentSet in _attachmentsSet) + { + foreach (var uriDataAttachment in attachmentSet.Attachments) + { + var attachmentOutput = string.Format(CultureInfo.CurrentCulture, CommandLineResources.AttachmentOutputFormat, uriDataAttachment.Uri.LocalPath); + _consoleOutput.Information(false, ConsoleColor.Gray, attachmentOutput); + } + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/PostProcessing/TestArtifacts.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/PostProcessing/TestArtifacts.cs new file mode 100644 index 0000000000..880aaaa1d5 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/PostProcessing/TestArtifacts.cs @@ -0,0 +1,37 @@ +// 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; + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.ArtifactProcessing; + +internal class TestArtifacts +{ + public TestArtifacts(string testSession, Artifact[] artifacts) + { + TestSession = testSession ?? throw new ArgumentNullException(nameof(testSession)); + Artifacts = artifacts ?? throw new ArgumentNullException(nameof(artifacts)); + } + + public Artifact[] Artifacts { get; set; } + + public string TestSession { get; } +} + +internal class Artifact +{ + public Artifact(string fileName, ArtifactType type) + { + FileName = fileName ?? throw new ArgumentNullException(nameof(fileName)); + Type = type; + } + + public string FileName { get; } + public ArtifactType Type { get; } +} + +internal enum ArtifactType +{ + ExecutionComplete, + Runsettings +} \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.Designer.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.Designer.cs index 9fb9692fc8..16c486e848 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.Designer.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.Designer.cs @@ -11,7 +11,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Resources { using System; using System.Reflection; - + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -78,6 +78,24 @@ internal static string AttachDebuggerToDefaultTestHostFailure { } } + /// + /// Looks up a localized string similar to {0}. + /// + internal static string AttachmentOutputFormat { + get { + return ResourceManager.GetString("AttachmentOutputFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Attachments:. + /// + internal static string AttachmentsBanner { + get { + return ResourceManager.GetString("AttachmentsBanner", resourceCulture); + } + } + /// /// Looks up a localized string similar to DataCollector debugging is enabled. Please attach debugger to datacollector process to continue.. /// diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.resx b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.resx index 74fc12636d..a7f9b33437 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.resx +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/Resources.resx @@ -219,4 +219,10 @@ The runsettings changed between the time when the test session was established and the time of the current run/discovery request. + + {0} + + + Attachments: + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.cs.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.cs.xlf index b86f8ac1cb..8707d1e83c 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.cs.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.cs.xlf @@ -167,6 +167,16 @@ Soubor runsettings se změnil mezi časem, kdy byla relace testu vytvořena, a časem aktuálního požadavku spuštění nebo zjišťování. + + {0} + {0} + + + + Attachments: + Attachments: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.de.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.de.xlf index e4f1b51412..01d5578ce6 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.de.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.de.xlf @@ -167,6 +167,16 @@ Die Ausführungseinstellungen wurden zwischen dem Zeitpunkt, zu dem die Testsitzung eingerichtet wurde, und dem Zeitpunkt der aktuellen Ausführungs-/Ermittlungsanforderung geändert. + + {0} + {0} + + + + Attachments: + Attachments: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.es.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.es.xlf index d1d560d6d7..fc67bdd415 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.es.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.es.xlf @@ -167,6 +167,16 @@ Los ajustes de ejecución cambiaron entre el momento en que se estableció la sesión de prueba y el momento de la solicitud de ejecución/detección actual. + + {0} + {0} + + + + Attachments: + Attachments: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.fr.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.fr.xlf index 7d0fc56e22..b472751948 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.fr.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.fr.xlf @@ -167,6 +167,16 @@ Les paramètres d'exécution ont changé entre le moment où la session de test a été établie et le moment de la demande d'exécution/découverte actuelle. + + {0} + {0} + + + + Attachments: + Attachments: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.it.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.it.xlf index d970444743..2f5b5efacf 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.it.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.it.xlf @@ -167,6 +167,16 @@ Le impostazioni di esecuzione sono cambiate tra l'ora in cui è stata stabilita la sessione di test e l'ora della richiesta di esecuzione/individuazione corrente. + + {0} + {0} + + + + Attachments: + Attachments: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ja.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ja.xlf index 0d2e1fc90a..ae95185cd9 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ja.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ja.xlf @@ -167,6 +167,16 @@ テスト セッションが確立された時刻と現在の実行/検出要求の時刻の間で、実行設定が変更されました。 + + {0} + {0} + + + + Attachments: + Attachments: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ko.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ko.xlf index e886160788..9dc5c7ba9f 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ko.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ko.xlf @@ -167,6 +167,16 @@ 테스트 세션이 설정된 시간과 현재 실행/검색 요청의 시간 사이에 runsettings가 변경되었습니다. + + {0} + {0} + + + + Attachments: + Attachments: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pl.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pl.xlf index fb9ed5b015..a55e5f24e9 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pl.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pl.xlf @@ -167,6 +167,16 @@ Ustawienia runsettings uległy zmianie między czasem ustanowienia sesji testowej a czasem bieżącego żądania uruchomienia/odnajdywania. + + {0} + {0} + + + + Attachments: + Attachments: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pt-BR.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pt-BR.xlf index 82b2a17367..ebdd0b9acc 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pt-BR.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.pt-BR.xlf @@ -167,6 +167,16 @@ As runsettings foram alteradas entre a hora em que a sessão de teste foi estabelecida e a hora da solicitação de execução/descoberta atual. + + {0} + {0} + + + + Attachments: + Attachments: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ru.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ru.xlf index 484a68b3d6..e0b4174db6 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ru.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.ru.xlf @@ -167,6 +167,16 @@ Параметры runsettings изменились между временем начала тестового сеанса и временем текущего запроса на запуск или обнаружение. + + {0} + {0} + + + + Attachments: + Attachments: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.tr.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.tr.xlf index 6af819a259..e525f44600 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.tr.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.tr.xlf @@ -167,6 +167,16 @@ runsettings, test oturumunun kurulduğu zamanla mevcut çalıştırma/keşif isteği zamanı arasında değişti. + + {0} + {0} + + + + Attachments: + Attachments: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.xlf index ad5ba9b685..4c6eba4a84 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.xlf @@ -153,6 +153,16 @@ Could not find an available proxy to match the original run settings. + + {0} + {0} + + + + Attachments: + Attachments: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hans.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hans.xlf index cd0a13ed5d..032e3f23e4 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hans.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hans.xlf @@ -167,6 +167,16 @@ runsettings 在建立测试会话的时间和当前运行/发现请求的时间之间发生了更改。 + + {0} + {0} + + + + Attachments: + Attachments: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hant.xlf b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hant.xlf index 147694c32d..18e35133e1 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hant.xlf +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Resources/xlf/Resources.zh-Hant.xlf @@ -167,6 +167,16 @@ Runsettings 在測試工作階段建立時間與目前執行/探索要求的時間之間有所變更。 + + {0} + {0} + + + + Attachments: + Attachments: + + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.PlatformAbstractions/common/Tracing/PlatformEqtTrace.cs b/src/Microsoft.TestPlatform.PlatformAbstractions/common/Tracing/PlatformEqtTrace.cs index 472bd96413..2e138f0da6 100644 --- a/src/Microsoft.TestPlatform.PlatformAbstractions/common/Tracing/PlatformEqtTrace.cs +++ b/src/Microsoft.TestPlatform.PlatformAbstractions/common/Tracing/PlatformEqtTrace.cs @@ -336,25 +336,39 @@ private static bool EnsureTraceIsInitialized() return true; } + using var process = Process.GetCurrentProcess(); + string runnerLogFileName = $"{Path.GetFileNameWithoutExtension(process.MainModule.FileName)}_{process.Id}.{DateTime.Now:yy-MM-dd_HH-mm-ss_fffff}.diag"; string logsDirectory = Path.GetTempPath(); // Set the trace level and add the trace listener if (LogFile == null) { - using var process = Process.GetCurrentProcess(); - // In case of parallel execution, there may be several processes with same name. // Add a process id to make the traces unique. - LogFile = Path.Combine( - logsDirectory, - Path.GetFileNameWithoutExtension(process.MainModule.FileName) + "." + process.Id + ".TpTrace.log"); + LogFile = Path.Combine(logsDirectory, runnerLogFileName); } // Add a default listener s_traceFileSize = DefaultTraceFileSize; try { - Source.Listeners.Add(new RollingFileTraceListener(LogFile, ListenerName, s_traceFileSize)); + // If we point to a folder we create default filename + if (LogFile.EndsWith(@"\") || LogFile.EndsWith("/")) + { + if (!Directory.Exists(LogFile)) + { + Directory.CreateDirectory(LogFile); + } + + runnerLogFileName = Path.Combine(LogFile, runnerLogFileName); + LogFile = Path.Combine(LogFile, $"{Path.GetFileNameWithoutExtension(process.MainModule.FileName)}_{process.Id}.diag"); + } + else + { + runnerLogFileName = LogFile; + } + + Source.Listeners.Add(new RollingFileTraceListener(runnerLogFileName, ListenerName, s_traceFileSize)); } catch (Exception e) { diff --git a/src/vstest.console/CommandLine/CommandLineOptions.cs b/src/vstest.console/CommandLine/CommandLineOptions.cs index 7e023b2534..04637aecee 100644 --- a/src/vstest.console/CommandLine/CommandLineOptions.cs +++ b/src/vstest.console/CommandLine/CommandLineOptions.cs @@ -15,6 +15,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine; using CommandLineResources = Resources.Resources; using vstest.console.Internal; using System.Globalization; +using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; /// /// Provides access to the command-line options. @@ -272,6 +273,16 @@ internal Framework TargetFrameworkVersion /// internal string SettingsFile { get; set; } + /// + /// Gets or sets the /ArtifactsProcessingMode value. + /// + internal ArtifactProcessingMode ArtifactProcessingMode { get; set; } + + /// + /// Gets or sets the /TestSessionCorrelationId value. + /// + internal string TestSessionCorrelationId { get; set; } + #endregion #region Public Methods diff --git a/src/vstest.console/CommandLine/Executor.cs b/src/vstest.console/CommandLine/Executor.cs index 1363714bad..507bb84f0b 100644 --- a/src/vstest.console/CommandLine/Executor.cs +++ b/src/vstest.console/CommandLine/Executor.cs @@ -103,7 +103,11 @@ internal int Execute(params string[] args) } else { - PrintSplashScreen(isDiag); + // If we're postprocessing we don't need to show the splash + if (!ArtifactProcessingPostProcessModeProcessor.ContainsPostProcessCommand(args)) + { + PrintSplashScreen(isDiag); + } } int exitCode = 0; diff --git a/src/vstest.console/Internal/ConsoleLogger.cs b/src/vstest.console/Internal/ConsoleLogger.cs index b7b09d9da0..6a043ff39e 100644 --- a/src/vstest.console/Internal/ConsoleLogger.cs +++ b/src/vstest.console/Internal/ConsoleLogger.cs @@ -125,11 +125,11 @@ public ConsoleLogger() /// /// Constructor added for testing purpose /// - /// - internal ConsoleLogger(IOutput output, IProgressIndicator progressIndicator) + internal ConsoleLogger(IOutput output, IProgressIndicator progressIndicator, IFeatureFlag featureFlag) { Output = output; _progressIndicator = progressIndicator; + _featureFlag = featureFlag; } #endregion @@ -148,6 +148,8 @@ protected static IOutput Output private IProgressIndicator _progressIndicator; + private readonly IFeatureFlag _featureFlag = FeatureFlag.Instance; + /// /// Get the verbosity level for the console logger /// @@ -689,13 +691,16 @@ private void TestRunCompleteHandler(object sender, TestRunCompleteEventArgs e) var runLevelAttachementCount = (e.AttachmentSets == null) ? 0 : e.AttachmentSets.Sum(attachmentSet => attachmentSet.Attachments.Count); if (runLevelAttachementCount > 0) { - Output.Information(false, CommandLineResources.AttachmentsBanner); - foreach (var attachmentSet in e.AttachmentSets) + if (!_featureFlag.IsEnabled(FeatureFlag.ARTIFACTS_POSTPROCESSING) || _featureFlag.IsEnabled(FeatureFlag.ARTIFACTS_POSTPROCESSING_SDK_KEEP_OLD_UX)) { - foreach (var uriDataAttachment in attachmentSet.Attachments) + Output.Information(false, CommandLineResources.AttachmentsBanner); + foreach (var attachmentSet in e.AttachmentSets) { - var attachmentOutput = string.Format(CultureInfo.CurrentCulture, CommandLineResources.AttachmentOutputFormat, uriDataAttachment.Uri.LocalPath); - Output.Information(false, attachmentOutput); + foreach (var uriDataAttachment in attachmentSet.Attachments) + { + var attachmentOutput = string.Format(CultureInfo.CurrentCulture, CommandLineResources.AttachmentOutputFormat, uriDataAttachment.Uri.LocalPath); + Output.Information(false, attachmentOutput); + } } } } diff --git a/src/vstest.console/Processors/ArtifactProcessingCollectModeProcessor.cs b/src/vstest.console/Processors/ArtifactProcessingCollectModeProcessor.cs new file mode 100644 index 0000000000..dd7154faf2 --- /dev/null +++ b/src/vstest.console/Processors/ArtifactProcessingCollectModeProcessor.cs @@ -0,0 +1,111 @@ +// 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; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; + +/// +/// Argument Processor for the "--artifactsProcessingMode-collect|/ArtifactsProcessingMode-Collect" command line argument. +/// +internal class ArtifactProcessingCollectModeProcessor : IArgumentProcessor +{ + /// + /// The name of the command line argument that the ArtifactProcessingModeProcessor handles. + /// + public const string CommandName = "/ArtifactsProcessingMode-Collect"; + + private Lazy _metadata; + + private Lazy _executor; + + /// + /// Gets the metadata. + /// + public Lazy Metadata + { + get + { + if (_metadata == null) + { + _metadata = new Lazy(() => new ArtifactProcessingCollectModeProcessorCapabilities(CommandLineOptions.Instance)); + } + + return _metadata; + } + } + + /// + /// Gets or sets the executor. + /// + public Lazy Executor + { + get + { + if (_executor == null) + { + _executor = new Lazy(() => new ArtifactProcessingCollectModeProcessorExecutor(CommandLineOptions.Instance)); + } + + return _executor; + } + + set + { + _executor = value; + } + } +} + +internal class ArtifactProcessingCollectModeProcessorCapabilities : BaseArgumentProcessorCapabilities +{ + private readonly CommandLineOptions _commandLineOptions; + + public ArtifactProcessingCollectModeProcessorCapabilities(CommandLineOptions options) + { + _commandLineOptions = options ?? throw new ArgumentNullException(nameof(options)); + } + + public override string CommandName => ArtifactProcessingCollectModeProcessor.CommandName; + + public override bool AllowMultiple => false; + + // We put priority at the same level of the argument processor for runsettings passed as argument through cli. + // We'll be sure to run before test run arg processor. + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.CliRunSettings; + + public override HelpContentPriority HelpPriority => HelpContentPriority.None; + + // We want to be sure that this command won't show in user help + public override string HelpContentResourceName => null; +} + +internal enum ArtifactProcessingMode +{ + None, + Collect, + PostProcess +} + +/// +/// Argument Executor for the "/ArtifactsProcessingMode-Collect" command line argument. +/// +internal class ArtifactProcessingCollectModeProcessorExecutor : IArgumentExecutor +{ + private readonly CommandLineOptions _commandLineOptions; + + public ArtifactProcessingCollectModeProcessorExecutor(CommandLineOptions options) + { + _commandLineOptions = options ?? throw new ArgumentNullException(nameof(options)); + } + + public void Initialize(string argument) + { + _commandLineOptions.ArtifactProcessingMode = ArtifactProcessingMode.Collect; + EqtTrace.Verbose($"ArtifactProcessingPostProcessModeProcessorExecutor.Initialize: ArtifactProcessingMode.Collect"); + } + + public ArgumentProcessorResult Execute() => ArgumentProcessorResult.Success; +} \ No newline at end of file diff --git a/src/vstest.console/Processors/ArtifactProcessingPostProcessModeProcessor.cs b/src/vstest.console/Processors/ArtifactProcessingPostProcessModeProcessor.cs new file mode 100644 index 0000000000..179f5ab6e0 --- /dev/null +++ b/src/vstest.console/Processors/ArtifactProcessingPostProcessModeProcessor.cs @@ -0,0 +1,131 @@ +// 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; +using System.Linq; + +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.ArtifactProcessing; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; +using Microsoft.VisualStudio.TestPlatform.Utilities; + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; + +/// +/// Argument Processor for the "--artifactsProcessingMode-postprocess|/ArtifactsProcessingMode-PostProcess" command line argument. +/// +internal class ArtifactProcessingPostProcessModeProcessor : IArgumentProcessor +{ + /// + /// The name of the command line argument that the ArtifactProcessingModeProcessor handles. + /// + public const string CommandName = "/ArtifactsProcessingMode-PostProcess"; + + private Lazy _metadata; + + private Lazy _executor; + + /// + /// Gets the metadata. + /// + public Lazy Metadata + { + get + { + if (_metadata == null) + { + _metadata = new Lazy(() => new ArtifactProcessingPostProcessModeProcessorCapabilities(CommandLineOptions.Instance)); + } + + return _metadata; + } + } + + /// + /// Gets or sets the executor. + /// + public Lazy Executor + { + get + { + if (_executor == null) + { + _executor = new Lazy(() => new ArtifactProcessingPostProcessModeProcessorExecutor(CommandLineOptions.Instance, + new ArtifactProcessingManager(CommandLineOptions.Instance.TestSessionCorrelationId))); + } + + return _executor; + } + + set + { + _executor = value; + } + } + + public static bool ContainsPostProcessCommand(string[] args, IFeatureFlag featureFlag = null) + => (featureFlag ?? FeatureFlag.Instance).IsEnabled(FeatureFlag.ARTIFACTS_POSTPROCESSING) && + (args?.Contains("--artifactsProcessingMode-postprocess", StringComparer.OrdinalIgnoreCase) == true || + args?.Contains(CommandName, StringComparer.OrdinalIgnoreCase) == true); +} + +internal class ArtifactProcessingPostProcessModeProcessorCapabilities : BaseArgumentProcessorCapabilities +{ + private readonly CommandLineOptions _commandLineOptions; + + public ArtifactProcessingPostProcessModeProcessorCapabilities(CommandLineOptions options) + { + _commandLineOptions = options ?? throw new ArgumentNullException(nameof(options)); + } + + public override string CommandName => ArtifactProcessingPostProcessModeProcessor.CommandName; + + public override bool AllowMultiple => false; + + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.Normal; + + public override HelpContentPriority HelpPriority => HelpContentPriority.None; + + // We want to be sure that this command won't show in user help + public override string HelpContentResourceName => null; + + public override bool IsAction => true; +} + +/// +/// Argument Executor for the "/ArtifactsProcessingMode-PostProcess" command line argument. +/// +internal class ArtifactProcessingPostProcessModeProcessorExecutor : IArgumentExecutor +{ + private readonly CommandLineOptions _commandLineOptions; + private readonly IArtifactProcessingManager _artifactProcessingManage; + + public ArtifactProcessingPostProcessModeProcessorExecutor(CommandLineOptions options, IArtifactProcessingManager artifactProcessingManager) + { + _commandLineOptions = options ?? throw new ArgumentNullException(nameof(options)); + _artifactProcessingManage = artifactProcessingManager ?? throw new ArgumentNullException(nameof(artifactProcessingManager)); ; + } + + public void Initialize(string argument) + { + _commandLineOptions.ArtifactProcessingMode = ArtifactProcessingMode.PostProcess; + EqtTrace.Verbose($"ArtifactProcessingPostProcessModeProcessorExecutor.Initialize: ArtifactProcessingMode.PostProcess"); + } + + public ArgumentProcessorResult Execute() + { + try + { + // We don't have async execution at the moment for the argument processors. + // Anyway post processing could involve a lot of I/O and so we make some space + // for some possible parallelization async/await and fair I/O for the callee. + _artifactProcessingManage.PostProcessArtifactsAsync().Wait(); + return ArgumentProcessorResult.Success; + } + catch (Exception e) + { + EqtTrace.Error("ArtifactProcessingPostProcessModeProcessorExecutor: Exception during artifact post processing: " + e); + return ArgumentProcessorResult.Fail; + } + } +} \ No newline at end of file diff --git a/src/vstest.console/Processors/EnableDiagArgumentProcessor.cs b/src/vstest.console/Processors/EnableDiagArgumentProcessor.cs index baf5dbb0d9..2e67109c99 100644 --- a/src/vstest.console/Processors/EnableDiagArgumentProcessor.cs +++ b/src/vstest.console/Processors/EnableDiagArgumentProcessor.cs @@ -226,8 +226,12 @@ private string GetDiagFilePath(string diagFilePathArgument) // Remove double quotes if present. diagFilePathArgument = diagFilePathArgument.Replace("\"", ""); - // Create base directory for diag file path (if doesn't exist) - CreateDirectoryIfNotExists(diagFilePathArgument); + // If we provide a directory we don't need to create the base directory. + if (!diagFilePathArgument.EndsWith(@"\") && !diagFilePathArgument.EndsWith("/")) + { + // Create base directory for diag file path (if doesn't exist) + CreateDirectoryIfNotExists(diagFilePathArgument); + } // return full diag file path. (This is done so that vstest and testhost create logs at same location.) return Path.GetFullPath(diagFilePathArgument); diff --git a/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs b/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs index 88f7e72f86..c35d91fd70 100644 --- a/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs +++ b/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs @@ -20,6 +20,8 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.Utilities; using CommandLineResources = Resources.Resources; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.ArtifactProcessing; internal class RunSpecificTestsArgumentProcessor : IArgumentProcessor { @@ -53,6 +55,7 @@ public Lazy Executor CommandLineOptions.Instance, RunSettingsManager.Instance, TestRequestManager.Instance, + new ArtifactProcessingManager(CommandLineOptions.Instance.TestSessionCorrelationId), ConsoleOutput.Instance)); } @@ -154,6 +157,7 @@ public RunSpecificTestsArgumentExecutor( CommandLineOptions options, IRunSettingsProvider runSettingsProvider, ITestRequestManager testRequestManager, + IArtifactProcessingManager artifactProcessingManager, IOutput output) { Contract.Requires(options != null); @@ -165,7 +169,7 @@ public RunSpecificTestsArgumentExecutor( _runSettingsManager = runSettingsProvider; Output = output; _discoveryEventsRegistrar = new DiscoveryEventsRegistrar(DiscoveryRequest_OnDiscoveredTests); - _testRunEventsRegistrar = new TestRunRequestEventsRegistrar(Output, _commandLineOptions); + _testRunEventsRegistrar = new TestRunRequestEventsRegistrar(Output, _commandLineOptions, artifactProcessingManager); } #endregion @@ -344,11 +348,13 @@ private class TestRunRequestEventsRegistrar : ITestRunEventsRegistrar { private readonly IOutput _output; private readonly CommandLineOptions _commandLineOptions; + private readonly IArtifactProcessingManager _artifactProcessingManager; - public TestRunRequestEventsRegistrar(IOutput output, CommandLineOptions commandLineOptions) + public TestRunRequestEventsRegistrar(IOutput output, CommandLineOptions commandLineOptions, IArtifactProcessingManager artifactProcessingManager) { _output = output; _commandLineOptions = commandLineOptions; + _artifactProcessingManager = artifactProcessingManager; } public void LogWarning(string message) @@ -384,6 +390,12 @@ private void TestRunRequest_OnRunCompletion(object sender, TestRunCompleteEventA { _output.Warning(false, CommandLineResources.SuggestTestAdapterPathIfNoTestsIsFound); } + + // Collect tests session artifacts for post processing + if (_commandLineOptions.ArtifactProcessingMode == ArtifactProcessingMode.Collect) + { + _artifactProcessingManager.CollectArtifacts(e, RunSettingsManager.Instance.ActiveRunSettings.SettingsXml); + } } } } diff --git a/src/vstest.console/Processors/RunTestsArgumentProcessor.cs b/src/vstest.console/Processors/RunTestsArgumentProcessor.cs index 602e8cf9b1..767a7a6067 100644 --- a/src/vstest.console/Processors/RunTestsArgumentProcessor.cs +++ b/src/vstest.console/Processors/RunTestsArgumentProcessor.cs @@ -19,6 +19,8 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; using Microsoft.VisualStudio.TestPlatform.Utilities; using CommandLineResources = Resources.Resources; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.ArtifactProcessing; internal class RunTestsArgumentProcessor : IArgumentProcessor { @@ -51,6 +53,7 @@ public Lazy Executor CommandLineOptions.Instance, RunSettingsManager.Instance, TestRequestManager.Instance, + new ArtifactProcessingManager(CommandLineOptions.Instance.TestSessionCorrelationId), ConsoleOutput.Instance)); } @@ -127,6 +130,7 @@ public RunTestsArgumentExecutor( CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider, ITestRequestManager testRequestManager, + IArtifactProcessingManager artifactProcessingManager, IOutput output) { Contract.Requires(commandLineOptions != null); @@ -135,7 +139,7 @@ public RunTestsArgumentExecutor( _runSettingsManager = runSettingsProvider; _testRequestManager = testRequestManager; Output = output; - _testRunEventsRegistrar = new TestRunRequestEventsRegistrar(Output, _commandLineOptions); + _testRunEventsRegistrar = new TestRunRequestEventsRegistrar(Output, _commandLineOptions, artifactProcessingManager); } #endregion @@ -155,8 +159,7 @@ public ArgumentProcessorResult Execute() if (_commandLineOptions.IsDesignMode) { - // Do not attempt execution in case of design mode. Expect execution to happen - // via the design mode client. + // Do not attempt execution in case of design mode. Expect execution to happen via the design mode client. return ArgumentProcessorResult.Success; } @@ -215,11 +218,13 @@ private class TestRunRequestEventsRegistrar : ITestRunEventsRegistrar { private readonly IOutput _output; private readonly CommandLineOptions _commandLineOptions; + private readonly IArtifactProcessingManager _artifactProcessingManager; - public TestRunRequestEventsRegistrar(IOutput output, CommandLineOptions commandLineOptions) + public TestRunRequestEventsRegistrar(IOutput output, CommandLineOptions commandLineOptions, IArtifactProcessingManager artifactProcessingManager) { _output = output; _commandLineOptions = commandLineOptions; + _artifactProcessingManager = artifactProcessingManager; } public void LogWarning(string message) @@ -256,6 +261,12 @@ private void TestRunRequest_OnRunCompletion(object sender, TestRunCompleteEventA { _output.Warning(false, CommandLineResources.SuggestTestAdapterPathIfNoTestsIsFound); } + + // Collect tests session artifacts for post processing + if (_commandLineOptions.ArtifactProcessingMode == ArtifactProcessingMode.Collect) + { + _artifactProcessingManager.CollectArtifacts(e, RunSettingsManager.Instance.ActiveRunSettings.SettingsXml); + } } } } diff --git a/src/vstest.console/Processors/TestSessionCorrelationIdProcessor.cs b/src/vstest.console/Processors/TestSessionCorrelationIdProcessor.cs new file mode 100644 index 0000000000..01d654da31 --- /dev/null +++ b/src/vstest.console/Processors/TestSessionCorrelationIdProcessor.cs @@ -0,0 +1,104 @@ +// 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; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; + +using CommandLineResources = Resources.Resources; + +/// +/// Argument Processor for the "--testSessionCorrelationId|/TestSessionCorrelationId" command line argument. +/// +internal class TestSessionCorrelationIdProcessor : IArgumentProcessor +{ + /// + /// The name of the command line argument that the TestSessionCorrelationIdProcessor handles. + /// + public const string CommandName = "/TestSessionCorrelationId"; + + private Lazy _metadata; + + private Lazy _executor; + + /// + /// Gets the metadata. + /// + public Lazy Metadata + { + get + { + if (_metadata == null) + { + _metadata = new Lazy(() => new TestSessionCorrelationIdProcessorCapabilities()); + } + + return _metadata; + } + } + + /// + /// Gets or sets the executor. + /// + public Lazy Executor + { + get + { + if (_executor == null) + { + _executor = new Lazy(() => new TestSessionCorrelationIdProcessorModeProcessorExecutor(CommandLineOptions.Instance)); + } + + return _executor; + } + + set + { + _executor = value; + } + } +} + +internal class TestSessionCorrelationIdProcessorCapabilities : BaseArgumentProcessorCapabilities +{ + public override string CommandName => TestSessionCorrelationIdProcessor.CommandName; + + public override bool AllowMultiple => false; + + // We put priority at the same level of the argument processor for runsettings passed as argument through cli. + // We'll be sure to run before test run or artifact post processing. + public override ArgumentProcessorPriority Priority => ArgumentProcessorPriority.CliRunSettings; + + public override HelpContentPriority HelpPriority => HelpContentPriority.None; + + // We want to be sure that this command won't show in user help + public override string HelpContentResourceName => null; +} + +/// +/// Argument Executor for the "/TestSessionCorrelationId" command line argument. +/// +internal class TestSessionCorrelationIdProcessorModeProcessorExecutor : IArgumentExecutor +{ + private readonly CommandLineOptions _commandLineOptions; + + public TestSessionCorrelationIdProcessorModeProcessorExecutor(CommandLineOptions options) + { + _commandLineOptions = options ?? throw new ArgumentNullException(nameof(options)); + } + + public void Initialize(string argument) + { + if (string.IsNullOrEmpty(argument)) + { + throw new CommandLineException(CommandLineResources.InvalidTestSessionCorrelationId); + } + + _commandLineOptions.TestSessionCorrelationId = argument; + EqtTrace.Verbose($"TestSessionCorrelationIdProcessorModeProcessorExecutor.Initialize: TestSessionCorrelationId '{argument}'"); + } + + public ArgumentProcessorResult Execute() => ArgumentProcessorResult.Success; +} \ No newline at end of file diff --git a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs index 1f8166b2b1..ba29c99afa 100644 --- a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs +++ b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs @@ -8,6 +8,8 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; using System.Diagnostics.Contracts; using System.Linq; +using Microsoft.VisualStudio.TestPlatform.Utilities; + using ObjectModel; /// @@ -41,12 +43,14 @@ internal class ArgumentProcessorFactory #region Constructor - /// /// Initializes the argument processor factory. /// /// /// The argument Processors. /// + /// + /// The feature flag support. + /// /// /// This is not public because the static Create method should be used to access the instance. /// @@ -63,11 +67,23 @@ protected ArgumentProcessorFactory(IEnumerable argumentProce /// /// Creates ArgumentProcessorFactory. /// + /// + /// The feature flag support. + /// /// ArgumentProcessorFactory. - internal static ArgumentProcessorFactory Create() + internal static ArgumentProcessorFactory Create(IFeatureFlag featureFlag = null) { + var defaultArgumentProcessor = DefaultArgumentProcessors; + + if ((featureFlag ?? FeatureFlag.Instance).IsEnabled(FeatureFlag.ARTIFACTS_POSTPROCESSING)) + { + defaultArgumentProcessor.Add(new ArtifactProcessingCollectModeProcessor()); + defaultArgumentProcessor.Add(new ArtifactProcessingPostProcessModeProcessor()); + defaultArgumentProcessor.Add(new TestSessionCorrelationIdProcessor()); + } + // Get the ArgumentProcessorFactory - return new ArgumentProcessorFactory(DefaultArgumentProcessors); + return new ArgumentProcessorFactory(defaultArgumentProcessor); } #endregion @@ -207,7 +223,7 @@ public IEnumerable GetArgumentProcessorsToAlwaysExecute() #region Private Methods - private static IEnumerable DefaultArgumentProcessors => new List { + private static IList DefaultArgumentProcessors => new List { new HelpArgumentProcessor(), new TestSourceArgumentProcessor(), new ListTestsArgumentProcessor(), diff --git a/src/vstest.console/Resources/Resources.Designer.cs b/src/vstest.console/Resources/Resources.Designer.cs index eda555c483..831dd46323 100644 --- a/src/vstest.console/Resources/Resources.Designer.cs +++ b/src/vstest.console/Resources/Resources.Designer.cs @@ -868,6 +868,15 @@ internal static string InvalidTestRunParameterArgument { } } + /// + /// Looks up a localized string similar to Invalid testSessionCorrelationId. + /// + internal static string InvalidTestSessionCorrelationId { + get { + return ResourceManager.GetString("InvalidTestSessionCorrelationId", resourceCulture); + } + } + /// /// Looks up a localized string similar to Argument {0} is not expected in the 'UseVsixExtensions' command. Specify the command indicating whether the vsix extensions should be used or skipped (Example: vstest.console.exe myTests.dll /UseVsixExtensions:true) and try again.. /// diff --git a/src/vstest.console/Resources/Resources.resx b/src/vstest.console/Resources/Resources.resx index d80d990139..0997bea995 100644 --- a/src/vstest.console/Resources/Resources.resx +++ b/src/vstest.console/Resources/Resources.resx @@ -767,4 +767,7 @@ Test Run Aborted with error {0}. + + Invalid testSessionCorrelationId + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.cs.xlf b/src/vstest.console/Resources/xlf/Resources.cs.xlf index 064a9ba62c..25a1ecdc48 100644 --- a/src/vstest.console/Resources/xlf/Resources.cs.xlf +++ b/src/vstest.console/Resources/xlf/Resources.cs.xlf @@ -1108,6 +1108,11 @@ Testovací běh se přerušil s chybou {0}. + + Invalid testSessionCorrelationId + Invalid testSessionCorrelationId + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.de.xlf b/src/vstest.console/Resources/xlf/Resources.de.xlf index 43a89b1e2d..3971873480 100644 --- a/src/vstest.console/Resources/xlf/Resources.de.xlf +++ b/src/vstest.console/Resources/xlf/Resources.de.xlf @@ -1108,6 +1108,11 @@ Der Testlauf wurde mit dem Fehler {0} abgebrochen. + + Invalid testSessionCorrelationId + Invalid testSessionCorrelationId + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.es.xlf b/src/vstest.console/Resources/xlf/Resources.es.xlf index 4c7e974124..f43d700ced 100644 --- a/src/vstest.console/Resources/xlf/Resources.es.xlf +++ b/src/vstest.console/Resources/xlf/Resources.es.xlf @@ -1111,6 +1111,11 @@ La serie de pruebas se ha anulado con el error {0}. + + Invalid testSessionCorrelationId + Invalid testSessionCorrelationId + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.fr.xlf b/src/vstest.console/Resources/xlf/Resources.fr.xlf index a65a8316ff..f4a8895135 100644 --- a/src/vstest.console/Resources/xlf/Resources.fr.xlf +++ b/src/vstest.console/Resources/xlf/Resources.fr.xlf @@ -1108,6 +1108,11 @@ Désolé... Nous n’avons pas pu procéder à la série de tests, car nous avons rencontré l’erreur suivante : {0}. + + Invalid testSessionCorrelationId + Invalid testSessionCorrelationId + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.it.xlf b/src/vstest.console/Resources/xlf/Resources.it.xlf index 8a179cbcf7..dd52431000 100644 --- a/src/vstest.console/Resources/xlf/Resources.it.xlf +++ b/src/vstest.console/Resources/xlf/Resources.it.xlf @@ -1108,6 +1108,11 @@ Esecuzione dei test interrotta con errore {0}. + + Invalid testSessionCorrelationId + Invalid testSessionCorrelationId + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.ja.xlf b/src/vstest.console/Resources/xlf/Resources.ja.xlf index 43c67530ab..07d1243f0d 100644 --- a/src/vstest.console/Resources/xlf/Resources.ja.xlf +++ b/src/vstest.console/Resources/xlf/Resources.ja.xlf @@ -1108,6 +1108,11 @@ テストの実行がエラー {0} により中止されました。 + + Invalid testSessionCorrelationId + Invalid testSessionCorrelationId + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.ko.xlf b/src/vstest.console/Resources/xlf/Resources.ko.xlf index 86be769735..e943b4a82c 100644 --- a/src/vstest.console/Resources/xlf/Resources.ko.xlf +++ b/src/vstest.console/Resources/xlf/Resources.ko.xlf @@ -1108,6 +1108,11 @@ 테스트 실행이 {0} 오류로 인해 중단되었습니다. + + Invalid testSessionCorrelationId + Invalid testSessionCorrelationId + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.pl.xlf b/src/vstest.console/Resources/xlf/Resources.pl.xlf index 4eea286a34..b2331e7b13 100644 --- a/src/vstest.console/Resources/xlf/Resources.pl.xlf +++ b/src/vstest.console/Resources/xlf/Resources.pl.xlf @@ -1108,6 +1108,11 @@ Przebieg testowy został przerwany z powodu błędu {0}. + + Invalid testSessionCorrelationId + Invalid testSessionCorrelationId + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.pt-BR.xlf b/src/vstest.console/Resources/xlf/Resources.pt-BR.xlf index ccc01cb4dd..a05535f358 100644 --- a/src/vstest.console/Resources/xlf/Resources.pt-BR.xlf +++ b/src/vstest.console/Resources/xlf/Resources.pt-BR.xlf @@ -1108,6 +1108,11 @@ Altere o prefixo de nível de diagnóstico do agente de console, como mostrado a Execução de Teste Abortada com Erro {0}. + + Invalid testSessionCorrelationId + Invalid testSessionCorrelationId + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.ru.xlf b/src/vstest.console/Resources/xlf/Resources.ru.xlf index 0fd2965937..fabd54a711 100644 --- a/src/vstest.console/Resources/xlf/Resources.ru.xlf +++ b/src/vstest.console/Resources/xlf/Resources.ru.xlf @@ -1108,6 +1108,11 @@ Тестовый запуск прерван с ошибкой {0}. + + Invalid testSessionCorrelationId + Invalid testSessionCorrelationId + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.tr.xlf b/src/vstest.console/Resources/xlf/Resources.tr.xlf index ecc7dfdba6..74a434a9cd 100644 --- a/src/vstest.console/Resources/xlf/Resources.tr.xlf +++ b/src/vstest.console/Resources/xlf/Resources.tr.xlf @@ -1108,6 +1108,11 @@ Günlükler için izleme düzeyini aşağıda gösterildiği gibi değiştirin Test Çalıştırması {0} hatasıyla durduruldu. + + Invalid testSessionCorrelationId + Invalid testSessionCorrelationId + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.xlf b/src/vstest.console/Resources/xlf/Resources.xlf index c289ffcb47..123ac1b7a9 100644 --- a/src/vstest.console/Resources/xlf/Resources.xlf +++ b/src/vstest.console/Resources/xlf/Resources.xlf @@ -901,6 +901,11 @@ Format : TestRunParameters.Parameter(name="<name>", value="<value>") Test Run Aborted with error {0}. + + Invalid testSessionCorrelationId + Invalid testSessionCorrelationId + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.zh-Hans.xlf b/src/vstest.console/Resources/xlf/Resources.zh-Hans.xlf index d4c3310596..59f988b7b8 100644 --- a/src/vstest.console/Resources/xlf/Resources.zh-Hans.xlf +++ b/src/vstest.console/Resources/xlf/Resources.zh-Hans.xlf @@ -1108,6 +1108,11 @@ 测试运行已中止,出现错误 {0}。 + + Invalid testSessionCorrelationId + Invalid testSessionCorrelationId + + \ No newline at end of file diff --git a/src/vstest.console/Resources/xlf/Resources.zh-Hant.xlf b/src/vstest.console/Resources/xlf/Resources.zh-Hant.xlf index f818e20189..42dac69b96 100644 --- a/src/vstest.console/Resources/xlf/Resources.zh-Hant.xlf +++ b/src/vstest.console/Resources/xlf/Resources.zh-Hant.xlf @@ -1108,6 +1108,11 @@ 測試執行已中止,錯誤 {0}。 + + Invalid testSessionCorrelationId + Invalid testSessionCorrelationId + + \ No newline at end of file diff --git a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs index 6911fb5fba..f53b1a46a3 100644 --- a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs +++ b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs @@ -1079,11 +1079,7 @@ private void CollectMetrics(IRequestData requestData, RunConfiguration runConfig /// /// Returns Telemetry Opted out or not private static bool IsTelemetryOptedIn() - { - var telemetryStatus = Environment.GetEnvironmentVariable("VSTEST_TELEMETRY_OPTEDIN"); - return !string.IsNullOrEmpty(telemetryStatus) - && telemetryStatus.Equals("1", StringComparison.Ordinal); - } + => Environment.GetEnvironmentVariable("VSTEST_TELEMETRY_OPTEDIN")?.Equals("1", StringComparison.Ordinal) == true; /// /// Log Command Line switches for Telemetry purposes diff --git a/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs b/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs index c3b498d923..368d56dd16 100644 --- a/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs +++ b/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs @@ -32,6 +32,7 @@ public class ConsoleLoggerTests private Mock _mockOutput; private ConsoleLogger _consoleLogger; private Mock _mockProgressIndicator; + private Mock _mockFeatureFlag; private const string PassedTestIndicator = " Passed "; private const string FailedTestIndicator = " Failed "; @@ -1230,11 +1231,13 @@ private void Setup() { _mockRequestData = new Mock(); _mockMetricsCollection = new Mock(); + _mockFeatureFlag = new Mock(); + _mockFeatureFlag.Setup(x => x.IsEnabled(It.IsAny())).Returns(true); _mockRequestData.Setup(rd => rd.MetricsCollection).Returns(_mockMetricsCollection.Object); _mockOutput = new Mock(); _mockProgressIndicator = new Mock(); - _consoleLogger = new ConsoleLogger(_mockOutput.Object, _mockProgressIndicator.Object); + _consoleLogger = new ConsoleLogger(_mockOutput.Object, _mockProgressIndicator.Object, _mockFeatureFlag.Object); } private List GetTestResultsObject() diff --git a/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs index 14b0bb2e4a..fe3c82ded5 100644 --- a/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs @@ -48,12 +48,13 @@ public class RunSpecificTestsArgumentProcessorTests private readonly Mock _mockMetricsPublisher; private readonly Mock _mockProcessHelper; private readonly Mock _mockAttachmentsProcessingManager; + private readonly Mock _mockArtifactProcessingManager; private RunSpecificTestsArgumentExecutor GetExecutor(ITestRequestManager testRequestManager) { var runSettingsProvider = new TestableRunSettingsProvider(); runSettingsProvider.AddDefaultRunSettings(); - return new RunSpecificTestsArgumentExecutor(CommandLineOptions.Instance, runSettingsProvider, testRequestManager, _mockOutput.Object); + return new RunSpecificTestsArgumentExecutor(CommandLineOptions.Instance, runSettingsProvider, testRequestManager, _mockArtifactProcessingManager.Object, _mockOutput.Object); } public RunSpecificTestsArgumentProcessorTests() @@ -73,6 +74,7 @@ public RunSpecificTestsArgumentProcessorTests() _mockProcessHelper.Setup(x => x.GetCurrentProcessId()).Returns(1234); _mockProcessHelper.Setup(x => x.GetProcessName(It.IsAny())).Returns("dotnet.exe"); _mockAttachmentsProcessingManager = new Mock(); + _mockArtifactProcessingManager = new Mock(); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs index 7b72f143e6..d2d351dfbc 100644 --- a/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs @@ -52,6 +52,7 @@ public class RunTestsArgumentProcessorTests private readonly Mock _mockMetricsPublisher; private readonly Mock _mockProcessHelper; private readonly Mock _mockAttachmentsProcessingManager; + private readonly Mock _artifactProcessingManager; public RunTestsArgumentProcessorTests() { @@ -63,6 +64,7 @@ public RunTestsArgumentProcessorTests() _mockMetricsPublisherTask = Task.FromResult(_mockMetricsPublisher.Object); _mockTestPlatformEventSource = new Mock(); _mockAssemblyMetadataProvider = new Mock(); + _artifactProcessingManager = new Mock(); _inferHelper = new InferHelper(_mockAssemblyMetadataProvider.Object); SetupMockExtensions(); _mockAssemblyMetadataProvider.Setup(a => a.GetArchitecture(It.IsAny())) @@ -117,7 +119,7 @@ public void ExecutorExecuteShouldReturnSuccessWithoutExecutionInDesignMode() CommandLineOptions.Instance.Reset(); CommandLineOptions.Instance.IsDesignMode = true; var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object); - var executor = new RunTestsArgumentExecutor(CommandLineOptions.Instance, runSettingsProvider, testRequestManager, _mockOutput.Object); + var executor = new RunTestsArgumentExecutor(CommandLineOptions.Instance, runSettingsProvider, testRequestManager, _artifactProcessingManager.Object, _mockOutput.Object); Assert.AreEqual(ArgumentProcessorResult.Success, executor.Execute()); } @@ -140,6 +142,7 @@ private RunTestsArgumentExecutor GetExecutor(ITestRequestManager testRequestMana CommandLineOptions.Instance, runSettingsProvider, testRequestManager, + _artifactProcessingManager.Object, _mockOutput.Object ); return executor; diff --git a/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs b/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs index 78e74c607f..ea224be3f6 100644 --- a/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs +++ b/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs @@ -8,6 +8,10 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors.U using System.Linq; using System.Reflection; +using Microsoft.VisualStudio.TestPlatform.Utilities; + +using Moq; + using TestPlatform.CommandLine.Processors; using TestTools.UnitTesting; @@ -148,7 +152,9 @@ public void BuildCommadMapsForMultipleProcessorAddsProcessorToAppropriateMaps() xplatShortCommandName.Add(name.Replace('/', '-')); } - ArgumentProcessorFactory factory = ArgumentProcessorFactory.Create(); + Mock featureFlag = new(); + featureFlag.Setup(x => x.IsEnabled(It.IsAny())).Returns(true); + ArgumentProcessorFactory factory = ArgumentProcessorFactory.Create(featureFlag.Object); // Expect command processors to contain both long and short commands. CollectionAssert.AreEquivalent( diff --git a/test/vstest.console.UnitTests/vstest.console.UnitTests.csproj b/test/vstest.console.UnitTests/vstest.console.UnitTests.csproj index 57bf788102..d41ebf1187 100644 --- a/test/vstest.console.UnitTests/vstest.console.UnitTests.csproj +++ b/test/vstest.console.UnitTests/vstest.console.UnitTests.csproj @@ -14,6 +14,7 @@ + true