From e34377d13a56af66300d0d5310af1fdd4e064357 Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Fri, 18 Feb 2022 14:49:44 +0100 Subject: [PATCH 01/20] add artifacts post processing --- .../AttachmentProcessorDataCollector.csproj | 9 + .../SampleDataCollector.cs | 95 +++++++ .../TestExtensionTypesAttribute.cs | 43 +++ .../AttachmentProcessorDataCollector.csproj | 9 + .../SampleDataCollector.cs | 54 ++++ .../VSTestMultiProjectSolution/sln.sln | 34 +++ .../test1/UnitTest1.cs | 10 + .../test1/Usings.cs | 1 + .../test1/test1.csproj | 18 ++ .../test2/UnitTest1.cs | 10 + .../test2/Usings.cs | 1 + .../test2/test2.csproj | 18 ++ .../test3/UnitTest1.cs | 10 + .../test3/Usings.cs | 1 + .../test3/test3.csproj | 18 ++ .../dotnet/commands/dotnet-test/Program.cs | 125 +++++++-- .../commands/dotnet-test/TestCommandParser.cs | 4 +- .../dotnet-test/VSTestArgumentConverter.cs | 4 +- .../commands/dotnet-test/VSTestFeatureFlag.cs | 46 ++++ .../dotnet-test/VSTestForwardingApp.cs | 5 + .../commands/dotnet-test/VSTestTrace.cs | 61 +++++ .../dotnet/commands/dotnet-vstest/Program.cs | 27 +- ...TestBuildsAndRunsArtifactPostProcessing.cs | 250 ++++++++++++++++++ 23 files changed, 829 insertions(+), 24 deletions(-) create mode 100644 src/Assets/TestProjects/VSTestDataCollectorSample/AttachmentProcessorDataCollector.csproj create mode 100644 src/Assets/TestProjects/VSTestDataCollectorSample/SampleDataCollector.cs create mode 100644 src/Assets/TestProjects/VSTestDataCollectorSample/TestExtensionTypesAttribute.cs create mode 100644 src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/AttachmentProcessorDataCollector.csproj create mode 100644 src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/SampleDataCollector.cs create mode 100644 src/Assets/TestProjects/VSTestMultiProjectSolution/sln.sln create mode 100644 src/Assets/TestProjects/VSTestMultiProjectSolution/test1/UnitTest1.cs create mode 100644 src/Assets/TestProjects/VSTestMultiProjectSolution/test1/Usings.cs create mode 100644 src/Assets/TestProjects/VSTestMultiProjectSolution/test1/test1.csproj create mode 100644 src/Assets/TestProjects/VSTestMultiProjectSolution/test2/UnitTest1.cs create mode 100644 src/Assets/TestProjects/VSTestMultiProjectSolution/test2/Usings.cs create mode 100644 src/Assets/TestProjects/VSTestMultiProjectSolution/test2/test2.csproj create mode 100644 src/Assets/TestProjects/VSTestMultiProjectSolution/test3/UnitTest1.cs create mode 100644 src/Assets/TestProjects/VSTestMultiProjectSolution/test3/Usings.cs create mode 100644 src/Assets/TestProjects/VSTestMultiProjectSolution/test3/test3.csproj create mode 100644 src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs create mode 100644 src/Cli/dotnet/commands/dotnet-test/VSTestTrace.cs create mode 100644 src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs diff --git a/src/Assets/TestProjects/VSTestDataCollectorSample/AttachmentProcessorDataCollector.csproj b/src/Assets/TestProjects/VSTestDataCollectorSample/AttachmentProcessorDataCollector.csproj new file mode 100644 index 000000000000..13e6881015de --- /dev/null +++ b/src/Assets/TestProjects/VSTestDataCollectorSample/AttachmentProcessorDataCollector.csproj @@ -0,0 +1,9 @@ + + + netstandard2.0 + + + + + + diff --git a/src/Assets/TestProjects/VSTestDataCollectorSample/SampleDataCollector.cs b/src/Assets/TestProjects/VSTestDataCollectorSample/SampleDataCollector.cs new file mode 100644 index 000000000000..a68628609586 --- /dev/null +++ b/src/Assets/TestProjects/VSTestDataCollectorSample/SampleDataCollector.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AttachmentProcessorDataCollector +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.IO; + using System.Linq; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using System.Xml; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + internal class ExtensionInfo + { + public const string ExtensionType = "DataCollector"; + public const string ExtensionIdentifier = "my://sample/datacollector"; + } + + [DataCollectorFriendlyName("SampleDataCollector")] + [DataCollectorTypeUri(ExtensionInfo.ExtensionIdentifier)] + [DataCollectorAttachmentProcessor(typeof(SampleDataCollectorAttachmentProcessor))] + public class SampleDataCollectorV2 : SampleDataCollectorV1 { } + + [DataCollectorFriendlyName("SampleDataCollector")] + [DataCollectorTypeUri(ExtensionInfo.ExtensionIdentifier)] + public class SampleDataCollectorV1 : DataCollector + { + private DataCollectionSink _dataCollectionSink; + private DataCollectionEnvironmentContext _context; + private readonly string _tempDirectoryPath = Path.GetTempPath(); + + public override void Initialize( + XmlElement configurationElement, + DataCollectionEvents events, + DataCollectionSink dataSink, + DataCollectionLogger logger, + DataCollectionEnvironmentContext environmentContext) + { + events.SessionEnd += SessionEnded_Handler; + _dataCollectionSink = dataSink; + _context = environmentContext; + } + + private void SessionEnded_Handler(object sender, SessionEndEventArgs e) + { + string tmpAttachment = Path.Combine(_tempDirectoryPath, Guid.NewGuid().ToString("N"), "DataCollectorAttachmentProcessor_1.txt"); + Directory.CreateDirectory(Path.GetDirectoryName(tmpAttachment)); + File.WriteAllText(tmpAttachment, $"SessionEnded_Handler_{Guid.NewGuid():N}"); + _dataCollectionSink.SendFileAsync(_context.SessionDataCollectionContext, tmpAttachment, true); + } + } + + public class SampleDataCollectorAttachmentProcessor : IDataCollectorAttachmentProcessor + { + public bool SupportsIncrementalProcessing => true; + + public IEnumerable GetExtensionUris() + => new List() { new Uri(ExtensionInfo.ExtensionIdentifier) }; + + public Task> ProcessAttachmentSetsAsync(XmlElement configurationElement, ICollection attachments, IProgress progressReporter, IMessageLogger logger, CancellationToken cancellationToken) + { + string finalFileName = configurationElement.FirstChild.InnerText; + StringBuilder stringBuilder = new StringBuilder(); + string finalFolder = null; + foreach (var attachmentSet in attachments) + { + foreach (var attachment in attachmentSet.Attachments.OrderBy(f => f.Uri.AbsolutePath)) + { + if (finalFolder is null) + { + finalFolder = Path.GetDirectoryName(attachment.Uri.AbsolutePath); + } + + stringBuilder.AppendLine(File.ReadAllText(attachment.Uri.AbsolutePath).Trim()); + } + } + + File.WriteAllText(Path.Combine(finalFolder, finalFileName), stringBuilder.ToString()); + + List mergedAttachment = new List(); + var mergedAttachmentSet = new AttachmentSet(new Uri("my://sample/datacollector"), "SampleDataCollector"); + mergedAttachmentSet.Attachments.Add(UriDataAttachment.CreateFrom(Path.Combine(finalFolder, finalFileName), string.Empty)); + mergedAttachment.Add(mergedAttachmentSet); + + return Task.FromResult((ICollection)new Collection(mergedAttachment)); + } + } +} diff --git a/src/Assets/TestProjects/VSTestDataCollectorSample/TestExtensionTypesAttribute.cs b/src/Assets/TestProjects/VSTestDataCollectorSample/TestExtensionTypesAttribute.cs new file mode 100644 index 000000000000..b092e3f79e6e --- /dev/null +++ b/src/Assets/TestProjects/VSTestDataCollectorSample/TestExtensionTypesAttribute.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using AttachmentProcessorDataCollector; + +using Microsoft.VisualStudio.TestPlatform; + +[assembly: TestExtensionTypes(typeof(SampleDataCollectorV1))] +[assembly: TestExtensionTypesV2(ExtensionInfo.ExtensionType, ExtensionInfo.ExtensionIdentifier, typeof(SampleDataCollectorV1), 1, "futureUnused")] +[assembly: TestExtensionTypesV2(ExtensionInfo.ExtensionType, ExtensionInfo.ExtensionIdentifier, typeof(SampleDataCollectorV2), 2)] + +namespace Microsoft.VisualStudio.TestPlatform +{ + using System; + + [AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)] + internal sealed class TestExtensionTypesAttribute : Attribute + { + public TestExtensionTypesAttribute(params Type[] types) + { + Types = types; + } + + public Type[] Types { get; } + } + + [AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = true)] + internal sealed class TestExtensionTypesV2Attribute : Attribute + { + public string ExtensionType { get; } + public string ExtensionIdentifier { get; } + public Type ExtensionImplementation { get; } + public int Version { get; } + + public TestExtensionTypesV2Attribute(string extensionType, string extensionIdentifier, Type extensionImplementation, int version, string _ = null) + { + ExtensionType = extensionType; + ExtensionIdentifier = extensionIdentifier; + ExtensionImplementation = extensionImplementation; + Version = version; + } + } +} diff --git a/src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/AttachmentProcessorDataCollector.csproj b/src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/AttachmentProcessorDataCollector.csproj new file mode 100644 index 000000000000..13e6881015de --- /dev/null +++ b/src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/AttachmentProcessorDataCollector.csproj @@ -0,0 +1,9 @@ + + + netstandard2.0 + + + + + + diff --git a/src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/SampleDataCollector.cs b/src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/SampleDataCollector.cs new file mode 100644 index 000000000000..a5051c10f069 --- /dev/null +++ b/src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/SampleDataCollector.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AttachmentProcessorDataCollector +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.IO; + using System.Linq; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using System.Xml; + + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + + internal class ExtensionInfo + { + public const string ExtensionType = "DataCollector"; + public const string ExtensionIdentifier = "my://sample/datacollector"; + } + + [DataCollectorFriendlyName("SampleDataCollector")] + [DataCollectorTypeUri(ExtensionInfo.ExtensionIdentifier)] + public class SampleDataCollectorV1 : DataCollector + { + private DataCollectionSink _dataCollectionSink; + private DataCollectionEnvironmentContext _context; + private readonly string _tempDirectoryPath = Path.GetTempPath(); + + public override void Initialize( + XmlElement configurationElement, + DataCollectionEvents events, + DataCollectionSink dataSink, + DataCollectionLogger logger, + DataCollectionEnvironmentContext environmentContext) + { + events.SessionEnd += SessionEnded_Handler; + _dataCollectionSink = dataSink; + _context = environmentContext; + } + + private void SessionEnded_Handler(object sender, SessionEndEventArgs e) + { + string tmpAttachment = Path.Combine(_tempDirectoryPath, Guid.NewGuid().ToString("N"), "DataCollectorAttachmentProcessor_1.txt"); + Directory.CreateDirectory(Path.GetDirectoryName(tmpAttachment)); + File.WriteAllText(tmpAttachment, $"SessionEnded_Handler_{Guid.NewGuid():N}"); + _dataCollectionSink.SendFileAsync(_context.SessionDataCollectionContext, tmpAttachment, true); + } + } +} diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/sln.sln b/src/Assets/TestProjects/VSTestMultiProjectSolution/sln.sln new file mode 100644 index 000000000000..1c8555438b8f --- /dev/null +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/sln.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "test1", "test1\test1.csproj", "{081584FC-1000-4F74-887E-480E08A2FAD4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "test2", "test2\test2.csproj", "{F77D1353-8580-46B5-820F-50A899BEA1DD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "test3", "test3\test3.csproj", "{9D8CCF24-5968-4E57-B181-98A026C8ABEA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {081584FC-1000-4F74-887E-480E08A2FAD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {081584FC-1000-4F74-887E-480E08A2FAD4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {081584FC-1000-4F74-887E-480E08A2FAD4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {081584FC-1000-4F74-887E-480E08A2FAD4}.Release|Any CPU.Build.0 = Release|Any CPU + {F77D1353-8580-46B5-820F-50A899BEA1DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F77D1353-8580-46B5-820F-50A899BEA1DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F77D1353-8580-46B5-820F-50A899BEA1DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F77D1353-8580-46B5-820F-50A899BEA1DD}.Release|Any CPU.Build.0 = Release|Any CPU + {9D8CCF24-5968-4E57-B181-98A026C8ABEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D8CCF24-5968-4E57-B181-98A026C8ABEA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D8CCF24-5968-4E57-B181-98A026C8ABEA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D8CCF24-5968-4E57-B181-98A026C8ABEA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/UnitTest1.cs b/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/UnitTest1.cs new file mode 100644 index 000000000000..1dde78d79758 --- /dev/null +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/UnitTest1.cs @@ -0,0 +1,10 @@ +namespace test1; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void TestMethod1() + { + } +} \ No newline at end of file diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/Usings.cs b/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/Usings.cs new file mode 100644 index 000000000000..ab67c7ea9df8 --- /dev/null +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/test1.csproj b/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/test1.csproj new file mode 100644 index 000000000000..719c31057f61 --- /dev/null +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/test1.csproj @@ -0,0 +1,18 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/UnitTest1.cs b/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/UnitTest1.cs new file mode 100644 index 000000000000..bbb77d8b0af4 --- /dev/null +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/UnitTest1.cs @@ -0,0 +1,10 @@ +namespace test2; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void TestMethod1() + { + } +} \ No newline at end of file diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/Usings.cs b/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/Usings.cs new file mode 100644 index 000000000000..ab67c7ea9df8 --- /dev/null +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/test2.csproj b/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/test2.csproj new file mode 100644 index 000000000000..719c31057f61 --- /dev/null +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/test2.csproj @@ -0,0 +1,18 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/UnitTest1.cs b/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/UnitTest1.cs new file mode 100644 index 000000000000..d04ecd95fe80 --- /dev/null +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/UnitTest1.cs @@ -0,0 +1,10 @@ +namespace test3; + +[TestClass] +public class UnitTest1 +{ + [TestMethod] + public void TestMethod1() + { + } +} \ No newline at end of file diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/Usings.cs b/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/Usings.cs new file mode 100644 index 000000000000..ab67c7ea9df8 --- /dev/null +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/test3.csproj b/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/test3.csproj new file mode 100644 index 000000000000..719c31057f61 --- /dev/null +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/test3.csproj @@ -0,0 +1,18 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + diff --git a/src/Cli/dotnet/commands/dotnet-test/Program.cs b/src/Cli/dotnet/commands/dotnet-test/Program.cs index 921f62264559..e95ee401a967 100644 --- a/src/Cli/dotnet/commands/dotnet-test/Program.cs +++ b/src/Cli/dotnet/commands/dotnet-test/Program.cs @@ -1,9 +1,11 @@ -// Copyright(c) .NET Foundation and contributors.All rights reserved. +// Copyright (c) .NET Foundation and contributors. 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.CommandLine; using System.CommandLine.Parsing; +using System.IO; using System.Linq; using Microsoft.DotNet.Cli; using Microsoft.DotNet.Cli.Utils; @@ -20,7 +22,7 @@ public TestCommand( { } - public static TestCommand FromParseResult(ParseResult result, string[] settings, string msbuildPath = null) + public static TestCommand FromParseResult(ParseResult result, string[] settings, string testSessionCorrelationId, string msbuildPath = null) { result.ShowHelpOrErrorIfAppropriate(); @@ -38,25 +40,32 @@ public static TestCommand FromParseResult(ParseResult result, string[] settings, if (settings.Any()) { // skip '--' and escape every \ to be \\ and every " to be \" to survive the next hop - var escaped = settings.Skip(1).Select(s => s.Replace("\\", "\\\\").Replace("\"", "\\\"")).ToArray(); + string[] escaped = settings.Skip(1).Select(s => s.Replace("\\", "\\\\").Replace("\"", "\\\"")).ToArray(); - var runSettingsArg = string.Join(";", escaped); + string runSettingsArg = string.Join(";", escaped); msbuildArgs.Add($"-property:VSTestCLIRunSettings=\"{runSettingsArg}\""); } - var verbosityArg = result.ForwardedOptionValues>(TestCommandParser.GetCommand(), "verbosity")?.SingleOrDefault() ?? null; + string verbosityArg = result.ForwardedOptionValues>(TestCommandParser.GetCommand(), "verbosity")?.SingleOrDefault() ?? null; if (verbosityArg != null) { - var verbosity = verbosityArg.Split(':', 2); + string[] verbosity = verbosityArg.Split(':', 2); if (verbosity.Length == 2) { msbuildArgs.Add($"-property:VSTestVerbosity={verbosity[1]}"); } } + if (FeatureFlag.Default.IsEnabled(FeatureFlag.ARTIFACTS_POSTPROCESSING)) + { + // Add artifacts processing mode and test session id for the artifact post-processing + msbuildArgs.Add("-property:VSTestArtifactsProcessingMode=collect"); + msbuildArgs.Add($"-property:VSTestSessionCorrelationId={testSessionCorrelationId}"); + } + bool noRestore = result.HasOption(TestCommandParser.NoRestoreOption) || result.HasOption(TestCommandParser.NoBuildOption); - TestCommand testCommand = new TestCommand( + TestCommand testCommand = new( msbuildArgs, noRestore, msbuildPath); @@ -70,8 +79,10 @@ public static TestCommand FromParseResult(ParseResult result, string[] settings, if (!hasRootVariable) { testCommand.EnvironmentVariable(rootVariableName, rootValue); + VSTestTrace.WriteTrace($"Root variable set {rootVariableName}:{rootValue}"); } + VSTestTrace.SafeWriteTrace(() => $"Starting test using MSBuild with arguments '{testCommand.GetArgumentsToMSBuild()}' custom MSBuild path '{msbuildPath}' norestore '{noRestore}'"); return testCommand; } @@ -79,18 +90,34 @@ public static int Run(ParseResult parseResult) { parseResult.HandleDebugSwitch(); - var args = parseResult.GetArguments(); + FeatureFlag.Default.PrintFlagFeatureState(); + + // We use also current process id for the correlation id for possible future usage in case we need to know the parent process + // from the VSTest side. + string testSessionCorrelationId = $"{Environment.ProcessId}_{Guid.NewGuid()}"; + + string[] args = parseResult.GetArguments(); + + if (VSTestTrace.TraceEnabled) + { + string commandLineParameters = ""; + if (args?.Length > 0) + { + commandLineParameters = args.Aggregate((a, b) => $"{a}|{b}"); + } + VSTestTrace.WriteTrace($"Argument list: '{commandLineParameters}'"); + } // settings parameters are after -- (including --), these should not be considered by the parser - var settings = args.SkipWhile(a => a != "--").ToArray(); + string[] settings = args.SkipWhile(a => a != "--").ToArray(); // all parameters before -- args = args.TakeWhile(a => a != "--").ToArray(); // Fix for https://github.com/Microsoft/vstest/issues/1453 // Try to run dll/exe directly using the VSTestForwardingApp + List convertedArgs = new VSTestArgumentConverter().Convert(args, out List ignoredArgs); if (ContainsBuiltTestSources(args)) { - var convertedArgs = new VSTestArgumentConverter().Convert(args, out var ignoredArgs); if (ignoredArgs.Any()) { Reporter.Output.WriteLine(string.Format(LocalizableStrings.IgnoredArgumentsMessage, string.Join(" ", ignoredArgs)).Yellow()); @@ -100,7 +127,19 @@ public static int Run(ParseResult parseResult) // one more time, there is no extra hop via msbuild convertedArgs.AddRange(settings); - return new VSTestForwardingApp(convertedArgs).Execute(); + if (FeatureFlag.Default.IsEnabled(FeatureFlag.ARTIFACTS_POSTPROCESSING)) + { + // Add artifacts processing mode and test session id for the artifact post-processing + convertedArgs.Add("--artifactsProcessingMode-collect"); + convertedArgs.Add($"--testSessionCorrelationId:{testSessionCorrelationId}"); + } + + int exitCode = new VSTestForwardingApp(convertedArgs).Execute(); + + // We run post processing also if execution is failed for possible partial successful result to post process. + RunArtifactPostProcessingIfNeeded(testSessionCorrelationId, parseResult, FeatureFlag.Default); + + return exitCode; } // Workaround for https://github.com/Microsoft/vstest/issues/1503 @@ -110,7 +149,12 @@ public static int Run(ParseResult parseResult) try { Environment.SetEnvironmentVariable(NodeWindowEnvironmentName, "1"); - return FromParseResult(parseResult, settings).Execute(); + int exitCode = FromParseResult(parseResult, settings, testSessionCorrelationId).Execute(); + + // We run post processing also if execution is failed for possible partial successful result to post process. + RunArtifactPostProcessingIfNeeded(testSessionCorrelationId, parseResult, FeatureFlag.Default); + + return exitCode; } finally { @@ -118,9 +162,54 @@ public static int Run(ParseResult parseResult) } } + internal static void RunArtifactPostProcessingIfNeeded(string testSessionCorrelationId, ParseResult parseResult, FeatureFlag featureFlag) + { + if (!featureFlag.IsEnabled(FeatureFlag.ARTIFACTS_POSTPROCESSING)) + { + return; + } + + // VSTest runner will save artifacts inside a temp folder if needed. + string expectedArtifactDirectory = Path.Combine(Path.GetTempPath(), testSessionCorrelationId); + if (!Directory.Exists(expectedArtifactDirectory)) + { + VSTestTrace.WriteTrace("No artifact found, post-processing won't run."); + return; + } + + VSTestTrace.WriteTrace($"Artifacts found inside '{expectedArtifactDirectory}', running post-processing."); + + var artifactsProcessingModePostprocess = new List { "--artifactsProcessingMode-postprocess", $"--testSessionCorrelationId:{testSessionCorrelationId}" }; + + if (parseResult.HasOption(TestCommandParser.DiagOption)) + { + artifactsProcessingModePostprocess.Add($"--diag:{parseResult.GetValueForOption(TestCommandParser.DiagOption)}"); + } + + try + { + new VSTestForwardingApp(artifactsProcessingModePostprocess).Execute(); + } + finally + { + if (Directory.Exists(expectedArtifactDirectory)) + { + VSTestTrace.WriteTrace($"Cleaning artifact directory '{expectedArtifactDirectory}'"); + try + { + Directory.Delete(expectedArtifactDirectory, true); + } + catch (Exception ex) + { + VSTestTrace.WriteTrace($"Exception during artifact cleanup:'{ex}'"); + } + } + } + } + private static bool ContainsBuiltTestSources(string[] args) { - foreach (var arg in args) + foreach (string arg in args) { if (!arg.StartsWith("-") && (arg.EndsWith("dll", StringComparison.OrdinalIgnoreCase) || arg.EndsWith("exe", StringComparison.OrdinalIgnoreCase))) @@ -133,19 +222,19 @@ private static bool ContainsBuiltTestSources(string[] args) private static void SetEnvironmentVariablesFromParameters(TestCommand testCommand, ParseResult parseResult) { - var option = TestCommandParser.EnvOption; + Option> option = TestCommandParser.EnvOption; if (!parseResult.HasOption(option)) { return; } - foreach (var env in parseResult.GetValueForOption(option)) + foreach (string env in parseResult.GetValueForOption(option)) { - var name = env; - var value = string.Empty; + string name = env; + string value = string.Empty; - var equalsIndex = env.IndexOf('='); + int equalsIndex = env.IndexOf('='); if (equalsIndex > 0) { name = env.Substring(0, equalsIndex); diff --git a/src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs b/src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs index c9e65ed2f9bb..33bcdc367ae9 100644 --- a/src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs +++ b/src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs @@ -64,7 +64,9 @@ internal static class TestCommandParser public static readonly Option DiagOption = new ForwardedOption(new string[] { "-d", "--diag" }, LocalizableStrings.CmdPathTologFileDescription) { ArgumentHelpName = LocalizableStrings.CmdPathToLogFile - }.ForwardAsSingle(o => $"-property:VSTestDiag={SurroundWithDoubleQuotes(CommandDirectoryContext.GetFullPath(o))}"); + } + // Temporarily we don't escape the diag path, issue https://github.com/dotnet/sdk/issues/23970 + .ForwardAsSingle(o => $"-property:VSTestDiag={CommandDirectoryContext.GetFullPath(o)}"); public static readonly Option NoBuildOption = new ForwardedOption("--no-build", LocalizableStrings.CmdNoBuildDescription) .ForwardAs("-property:VSTestNoBuild=true"); diff --git a/src/Cli/dotnet/commands/dotnet-test/VSTestArgumentConverter.cs b/src/Cli/dotnet/commands/dotnet-test/VSTestArgumentConverter.cs index dfdbd86f9ed0..8db8cf3f8d5f 100644 --- a/src/Cli/dotnet/commands/dotnet-test/VSTestArgumentConverter.cs +++ b/src/Cli/dotnet/commands/dotnet-test/VSTestArgumentConverter.cs @@ -49,7 +49,9 @@ public class VSTestArgumentConverter "--output", "--no-build", "--no-restore", - "--interactive" + "--interactive", + "--testSessionId", + "--artifacts-processing-mode" }; /// diff --git a/src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs b/src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs new file mode 100644 index 000000000000..5d7f24ae9a06 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs @@ -0,0 +1,46 @@ +// Copyright (c) .NET Foundation and contributors. 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; + +namespace Microsoft.DotNet.Tools.Test +{ + // !!! FEATURES MUST BE KEPT IN SYNC WITH https://github.com/microsoft/vstest/blob/main/src/Microsoft.TestPlatform.CoreUtilities/FeatureFlag/FeatureFlag.cs !!! + internal class FeatureFlag + { + private const string Prefix = "VSTEST_FEATURE_"; + + public Dictionary FeatureFlagsBag { get; } = new(); + + public static FeatureFlag Default { get; } = new FeatureFlag(); + + public FeatureFlag() + { + FeatureFlagsBag.Add(ARTIFACTS_POSTPROCESSING, false); + } + + // Added for artifact porst-processing, it enable/disable the post processing. + // Added in 17.2-preview 7.0-preview + public const string ARTIFACTS_POSTPROCESSING = Prefix + "ARTIFACTS_POSTPROCESSING"; + + // 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 : + FeatureFlagsBag.TryGetValue(featureName, out bool isEnabled) && isEnabled; + + public void PrintFlagFeatureState() + { + if (VSTestTrace.TraceEnabled) + { + foreach (KeyValuePair flag in FeatureFlagsBag) + { + VSTestTrace.WriteTrace($"Feature {flag.Key}: {IsEnabled(flag.Key)}"); + } + } + } + } +} diff --git a/src/Cli/dotnet/commands/dotnet-test/VSTestForwardingApp.cs b/src/Cli/dotnet/commands/dotnet-test/VSTestForwardingApp.cs index 39639f4d2757..16a8593936d5 100644 --- a/src/Cli/dotnet/commands/dotnet-test/VSTestForwardingApp.cs +++ b/src/Cli/dotnet/commands/dotnet-test/VSTestForwardingApp.cs @@ -2,9 +2,11 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Tools.Test; using System; using System.Collections.Generic; using System.IO; +using System.Linq; namespace Microsoft.DotNet.Cli { @@ -19,7 +21,10 @@ public VSTestForwardingApp(IEnumerable argsToForward) if (!hasRootVariable) { WithEnvironmentVariable(rootVariableName, rootValue); + VSTestTrace.WriteTrace($"Root variable set {rootVariableName}:{rootValue}"); } + + VSTestTrace.SafeWriteTrace(() => $"Forwarding to '{GetVSTestExePath()}' with args \"{argsToForward?.Aggregate((a, b) => $"{a} | {b}")}\""); } private static string GetVSTestExePath() diff --git a/src/Cli/dotnet/commands/dotnet-test/VSTestTrace.cs b/src/Cli/dotnet/commands/dotnet-test/VSTestTrace.cs new file mode 100644 index 000000000000..9719cfc4e477 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-test/VSTestTrace.cs @@ -0,0 +1,61 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.IO; + +namespace Microsoft.DotNet.Tools.Test +{ + internal class VSTestTrace + { + public static bool TraceEnabled { get; set; } + private static readonly string s_traceFilePath; + + static VSTestTrace() + { + TraceEnabled = int.TryParse(Environment.GetEnvironmentVariable("DOTNET_CLI_VSTEST_TRACE"), out int enabled) && enabled == 1; + s_traceFilePath = Environment.GetEnvironmentVariable("DOTNET_CLI_VSTEST_TRACEFILE"); + if (TraceEnabled) + { + Console.WriteLine($"[dotnet test - {DateTime.UtcNow}]Logging to {(!string.IsNullOrEmpty(s_traceFilePath) ? s_traceFilePath : "console")}"); + } + } + + public static void WriteTrace(string logText) + { + if (TraceEnabled) + { + try + { + string message = $"[dotnet test - {DateTime.UtcNow}]{logText}"; + if (!string.IsNullOrEmpty(s_traceFilePath)) + { + using StreamWriter logFile = File.AppendText(s_traceFilePath); + logFile.WriteLine(message); + } + else + { + Console.WriteLine(message); + } + } + catch + { + // Avoid exception if we have issue with the log file. + } + } + } + + public static void SafeWriteTrace(Func messageLog) + { + try + { + WriteTrace(messageLog()); + } + catch + { + // Avoid exception in case of something is wrong with log composition. + } + } + } +} diff --git a/src/Cli/dotnet/commands/dotnet-vstest/Program.cs b/src/Cli/dotnet/commands/dotnet-vstest/Program.cs index 76cd3b56fe54..a8755e51abb3 100644 --- a/src/Cli/dotnet/commands/dotnet-vstest/Program.cs +++ b/src/Cli/dotnet/commands/dotnet-vstest/Program.cs @@ -1,12 +1,12 @@ // Copyright(c) .NET Foundation and contributors.All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli; using System.CommandLine.Parsing; using System.Collections.Generic; using System.Linq; using System; +using Microsoft.DotNet.Tools.Test; namespace Microsoft.DotNet.Tools.VSTest { @@ -16,9 +16,28 @@ public static int Run(ParseResult parseResult) { parseResult.HandleDebugSwitch(); - VSTestForwardingApp vsTestforwardingApp = new VSTestForwardingApp(GetArgs(parseResult)); + // We use also current process id for the correlation id for possible future usage in case we need to know the parent process + // from the VSTest side. + string testSessionCorrelationId = $"{Environment.ProcessId}_{Guid.NewGuid()}"; - return vsTestforwardingApp.Execute(); + var args = new List(); + args.AddRange(GetArgs(parseResult)); + + if (FeatureFlag.Default.IsEnabled(FeatureFlag.ARTIFACTS_POSTPROCESSING)) + { + // Add artifacts processing mode and test session id for the artifact post-processing + args.Add("--artifactsProcessingMode-collect"); + args.Add($"--testSessionCorrelationId:{testSessionCorrelationId}"); + } + + VSTestForwardingApp vsTestforwardingApp = new(args); + + int exitCode = vsTestforwardingApp.Execute(); + + // We run post processing also if execution is failed for possible partial successful result to post process. + TestCommand.RunArtifactPostProcessingIfNeeded(testSessionCorrelationId, parseResult, FeatureFlag.Default); + + return exitCode; } private static string[] GetArgs(ParseResult parseResult) @@ -28,7 +47,7 @@ private static string[] GetArgs(ParseResult parseResult) if (parseResult.HasOption(CommonOptions.TestLoggerOption)) { // System command line might have mutated the options, reformat test logger option so vstest recognizes it - var loggerValue = parseResult.GetValueForOption(CommonOptions.TestLoggerOption); + string loggerValue = parseResult.GetValueForOption(CommonOptions.TestLoggerOption); args = args.Where(a => !a.Equals(loggerValue) && !CommonOptions.TestLoggerOption.Aliases.Contains(a)); args = args.Prepend($"{CommonOptions.TestLoggerOption.Aliases.First()}:{loggerValue}"); } diff --git a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs new file mode 100644 index 000000000000..e3a37eb92945 --- /dev/null +++ b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs @@ -0,0 +1,250 @@ +// Copyright (c) .NET Foundation and contributors. 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.IO; +using System.Linq; +using System.Threading; +using System.Xml; +using System.Xml.Linq; +using FluentAssertions; +using Microsoft.DotNet.Tools.Test; +using Microsoft.NET.TestFramework; +using Microsoft.NET.TestFramework.Assertions; +using Microsoft.NET.TestFramework.Commands; +using Xunit; +using Xunit.Abstractions; +using CommandResult = Microsoft.DotNet.Cli.Utils.CommandResult; + +namespace Microsoft.DotNet.Cli.Test.Tests +{ + public class GivenDotnetTestBuildsAndRunsArtifactPostProcessing : SdkTest + { + private static object s_dataCollectorInitLock = new(); + private static string s_dataCollectorDll; + private static string s_dataCollectorNoMergeDll; + + public GivenDotnetTestBuildsAndRunsArtifactPostProcessing(ITestOutputHelper log) : base(log) + { + BuildDataCollector(); + BuildDataCollectorNoMerge(); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ArtifactPostProcessing_CsProjs(bool merge) + { + TestAsset testInstance = _testAssetsManager.CopyTestAsset("VSTestMultiProjectSolution") + .WithSource(); + + string runsettings = GetRunsetting(testInstance.Path); + + CommandResult result = new DotnetTestCommand(Log) + .WithWorkingDirectory(testInstance.Path) + .WithEnvironmentVariable(FeatureFlag.ARTIFACTS_POSTPROCESSING, "1") + .Execute( + "--configuration", "release", + "--collect", "SampleDataCollector", + "--test-adapter-path", merge ? Path.GetDirectoryName(s_dataCollectorDll) : Path.GetDirectoryName(s_dataCollectorNoMergeDll), + "--settings", runsettings, + "--diag", testInstance.Path + "/logs/"); + + result.ExitCode.Should().Be(0); + AssertOutput(result.StdOut, merge); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ArtifactPostProcessing_TestContainers(bool merge) + { + TestAsset testInstance = _testAssetsManager.CopyTestAsset("VSTestMultiProjectSolution") + .WithSource(); + + string runsettings = GetRunsetting(testInstance.Path); + + new PublishCommand(Log, Path.Combine(testInstance.Path, "sln.sln")).Execute("/p:Configuration=Release").Should().Pass(); + + CommandResult result = new DotnetTestCommand(Log) + .WithWorkingDirectory(testInstance.Path) + .WithEnvironmentVariable(FeatureFlag.ARTIFACTS_POSTPROCESSING, "1") + .WithEnvironmentVariable("DOTNET_CLI_VSTEST_TRACE", "1") + .Execute( + Directory.GetFiles(testInstance.Path, "test1.dll", SearchOption.AllDirectories).SingleOrDefault(x => x.Contains("publish")), + Directory.GetFiles(testInstance.Path, "test2.dll", SearchOption.AllDirectories).SingleOrDefault(x => x.Contains("publish")), + Directory.GetFiles(testInstance.Path, "test3.dll", SearchOption.AllDirectories).SingleOrDefault(x => x.Contains("publish")), + "--collect:SampleDataCollector", + $"--test-adapter-path:{(merge ? Path.GetDirectoryName(s_dataCollectorDll) : Path.GetDirectoryName(s_dataCollectorNoMergeDll))}", + $"--settings:{runsettings}", + "--diag:" + testInstance.Path + "/logs/"); + + result.ExitCode.Should().Be(0); + AssertOutput(result.StdOut, merge); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ArtifactPostProcessing_VSTest_TestContainers(bool merge) + { + TestAsset testInstance = _testAssetsManager.CopyTestAsset("VSTestMultiProjectSolution") + .WithSource(); + + string runsettings = GetRunsetting(testInstance.Path); + + new PublishCommand(Log, Path.Combine(testInstance.Path, "sln.sln")).Execute("/p:Configuration=Release").Should().Pass(); + + CommandResult result = new DotnetVSTestCommand(Log) + .WithWorkingDirectory(testInstance.Path) + .WithEnvironmentVariable(FeatureFlag.ARTIFACTS_POSTPROCESSING, "1") + .Execute( + Directory.GetFiles(testInstance.Path, "test1.dll", SearchOption.AllDirectories).SingleOrDefault(x => x.Contains("publish")), + Directory.GetFiles(testInstance.Path, "test2.dll", SearchOption.AllDirectories).SingleOrDefault(x => x.Contains("publish")), + Directory.GetFiles(testInstance.Path, "test3.dll", SearchOption.AllDirectories).SingleOrDefault(x => x.Contains("publish")), + "--collect:SampleDataCollector", + $"--testAdapterPath:{(merge ? Path.GetDirectoryName(s_dataCollectorDll) : Path.GetDirectoryName(s_dataCollectorNoMergeDll))}", + $"--settings:{runsettings}", + "--diag:" + testInstance.Path + "/logs/"); + + result.ExitCode.Should().Be(0); + AssertOutput(result.StdOut, merge); + } + + private static void AssertOutput(string stdOut, bool merge) + { + List output = new(); + using StringReader reader = new(stdOut); + while (true) + { + string line = reader.ReadLine()?.Trim(); + if (line is null) break; + output.Add(line); + } + + if (merge) + { + Assert.Equal(string.Empty, output[output.Count - 3].Trim()); + Assert.Equal("Attachments:", output[output.Count - 2].Trim()); + string mergedFile = output[output.Count - 1].Trim(); + + var fileContent = new List(); + using var streamReader = new StreamReader(mergedFile); + while (!streamReader.EndOfStream) + { + string line = streamReader.ReadLine(); + Assert.StartsWith("SessionEnded_Handler_", line); + fileContent.Add(line); + } + + Assert.Equal(3, fileContent.Distinct().Count()); + } + else + { + Assert.Equal(string.Empty, output[output.Count - 5].Trim()); + Assert.Equal("Attachments:", output[output.Count - 4].Trim()); + + int currentLine = 0; + for (int i = 3; i > 0; i--) + { + currentLine = output.Count - i; + string file = output[currentLine].Trim(); + var fileContent = new List(); + using var streamReader = new StreamReader(file); + while (!streamReader.EndOfStream) + { + string line = streamReader.ReadLine(); + Assert.StartsWith("SessionEnded_Handler_", line); + fileContent.Add(line); + } + + Assert.True(fileContent.Distinct().Count() == 1); + } + + Assert.Equal(output.Count, currentLine + 1); + } + } + + private void BuildDataCollector() + => LazyInitializer.EnsureInitialized(ref s_dataCollectorDll, ref s_dataCollectorInitLock, () => + { + TestAsset testInstance = _testAssetsManager.CopyTestAsset("VSTestDataCollectorSample").WithSource(); + + string testProjectDirectory = testInstance.Path; + + new BuildCommand(testInstance) + .Execute("/p:Configuration=Release") + .Should() + .Pass(); + + return Directory.GetFiles(testProjectDirectory, "AttachmentProcessorDataCollector.dll", SearchOption.AllDirectories).Single(x => x.Contains("bin")); + }); + + private void BuildDataCollectorNoMerge() + => LazyInitializer.EnsureInitialized(ref s_dataCollectorNoMergeDll, ref s_dataCollectorInitLock, () => + { + TestAsset testInstance = _testAssetsManager.CopyTestAsset("VSTestDataCollectorSampleNoMerge").WithSource(); + + string testProjectDirectory = testInstance.Path; + + new BuildCommand(testInstance) + .Execute("/p:Configuration=Release") + .Should() + .Pass(); + + return Directory.GetFiles(testProjectDirectory, "AttachmentProcessorDataCollector.dll", SearchOption.AllDirectories).Single(x => x.Contains("bin")); + }); + + private static string GetRunsetting(string directory) + { + string runSettings = GetRunsettingsFilePath(directory); + // Set datacollector parameters + XElement runSettingsXml = XElement.Load(runSettings); + runSettingsXml.Element("DataCollectionRunSettings") + .Element("DataCollectors") + .Element("DataCollector") + .Add(new XElement("Configuration", new XElement("MergeFile", "MergedFile.txt"))); + runSettingsXml.Save(runSettings); + return runSettings; + } + + private static string GetRunsettingsFilePath(string resultsDir) + { + string runsettingsPath = Path.Combine(resultsDir, "test_" + Guid.NewGuid() + ".runsettings"); + var dataCollectionAttributes = new Dictionary + { + { "friendlyName", "SampleDataCollector" }, + { "uri", "my://sample/datacollector" } + }; + + CreateDataCollectionRunSettingsFile(runsettingsPath, dataCollectionAttributes); + return runsettingsPath; + } + + private static void CreateDataCollectionRunSettingsFile(string destinationRunsettingsPath, Dictionary dataCollectionAttributes) + { + var doc = new XmlDocument(); + XmlNode xmlDeclaration = doc.CreateNode(XmlNodeType.XmlDeclaration, string.Empty, string.Empty); + + doc.AppendChild(xmlDeclaration); + XmlElement runSettingsNode = doc.CreateElement("RunSettings"); + doc.AppendChild(runSettingsNode); + XmlElement dcConfigNode = doc.CreateElement("DataCollectionRunSettings"); + runSettingsNode.AppendChild(dcConfigNode); + XmlElement dataCollectorsNode = doc.CreateElement("DataCollectors"); + dcConfigNode.AppendChild(dataCollectorsNode); + XmlElement dataCollectorNode = doc.CreateElement("DataCollector"); + dataCollectorsNode.AppendChild(dataCollectorNode); + + foreach (KeyValuePair kvp in dataCollectionAttributes) + { + dataCollectorNode.SetAttribute(kvp.Key, kvp.Value); + } + + using var stream = new FileStream(destinationRunsettingsPath, FileMode.Create); + doc.Save(stream); + } + } +} From 39697de4c80c3d0c7955ba4d21a0eeff0863e65a Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Fri, 18 Feb 2022 14:55:50 +0100 Subject: [PATCH 02/20] add post processing errors --- src/Cli/dotnet/commands/dotnet-test/Program.cs | 12 ++++++------ src/Cli/dotnet/commands/dotnet-vstest/Program.cs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Cli/dotnet/commands/dotnet-test/Program.cs b/src/Cli/dotnet/commands/dotnet-test/Program.cs index e95ee401a967..5581f5d096e2 100644 --- a/src/Cli/dotnet/commands/dotnet-test/Program.cs +++ b/src/Cli/dotnet/commands/dotnet-test/Program.cs @@ -137,7 +137,7 @@ public static int Run(ParseResult parseResult) int exitCode = new VSTestForwardingApp(convertedArgs).Execute(); // We run post processing also if execution is failed for possible partial successful result to post process. - RunArtifactPostProcessingIfNeeded(testSessionCorrelationId, parseResult, FeatureFlag.Default); + exitCode |= RunArtifactPostProcessingIfNeeded(testSessionCorrelationId, parseResult, FeatureFlag.Default); return exitCode; } @@ -152,7 +152,7 @@ public static int Run(ParseResult parseResult) int exitCode = FromParseResult(parseResult, settings, testSessionCorrelationId).Execute(); // We run post processing also if execution is failed for possible partial successful result to post process. - RunArtifactPostProcessingIfNeeded(testSessionCorrelationId, parseResult, FeatureFlag.Default); + exitCode |= RunArtifactPostProcessingIfNeeded(testSessionCorrelationId, parseResult, FeatureFlag.Default); return exitCode; } @@ -162,11 +162,11 @@ public static int Run(ParseResult parseResult) } } - internal static void RunArtifactPostProcessingIfNeeded(string testSessionCorrelationId, ParseResult parseResult, FeatureFlag featureFlag) + internal static int RunArtifactPostProcessingIfNeeded(string testSessionCorrelationId, ParseResult parseResult, FeatureFlag featureFlag) { if (!featureFlag.IsEnabled(FeatureFlag.ARTIFACTS_POSTPROCESSING)) { - return; + return 0; } // VSTest runner will save artifacts inside a temp folder if needed. @@ -174,7 +174,7 @@ internal static void RunArtifactPostProcessingIfNeeded(string testSessionCorrela if (!Directory.Exists(expectedArtifactDirectory)) { VSTestTrace.WriteTrace("No artifact found, post-processing won't run."); - return; + return 0; } VSTestTrace.WriteTrace($"Artifacts found inside '{expectedArtifactDirectory}', running post-processing."); @@ -188,7 +188,7 @@ internal static void RunArtifactPostProcessingIfNeeded(string testSessionCorrela try { - new VSTestForwardingApp(artifactsProcessingModePostprocess).Execute(); + return new VSTestForwardingApp(artifactsProcessingModePostprocess).Execute(); } finally { diff --git a/src/Cli/dotnet/commands/dotnet-vstest/Program.cs b/src/Cli/dotnet/commands/dotnet-vstest/Program.cs index a8755e51abb3..e01f65c9e489 100644 --- a/src/Cli/dotnet/commands/dotnet-vstest/Program.cs +++ b/src/Cli/dotnet/commands/dotnet-vstest/Program.cs @@ -35,7 +35,7 @@ public static int Run(ParseResult parseResult) int exitCode = vsTestforwardingApp.Execute(); // We run post processing also if execution is failed for possible partial successful result to post process. - TestCommand.RunArtifactPostProcessingIfNeeded(testSessionCorrelationId, parseResult, FeatureFlag.Default); + exitCode |= TestCommand.RunArtifactPostProcessingIfNeeded(testSessionCorrelationId, parseResult, FeatureFlag.Default); return exitCode; } From 1f156bcc2b546fb65ca00f9b163cd46abbf13b82 Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Fri, 18 Feb 2022 14:59:36 +0100 Subject: [PATCH 03/20] cleanup --- .../GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs index e3a37eb92945..4fcbe0ca74dd 100644 --- a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs +++ b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs @@ -1,6 +1,5 @@ // Copyright (c) .NET Foundation and contributors. 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; From e8505755c871e1b6b7cdb556aa64e2f4cffa6aec Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Fri, 18 Feb 2022 16:07:33 +0100 Subject: [PATCH 04/20] fix tests --- .../GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs | 6 +++--- .../GivenDotnetTestBuildsAndRunsTestfromCsproj.cs | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs index 4fcbe0ca74dd..c110b84935d9 100644 --- a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs +++ b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs @@ -36,7 +36,7 @@ public GivenDotnetTestBuildsAndRunsArtifactPostProcessing(ITestOutputHelper log) [InlineData(false)] public void ArtifactPostProcessing_CsProjs(bool merge) { - TestAsset testInstance = _testAssetsManager.CopyTestAsset("VSTestMultiProjectSolution") + TestAsset testInstance = _testAssetsManager.CopyTestAsset("VSTestMultiProjectSolution", merge.ToString()) .WithSource(); string runsettings = GetRunsetting(testInstance.Path); @@ -60,7 +60,7 @@ public void ArtifactPostProcessing_CsProjs(bool merge) [InlineData(false)] public void ArtifactPostProcessing_TestContainers(bool merge) { - TestAsset testInstance = _testAssetsManager.CopyTestAsset("VSTestMultiProjectSolution") + TestAsset testInstance = _testAssetsManager.CopyTestAsset("VSTestMultiProjectSolution", merge.ToString()) .WithSource(); string runsettings = GetRunsetting(testInstance.Path); @@ -89,7 +89,7 @@ public void ArtifactPostProcessing_TestContainers(bool merge) [InlineData(false)] public void ArtifactPostProcessing_VSTest_TestContainers(bool merge) { - TestAsset testInstance = _testAssetsManager.CopyTestAsset("VSTestMultiProjectSolution") + TestAsset testInstance = _testAssetsManager.CopyTestAsset("VSTestMultiProjectSolution", merge.ToString()) .WithSource(); string runsettings = GetRunsetting(testInstance.Path); diff --git a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTestfromCsproj.cs b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTestfromCsproj.cs index 2c8d3ba1212f..6868d1938181 100644 --- a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTestfromCsproj.cs +++ b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsTestfromCsproj.cs @@ -728,7 +728,8 @@ public void FilterPropertyCorrectlyHandlesComma(string filter, string folderSuff [Theory] [InlineData("--output")] - [InlineData("--diag")] + // Temporarily we don't escape the diag path, issue https://github.com/dotnet/sdk/issues/23970 + // [InlineData("--diag")] [InlineData("--results-directory")] public void EnsureOutputPathEscaped(string flag) { From fb0a59a000bcf592e9bab8bc4f2081a817a68791 Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Fri, 18 Feb 2022 17:03:44 +0100 Subject: [PATCH 05/20] fix tests round 2 --- .../GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs index c110b84935d9..4db0bbe4d340 100644 --- a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs +++ b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs @@ -36,7 +36,7 @@ public GivenDotnetTestBuildsAndRunsArtifactPostProcessing(ITestOutputHelper log) [InlineData(false)] public void ArtifactPostProcessing_CsProjs(bool merge) { - TestAsset testInstance = _testAssetsManager.CopyTestAsset("VSTestMultiProjectSolution", merge.ToString()) + TestAsset testInstance = _testAssetsManager.CopyTestAsset("VSTestMultiProjectSolution", Guid.NewGuid().ToString()) .WithSource(); string runsettings = GetRunsetting(testInstance.Path); @@ -60,7 +60,7 @@ public void ArtifactPostProcessing_CsProjs(bool merge) [InlineData(false)] public void ArtifactPostProcessing_TestContainers(bool merge) { - TestAsset testInstance = _testAssetsManager.CopyTestAsset("VSTestMultiProjectSolution", merge.ToString()) + TestAsset testInstance = _testAssetsManager.CopyTestAsset("VSTestMultiProjectSolution", Guid.NewGuid().ToString()) .WithSource(); string runsettings = GetRunsetting(testInstance.Path); @@ -89,7 +89,7 @@ public void ArtifactPostProcessing_TestContainers(bool merge) [InlineData(false)] public void ArtifactPostProcessing_VSTest_TestContainers(bool merge) { - TestAsset testInstance = _testAssetsManager.CopyTestAsset("VSTestMultiProjectSolution", merge.ToString()) + TestAsset testInstance = _testAssetsManager.CopyTestAsset("VSTestMultiProjectSolution", Guid.NewGuid().ToString()) .WithSource(); string runsettings = GetRunsetting(testInstance.Path); From 5add2a1f2d02bd35e0bce6a4bda71ea9f8abfa30 Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Fri, 18 Feb 2022 18:37:15 +0100 Subject: [PATCH 06/20] fix tests round 3 --- .../VSTestMultiProjectSolution/test1/UnitTest1.cs | 4 +++- .../TestProjects/VSTestMultiProjectSolution/test1/Usings.cs | 1 - .../VSTestMultiProjectSolution/test1/test1.csproj | 3 --- .../VSTestMultiProjectSolution/test2/UnitTest1.cs | 4 +++- .../TestProjects/VSTestMultiProjectSolution/test2/Usings.cs | 1 - .../VSTestMultiProjectSolution/test2/test2.csproj | 3 --- .../VSTestMultiProjectSolution/test3/UnitTest1.cs | 4 +++- .../TestProjects/VSTestMultiProjectSolution/test3/Usings.cs | 1 - .../VSTestMultiProjectSolution/test3/test3.csproj | 3 --- 9 files changed, 9 insertions(+), 15 deletions(-) delete mode 100644 src/Assets/TestProjects/VSTestMultiProjectSolution/test1/Usings.cs delete mode 100644 src/Assets/TestProjects/VSTestMultiProjectSolution/test2/Usings.cs delete mode 100644 src/Assets/TestProjects/VSTestMultiProjectSolution/test3/Usings.cs diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/UnitTest1.cs b/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/UnitTest1.cs index 1dde78d79758..64c4cdff8f23 100644 --- a/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/UnitTest1.cs +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/UnitTest1.cs @@ -1,3 +1,5 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + namespace test1; [TestClass] @@ -7,4 +9,4 @@ public class UnitTest1 public void TestMethod1() { } -} \ No newline at end of file +} diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/Usings.cs b/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/Usings.cs deleted file mode 100644 index ab67c7ea9df8..000000000000 --- a/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/Usings.cs +++ /dev/null @@ -1 +0,0 @@ -global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/test1.csproj b/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/test1.csproj index 719c31057f61..c3c2327b2613 100644 --- a/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/test1.csproj +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/test1.csproj @@ -2,9 +2,6 @@ net7.0 - enable - enable - false diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/UnitTest1.cs b/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/UnitTest1.cs index bbb77d8b0af4..99c8962f675e 100644 --- a/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/UnitTest1.cs +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/UnitTest1.cs @@ -1,3 +1,5 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + namespace test2; [TestClass] @@ -7,4 +9,4 @@ public class UnitTest1 public void TestMethod1() { } -} \ No newline at end of file +} diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/Usings.cs b/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/Usings.cs deleted file mode 100644 index ab67c7ea9df8..000000000000 --- a/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/Usings.cs +++ /dev/null @@ -1 +0,0 @@ -global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/test2.csproj b/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/test2.csproj index 719c31057f61..c3c2327b2613 100644 --- a/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/test2.csproj +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/test2.csproj @@ -2,9 +2,6 @@ net7.0 - enable - enable - false diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/UnitTest1.cs b/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/UnitTest1.cs index d04ecd95fe80..f9df77e9ba95 100644 --- a/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/UnitTest1.cs +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/UnitTest1.cs @@ -1,3 +1,5 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + namespace test3; [TestClass] @@ -7,4 +9,4 @@ public class UnitTest1 public void TestMethod1() { } -} \ No newline at end of file +} diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/Usings.cs b/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/Usings.cs deleted file mode 100644 index ab67c7ea9df8..000000000000 --- a/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/Usings.cs +++ /dev/null @@ -1 +0,0 @@ -global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/test3.csproj b/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/test3.csproj index 719c31057f61..c3c2327b2613 100644 --- a/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/test3.csproj +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/test3.csproj @@ -2,9 +2,6 @@ net7.0 - enable - enable - false From 4888bdd2a77c61c6a967bd61f5882064a2081d80 Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Fri, 18 Feb 2022 20:33:12 +0100 Subject: [PATCH 07/20] Update src/Assets/TestProjects/VSTestDataCollectorSample/AttachmentProcessorDataCollector.csproj MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Amaury Levé --- .../AttachmentProcessorDataCollector.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Assets/TestProjects/VSTestDataCollectorSample/AttachmentProcessorDataCollector.csproj b/src/Assets/TestProjects/VSTestDataCollectorSample/AttachmentProcessorDataCollector.csproj index 13e6881015de..d954b636a5ca 100644 --- a/src/Assets/TestProjects/VSTestDataCollectorSample/AttachmentProcessorDataCollector.csproj +++ b/src/Assets/TestProjects/VSTestDataCollectorSample/AttachmentProcessorDataCollector.csproj @@ -4,6 +4,6 @@ - + From d0374e1dd96fd6b3cd7a7b17f9a602e99e077815 Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Fri, 18 Feb 2022 20:33:19 +0100 Subject: [PATCH 08/20] Update src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/AttachmentProcessorDataCollector.csproj MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Amaury Levé --- .../AttachmentProcessorDataCollector.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/AttachmentProcessorDataCollector.csproj b/src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/AttachmentProcessorDataCollector.csproj index 13e6881015de..d954b636a5ca 100644 --- a/src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/AttachmentProcessorDataCollector.csproj +++ b/src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/AttachmentProcessorDataCollector.csproj @@ -4,6 +4,6 @@ - + From b69187ad865ae5d191c71ad3e3388e3092dc7f13 Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Fri, 18 Feb 2022 20:45:50 +0100 Subject: [PATCH 09/20] Update src/Cli/dotnet/commands/dotnet-test/Program.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Amaury Levé --- src/Cli/dotnet/commands/dotnet-test/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cli/dotnet/commands/dotnet-test/Program.cs b/src/Cli/dotnet/commands/dotnet-test/Program.cs index 5581f5d096e2..030fb887e774 100644 --- a/src/Cli/dotnet/commands/dotnet-test/Program.cs +++ b/src/Cli/dotnet/commands/dotnet-test/Program.cs @@ -201,7 +201,7 @@ internal static int RunArtifactPostProcessingIfNeeded(string testSessionCorrelat } catch (Exception ex) { - VSTestTrace.WriteTrace($"Exception during artifact cleanup:'{ex}'"); + VSTestTrace.WriteTrace($"Exception during artifact cleanup: {ex}"); } } } From 17aa2052a83ada500f1a613ecc646e32f3688cb5 Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Fri, 18 Feb 2022 20:45:57 +0100 Subject: [PATCH 10/20] Update src/Cli/dotnet/commands/dotnet-test/VSTestTrace.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Amaury Levé --- src/Cli/dotnet/commands/dotnet-test/VSTestTrace.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Cli/dotnet/commands/dotnet-test/VSTestTrace.cs b/src/Cli/dotnet/commands/dotnet-test/VSTestTrace.cs index 9719cfc4e477..0cf9ccb393c8 100644 --- a/src/Cli/dotnet/commands/dotnet-test/VSTestTrace.cs +++ b/src/Cli/dotnet/commands/dotnet-test/VSTestTrace.cs @@ -1,6 +1,5 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -// using System; using System.IO; From abab4725d53c1fcbca10fb2016608edbf4624557 Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Fri, 18 Feb 2022 20:46:43 +0100 Subject: [PATCH 11/20] Update src/Cli/dotnet/commands/dotnet-test/Program.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Amaury Levé --- src/Cli/dotnet/commands/dotnet-test/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cli/dotnet/commands/dotnet-test/Program.cs b/src/Cli/dotnet/commands/dotnet-test/Program.cs index 030fb887e774..2f4bc6eff122 100644 --- a/src/Cli/dotnet/commands/dotnet-test/Program.cs +++ b/src/Cli/dotnet/commands/dotnet-test/Program.cs @@ -194,7 +194,7 @@ internal static int RunArtifactPostProcessingIfNeeded(string testSessionCorrelat { if (Directory.Exists(expectedArtifactDirectory)) { - VSTestTrace.WriteTrace($"Cleaning artifact directory '{expectedArtifactDirectory}'"); + VSTestTrace.WriteTrace($"Cleaning artifact directory '{expectedArtifactDirectory}'."); try { Directory.Delete(expectedArtifactDirectory, true); From a5ee5e9cd3ce2b048bfc50305a0dda1930d25164 Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Fri, 18 Feb 2022 20:52:09 +0100 Subject: [PATCH 12/20] Update src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Amaury Levé --- src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs b/src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs index 5d7f24ae9a06..81eda04da5f4 100644 --- a/src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs +++ b/src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs @@ -1,6 +1,5 @@ // Copyright (c) .NET Foundation and contributors. 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; From d52324b559b1ca9a6128077c45ce2a474ee0dda7 Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Fri, 18 Feb 2022 20:59:12 +0100 Subject: [PATCH 13/20] Update src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Amaury Levé --- ...venDotnetTestBuildsAndRunsArtifactPostProcessing.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs index 4db0bbe4d340..cb98896521c0 100644 --- a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs +++ b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs @@ -45,11 +45,11 @@ public void ArtifactPostProcessing_CsProjs(bool merge) .WithWorkingDirectory(testInstance.Path) .WithEnvironmentVariable(FeatureFlag.ARTIFACTS_POSTPROCESSING, "1") .Execute( - "--configuration", "release", - "--collect", "SampleDataCollector", - "--test-adapter-path", merge ? Path.GetDirectoryName(s_dataCollectorDll) : Path.GetDirectoryName(s_dataCollectorNoMergeDll), - "--settings", runsettings, - "--diag", testInstance.Path + "/logs/"); + "--configuration", "release", + "--collect", "SampleDataCollector", + "--test-adapter-path", merge ? Path.GetDirectoryName(s_dataCollectorDll) : Path.GetDirectoryName(s_dataCollectorNoMergeDll), + "--settings", runsettings, + "--diag", testInstance.Path + "/logs/"); result.ExitCode.Should().Be(0); AssertOutput(result.StdOut, merge); From addb2d340c9a4d4b73f737a77fb14f8e80d6d569 Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Fri, 18 Feb 2022 21:14:53 +0100 Subject: [PATCH 14/20] Update src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Amaury Levé --- src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs b/src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs index 81eda04da5f4..fb7ee322d1c4 100644 --- a/src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs +++ b/src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs @@ -27,9 +27,9 @@ public FeatureFlag() // 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 : - FeatureFlagsBag.TryGetValue(featureName, out bool isEnabled) && isEnabled; + int.TryParse(Environment.GetEnvironmentVariable(featureName), out int enabled) + ? enabled == 1 + : FeatureFlagsBag.TryGetValue(featureName, out bool isEnabled) && isEnabled; public void PrintFlagFeatureState() { From abac77248da41610ab4e2882fb62eacbcab1e3ae Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Fri, 18 Feb 2022 21:22:11 +0100 Subject: [PATCH 15/20] Update src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Amaury Levé --- ...TestBuildsAndRunsArtifactPostProcessing.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs index cb98896521c0..8ba4f43e65ef 100644 --- a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs +++ b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs @@ -97,16 +97,16 @@ public void ArtifactPostProcessing_VSTest_TestContainers(bool merge) new PublishCommand(Log, Path.Combine(testInstance.Path, "sln.sln")).Execute("/p:Configuration=Release").Should().Pass(); CommandResult result = new DotnetVSTestCommand(Log) - .WithWorkingDirectory(testInstance.Path) - .WithEnvironmentVariable(FeatureFlag.ARTIFACTS_POSTPROCESSING, "1") - .Execute( - Directory.GetFiles(testInstance.Path, "test1.dll", SearchOption.AllDirectories).SingleOrDefault(x => x.Contains("publish")), - Directory.GetFiles(testInstance.Path, "test2.dll", SearchOption.AllDirectories).SingleOrDefault(x => x.Contains("publish")), - Directory.GetFiles(testInstance.Path, "test3.dll", SearchOption.AllDirectories).SingleOrDefault(x => x.Contains("publish")), - "--collect:SampleDataCollector", - $"--testAdapterPath:{(merge ? Path.GetDirectoryName(s_dataCollectorDll) : Path.GetDirectoryName(s_dataCollectorNoMergeDll))}", - $"--settings:{runsettings}", - "--diag:" + testInstance.Path + "/logs/"); + .WithWorkingDirectory(testInstance.Path) + .WithEnvironmentVariable(FeatureFlag.ARTIFACTS_POSTPROCESSING, "1") + .Execute( + Directory.GetFiles(testInstance.Path, "test1.dll", SearchOption.AllDirectories).SingleOrDefault(x => x.Contains("publish")), + Directory.GetFiles(testInstance.Path, "test2.dll", SearchOption.AllDirectories).SingleOrDefault(x => x.Contains("publish")), + Directory.GetFiles(testInstance.Path, "test3.dll", SearchOption.AllDirectories).SingleOrDefault(x => x.Contains("publish")), + "--collect:SampleDataCollector", + $"--testAdapterPath:{(merge ? Path.GetDirectoryName(s_dataCollectorDll) : Path.GetDirectoryName(s_dataCollectorNoMergeDll))}", + $"--settings:{runsettings}", + $"--diag:{testInstance.Path}/logs/"); result.ExitCode.Should().Be(0); AssertOutput(result.StdOut, merge); From fbd09d0f303f30d827a5d6d8d49eb38d049a5a43 Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Fri, 18 Feb 2022 22:45:54 +0100 Subject: [PATCH 16/20] address PR feedback --- .../AttachmentProcessorDataCollector.csproj | 5 +- .../AttachmentProcessorDataCollector.csproj | 5 +- .../test1/test1.csproj | 14 +++--- .../test2/test2.csproj | 14 +++--- .../test3/test3.csproj | 14 +++--- .../dotnet/commands/dotnet-test/Program.cs | 8 ++-- .../commands/dotnet-test/VSTestFeatureFlag.cs | 8 ++-- .../commands/dotnet-test/VSTestTrace.cs | 41 +++++++++------- .../dotnet/commands/dotnet-vstest/Program.cs | 8 ++-- ...TestBuildsAndRunsArtifactPostProcessing.cs | 47 +++++++++---------- 10 files changed, 83 insertions(+), 81 deletions(-) diff --git a/src/Assets/TestProjects/VSTestDataCollectorSample/AttachmentProcessorDataCollector.csproj b/src/Assets/TestProjects/VSTestDataCollectorSample/AttachmentProcessorDataCollector.csproj index d954b636a5ca..25f68d6620b4 100644 --- a/src/Assets/TestProjects/VSTestDataCollectorSample/AttachmentProcessorDataCollector.csproj +++ b/src/Assets/TestProjects/VSTestDataCollectorSample/AttachmentProcessorDataCollector.csproj @@ -1,9 +1,10 @@ - + + netstandard2.0 - + diff --git a/src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/AttachmentProcessorDataCollector.csproj b/src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/AttachmentProcessorDataCollector.csproj index d954b636a5ca..25f68d6620b4 100644 --- a/src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/AttachmentProcessorDataCollector.csproj +++ b/src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/AttachmentProcessorDataCollector.csproj @@ -1,9 +1,10 @@ - + + netstandard2.0 - + diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/test1.csproj b/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/test1.csproj index c3c2327b2613..5f216b8e9841 100644 --- a/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/test1.csproj +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/test1.csproj @@ -1,15 +1,13 @@ - + + - net7.0 - false + $(CurrentTargetFramework) - - - - + + + - diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/test2.csproj b/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/test2.csproj index c3c2327b2613..5f216b8e9841 100644 --- a/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/test2.csproj +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/test2.csproj @@ -1,15 +1,13 @@ - + + - net7.0 - false + $(CurrentTargetFramework) - - - - + + + - diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/test3.csproj b/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/test3.csproj index c3c2327b2613..5f216b8e9841 100644 --- a/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/test3.csproj +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/test3.csproj @@ -1,15 +1,13 @@ - + + - net7.0 - false + $(CurrentTargetFramework) - - - - + + + - diff --git a/src/Cli/dotnet/commands/dotnet-test/Program.cs b/src/Cli/dotnet/commands/dotnet-test/Program.cs index 2f4bc6eff122..d37e67910434 100644 --- a/src/Cli/dotnet/commands/dotnet-test/Program.cs +++ b/src/Cli/dotnet/commands/dotnet-test/Program.cs @@ -103,7 +103,7 @@ public static int Run(ParseResult parseResult) string commandLineParameters = ""; if (args?.Length > 0) { - commandLineParameters = args.Aggregate((a, b) => $"{a}|{b}"); + commandLineParameters = args.Aggregate((a, b) => $"{a} | {b}"); } VSTestTrace.WriteTrace($"Argument list: '{commandLineParameters}'"); } @@ -179,16 +179,16 @@ internal static int RunArtifactPostProcessingIfNeeded(string testSessionCorrelat VSTestTrace.WriteTrace($"Artifacts found inside '{expectedArtifactDirectory}', running post-processing."); - var artifactsProcessingModePostprocess = new List { "--artifactsProcessingMode-postprocess", $"--testSessionCorrelationId:{testSessionCorrelationId}" }; + var artifactsPostProcessArgs = new List { "--artifactsProcessingMode-postprocess", $"--testSessionCorrelationId:{testSessionCorrelationId}" }; if (parseResult.HasOption(TestCommandParser.DiagOption)) { - artifactsProcessingModePostprocess.Add($"--diag:{parseResult.GetValueForOption(TestCommandParser.DiagOption)}"); + artifactsPostProcessArgs.Add($"--diag:{parseResult.GetValueForOption(TestCommandParser.DiagOption)}"); } try { - return new VSTestForwardingApp(artifactsProcessingModePostprocess).Execute(); + return new VSTestForwardingApp(artifactsPostProcessArgs).Execute(); } finally { diff --git a/src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs b/src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs index fb7ee322d1c4..a8e37a6d2bb8 100644 --- a/src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs +++ b/src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs @@ -11,13 +11,13 @@ internal class FeatureFlag { private const string Prefix = "VSTEST_FEATURE_"; - public Dictionary FeatureFlagsBag { get; } = new(); + public Dictionary FeatureFlags { get; } = new(); public static FeatureFlag Default { get; } = new FeatureFlag(); public FeatureFlag() { - FeatureFlagsBag.Add(ARTIFACTS_POSTPROCESSING, false); + FeatureFlags.Add(ARTIFACTS_POSTPROCESSING, false); } // Added for artifact porst-processing, it enable/disable the post processing. @@ -29,13 +29,13 @@ public FeatureFlag() public bool IsEnabled(string featureName) => int.TryParse(Environment.GetEnvironmentVariable(featureName), out int enabled) ? enabled == 1 - : FeatureFlagsBag.TryGetValue(featureName, out bool isEnabled) && isEnabled; + : FeatureFlags.TryGetValue(featureName, out bool isEnabled) && isEnabled; public void PrintFlagFeatureState() { if (VSTestTrace.TraceEnabled) { - foreach (KeyValuePair flag in FeatureFlagsBag) + foreach (KeyValuePair flag in FeatureFlags) { VSTestTrace.WriteTrace($"Feature {flag.Key}: {IsEnabled(flag.Key)}"); } diff --git a/src/Cli/dotnet/commands/dotnet-test/VSTestTrace.cs b/src/Cli/dotnet/commands/dotnet-test/VSTestTrace.cs index 0cf9ccb393c8..7819ab7d4f34 100644 --- a/src/Cli/dotnet/commands/dotnet-test/VSTestTrace.cs +++ b/src/Cli/dotnet/commands/dotnet-test/VSTestTrace.cs @@ -8,7 +8,7 @@ namespace Microsoft.DotNet.Tools.Test { internal class VSTestTrace { - public static bool TraceEnabled { get; set; } + public static bool TraceEnabled { get; private set; } private static readonly string s_traceFilePath; static VSTestTrace() @@ -23,37 +23,44 @@ static VSTestTrace() public static void WriteTrace(string logText) { - if (TraceEnabled) + if (!TraceEnabled) + { + return; + } + + try { - try + string message = $"[dotnet test - {DateTimeOffset.UtcNow}]{logText}"; + if (!string.IsNullOrEmpty(s_traceFilePath)) { - string message = $"[dotnet test - {DateTime.UtcNow}]{logText}"; - if (!string.IsNullOrEmpty(s_traceFilePath)) - { - using StreamWriter logFile = File.AppendText(s_traceFilePath); - logFile.WriteLine(message); - } - else - { - Console.WriteLine(message); - } + using StreamWriter logFile = File.AppendText(s_traceFilePath); + logFile.WriteLine(message); } - catch + else { - // Avoid exception if we have issue with the log file. + Console.WriteLine(message); } } + catch (Exception ex) + { + Console.WriteLine($"[dotnet test - {DateTimeOffset.UtcNow}]{ex}"); + } } public static void SafeWriteTrace(Func messageLog) { + if (!TraceEnabled) + { + return; + } + try { WriteTrace(messageLog()); } - catch + catch (Exception ex) { - // Avoid exception in case of something is wrong with log composition. + Console.WriteLine($"[dotnet test - {DateTimeOffset.UtcNow}]{ex}"); } } } diff --git a/src/Cli/dotnet/commands/dotnet-vstest/Program.cs b/src/Cli/dotnet/commands/dotnet-vstest/Program.cs index e01f65c9e489..a10a3f29ad1c 100644 --- a/src/Cli/dotnet/commands/dotnet-vstest/Program.cs +++ b/src/Cli/dotnet/commands/dotnet-vstest/Program.cs @@ -1,11 +1,11 @@ -// Copyright(c) .NET Foundation and contributors.All rights reserved. +// Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using Microsoft.DotNet.Cli; -using System.CommandLine.Parsing; +using System; using System.Collections.Generic; +using System.CommandLine.Parsing; using System.Linq; -using System; +using Microsoft.DotNet.Cli; using Microsoft.DotNet.Tools.Test; namespace Microsoft.DotNet.Tools.VSTest diff --git a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs index 8ba4f43e65ef..c8e5c7d132c0 100644 --- a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs +++ b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs @@ -9,6 +9,7 @@ using System.Xml; using System.Xml.Linq; using FluentAssertions; +using FluentAssertions.Common; using Microsoft.DotNet.Tools.Test; using Microsoft.NET.TestFramework; using Microsoft.NET.TestFramework.Assertions; @@ -125,44 +126,42 @@ private static void AssertOutput(string stdOut, bool merge) if (merge) { - Assert.Equal(string.Empty, output[output.Count - 3].Trim()); - Assert.Equal("Attachments:", output[output.Count - 2].Trim()); - string mergedFile = output[output.Count - 1].Trim(); + output[^3].Trim().Should().BeEmpty(); + output[^2].Trim().Should().Be("Attachments:"); + string mergedFile = output[^1].Trim(); - var fileContent = new List(); + var fileContent = new HashSet(); using var streamReader = new StreamReader(mergedFile); - while (!streamReader.EndOfStream) - { - string line = streamReader.ReadLine(); - Assert.StartsWith("SessionEnded_Handler_", line); - fileContent.Add(line); - } - - Assert.Equal(3, fileContent.Distinct().Count()); + LoadLines(streamReader, fileContent); + fileContent.Count.Should().Be(3); } else { - Assert.Equal(string.Empty, output[output.Count - 5].Trim()); - Assert.Equal("Attachments:", output[output.Count - 4].Trim()); + output[^5].Trim().Should().BeEmpty(); + output[^4].Trim().Should().Be("Attachments:"); int currentLine = 0; for (int i = 3; i > 0; i--) { currentLine = output.Count - i; string file = output[currentLine].Trim(); - var fileContent = new List(); + var fileContent = new HashSet(); using var streamReader = new StreamReader(file); - while (!streamReader.EndOfStream) - { - string line = streamReader.ReadLine(); - Assert.StartsWith("SessionEnded_Handler_", line); - fileContent.Add(line); - } - - Assert.True(fileContent.Distinct().Count() == 1); + LoadLines(streamReader, fileContent); + fileContent.Count.Should().Be(1); } - Assert.Equal(output.Count, currentLine + 1); + output.Count.Should().Be(currentLine + 1); + } + + static void LoadLines(StreamReader stream, HashSet fileContent) + { + while (!stream.EndOfStream) + { + string line = stream.ReadLine(); + line.Should().StartWith("SessionEnded_Handler_"); + fileContent.Add(line); + } } } From aba96520a1e9e6c7224f94fa9afe46c481ccf238 Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Fri, 18 Feb 2022 22:49:21 +0100 Subject: [PATCH 17/20] rename test --- .../GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs index c8e5c7d132c0..3b20d9fe5ef5 100644 --- a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs +++ b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs @@ -35,7 +35,7 @@ public GivenDotnetTestBuildsAndRunsArtifactPostProcessing(ITestOutputHelper log) [Theory] [InlineData(true)] [InlineData(false)] - public void ArtifactPostProcessing_CsProjs(bool merge) + public void ArtifactPostProcessing_SolutionProjects(bool merge) { TestAsset testInstance = _testAssetsManager.CopyTestAsset("VSTestMultiProjectSolution", Guid.NewGuid().ToString()) .WithSource(); From cfd8f3b02190734481d8236be76c000ab7aaa16c Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Fri, 18 Feb 2022 22:52:46 +0100 Subject: [PATCH 18/20] cleanup namespace --- .../GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs index 3b20d9fe5ef5..b0ebac72f43b 100644 --- a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs +++ b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs @@ -9,7 +9,6 @@ using System.Xml; using System.Xml.Linq; using FluentAssertions; -using FluentAssertions.Common; using Microsoft.DotNet.Tools.Test; using Microsoft.NET.TestFramework; using Microsoft.NET.TestFramework.Assertions; From 894fc75c436b01e1a45c367e95da8fd99606318a Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Sat, 19 Feb 2022 12:44:30 +0100 Subject: [PATCH 19/20] refactoring, address PR feedback --- .../AttachmentProcessorDataCollector.csproj | 6 + .../AttachmentProcessorDataCollector.csproj | 6 + .../test1/test1.csproj | 6 + .../test2/test2.csproj | 6 + .../test3/test3.csproj | 6 + .../dotnet/commands/dotnet-test/Program.cs | 173 +++++++++--------- .../commands/dotnet-test/VSTestFeatureFlag.cs | 2 +- .../dotnet-test/VSTestForwardingApp.cs | 2 +- .../commands/dotnet-test/VSTestTrace.cs | 28 +-- ...TestBuildsAndRunsArtifactPostProcessing.cs | 20 +- 10 files changed, 140 insertions(+), 115 deletions(-) diff --git a/src/Assets/TestProjects/VSTestDataCollectorSample/AttachmentProcessorDataCollector.csproj b/src/Assets/TestProjects/VSTestDataCollectorSample/AttachmentProcessorDataCollector.csproj index 25f68d6620b4..27aab5399acf 100644 --- a/src/Assets/TestProjects/VSTestDataCollectorSample/AttachmentProcessorDataCollector.csproj +++ b/src/Assets/TestProjects/VSTestDataCollectorSample/AttachmentProcessorDataCollector.csproj @@ -7,4 +7,10 @@ + + + + + + diff --git a/src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/AttachmentProcessorDataCollector.csproj b/src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/AttachmentProcessorDataCollector.csproj index 25f68d6620b4..27aab5399acf 100644 --- a/src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/AttachmentProcessorDataCollector.csproj +++ b/src/Assets/TestProjects/VSTestDataCollectorSampleNoMerge/AttachmentProcessorDataCollector.csproj @@ -7,4 +7,10 @@ + + + + + + diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/test1.csproj b/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/test1.csproj index 5f216b8e9841..0aa9091b66a5 100644 --- a/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/test1.csproj +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/test1.csproj @@ -10,4 +10,10 @@ + + + + + + diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/test2.csproj b/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/test2.csproj index 5f216b8e9841..0aa9091b66a5 100644 --- a/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/test2.csproj +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/test2.csproj @@ -10,4 +10,10 @@ + + + + + + diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/test3.csproj b/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/test3.csproj index 5f216b8e9841..0aa9091b66a5 100644 --- a/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/test3.csproj +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/test3.csproj @@ -10,4 +10,10 @@ + + + + + + diff --git a/src/Cli/dotnet/commands/dotnet-test/Program.cs b/src/Cli/dotnet/commands/dotnet-test/Program.cs index d37e67910434..ab1d7c3758c2 100644 --- a/src/Cli/dotnet/commands/dotnet-test/Program.cs +++ b/src/Cli/dotnet/commands/dotnet-test/Program.cs @@ -22,7 +22,92 @@ public TestCommand( { } - public static TestCommand FromParseResult(ParseResult result, string[] settings, string testSessionCorrelationId, string msbuildPath = null) + public static int Run(ParseResult parseResult) + { + parseResult.HandleDebugSwitch(); + + FeatureFlag.Default.PrintFlagFeatureState(); + + // We use also current process id for the correlation id for possible future usage in case we need to know the parent process + // from the VSTest side. + string testSessionCorrelationId = $"{Environment.ProcessId}_{Guid.NewGuid()}"; + + string[] args = parseResult.GetArguments(); + + if (VSTestTrace.TraceEnabled) + { + string commandLineParameters = ""; + if (args?.Length > 0) + { + commandLineParameters = args.Aggregate((a, b) => $"{a} | {b}"); + } + VSTestTrace.SafeWriteTrace(() => $"Argument list: '{commandLineParameters}'"); + } + + // settings parameters are after -- (including --), these should not be considered by the parser + string[] settings = args.SkipWhile(a => a != "--").ToArray(); + // all parameters before -- + args = args.TakeWhile(a => a != "--").ToArray(); + + // Fix for https://github.com/Microsoft/vstest/issues/1453 + // Run dll/exe directly using the VSTestForwardingApp + if (ContainsBuiltTestSources(args)) + { + return ForwardToVSTestConsole(parseResult, args, settings, testSessionCorrelationId); + } + + return ForwardToMsbuild(parseResult, settings, testSessionCorrelationId); + } + + private static int ForwardToMsbuild(ParseResult parseResult, string[] settings, string testSessionCorrelationId) + { + // Workaround for https://github.com/Microsoft/vstest/issues/1503 + const string NodeWindowEnvironmentName = "MSBUILDENSURESTDOUTFORTASKPROCESSES"; + string previousNodeWindowSetting = Environment.GetEnvironmentVariable(NodeWindowEnvironmentName); + try + { + Environment.SetEnvironmentVariable(NodeWindowEnvironmentName, "1"); + int exitCode = FromParseResult(parseResult, settings, testSessionCorrelationId).Execute(); + + // We run post processing also if execution is failed for possible partial successful result to post process. + exitCode |= RunArtifactPostProcessingIfNeeded(testSessionCorrelationId, parseResult, FeatureFlag.Default); + + return exitCode; + } + finally + { + Environment.SetEnvironmentVariable(NodeWindowEnvironmentName, previousNodeWindowSetting); + } + } + + private static int ForwardToVSTestConsole(ParseResult parseResult, string[] args, string[] settings, string testSessionCorrelationId) + { + List convertedArgs = new VSTestArgumentConverter().Convert(args, out List ignoredArgs); + if (ignoredArgs.Any()) + { + Reporter.Output.WriteLine(string.Format(LocalizableStrings.IgnoredArgumentsMessage, string.Join(" ", ignoredArgs)).Yellow()); + } + + // merge the args settings, we don't need to escape + // one more time, there is no extra hop via msbuild + convertedArgs.AddRange(settings); + + if (FeatureFlag.Default.IsEnabled(FeatureFlag.ARTIFACTS_POSTPROCESSING)) + { + // Add artifacts processing mode and test session id for the artifact post-processing + convertedArgs.Add("--artifactsProcessingMode-collect"); + convertedArgs.Add($"--testSessionCorrelationId:{testSessionCorrelationId}"); + } + + int exitCode = new VSTestForwardingApp(convertedArgs).Execute(); + + // We run post processing also if execution is failed for possible partial successful result to post process. + exitCode |= RunArtifactPostProcessingIfNeeded(testSessionCorrelationId, parseResult, FeatureFlag.Default); + + return exitCode; + } + + private static TestCommand FromParseResult(ParseResult result, string[] settings, string testSessionCorrelationId, string msbuildPath = null) { result.ShowHelpOrErrorIfAppropriate(); @@ -79,89 +164,13 @@ public static TestCommand FromParseResult(ParseResult result, string[] settings, if (!hasRootVariable) { testCommand.EnvironmentVariable(rootVariableName, rootValue); - VSTestTrace.WriteTrace($"Root variable set {rootVariableName}:{rootValue}"); + VSTestTrace.SafeWriteTrace(() => $"Root variable set {rootVariableName}:{rootValue}"); } VSTestTrace.SafeWriteTrace(() => $"Starting test using MSBuild with arguments '{testCommand.GetArgumentsToMSBuild()}' custom MSBuild path '{msbuildPath}' norestore '{noRestore}'"); return testCommand; } - public static int Run(ParseResult parseResult) - { - parseResult.HandleDebugSwitch(); - - FeatureFlag.Default.PrintFlagFeatureState(); - - // We use also current process id for the correlation id for possible future usage in case we need to know the parent process - // from the VSTest side. - string testSessionCorrelationId = $"{Environment.ProcessId}_{Guid.NewGuid()}"; - - string[] args = parseResult.GetArguments(); - - if (VSTestTrace.TraceEnabled) - { - string commandLineParameters = ""; - if (args?.Length > 0) - { - commandLineParameters = args.Aggregate((a, b) => $"{a} | {b}"); - } - VSTestTrace.WriteTrace($"Argument list: '{commandLineParameters}'"); - } - - // settings parameters are after -- (including --), these should not be considered by the parser - string[] settings = args.SkipWhile(a => a != "--").ToArray(); - // all parameters before -- - args = args.TakeWhile(a => a != "--").ToArray(); - - // Fix for https://github.com/Microsoft/vstest/issues/1453 - // Try to run dll/exe directly using the VSTestForwardingApp - List convertedArgs = new VSTestArgumentConverter().Convert(args, out List ignoredArgs); - if (ContainsBuiltTestSources(args)) - { - if (ignoredArgs.Any()) - { - Reporter.Output.WriteLine(string.Format(LocalizableStrings.IgnoredArgumentsMessage, string.Join(" ", ignoredArgs)).Yellow()); - } - - // merge the args settings, we don't need to escape - // one more time, there is no extra hop via msbuild - convertedArgs.AddRange(settings); - - if (FeatureFlag.Default.IsEnabled(FeatureFlag.ARTIFACTS_POSTPROCESSING)) - { - // Add artifacts processing mode and test session id for the artifact post-processing - convertedArgs.Add("--artifactsProcessingMode-collect"); - convertedArgs.Add($"--testSessionCorrelationId:{testSessionCorrelationId}"); - } - - int exitCode = new VSTestForwardingApp(convertedArgs).Execute(); - - // We run post processing also if execution is failed for possible partial successful result to post process. - exitCode |= RunArtifactPostProcessingIfNeeded(testSessionCorrelationId, parseResult, FeatureFlag.Default); - - return exitCode; - } - - // Workaround for https://github.com/Microsoft/vstest/issues/1503 - const string NodeWindowEnvironmentName = "MSBUILDENSURESTDOUTFORTASKPROCESSES"; - string previousNodeWindowSetting = Environment.GetEnvironmentVariable(NodeWindowEnvironmentName); - - try - { - Environment.SetEnvironmentVariable(NodeWindowEnvironmentName, "1"); - int exitCode = FromParseResult(parseResult, settings, testSessionCorrelationId).Execute(); - - // We run post processing also if execution is failed for possible partial successful result to post process. - exitCode |= RunArtifactPostProcessingIfNeeded(testSessionCorrelationId, parseResult, FeatureFlag.Default); - - return exitCode; - } - finally - { - Environment.SetEnvironmentVariable(NodeWindowEnvironmentName, previousNodeWindowSetting); - } - } - internal static int RunArtifactPostProcessingIfNeeded(string testSessionCorrelationId, ParseResult parseResult, FeatureFlag featureFlag) { if (!featureFlag.IsEnabled(FeatureFlag.ARTIFACTS_POSTPROCESSING)) @@ -173,11 +182,11 @@ internal static int RunArtifactPostProcessingIfNeeded(string testSessionCorrelat string expectedArtifactDirectory = Path.Combine(Path.GetTempPath(), testSessionCorrelationId); if (!Directory.Exists(expectedArtifactDirectory)) { - VSTestTrace.WriteTrace("No artifact found, post-processing won't run."); + VSTestTrace.SafeWriteTrace(() => "No artifact found, post-processing won't run."); return 0; } - VSTestTrace.WriteTrace($"Artifacts found inside '{expectedArtifactDirectory}', running post-processing."); + VSTestTrace.SafeWriteTrace(() => $"Artifacts directory found '{expectedArtifactDirectory}', running post-processing."); var artifactsPostProcessArgs = new List { "--artifactsProcessingMode-postprocess", $"--testSessionCorrelationId:{testSessionCorrelationId}" }; @@ -194,14 +203,14 @@ internal static int RunArtifactPostProcessingIfNeeded(string testSessionCorrelat { if (Directory.Exists(expectedArtifactDirectory)) { - VSTestTrace.WriteTrace($"Cleaning artifact directory '{expectedArtifactDirectory}'."); + VSTestTrace.SafeWriteTrace(() => $"Cleaning artifact directory '{expectedArtifactDirectory}'."); try { Directory.Delete(expectedArtifactDirectory, true); } catch (Exception ex) { - VSTestTrace.WriteTrace($"Exception during artifact cleanup: {ex}"); + VSTestTrace.SafeWriteTrace(() => $"Exception during artifact cleanup: \n{ex}"); } } } diff --git a/src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs b/src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs index a8e37a6d2bb8..b15c86c60db8 100644 --- a/src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs +++ b/src/Cli/dotnet/commands/dotnet-test/VSTestFeatureFlag.cs @@ -37,7 +37,7 @@ public void PrintFlagFeatureState() { foreach (KeyValuePair flag in FeatureFlags) { - VSTestTrace.WriteTrace($"Feature {flag.Key}: {IsEnabled(flag.Key)}"); + VSTestTrace.SafeWriteTrace(() => $"Feature {flag.Key}: {IsEnabled(flag.Key)}"); } } } diff --git a/src/Cli/dotnet/commands/dotnet-test/VSTestForwardingApp.cs b/src/Cli/dotnet/commands/dotnet-test/VSTestForwardingApp.cs index 16a8593936d5..0190ff78a8d4 100644 --- a/src/Cli/dotnet/commands/dotnet-test/VSTestForwardingApp.cs +++ b/src/Cli/dotnet/commands/dotnet-test/VSTestForwardingApp.cs @@ -21,7 +21,7 @@ public VSTestForwardingApp(IEnumerable argsToForward) if (!hasRootVariable) { WithEnvironmentVariable(rootVariableName, rootValue); - VSTestTrace.WriteTrace($"Root variable set {rootVariableName}:{rootValue}"); + VSTestTrace.SafeWriteTrace(() => $"Root variable set {rootVariableName}:{rootValue}"); } VSTestTrace.SafeWriteTrace(() => $"Forwarding to '{GetVSTestExePath()}' with args \"{argsToForward?.Aggregate((a, b) => $"{a} | {b}")}\""); diff --git a/src/Cli/dotnet/commands/dotnet-test/VSTestTrace.cs b/src/Cli/dotnet/commands/dotnet-test/VSTestTrace.cs index 7819ab7d4f34..0df075b60032 100644 --- a/src/Cli/dotnet/commands/dotnet-test/VSTestTrace.cs +++ b/src/Cli/dotnet/commands/dotnet-test/VSTestTrace.cs @@ -21,7 +21,7 @@ static VSTestTrace() } } - public static void WriteTrace(string logText) + public static void SafeWriteTrace(Func messageLog) { if (!TraceEnabled) { @@ -30,11 +30,14 @@ public static void WriteTrace(string logText) try { - string message = $"[dotnet test - {DateTimeOffset.UtcNow}]{logText}"; + string message = $"[dotnet test - {DateTimeOffset.UtcNow}]{messageLog()}"; if (!string.IsNullOrEmpty(s_traceFilePath)) { - using StreamWriter logFile = File.AppendText(s_traceFilePath); - logFile.WriteLine(message); + lock (s_traceFilePath) + { + using StreamWriter logFile = File.AppendText(s_traceFilePath); + logFile.WriteLine(message); + } } else { @@ -46,22 +49,5 @@ public static void WriteTrace(string logText) Console.WriteLine($"[dotnet test - {DateTimeOffset.UtcNow}]{ex}"); } } - - public static void SafeWriteTrace(Func messageLog) - { - if (!TraceEnabled) - { - return; - } - - try - { - WriteTrace(messageLog()); - } - catch (Exception ex) - { - Console.WriteLine($"[dotnet test - {DateTimeOffset.UtcNow}]{ex}"); - } - } } } diff --git a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs index b0ebac72f43b..583e22c60fae 100644 --- a/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs +++ b/src/Tests/dotnet-test.Tests/GivenDotnetTestBuildsAndRunsArtifactPostProcessing.cs @@ -97,16 +97,16 @@ public void ArtifactPostProcessing_VSTest_TestContainers(bool merge) new PublishCommand(Log, Path.Combine(testInstance.Path, "sln.sln")).Execute("/p:Configuration=Release").Should().Pass(); CommandResult result = new DotnetVSTestCommand(Log) - .WithWorkingDirectory(testInstance.Path) - .WithEnvironmentVariable(FeatureFlag.ARTIFACTS_POSTPROCESSING, "1") - .Execute( - Directory.GetFiles(testInstance.Path, "test1.dll", SearchOption.AllDirectories).SingleOrDefault(x => x.Contains("publish")), - Directory.GetFiles(testInstance.Path, "test2.dll", SearchOption.AllDirectories).SingleOrDefault(x => x.Contains("publish")), - Directory.GetFiles(testInstance.Path, "test3.dll", SearchOption.AllDirectories).SingleOrDefault(x => x.Contains("publish")), - "--collect:SampleDataCollector", - $"--testAdapterPath:{(merge ? Path.GetDirectoryName(s_dataCollectorDll) : Path.GetDirectoryName(s_dataCollectorNoMergeDll))}", - $"--settings:{runsettings}", - $"--diag:{testInstance.Path}/logs/"); + .WithWorkingDirectory(testInstance.Path) + .WithEnvironmentVariable(FeatureFlag.ARTIFACTS_POSTPROCESSING, "1") + .Execute( + Directory.GetFiles(testInstance.Path, "test1.dll", SearchOption.AllDirectories).SingleOrDefault(x => x.Contains("publish")), + Directory.GetFiles(testInstance.Path, "test2.dll", SearchOption.AllDirectories).SingleOrDefault(x => x.Contains("publish")), + Directory.GetFiles(testInstance.Path, "test3.dll", SearchOption.AllDirectories).SingleOrDefault(x => x.Contains("publish")), + "--collect:SampleDataCollector", + $"--testAdapterPath:{(merge ? Path.GetDirectoryName(s_dataCollectorDll) : Path.GetDirectoryName(s_dataCollectorNoMergeDll))}", + $"--settings:{runsettings}", + $"--diag:{testInstance.Path}/logs/"); result.ExitCode.Should().Be(0); AssertOutput(result.StdOut, merge); From f07bff6d33657846678d105e35f4a33feb0fb475 Mon Sep 17 00:00:00 2001 From: Marco Rossignoli Date: Sun, 20 Feb 2022 13:02:26 +0100 Subject: [PATCH 20/20] fix tests round 4 --- .../VSTestMultiProjectSolution/test1/UnitTest1.cs | 13 +++++++------ .../VSTestMultiProjectSolution/test2/UnitTest1.cs | 13 +++++++------ .../VSTestMultiProjectSolution/test3/UnitTest1.cs | 13 +++++++------ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/UnitTest1.cs b/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/UnitTest1.cs index 64c4cdff8f23..ef905ecd958f 100644 --- a/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/UnitTest1.cs +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test1/UnitTest1.cs @@ -1,12 +1,13 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace test1; - -[TestClass] -public class UnitTest1 +namespace test1 { - [TestMethod] - public void TestMethod1() + [TestClass] + public class UnitTest1 { + [TestMethod] + public void TestMethod1() + { + } } } diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/UnitTest1.cs b/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/UnitTest1.cs index 99c8962f675e..64e82189211c 100644 --- a/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/UnitTest1.cs +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test2/UnitTest1.cs @@ -1,12 +1,13 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace test2; - -[TestClass] -public class UnitTest1 +namespace test2 { - [TestMethod] - public void TestMethod1() + [TestClass] + public class UnitTest1 { + [TestMethod] + public void TestMethod1() + { + } } } diff --git a/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/UnitTest1.cs b/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/UnitTest1.cs index f9df77e9ba95..8ae2e4739ef1 100644 --- a/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/UnitTest1.cs +++ b/src/Assets/TestProjects/VSTestMultiProjectSolution/test3/UnitTest1.cs @@ -1,12 +1,13 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace test3; - -[TestClass] -public class UnitTest1 +namespace test3 { - [TestMethod] - public void TestMethod1() + [TestClass] + public class UnitTest1 { + [TestMethod] + public void TestMethod1() + { + } } }