From cadf1570e9345b9151f6063061fc7d8f5674dfda Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Fri, 25 Aug 2023 11:18:12 -0700 Subject: [PATCH 01/23] add runtests --- Directory.Packages.props | 1 + MSBuildSdks.sln | 6 + src/RunTests/.gitignore | 362 ++++++++++++++++++ src/RunTests/ArgumentEscaper.cs | 101 +++++ src/RunTests/Microsoft.Build.RunTests.csproj | 31 ++ src/RunTests/README.md | 30 ++ src/RunTests/RunTestsTask.cs | 347 +++++++++++++++++ .../build/Microsoft.Build.RunTests.props | 7 + .../build/Microsoft.Build.RunTests.targets | 12 + src/RunTests/version.json | 4 + stylecop.json | 3 +- 11 files changed, 902 insertions(+), 2 deletions(-) create mode 100644 src/RunTests/.gitignore create mode 100644 src/RunTests/ArgumentEscaper.cs create mode 100644 src/RunTests/Microsoft.Build.RunTests.csproj create mode 100644 src/RunTests/README.md create mode 100644 src/RunTests/RunTestsTask.cs create mode 100644 src/RunTests/build/Microsoft.Build.RunTests.props create mode 100644 src/RunTests/build/Microsoft.Build.RunTests.targets create mode 100644 src/RunTests/version.json diff --git a/Directory.Packages.props b/Directory.Packages.props index c71c739b..4ab0260b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,6 +8,7 @@ + diff --git a/MSBuildSdks.sln b/MSBuildSdks.sln index 0171088c..b17bfd71 100644 --- a/MSBuildSdks.sln +++ b/MSBuildSdks.sln @@ -82,6 +82,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SampleNoTargets", "SampleNo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.CopyOnWrite", "src\CopyOnWrite\Microsoft.Build.CopyOnWrite.csproj", "{153D1183-2953-4D4D-A5AD-AA2CF99B0DE3}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.RunTests", "src\RunTests\Microsoft.Build.RunTests.csproj", "{B4CA4749-4CDE-499F-8372-C71966C6DB16}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -140,6 +142,10 @@ Global {153D1183-2953-4D4D-A5AD-AA2CF99B0DE3}.Debug|Any CPU.Build.0 = Debug|Any CPU {153D1183-2953-4D4D-A5AD-AA2CF99B0DE3}.Release|Any CPU.ActiveCfg = Release|Any CPU {153D1183-2953-4D4D-A5AD-AA2CF99B0DE3}.Release|Any CPU.Build.0 = Release|Any CPU + {B4CA4749-4CDE-499F-8372-C71966C6DB16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4CA4749-4CDE-499F-8372-C71966C6DB16}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B4CA4749-4CDE-499F-8372-C71966C6DB16}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4CA4749-4CDE-499F-8372-C71966C6DB16}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/RunTests/.gitignore b/src/RunTests/.gitignore new file mode 100644 index 00000000..3a8542dc --- /dev/null +++ b/src/RunTests/.gitignore @@ -0,0 +1,362 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd \ No newline at end of file diff --git a/src/RunTests/ArgumentEscaper.cs b/src/RunTests/ArgumentEscaper.cs new file mode 100644 index 00000000..d63e57e3 --- /dev/null +++ b/src/RunTests/ArgumentEscaper.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Text; + +namespace Microsoft.Build +{ + public static class ArgumentEscaper + { + /// + /// Undo the processing which took place to create string[] args in Main, + /// so that the next process will receive the same string[] args + /// + /// See here for more info: + /// http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx + /// + /// + /// Return original string passed by client + public static string HandleEscapeSequenceInArgForProcessStart(string arg) + { + var sb = new StringBuilder(); + + var needsQuotes = ShouldSurroundWithQuotes(arg); + var isQuoted = needsQuotes || IsSurroundedWithQuotes(arg); + + if (needsQuotes) + { + sb.Append('\"'); + } + + for (int i = 0; i < arg.Length; ++i) + { + var backslashCount = 0; + + // Consume All Backslashes + while (i < arg.Length && arg[i] == '\\') + { + backslashCount++; + i++; + } + + // Escape any backslashes at the end of the arg + // when the argument is also quoted. + // This ensures the outside quote is interpreted as + // an argument delimiter + if (i == arg.Length && isQuoted) + { + sb.Append('\\', 2 * backslashCount); + } + + // At then end of the arg, which isn't quoted, + // just add the backslashes, no need to escape + else if (i == arg.Length) + { + sb.Append('\\', backslashCount); + } + + // Escape any preceding backslashes and the quote + else if (arg[i] == '"') + { + sb.Append('\\', (2 * backslashCount) + 1); + sb.Append('"'); + } + + // Output any consumed backslashes and the character + else + { + sb.Append('\\', backslashCount); + sb.Append(arg[i]); + } + } + + if (needsQuotes) + { + sb.Append('\"'); + } + + return sb.ToString(); + } + + internal static bool ShouldSurroundWithQuotes(string argument) + { + // Don't quote already quoted strings + if (IsSurroundedWithQuotes(argument)) + { + return false; + } + + // Only quote if whitespace exists in the string + return ArgumentContainsWhitespace(argument); + } + + internal static bool IsSurroundedWithQuotes(string argument) + => argument.StartsWith("\"", StringComparison.Ordinal) + && argument.EndsWith("\"", StringComparison.Ordinal); + + internal static bool ArgumentContainsWhitespace(string argument) + => argument.Contains(" ") || argument.Contains("\t") || argument.Contains("\n"); + } +} \ No newline at end of file diff --git a/src/RunTests/Microsoft.Build.RunTests.csproj b/src/RunTests/Microsoft.Build.RunTests.csproj new file mode 100644 index 00000000..b093ae10 --- /dev/null +++ b/src/RunTests/Microsoft.Build.RunTests.csproj @@ -0,0 +1,31 @@ + + + netstandard2.0 + true + Microsoft.Build.RunTests + Runs VS Test Console + true + build\ + true + true + latest + snupkg + + + + + + + + build\ + true + + + build\ + true + + + + + + diff --git a/src/RunTests/README.md b/src/RunTests/README.md new file mode 100644 index 00000000..073ff0e2 --- /dev/null +++ b/src/RunTests/README.md @@ -0,0 +1,30 @@ +# Microsoft.Build.RunTests + +The `Microsoft.Build.RunTests` MSBuild SDK adds support for running tests from MSBuild, similarly to how one would use `dotnet test`. + +## Usage in `Directory.Packages.Props` +In your `Directory.Packages.props`: +```xml + + + + + + + + +``` +This example will include the `Microsoft.Build.RunTests` task for all NuGet-based projects in your repo. + +## Example +To run tests +``` +$env:MSBUILDENSURESTDOUTFORTASKPROCESSES=1 +msbuild /nodereuse:false /t:Test +``` + +To build and run tests +``` +$env:MSBUILDENSURESTDOUTFORTASKPROCESSES=1 +msbuild /nodereuse:false /t:Build;Test +``` \ No newline at end of file diff --git a/src/RunTests/RunTestsTask.cs b/src/RunTests/RunTestsTask.cs new file mode 100644 index 00000000..fb1be566 --- /dev/null +++ b/src/RunTests/RunTestsTask.cs @@ -0,0 +1,347 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Build +{ + public class RunTestsTask : Build.Utilities.Task + { + private const string CodeCoverageString = "Code Coverage"; + + // Allows the execution of the test to be skipped. This is useful when the task is invoked from a target and the condition for running the target is not met or if test caching is enabled. + public bool SkipExecution { get; set; } + + public string TestFileFullPath { get; set; } + + public string VSTestSetting { get; set; } + + public string[] VSTestTestAdapterPath { get; set; } + + public string VSTestFramework { get; set; } + + public string VSTestPlatform { get; set; } + + public string VSTestTestCaseFilter { get; set; } + + public string[] VSTestLogger { get; set; } + + public string VSTestListTests { get; set; } + + public string VSTestDiag { get; set; } + + public string[] VSTestCLIRunSettings { get; set; } + + // Initialized to empty string to allow declaring as non-nullable, the property is marked as + // required so we can ensure that the property is set to non-null before the task is executed. + public string VSTestConsolePath { get; set; } = string.Empty; + + public string VSTestResultsDirectory { get; set; } + + public string VSTestVerbosity { get; set; } + + public string[] VSTestCollect { get; set; } + + public string VSTestBlame { get; set; } + + public string VSTestBlameCrash { get; set; } + + public string VSTestBlameCrashDumpType { get; set; } + + public string VSTestBlameCrashCollectAlways { get; set; } + + public string VSTestBlameHang { get; set; } + + public string VSTestBlameHangDumpType { get; set; } + + public string VSTestBlameHangTimeout { get; set; } + + public string VSTestTraceDataCollectorDirectoryPath { get; set; } + + public string VSTestNoLogo { get; set; } + + public string VSTestArtifactsProcessingMode { get; set; } + + public string VSTestSessionCorrelationId { get; set; } + + public string VSTestRunnerVersion { get; set; } + + public override bool Execute() + { + var traceEnabledValue = Environment.GetEnvironmentVariable("VSTEST_BUILD_TRACE"); + if (SkipExecution) + { + if (!string.IsNullOrEmpty(traceEnabledValue) && traceEnabledValue.Equals("1", StringComparison.Ordinal)) + { + Log.LogMessage("Skipping test execution."); + } + + return true; + } + + var debugEnabled = Environment.GetEnvironmentVariable("VSTEST_BUILD_DEBUG"); + if (!string.IsNullOrEmpty(debugEnabled) && debugEnabled.Equals("1", StringComparison.Ordinal)) + { + Log.LogMessage("Waiting for debugger attach..."); + + var currentProcess = Process.GetCurrentProcess(); + Log.LogMessage($"Process Id: {currentProcess.Id}, Name: {currentProcess.ProcessName}"); + + while (!Debugger.IsAttached) + { + Thread.Sleep(1000); + } + + Debugger.Break(); + } + + // Avoid logging "Task returned false but did not log an error." on test failure, because we don't + // write MSBuild error. https://github.com/dotnet/msbuild/blob/51a1071f8871e0c93afbaf1b2ac2c9e59c7b6491/src/Framework/IBuildEngine7.cs#L12 + var allowfailureWithoutError = BuildEngine.GetType().GetProperty("AllowFailureWithoutError"); + allowfailureWithoutError?.SetValue(BuildEngine, true); + + return ExecuteTest().GetAwaiter().GetResult() != 0 ? false : true; + } + + internal IEnumerable CreateArgument() + { + var allArgs = AddArgs(); + + // VSTestCLIRunSettings should be last argument in allArgs as vstest.console ignore options after "--"(CLIRunSettings option). + AddCliRunSettingsArgs(allArgs); + + return allArgs; + } + + private void AddCliRunSettingsArgs(List allArgs) + { + if (VSTestCLIRunSettings != null && VSTestCLIRunSettings.Length > 0) + { + allArgs.Add("--"); + foreach (var arg in VSTestCLIRunSettings) + { + allArgs.Add(ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(arg)); + } + } + } + + private List AddArgs() + { + var isConsoleLoggerSpecifiedByUser = false; + var isCollectCodeCoverageEnabled = false; + var isRunSettingsEnabled = false; + var allArgs = new List(); + + // TODO log arguments in task + if (!string.IsNullOrEmpty(VSTestSetting)) + { + isRunSettingsEnabled = true; + allArgs.Add("--settings:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(VSTestSetting)); + } + + if (VSTestTestAdapterPath != null && VSTestTestAdapterPath.Length > 0) + { + foreach (var arg in VSTestTestAdapterPath) + { + allArgs.Add("--testAdapterPath:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(arg)); + } + } + + if (!string.IsNullOrEmpty(VSTestFramework)) + { + allArgs.Add("--framework:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(VSTestFramework)); + } + + // vstest.console only support x86 and x64 for argument platform + if (!string.IsNullOrEmpty(VSTestPlatform) && !VSTestPlatform.Contains("AnyCPU")) + { + allArgs.Add("--platform:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(VSTestPlatform)); + } + + if (!string.IsNullOrEmpty(VSTestTestCaseFilter)) + { + allArgs.Add("--testCaseFilter:" + + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(VSTestTestCaseFilter)); + } + + if (VSTestLogger != null && VSTestLogger.Length > 0) + { + foreach (var arg in VSTestLogger) + { + allArgs.Add("--logger:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(arg)); + + if (arg.StartsWith("console", StringComparison.OrdinalIgnoreCase)) + { + isConsoleLoggerSpecifiedByUser = true; + } + } + } + + if (!string.IsNullOrEmpty(VSTestResultsDirectory)) + { + allArgs.Add("--resultsDirectory:" + + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(VSTestResultsDirectory)); + } + + if (!string.IsNullOrEmpty(VSTestListTests)) + { + allArgs.Add("--listTests"); + } + + if (!string.IsNullOrEmpty(VSTestDiag)) + { + allArgs.Add("--Diag:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(VSTestDiag)); + } + + if (string.IsNullOrEmpty(TestFileFullPath)) + { + Log.LogError("Test file path cannot be empty or null."); + } + else + { + allArgs.Add(ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(TestFileFullPath)); + } + + // Console logger was not specified by user, but verbosity was, hence add default console logger with verbosity as specified + if (!string.IsNullOrEmpty(VSTestVerbosity) && !isConsoleLoggerSpecifiedByUser) + { + var normalTestLogging = new List() { "n", "normal", "d", "detailed", "diag", "diagnostic" }; + var quietTestLogging = new List() { "q", "quiet" }; + + string vsTestVerbosity = "minimal"; + if (normalTestLogging.Contains(VSTestVerbosity.ToLowerInvariant())) + { + vsTestVerbosity = "normal"; + } + else if (quietTestLogging.Contains(VSTestVerbosity.ToLowerInvariant())) + { + vsTestVerbosity = "quiet"; + } + + allArgs.Add("--logger:Console;Verbosity=" + vsTestVerbosity); + } + + var blameCrash = !string.IsNullOrEmpty(VSTestBlameCrash); + var blameHang = !string.IsNullOrEmpty(VSTestBlameHang); + if (!string.IsNullOrEmpty(VSTestBlame) || blameCrash || blameHang) + { + var blameArgs = "--Blame"; + + var dumpArgs = new List(); + if (blameCrash || blameHang) + { + if (blameCrash) + { + dumpArgs.Add("CollectDump"); + if (!string.IsNullOrEmpty(VSTestBlameCrashCollectAlways)) + { + dumpArgs.Add($"CollectAlways={string.IsNullOrEmpty(VSTestBlameCrashCollectAlways)}"); + } + + if (!string.IsNullOrEmpty(VSTestBlameCrashDumpType)) + { + dumpArgs.Add($"DumpType={VSTestBlameCrashDumpType}"); + } + } + + if (blameHang) + { + dumpArgs.Add("CollectHangDump"); + + if (!string.IsNullOrEmpty(VSTestBlameHangDumpType)) + { + dumpArgs.Add($"HangDumpType={VSTestBlameHangDumpType}"); + } + + if (!string.IsNullOrEmpty(VSTestBlameHangTimeout)) + { + dumpArgs.Add($"TestTimeout={VSTestBlameHangTimeout}"); + } + } + + if (dumpArgs.Count != 0) + { + blameArgs += $":\"{string.Join(";", dumpArgs)}\""; + } + } + + allArgs.Add(blameArgs); + } + + if (VSTestCollect != null && VSTestCollect.Length > 0) + { + foreach (var arg in VSTestCollect) + { + // For collecting code coverage, argument value can be either "Code Coverage" or "Code Coverage;a=b;c=d". + // Split the argument with ';' and compare first token value. + var tokens = arg.Split(';'); + + if (arg.Equals(CodeCoverageString, StringComparison.OrdinalIgnoreCase) || + tokens[0].Equals(CodeCoverageString, StringComparison.OrdinalIgnoreCase)) + { + isCollectCodeCoverageEnabled = true; + } + + allArgs.Add("--collect:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(arg)); + } + } + + if (isCollectCodeCoverageEnabled || isRunSettingsEnabled) + { + // Pass TraceDataCollector path to vstest.console as TestAdapterPath if --collect "Code Coverage" + // or --settings (User can enable code coverage from runsettings) option given. + // Not parsing the runsettings for two reason: + // 1. To keep no knowledge of runsettings structure in VSTestTask. + // 2. Impact of adding adapter path always is minimal. (worst case: loads additional data collector assembly in datacollector process.) + // This is required due to currently trace datacollector not ships with dotnet sdk, can be remove once we have + // go code coverage x-plat. + if (!string.IsNullOrEmpty(VSTestTraceDataCollectorDirectoryPath)) + { + allArgs.Add("--testAdapterPath:" + + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart( + VSTestTraceDataCollectorDirectoryPath)); + } + } + + if (!string.IsNullOrEmpty(VSTestNoLogo)) + { + allArgs.Add("--nologo"); + } + + if (!string.IsNullOrEmpty(VSTestArtifactsProcessingMode) && VSTestArtifactsProcessingMode.Equals("collect", StringComparison.OrdinalIgnoreCase)) + { + allArgs.Add("--artifactsProcessingMode-collect"); + } + + if (!string.IsNullOrEmpty(VSTestSessionCorrelationId)) + { + allArgs.Add("--testSessionCorrelationId:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(VSTestSessionCorrelationId)); + } + + return allArgs; + } + + private Task ExecuteTest() + { + string packagePath = $@"{Environment.GetEnvironmentVariable("NugetPath")}\packages\microsoft.testplatform\{VSTestRunnerVersion}\tools\netstandard2.0\Common7\IDE\Extensions\TestPlatform\"; + + var processInfo = new ProcessStartInfo + { + FileName = $"{packagePath}vstest.console.exe", + Arguments = string.Join(" ", CreateArgument()), + UseShellExecute = false, + }; + + using (var activeProcess = new Process { StartInfo = processInfo }) + { + activeProcess.Start(); + activeProcess.WaitForExit(); + return Task.FromResult(activeProcess.ExitCode); + } + } + } +} \ No newline at end of file diff --git a/src/RunTests/build/Microsoft.Build.RunTests.props b/src/RunTests/build/Microsoft.Build.RunTests.props new file mode 100644 index 00000000..1c19cf44 --- /dev/null +++ b/src/RunTests/build/Microsoft.Build.RunTests.props @@ -0,0 +1,7 @@ + + + $(USERPROFILE)\.nuget + $(NUGET_PACKAGES) + 17.6.3 + + \ No newline at end of file diff --git a/src/RunTests/build/Microsoft.Build.RunTests.targets b/src/RunTests/build/Microsoft.Build.RunTests.targets new file mode 100644 index 00000000..53ac77f2 --- /dev/null +++ b/src/RunTests/build/Microsoft.Build.RunTests.targets @@ -0,0 +1,12 @@ + + + + + + diff --git a/src/RunTests/version.json b/src/RunTests/version.json new file mode 100644 index 00000000..991aada3 --- /dev/null +++ b/src/RunTests/version.json @@ -0,0 +1,4 @@ +{ + "inherit": false, + "version": "1.0" +} diff --git a/stylecop.json b/stylecop.json index 705ef6e0..a75cb5f3 100644 --- a/stylecop.json +++ b/stylecop.json @@ -1,6 +1,5 @@ { - "$schema": - "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", "settings": { "orderingRules": { "usingDirectivesPlacement": "outsideNamespace", From 8a22a79be2e7b1514bddac3914ff1d5b0d719a95 Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Mon, 28 Aug 2023 10:37:00 -0700 Subject: [PATCH 02/23] clean up --- Directory.Packages.props | 4 ++-- src/RunTests/ArgumentEscaper.cs | 5 +++-- src/RunTests/RunTestsTask.cs | 5 +++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 4ab0260b..14c55b7f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,11 +8,11 @@ - - + + diff --git a/src/RunTests/ArgumentEscaper.cs b/src/RunTests/ArgumentEscaper.cs index d63e57e3..baf73465 100644 --- a/src/RunTests/ArgumentEscaper.cs +++ b/src/RunTests/ArgumentEscaper.cs @@ -1,5 +1,6 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// Licensed under the MIT license. using System; using System.Text; diff --git a/src/RunTests/RunTestsTask.cs b/src/RunTests/RunTestsTask.cs index fb1be566..93ad50ab 100644 --- a/src/RunTests/RunTestsTask.cs +++ b/src/RunTests/RunTestsTask.cs @@ -1,5 +1,6 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// Licensed under the MIT license. using System; using System.Collections.Generic; From 10381c35fcd371112944422861e62c4d3436c7b9 Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Mon, 28 Aug 2023 11:53:00 -0700 Subject: [PATCH 03/23] adds comments --- src/RunTests/RunTestsTask.cs | 99 ++++++++++++++++++- .../build/Microsoft.Build.RunTests.targets | 2 +- 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/src/RunTests/RunTestsTask.cs b/src/RunTests/RunTestsTask.cs index 93ad50ab..b4c2db9d 100644 --- a/src/RunTests/RunTestsTask.cs +++ b/src/RunTests/RunTestsTask.cs @@ -15,62 +15,154 @@ public class RunTestsTask : Build.Utilities.Task private const string CodeCoverageString = "Code Coverage"; // Allows the execution of the test to be skipped. This is useful when the task is invoked from a target and the condition for running the target is not met or if test caching is enabled. + + /// + /// Gets or Sets a value indicating whether to Skip Execution. + /// public bool SkipExecution { get; set; } + /// + /// Gets or Sets Full path to the test file. + /// public string TestFileFullPath { get; set; } + /// + /// Gets or Sets Settings for VSTest. + /// public string VSTestSetting { get; set; } + /// + /// Gets or Sets Paths to test adapter DLLs. + /// public string[] VSTestTestAdapterPath { get; set; } + /// + /// Gets or Sets Framework for VSTest. + /// public string VSTestFramework { get; set; } + /// + /// Gets or Sets Platform for VSTest. + /// public string VSTestPlatform { get; set; } + /// + /// Gets or Sets Filter used to select test cases. + /// public string VSTestTestCaseFilter { get; set; } + /// + /// Gets or Sets Logger used for VSTest. + /// public string[] VSTestLogger { get; set; } + /// + /// Gets or Sets Indicates whether to list test cases. + /// public string VSTestListTests { get; set; } + /// + /// Gets or Sets Diagnostic data for VSTest. + /// public string VSTestDiag { get; set; } + /// + /// Gets or Sets Command line options for VSTest. + /// public string[] VSTestCLIRunSettings { get; set; } // Initialized to empty string to allow declaring as non-nullable, the property is marked as // required so we can ensure that the property is set to non-null before the task is executed. + + /// + /// Gets or Sets Path to VSTest console executable. + /// public string VSTestConsolePath { get; set; } = string.Empty; + /// + /// Gets or Sets Directory where VSTest results are saved. + /// public string VSTestResultsDirectory { get; set; } + /// + /// Gets or Sets Verbosity level of VSTest output. + /// public string VSTestVerbosity { get; set; } + /// + /// Gets or Sets Collectors for VSTest run. + /// public string[] VSTestCollect { get; set; } + /// + /// Gets or Sets source blame on test failure. + /// public string VSTestBlame { get; set; } + /// + /// Gets or Sets source blame on test crash. + /// public string VSTestBlameCrash { get; set; } + /// + /// Gets or Sets Dumptype used for crash source blame. + /// public string VSTestBlameCrashDumpType { get; set; } + /// + /// Gets or Sets source blame on test crash even if test pass. + /// public string VSTestBlameCrashCollectAlways { get; set; } + /// + /// Gets or Sets source blame on test hang. + /// public string VSTestBlameHang { get; set; } + /// + /// Gets or Sets Dumptype used for hang source blame. + /// public string VSTestBlameHangDumpType { get; set; } + /// + /// Gets or Sets Time out for hang source blame. + /// public string VSTestBlameHangTimeout { get; set; } + /// + /// Gets or Sets The directory path where trace data collector is. + /// public string VSTestTraceDataCollectorDirectoryPath { get; set; } + /// + /// Gets or Sets disabling Microsoft logo while running test through VSTest. + /// public string VSTestNoLogo { get; set; } + /// + /// Gets or Sets Test artifacts processing mode which is applicable for .NET 5.0 or later versions. + /// public string VSTestArtifactsProcessingMode { get; set; } + /// + /// Gets or Sets Correlation Id of test session. + /// public string VSTestSessionCorrelationId { get; set; } + /// + /// Gets or Sets Runner version of VSTest. + /// public string VSTestRunnerVersion { get; set; } + /// + /// Gets or Sets Path to nuget package cache. + /// + public string NugetPath { get; set; } + + /// + /// Executes the test. Skips execution if specified. + /// + /// Returns true if the test was executed, otherwise false. public override bool Execute() { var traceEnabledValue = Environment.GetEnvironmentVariable("VSTEST_BUILD_TRACE"); @@ -328,8 +420,11 @@ private List AddArgs() private Task ExecuteTest() { - string packagePath = $@"{Environment.GetEnvironmentVariable("NugetPath")}\packages\microsoft.testplatform\{VSTestRunnerVersion}\tools\netstandard2.0\Common7\IDE\Extensions\TestPlatform\"; - +#if NET6_0_OR_GREATER + string packagePath = $@"{NugetPath}\packages\microsoft.testplatform\{VSTestRunnerVersion}\tools\net6.0\Common7\IDE\Extensions\TestPlatform\"; +#else + string packagePath = $@"{NugetPath}\packages\microsoft.testplatform\{VSTestRunnerVersion}\tools\net462\Common7\IDE\Extensions\TestPlatform\"; +#endif var processInfo = new ProcessStartInfo { FileName = $"{packagePath}vstest.console.exe", diff --git a/src/RunTests/build/Microsoft.Build.RunTests.targets b/src/RunTests/build/Microsoft.Build.RunTests.targets index 53ac77f2..c04fc2b5 100644 --- a/src/RunTests/build/Microsoft.Build.RunTests.targets +++ b/src/RunTests/build/Microsoft.Build.RunTests.targets @@ -7,6 +7,6 @@ VSTestBlameCrash="$(VSTestBlameCrash)" VSTestBlameCrashDumpType="$(VSTestBlameCrashDumpType)" VSTestBlameCrashCollectAlways="$(VSTestBlameCrashCollectAlways)" VSTestBlameHang="$(VSTestBlameHang)" VSTestBlameHangDumpType="$(VSTestBlameHangDumpType)" VSTestTraceDataCollectorDirectoryPath="$(VSTestTraceDataCollectorDirectoryPath)" VSTestNoLogo="$(VSTestNoLogo)" VSTestArtifactsProcessingMode="$(VSTestArtifactsProcessingMode)" VSTestSessionCorrelationId="$(VSTestSessionCorrelationId)" - SkipExecution="$(CacheHit)" /> + SkipExecution="$(CacheHit)" NugetPath="$(NugetPath)" /> From 0aea88e3306512ebf85d25329224de6a248d1676 Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Wed, 30 Aug 2023 13:50:48 -0700 Subject: [PATCH 04/23] updates target name --- src/RunTests/RunTestsTask.cs | 2 +- .../build/Microsoft.Build.RunTests.targets | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/RunTests/RunTestsTask.cs b/src/RunTests/RunTestsTask.cs index b4c2db9d..098421fa 100644 --- a/src/RunTests/RunTestsTask.cs +++ b/src/RunTests/RunTestsTask.cs @@ -197,7 +197,7 @@ public override bool Execute() var allowfailureWithoutError = BuildEngine.GetType().GetProperty("AllowFailureWithoutError"); allowfailureWithoutError?.SetValue(BuildEngine, true); - return ExecuteTest().GetAwaiter().GetResult() != 0 ? false : true; + return ExecuteTest().GetAwaiter().GetResult() == 0; } internal IEnumerable CreateArgument() diff --git a/src/RunTests/build/Microsoft.Build.RunTests.targets b/src/RunTests/build/Microsoft.Build.RunTests.targets index c04fc2b5..b1745ecb 100644 --- a/src/RunTests/build/Microsoft.Build.RunTests.targets +++ b/src/RunTests/build/Microsoft.Build.RunTests.targets @@ -1,12 +1,12 @@ - - - - + + + + From b16e42d248aa49259969e455bb5f53d1615bc714 Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Wed, 6 Sep 2023 10:17:16 -0700 Subject: [PATCH 05/23] update target name --- src/RunTests/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RunTests/README.md b/src/RunTests/README.md index 073ff0e2..15df69eb 100644 --- a/src/RunTests/README.md +++ b/src/RunTests/README.md @@ -20,11 +20,11 @@ This example will include the `Microsoft.Build.RunTests` task for all NuGet-base To run tests ``` $env:MSBUILDENSURESTDOUTFORTASKPROCESSES=1 -msbuild /nodereuse:false /t:Test +msbuild /nodereuse:false /t:MSBuildRunTests ``` To build and run tests ``` $env:MSBUILDENSURESTDOUTFORTASKPROCESSES=1 -msbuild /nodereuse:false /t:Build;Test +msbuild /nodereuse:false /t:Build;MSBuildRunTests ``` \ No newline at end of file From c61e798827ac2719f7ef7e7f23001057f462ec99 Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Thu, 2 Nov 2023 11:38:39 -0700 Subject: [PATCH 06/23] update package name --- MSBuildSdks.sln | 2 +- ...Tests.csproj => Microsoft.Build.RunVSTest.csproj} | 8 +++++--- src/RunTests/README.md | 12 ++++++------ src/RunTests/{RunTestsTask.cs => RunVSTestTask.cs} | 2 +- ...unTests.props => Microsoft.Build.RunVSTest.props} | 0 ...sts.targets => Microsoft.Build.RunVSTest.targets} | 8 ++++---- 6 files changed, 17 insertions(+), 15 deletions(-) rename src/RunTests/{Microsoft.Build.RunTests.csproj => Microsoft.Build.RunVSTest.csproj} (78%) rename src/RunTests/{RunTestsTask.cs => RunVSTestTask.cs} (99%) rename src/RunTests/build/{Microsoft.Build.RunTests.props => Microsoft.Build.RunVSTest.props} (100%) rename src/RunTests/build/{Microsoft.Build.RunTests.targets => Microsoft.Build.RunVSTest.targets} (72%) diff --git a/MSBuildSdks.sln b/MSBuildSdks.sln index b17bfd71..532a491d 100644 --- a/MSBuildSdks.sln +++ b/MSBuildSdks.sln @@ -82,7 +82,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SampleNoTargets", "SampleNo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.CopyOnWrite", "src\CopyOnWrite\Microsoft.Build.CopyOnWrite.csproj", "{153D1183-2953-4D4D-A5AD-AA2CF99B0DE3}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.RunTests", "src\RunTests\Microsoft.Build.RunTests.csproj", "{B4CA4749-4CDE-499F-8372-C71966C6DB16}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Build.RunVSTest", "src\RunTests\Microsoft.Build.RunVSTest.csproj", "{B4CA4749-4CDE-499F-8372-C71966C6DB16}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/RunTests/Microsoft.Build.RunTests.csproj b/src/RunTests/Microsoft.Build.RunVSTest.csproj similarity index 78% rename from src/RunTests/Microsoft.Build.RunTests.csproj rename to src/RunTests/Microsoft.Build.RunVSTest.csproj index b093ae10..19f82b99 100644 --- a/src/RunTests/Microsoft.Build.RunTests.csproj +++ b/src/RunTests/Microsoft.Build.RunVSTest.csproj @@ -2,7 +2,7 @@ netstandard2.0 true - Microsoft.Build.RunTests + Microsoft.Build.RunVSTest Runs VS Test Console true build\ @@ -16,13 +16,15 @@ - + build\ true + Never - + build\ true + Never diff --git a/src/RunTests/README.md b/src/RunTests/README.md index 15df69eb..3d8d3af2 100644 --- a/src/RunTests/README.md +++ b/src/RunTests/README.md @@ -1,6 +1,6 @@ -# Microsoft.Build.RunTests +# Microsoft.Build.RunVSTest -The `Microsoft.Build.RunTests` MSBuild SDK adds support for running tests from MSBuild, similarly to how one would use `dotnet test`. +The `Microsoft.Build.RunVSTest` MSBuild SDK adds support for running tests from MSBuild, similarly to how one would use `dotnet test`. ## Usage in `Directory.Packages.Props` In your `Directory.Packages.props`: @@ -10,21 +10,21 @@ In your `Directory.Packages.props`: - + ``` -This example will include the `Microsoft.Build.RunTests` task for all NuGet-based projects in your repo. +This example will include the `Microsoft.Build.RunVSTest` task for all NuGet-based projects in your repo. ## Example To run tests ``` $env:MSBUILDENSURESTDOUTFORTASKPROCESSES=1 -msbuild /nodereuse:false /t:MSBuildRunTests +msbuild /nodereuse:false /t:VSTest ``` To build and run tests ``` $env:MSBUILDENSURESTDOUTFORTASKPROCESSES=1 -msbuild /nodereuse:false /t:Build;MSBuildRunTests +msbuild /nodereuse:false /t:Build;VSTest ``` \ No newline at end of file diff --git a/src/RunTests/RunTestsTask.cs b/src/RunTests/RunVSTestTask.cs similarity index 99% rename from src/RunTests/RunTestsTask.cs rename to src/RunTests/RunVSTestTask.cs index 098421fa..70e996dd 100644 --- a/src/RunTests/RunTestsTask.cs +++ b/src/RunTests/RunVSTestTask.cs @@ -10,7 +10,7 @@ namespace Microsoft.Build { - public class RunTestsTask : Build.Utilities.Task + public class RunVSTestTask : Build.Utilities.Task { private const string CodeCoverageString = "Code Coverage"; diff --git a/src/RunTests/build/Microsoft.Build.RunTests.props b/src/RunTests/build/Microsoft.Build.RunVSTest.props similarity index 100% rename from src/RunTests/build/Microsoft.Build.RunTests.props rename to src/RunTests/build/Microsoft.Build.RunVSTest.props diff --git a/src/RunTests/build/Microsoft.Build.RunTests.targets b/src/RunTests/build/Microsoft.Build.RunVSTest.targets similarity index 72% rename from src/RunTests/build/Microsoft.Build.RunTests.targets rename to src/RunTests/build/Microsoft.Build.RunVSTest.targets index b1745ecb..60f47ab3 100644 --- a/src/RunTests/build/Microsoft.Build.RunTests.targets +++ b/src/RunTests/build/Microsoft.Build.RunVSTest.targets @@ -1,12 +1,12 @@ - - - + + + SkipExecution="$(SkipExecution)" NugetPath="$(NugetPath)" /> From 25938fa17b9e378dbfd82c4cd8a1aeb486e513c1 Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Thu, 2 Nov 2023 14:04:42 -0700 Subject: [PATCH 07/23] task change --- Directory.Packages.props | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 900225a9..0f656b26 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,7 +11,7 @@ - + @@ -24,7 +24,8 @@ - + + From 480683f25d896b75cc2fdfa13959fa43bc9784b9 Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Tue, 5 Dec 2023 13:07:37 -0800 Subject: [PATCH 08/23] refactoring, readme updates --- Directory.Packages.props | 2 +- src/RunTests/Microsoft.Build.RunVSTest.csproj | 14 +++- src/RunTests/README.md | 50 ++++++++++-- src/RunTests/RunVSTestTask.cs | 77 ++++++++----------- src/RunTests/Sdk/Sdk.props | 15 ++++ src/RunTests/Sdk/Sdk.targets | 35 +++++++++ .../build/Microsoft.Build.RunVSTest.props | 14 +++- .../build/Microsoft.Build.RunVSTest.targets | 40 +++++++--- 8 files changed, 182 insertions(+), 65 deletions(-) create mode 100644 src/RunTests/Sdk/Sdk.props create mode 100644 src/RunTests/Sdk/Sdk.targets diff --git a/Directory.Packages.props b/Directory.Packages.props index 0f656b26..07e8ee0e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,7 +11,7 @@ - + diff --git a/src/RunTests/Microsoft.Build.RunVSTest.csproj b/src/RunTests/Microsoft.Build.RunVSTest.csproj index 19f82b99..c9ce5433 100644 --- a/src/RunTests/Microsoft.Build.RunVSTest.csproj +++ b/src/RunTests/Microsoft.Build.RunVSTest.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.0 true Microsoft.Build.RunVSTest Runs VS Test Console @@ -27,6 +27,18 @@ Never + + + Sdk\ + true + Never + + + Sdk\ + true + Never + + diff --git a/src/RunTests/README.md b/src/RunTests/README.md index 19bbc9f0..bf51aabd 100644 --- a/src/RunTests/README.md +++ b/src/RunTests/README.md @@ -2,7 +2,7 @@ The `Microsoft.Build.RunVSTest` MSBuild SDK adds support for running tests from MSBuild, similarly to how one would use `dotnet test`. -## Usage in `Directory.Packages.Props` +## For projects that cannot use package references such as vcxproj. Usage in `Directory.Packages.Props` In your global.json add the following: ```json { @@ -11,29 +11,65 @@ In your global.json add the following: } } ``` +In your ..vcxproj file +```xml + + + ... + +``` -In your `Directory.Packages.props`: +## For projects that use packages references. In your `Directory.Packages.props`: ```xml +... - + + +``` +in your .csproj file +``` + - + ``` + This example will include the `Microsoft.Build.RunVSTest` task for all NuGet-based projects in your repo. +## Dirs.proj example +Use with traversal project +``` + + + + + + + + +``` + +## Sln +For sln project it is sufficent to simply add the package reference to your test project csproj files +``` + + + + + +``` + ## Example To run tests ``` -$env:=1 -msbuild /nodereuse:false /t:VSTest +msbuild /nodereuse:false /t:Test ``` To build and run tests ``` $env:MSBUILDENSURESTDOUTFORTASKPROCESSES=1 -msbuild /nodereuse:false /t:Build;VSTest +msbuild /nodereuse:false /t:Build;Test ``` \ No newline at end of file diff --git a/src/RunTests/RunVSTestTask.cs b/src/RunTests/RunVSTestTask.cs index 70e996dd..ca1b2167 100644 --- a/src/RunTests/RunVSTestTask.cs +++ b/src/RunTests/RunVSTestTask.cs @@ -2,24 +2,29 @@ // // Licensed under the MIT license. +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Threading; -using System.Threading.Tasks; namespace Microsoft.Build { - public class RunVSTestTask : Build.Utilities.Task + /// + /// Runs tests with vstest. + /// + public class RunVSTestTask : Task { private const string CodeCoverageString = "Code Coverage"; - - // Allows the execution of the test to be skipped. This is useful when the task is invoked from a target and the condition for running the target is not met or if test caching is enabled. + private static readonly HashSet NormalTestLogging = new(new[] { "n", "normal", "d", "detailed", "diag", "diagnostic" }, StringComparer.OrdinalIgnoreCase); + private static readonly HashSet QuietTestLogging = new(new[] { "q", "quiet" }, StringComparer.OrdinalIgnoreCase); /// - /// Gets or Sets a value indicating whether to Skip Execution. + /// Gets or Sets Full path to the test file. /// - public bool SkipExecution { get; set; } + public string IsTestProject { get; set; } /// /// Gets or Sets Full path to the test file. @@ -152,30 +157,21 @@ public class RunVSTestTask : Build.Utilities.Task /// /// Gets or Sets Runner version of VSTest. /// + [Required] public string VSTestRunnerVersion { get; set; } /// /// Gets or Sets Path to nuget package cache. /// + [Required] public string NugetPath { get; set; } /// - /// Executes the test. Skips execution if specified. + /// Executes the test. /// /// Returns true if the test was executed, otherwise false. public override bool Execute() { - var traceEnabledValue = Environment.GetEnvironmentVariable("VSTEST_BUILD_TRACE"); - if (SkipExecution) - { - if (!string.IsNullOrEmpty(traceEnabledValue) && traceEnabledValue.Equals("1", StringComparison.Ordinal)) - { - Log.LogMessage("Skipping test execution."); - } - - return true; - } - var debugEnabled = Environment.GetEnvironmentVariable("VSTEST_BUILD_DEBUG"); if (!string.IsNullOrEmpty(debugEnabled) && debugEnabled.Equals("1", StringComparison.Ordinal)) { @@ -192,15 +188,10 @@ public override bool Execute() Debugger.Break(); } - // Avoid logging "Task returned false but did not log an error." on test failure, because we don't - // write MSBuild error. https://github.com/dotnet/msbuild/blob/51a1071f8871e0c93afbaf1b2ac2c9e59c7b6491/src/Framework/IBuildEngine7.cs#L12 - var allowfailureWithoutError = BuildEngine.GetType().GetProperty("AllowFailureWithoutError"); - allowfailureWithoutError?.SetValue(BuildEngine, true); - - return ExecuteTest().GetAwaiter().GetResult() == 0; + return ExecuteTest() == 0; } - internal IEnumerable CreateArgument() + internal IEnumerable CreateArguments() { var allArgs = AddArgs(); @@ -302,15 +293,12 @@ private List AddArgs() // Console logger was not specified by user, but verbosity was, hence add default console logger with verbosity as specified if (!string.IsNullOrEmpty(VSTestVerbosity) && !isConsoleLoggerSpecifiedByUser) { - var normalTestLogging = new List() { "n", "normal", "d", "detailed", "diag", "diagnostic" }; - var quietTestLogging = new List() { "q", "quiet" }; - string vsTestVerbosity = "minimal"; - if (normalTestLogging.Contains(VSTestVerbosity.ToLowerInvariant())) + if (NormalTestLogging.Contains(VSTestVerbosity)) { vsTestVerbosity = "normal"; } - else if (quietTestLogging.Contains(VSTestVerbosity.ToLowerInvariant())) + else if (QuietTestLogging.Contains(VSTestVerbosity)) { vsTestVerbosity = "quiet"; } @@ -418,26 +406,29 @@ private List AddArgs() return allArgs; } - private Task ExecuteTest() + private int ExecuteTest() { -#if NET6_0_OR_GREATER - string packagePath = $@"{NugetPath}\packages\microsoft.testplatform\{VSTestRunnerVersion}\tools\net6.0\Common7\IDE\Extensions\TestPlatform\"; -#else - string packagePath = $@"{NugetPath}\packages\microsoft.testplatform\{VSTestRunnerVersion}\tools\net462\Common7\IDE\Extensions\TestPlatform\"; -#endif + string packagePath = $@"{NugetPath}\microsoft.testplatform\{VSTestRunnerVersion}\tools\net462\Common7\IDE\Extensions\TestPlatform\"; + var processInfo = new ProcessStartInfo { FileName = $"{packagePath}vstest.console.exe", - Arguments = string.Join(" ", CreateArgument()), + Arguments = string.Join(" ", CreateArguments()), UseShellExecute = false, + RedirectStandardError = true, + RedirectStandardOutput = true, }; - using (var activeProcess = new Process { StartInfo = processInfo }) - { - activeProcess.Start(); - activeProcess.WaitForExit(); - return Task.FromResult(activeProcess.ExitCode); - } + using var activeProcess = new Process { StartInfo = processInfo }; + activeProcess.Start(); + using StreamReader errReader = activeProcess.StandardError; + _ = Log.LogMessagesFromStream(errReader, MessageImportance.Normal); + + using StreamReader outReader = activeProcess.StandardOutput; + _ = Log.LogMessagesFromStream(outReader, MessageImportance.Normal); + activeProcess.WaitForExit(); + + return activeProcess.ExitCode; } } } \ No newline at end of file diff --git a/src/RunTests/Sdk/Sdk.props b/src/RunTests/Sdk/Sdk.props new file mode 100644 index 00000000..7ed5c0c9 --- /dev/null +++ b/src/RunTests/Sdk/Sdk.props @@ -0,0 +1,15 @@ + + + + + $(NuGetPackageRoot) + $(NUGET_PACKAGES) + false + false + 17.8.0 + + \ No newline at end of file diff --git a/src/RunTests/Sdk/Sdk.targets b/src/RunTests/Sdk/Sdk.targets new file mode 100644 index 00000000..5b37a2db --- /dev/null +++ b/src/RunTests/Sdk/Sdk.targets @@ -0,0 +1,35 @@ + + + + + + + + diff --git a/src/RunTests/build/Microsoft.Build.RunVSTest.props b/src/RunTests/build/Microsoft.Build.RunVSTest.props index b228245d..fd42bc02 100644 --- a/src/RunTests/build/Microsoft.Build.RunVSTest.props +++ b/src/RunTests/build/Microsoft.Build.RunVSTest.props @@ -1,7 +1,13 @@ - + + + - $(USERPROFILE)\.nuget - $(NUGET_PACKAGES) - $(MicrosoftBuildPackageVersion) + $(NuGetPackageRoot) + false + 17.8.0 \ No newline at end of file diff --git a/src/RunTests/build/Microsoft.Build.RunVSTest.targets b/src/RunTests/build/Microsoft.Build.RunVSTest.targets index 60f47ab3..da916eb5 100644 --- a/src/RunTests/build/Microsoft.Build.RunVSTest.targets +++ b/src/RunTests/build/Microsoft.Build.RunVSTest.targets @@ -1,12 +1,34 @@ - + + + - - + + From f24f2928d042edd1c3ad29ce52578fd20aa11459 Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Tue, 5 Dec 2023 13:59:53 -0800 Subject: [PATCH 09/23] update readme with version override instructions --- src/RunTests/README.md | 9 ++++++++- src/RunTests/Sdk/Sdk.props | 13 ++++++------- src/RunTests/build/Microsoft.Build.RunVSTest.props | 11 ++++++----- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/RunTests/README.md b/src/RunTests/README.md index bf51aabd..68af109d 100644 --- a/src/RunTests/README.md +++ b/src/RunTests/README.md @@ -72,4 +72,11 @@ To build and run tests ``` $env:MSBUILDENSURESTDOUTFORTASKPROCESSES=1 msbuild /nodereuse:false /t:Build;Test -``` \ No newline at end of file +``` + +## Microsoft.TestPlatform version +A default version is set for Microsoft.TestPlatform. To use a version other than the one included with this task, +override the VSTestRunnerVersion property. +``` +msbuild /nodereuse:false /t:Test /p:VSTestRunnerVersion=17.7.2 +``` diff --git a/src/RunTests/Sdk/Sdk.props b/src/RunTests/Sdk/Sdk.props index 7ed5c0c9..8e3a7351 100644 --- a/src/RunTests/Sdk/Sdk.props +++ b/src/RunTests/Sdk/Sdk.props @@ -5,11 +5,10 @@ Licensed under the MIT license. --> - - $(NuGetPackageRoot) - $(NUGET_PACKAGES) - false - false - 17.8.0 - + + $(NuGetPackageRoot) + $(NUGET_PACKAGES) + false + 17.8.0 + \ No newline at end of file diff --git a/src/RunTests/build/Microsoft.Build.RunVSTest.props b/src/RunTests/build/Microsoft.Build.RunVSTest.props index fd42bc02..49cd06cc 100644 --- a/src/RunTests/build/Microsoft.Build.RunVSTest.props +++ b/src/RunTests/build/Microsoft.Build.RunVSTest.props @@ -5,9 +5,10 @@ Licensed under the MIT license. --> - - $(NuGetPackageRoot) - false - 17.8.0 - + + $(NuGetPackageRoot) + $(NUGET_PACKAGES) + false + 17.8.0 + \ No newline at end of file From cc217ca93235bad00f9e4595e68c69e67ea8d063 Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Tue, 5 Dec 2023 14:44:47 -0800 Subject: [PATCH 10/23] fix formatting --- Directory.Packages.props | 12 ++--- src/RunTests/Sdk/Sdk.targets | 49 ++++++++++--------- .../build/Microsoft.Build.RunVSTest.targets | 48 +++++++++--------- 3 files changed, 56 insertions(+), 53 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 07e8ee0e..2db15afc 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -19,12 +19,12 @@ - - - - - - + + + + + + diff --git a/src/RunTests/Sdk/Sdk.targets b/src/RunTests/Sdk/Sdk.targets index 5b37a2db..d3138b85 100644 --- a/src/RunTests/Sdk/Sdk.targets +++ b/src/RunTests/Sdk/Sdk.targets @@ -7,29 +7,30 @@ - + diff --git a/src/RunTests/build/Microsoft.Build.RunVSTest.targets b/src/RunTests/build/Microsoft.Build.RunVSTest.targets index da916eb5..1385b9ff 100644 --- a/src/RunTests/build/Microsoft.Build.RunVSTest.targets +++ b/src/RunTests/build/Microsoft.Build.RunVSTest.targets @@ -7,28 +7,30 @@ - + From c156eecb6214aefb1d4a947b0de85c9a9f546fcb Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Tue, 5 Dec 2023 14:57:08 -0800 Subject: [PATCH 11/23] remove redundant lines, update reference --- Directory.Packages.props | 2 -- src/RunTests/Microsoft.Build.RunVSTest.csproj | 2 +- src/RunTests/RunVSTestTask.cs | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 10abdcac..a4ff21e8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -9,8 +9,6 @@ - - diff --git a/src/RunTests/Microsoft.Build.RunVSTest.csproj b/src/RunTests/Microsoft.Build.RunVSTest.csproj index c9ce5433..32bc2528 100644 --- a/src/RunTests/Microsoft.Build.RunVSTest.csproj +++ b/src/RunTests/Microsoft.Build.RunVSTest.csproj @@ -12,7 +12,7 @@ snupkg - + diff --git a/src/RunTests/RunVSTestTask.cs b/src/RunTests/RunVSTestTask.cs index ca1b2167..19a03a3e 100644 --- a/src/RunTests/RunVSTestTask.cs +++ b/src/RunTests/RunVSTestTask.cs @@ -18,8 +18,8 @@ namespace Microsoft.Build public class RunVSTestTask : Task { private const string CodeCoverageString = "Code Coverage"; - private static readonly HashSet NormalTestLogging = new(new[] { "n", "normal", "d", "detailed", "diag", "diagnostic" }, StringComparer.OrdinalIgnoreCase); - private static readonly HashSet QuietTestLogging = new(new[] { "q", "quiet" }, StringComparer.OrdinalIgnoreCase); + private static readonly HashSet NormalTestLogging = new (new[] { "n", "normal", "d", "detailed", "diag", "diagnostic" }, StringComparer.OrdinalIgnoreCase); + private static readonly HashSet QuietTestLogging = new (new[] { "q", "quiet" }, StringComparer.OrdinalIgnoreCase); /// /// Gets or Sets Full path to the test file. From f180ca40132333a66cf79cd11dcc265c3f4dddab Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Wed, 6 Dec 2023 10:04:02 -0800 Subject: [PATCH 12/23] import project --- src/RunTests/Microsoft.Build.RunVSTest.csproj | 13 +-------- src/RunTests/Sdk/Sdk.props | 7 +---- src/RunTests/Sdk/Sdk.targets | 29 +------------------ .../build/Microsoft.Build.RunVSTest.targets | 1 - 4 files changed, 3 insertions(+), 47 deletions(-) diff --git a/src/RunTests/Microsoft.Build.RunVSTest.csproj b/src/RunTests/Microsoft.Build.RunVSTest.csproj index 32bc2528..4a57d3b6 100644 --- a/src/RunTests/Microsoft.Build.RunVSTest.csproj +++ b/src/RunTests/Microsoft.Build.RunVSTest.csproj @@ -27,18 +27,7 @@ Never - - - Sdk\ - true - Never - - - Sdk\ - true - Never - - + diff --git a/src/RunTests/Sdk/Sdk.props b/src/RunTests/Sdk/Sdk.props index 8e3a7351..3a504cff 100644 --- a/src/RunTests/Sdk/Sdk.props +++ b/src/RunTests/Sdk/Sdk.props @@ -5,10 +5,5 @@ Licensed under the MIT license. --> - - $(NuGetPackageRoot) - $(NUGET_PACKAGES) - false - 17.8.0 - + \ No newline at end of file diff --git a/src/RunTests/Sdk/Sdk.targets b/src/RunTests/Sdk/Sdk.targets index d3138b85..53de4696 100644 --- a/src/RunTests/Sdk/Sdk.targets +++ b/src/RunTests/Sdk/Sdk.targets @@ -5,32 +5,5 @@ Licensed under the MIT license. --> - - - - + diff --git a/src/RunTests/build/Microsoft.Build.RunVSTest.targets b/src/RunTests/build/Microsoft.Build.RunVSTest.targets index 1385b9ff..09c8d2c8 100644 --- a/src/RunTests/build/Microsoft.Build.RunVSTest.targets +++ b/src/RunTests/build/Microsoft.Build.RunVSTest.targets @@ -30,7 +30,6 @@ VSTestArtifactsProcessingMode="$(VSTestArtifactsProcessingMode)" VSTestSessionCorrelationId="$(VSTestSessionCorrelationId)" NugetPath="$(NugetPath)" - IsTestProject="$(IsTestProject)" /> From 8c3a6c96c2ab5e09f1015dc674fb97af188d9817 Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Tue, 2 Jan 2024 10:22:55 -0800 Subject: [PATCH 13/23] switch to tool task --- src/RunTests/README.md | 71 +++++++++++++++--- src/RunTests/RunVSTestTask.cs | 136 ++++++++-------------------------- 2 files changed, 88 insertions(+), 119 deletions(-) diff --git a/src/RunTests/README.md b/src/RunTests/README.md index 19bbc9f0..d6b8cc93 100644 --- a/src/RunTests/README.md +++ b/src/RunTests/README.md @@ -1,16 +1,63 @@ -# Microsoft.Build.RunVSTest +## MSBuild Test Target and Task +See: [MSBuild Test Target](https://github.com/dotnet/msbuild/pull/9193) +### Motivation +The primary motivation of the MSBuild Test Target is to offer a convienent and standardardized way for executing tests within the msbuild environment. This functionality aims to mirror the simplicity of the `dotnet test` command. The proposed command for initiating test within MSBuild would be `msbuild /t:Test` -The `Microsoft.Build.RunVSTest` MSBuild SDK adds support for running tests from MSBuild, similarly to how one would use `dotnet test`. +Another significatnt benefit of integrating this target is to faciliatet the caching of test executions, using MSBuild project caching capabilities. This enhancement will optimize the testing process by reducing test runs which could significantly reduce time spent building and testing, as tests would only execute, (after the initial run) if there are changes to those tests. As an example running with [MemoBuild](https://dev.azure.com/mseng/Domino/_git/MemoBuild) we can cache both build and test executions. Functionally, this means skipping test executions that have been determined to have not changed. +Example usage: +`msbuild /graph /restore:false /m /nr:false /reportfileaccesses /t:"Build;Test"` -## Usage in `Directory.Packages.Props` -In your global.json add the following: -```json -{ - "msbuild-sdks": { - "Microsoft.Build.RunVSTest": "1.0.0" - } -} +### Design Overview +The 'Microsoft.Common.Test.targets' file contains a stub test target. +``` + + + +``` +This target serves a placeholder and entry point for test target implementations. + +#### Conditional Import +* This stub target is conditionally imported, determined by a condition named +`$(UseMSBuildTestInfrastructure)`. +* This condition allows for users to opt-in to this test target, which helps to prevent breaking changes, with respect the the target name, since there are likely 'Test' targets that exist in the wild already. + +#### Extensibility for Test Runners +* Test runner implemenations can hook into the provided stub using the `AfterTargets` property. +* This approach enables different test runners to extend the basic funcionarlity of the test target. + +For instance, an implementation for running VSTest would look like: ``` + + + +``` + +#### Usage Scenario +* Users who wish to utilize this target will set the `$(UseMSBuildTestInfrastructure)` condition in their project file, rsp or via the command line. +* By executing `msbuild /t:Test`, the MSBuild engine will envoke the `Test` taget, which in turn triggers any test runner targets defined to run after it. + +### Default Task Implementation +See: [MSBuild Test Task](https://github.com/microsoft/MSBuildSdks/pull/473) + +#### Nuget package for default implementaion +* The default implementation will be provided through a nuget package +* This package will contain an MSBuild Task deigned to execute `vstest.console.exe` + +#### MSBuild Task Functionality +* The core of this implemenation is an MSBUild task that interfaces with `vstest.console.exe`. +* This task will accept arguments as properties and pass them directly into the command line test runner. + +* This task is functionally equivalent to the implementation of `dotnet test`, modified for use within MSBuild. + +* This ensures consistency in user experience and functionality between `dotnet test` and `msbuild /t:Test`. + +#### Using The Default Implementation +* Users would install the provided Nuget Package to incorporate it into their projects +* Add the package to their GlobalPackageReferences or specific projects +* Once integrated, executing `msbuild /t:Test` would trigger the MSBuild Task, ultimately executing `vstest.console.exe` + +## Usage in `Directory.Packages.Props` + In your `Directory.Packages.props`: ```xml @@ -29,11 +76,11 @@ This example will include the `Microsoft.Build.RunVSTest` task for all NuGet-bas To run tests ``` $env:=1 -msbuild /nodereuse:false /t:VSTest +msbuild /nodereuse:false /t:Test ``` To build and run tests ``` $env:MSBUILDENSURESTDOUTFORTASKPROCESSES=1 -msbuild /nodereuse:false /t:Build;VSTest +msbuild /nodereuse:false /t:Build;Test ``` \ No newline at end of file diff --git a/src/RunTests/RunVSTestTask.cs b/src/RunTests/RunVSTestTask.cs index 70e996dd..99fc6b9c 100644 --- a/src/RunTests/RunVSTestTask.cs +++ b/src/RunTests/RunVSTestTask.cs @@ -2,15 +2,13 @@ // // Licensed under the MIT license. +using Microsoft.Build.Utilities; using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; namespace Microsoft.Build { - public class RunVSTestTask : Build.Utilities.Task + public class RunVSTestTask : ToolTask { private const string CodeCoverageString = "Code Coverage"; @@ -159,71 +157,21 @@ public class RunVSTestTask : Build.Utilities.Task /// public string NugetPath { get; set; } - /// - /// Executes the test. Skips execution if specified. - /// - /// Returns true if the test was executed, otherwise false. - public override bool Execute() - { - var traceEnabledValue = Environment.GetEnvironmentVariable("VSTEST_BUILD_TRACE"); - if (SkipExecution) - { - if (!string.IsNullOrEmpty(traceEnabledValue) && traceEnabledValue.Equals("1", StringComparison.Ordinal)) - { - Log.LogMessage("Skipping test execution."); - } - - return true; - } - - var debugEnabled = Environment.GetEnvironmentVariable("VSTEST_BUILD_DEBUG"); - if (!string.IsNullOrEmpty(debugEnabled) && debugEnabled.Equals("1", StringComparison.Ordinal)) - { - Log.LogMessage("Waiting for debugger attach..."); - - var currentProcess = Process.GetCurrentProcess(); - Log.LogMessage($"Process Id: {currentProcess.Id}, Name: {currentProcess.ProcessName}"); - - while (!Debugger.IsAttached) - { - Thread.Sleep(1000); - } - - Debugger.Break(); - } - - // Avoid logging "Task returned false but did not log an error." on test failure, because we don't - // write MSBuild error. https://github.com/dotnet/msbuild/blob/51a1071f8871e0c93afbaf1b2ac2c9e59c7b6491/src/Framework/IBuildEngine7.cs#L12 - var allowfailureWithoutError = BuildEngine.GetType().GetProperty("AllowFailureWithoutError"); - allowfailureWithoutError?.SetValue(BuildEngine, true); - - return ExecuteTest().GetAwaiter().GetResult() == 0; - } - - internal IEnumerable CreateArgument() + protected override string ToolName { - var allArgs = AddArgs(); - - // VSTestCLIRunSettings should be last argument in allArgs as vstest.console ignore options after "--"(CLIRunSettings option). - AddCliRunSettingsArgs(allArgs); - - return allArgs; - } - - private void AddCliRunSettingsArgs(List allArgs) - { - if (VSTestCLIRunSettings != null && VSTestCLIRunSettings.Length > 0) + get { - allArgs.Add("--"); - foreach (var arg in VSTestCLIRunSettings) - { - allArgs.Add(ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(arg)); - } +#if NET6_0_OR_GREATER + return $@"{NugetPath}\packages\microsoft.testplatform\{VSTestRunnerVersion}\tools\net6.0\Common7\IDE\Extensions\TestPlatform\"; +#else + return $@"{NugetPath}\packages\microsoft.testplatform\{VSTestRunnerVersion}\tools\net462\Common7\IDE\Extensions\TestPlatform\"; +#endif } } - private List AddArgs() + protected override string GenerateFullPathToTool() { + CommandLineBuilder commandLineBuilder = new CommandLineBuilder(); var isConsoleLoggerSpecifiedByUser = false; var isCollectCodeCoverageEnabled = false; var isRunSettingsEnabled = false; @@ -233,39 +181,38 @@ private List AddArgs() if (!string.IsNullOrEmpty(VSTestSetting)) { isRunSettingsEnabled = true; - allArgs.Add("--settings:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(VSTestSetting)); + commandLineBuilder.AppendSwitchIfNotNull("--settings", VSTestSetting); } if (VSTestTestAdapterPath != null && VSTestTestAdapterPath.Length > 0) { foreach (var arg in VSTestTestAdapterPath) { - allArgs.Add("--testAdapterPath:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(arg)); + commandLineBuilder.AppendSwitchIfNotNull("--testAdapterPath:", arg); } } if (!string.IsNullOrEmpty(VSTestFramework)) { - allArgs.Add("--framework:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(VSTestFramework)); + commandLineBuilder.AppendSwitchIfNotNull("--framework:", VSTestFramework); } // vstest.console only support x86 and x64 for argument platform if (!string.IsNullOrEmpty(VSTestPlatform) && !VSTestPlatform.Contains("AnyCPU")) { - allArgs.Add("--platform:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(VSTestPlatform)); + commandLineBuilder.AppendSwitchIfNotNull("--platform:", VSTestPlatform); } if (!string.IsNullOrEmpty(VSTestTestCaseFilter)) { - allArgs.Add("--testCaseFilter:" + - ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(VSTestTestCaseFilter)); + commandLineBuilder.AppendSwitchIfNotNull("--testCaseFilter:", VSTestTestCaseFilter); } if (VSTestLogger != null && VSTestLogger.Length > 0) { foreach (var arg in VSTestLogger) { - allArgs.Add("--logger:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(arg)); + commandLineBuilder.AppendSwitchIfNotNull("--logger:", arg); if (arg.StartsWith("console", StringComparison.OrdinalIgnoreCase)) { @@ -276,18 +223,17 @@ private List AddArgs() if (!string.IsNullOrEmpty(VSTestResultsDirectory)) { - allArgs.Add("--resultsDirectory:" + - ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(VSTestResultsDirectory)); + commandLineBuilder.AppendSwitchIfNotNull("--resultsDirectory:", VSTestResultsDirectory); } if (!string.IsNullOrEmpty(VSTestListTests)) { - allArgs.Add("--listTests"); + commandLineBuilder.AppendSwitchIfNotNull("--listTests", VSTestListTests); } if (!string.IsNullOrEmpty(VSTestDiag)) { - allArgs.Add("--Diag:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(VSTestDiag)); + commandLineBuilder.AppendSwitchIfNotNull("--Diag:", VSTestDiag); } if (string.IsNullOrEmpty(TestFileFullPath)) @@ -296,7 +242,7 @@ private List AddArgs() } else { - allArgs.Add(ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(TestFileFullPath)); + commandLineBuilder.AppendSwitch(TestFileFullPath); } // Console logger was not specified by user, but verbosity was, hence add default console logger with verbosity as specified @@ -315,7 +261,7 @@ private List AddArgs() vsTestVerbosity = "quiet"; } - allArgs.Add("--logger:Console;Verbosity=" + vsTestVerbosity); + commandLineBuilder.AppendSwitchIfNotNull("--logger:Console;Verbosity=", vsTestVerbosity); } var blameCrash = !string.IsNullOrEmpty(VSTestBlameCrash); @@ -362,7 +308,7 @@ private List AddArgs() } } - allArgs.Add(blameArgs); + commandLineBuilder.AppendSwitch(blameArgs); } if (VSTestCollect != null && VSTestCollect.Length > 0) @@ -379,7 +325,7 @@ private List AddArgs() isCollectCodeCoverageEnabled = true; } - allArgs.Add("--collect:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(arg)); + commandLineBuilder.AppendSwitchIfNotNull("--collect:", arg); } } @@ -394,50 +340,26 @@ private List AddArgs() // go code coverage x-plat. if (!string.IsNullOrEmpty(VSTestTraceDataCollectorDirectoryPath)) { - allArgs.Add("--testAdapterPath:" + - ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart( - VSTestTraceDataCollectorDirectoryPath)); + commandLineBuilder.AppendSwitchIfNotNull("--testAdapterPath:", VSTestTraceDataCollectorDirectoryPath); } } if (!string.IsNullOrEmpty(VSTestNoLogo)) { - allArgs.Add("--nologo"); + commandLineBuilder.AppendSwitch("--nologo"); } if (!string.IsNullOrEmpty(VSTestArtifactsProcessingMode) && VSTestArtifactsProcessingMode.Equals("collect", StringComparison.OrdinalIgnoreCase)) { - allArgs.Add("--artifactsProcessingMode-collect"); + commandLineBuilder.AppendSwitch("--artifactsProcessingMode-collect"); } if (!string.IsNullOrEmpty(VSTestSessionCorrelationId)) { - allArgs.Add("--testSessionCorrelationId:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(VSTestSessionCorrelationId)); + commandLineBuilder.AppendSwitchIfNotNull("--testSessionCorrelationId:", VSTestSessionCorrelationId); } - return allArgs; - } - - private Task ExecuteTest() - { -#if NET6_0_OR_GREATER - string packagePath = $@"{NugetPath}\packages\microsoft.testplatform\{VSTestRunnerVersion}\tools\net6.0\Common7\IDE\Extensions\TestPlatform\"; -#else - string packagePath = $@"{NugetPath}\packages\microsoft.testplatform\{VSTestRunnerVersion}\tools\net462\Common7\IDE\Extensions\TestPlatform\"; -#endif - var processInfo = new ProcessStartInfo - { - FileName = $"{packagePath}vstest.console.exe", - Arguments = string.Join(" ", CreateArgument()), - UseShellExecute = false, - }; - - using (var activeProcess = new Process { StartInfo = processInfo }) - { - activeProcess.Start(); - activeProcess.WaitForExit(); - return Task.FromResult(activeProcess.ExitCode); - } + return commandLineBuilder.ToString(); } } } \ No newline at end of file From d5eda0108198c5b20e5d2d0a5da28dad1dac2657 Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Tue, 2 Jan 2024 14:05:15 -0800 Subject: [PATCH 14/23] change to tool task --- src/RunTests/ArgumentEscaper.cs | 102 ---------------------------- src/RunTests/README.md | 73 +------------------- src/RunTests/RunVSTestTask.cs | 114 +++++--------------------------- 3 files changed, 18 insertions(+), 271 deletions(-) delete mode 100644 src/RunTests/ArgumentEscaper.cs diff --git a/src/RunTests/ArgumentEscaper.cs b/src/RunTests/ArgumentEscaper.cs deleted file mode 100644 index baf73465..00000000 --- a/src/RunTests/ArgumentEscaper.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// Licensed under the MIT license. - -using System; -using System.Text; - -namespace Microsoft.Build -{ - public static class ArgumentEscaper - { - /// - /// Undo the processing which took place to create string[] args in Main, - /// so that the next process will receive the same string[] args - /// - /// See here for more info: - /// http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx - /// - /// - /// Return original string passed by client - public static string HandleEscapeSequenceInArgForProcessStart(string arg) - { - var sb = new StringBuilder(); - - var needsQuotes = ShouldSurroundWithQuotes(arg); - var isQuoted = needsQuotes || IsSurroundedWithQuotes(arg); - - if (needsQuotes) - { - sb.Append('\"'); - } - - for (int i = 0; i < arg.Length; ++i) - { - var backslashCount = 0; - - // Consume All Backslashes - while (i < arg.Length && arg[i] == '\\') - { - backslashCount++; - i++; - } - - // Escape any backslashes at the end of the arg - // when the argument is also quoted. - // This ensures the outside quote is interpreted as - // an argument delimiter - if (i == arg.Length && isQuoted) - { - sb.Append('\\', 2 * backslashCount); - } - - // At then end of the arg, which isn't quoted, - // just add the backslashes, no need to escape - else if (i == arg.Length) - { - sb.Append('\\', backslashCount); - } - - // Escape any preceding backslashes and the quote - else if (arg[i] == '"') - { - sb.Append('\\', (2 * backslashCount) + 1); - sb.Append('"'); - } - - // Output any consumed backslashes and the character - else - { - sb.Append('\\', backslashCount); - sb.Append(arg[i]); - } - } - - if (needsQuotes) - { - sb.Append('\"'); - } - - return sb.ToString(); - } - - internal static bool ShouldSurroundWithQuotes(string argument) - { - // Don't quote already quoted strings - if (IsSurroundedWithQuotes(argument)) - { - return false; - } - - // Only quote if whitespace exists in the string - return ArgumentContainsWhitespace(argument); - } - - internal static bool IsSurroundedWithQuotes(string argument) - => argument.StartsWith("\"", StringComparison.Ordinal) - && argument.EndsWith("\"", StringComparison.Ordinal); - - internal static bool ArgumentContainsWhitespace(string argument) - => argument.Contains(" ") || argument.Contains("\t") || argument.Contains("\n"); - } -} \ No newline at end of file diff --git a/src/RunTests/README.md b/src/RunTests/README.md index bc08b96b..c7c3b044 100644 --- a/src/RunTests/README.md +++ b/src/RunTests/README.md @@ -1,65 +1,7 @@ -## MSBuild Test Target and Task -See: [MSBuild Test Target](https://github.com/dotnet/msbuild/pull/9193) -### Motivation -The primary motivation of the MSBuild Test Target is to offer a convienent and standardardized way for executing tests within the msbuild environment. This functionality aims to mirror the simplicity of the `dotnet test` command. The proposed command for initiating test within MSBuild would be `msbuild /t:Test` +# Microsoft.Build.RunVSTest -Another significatnt benefit of integrating this target is to faciliatet the caching of test executions, using MSBuild project caching capabilities. This enhancement will optimize the testing process by reducing test runs which could significantly reduce time spent building and testing, as tests would only execute, (after the initial run) if there are changes to those tests. As an example running with [MemoBuild](https://dev.azure.com/mseng/Domino/_git/MemoBuild) we can cache both build and test executions. Functionally, this means skipping test executions that have been determined to have not changed. -Example usage: -`msbuild /graph /restore:false /m /nr:false /reportfileaccesses /t:"Build;Test"` +The `Microsoft.Build.RunVSTest` MSBuild SDK adds support for running tests from MSBuild, similarly to how one would use `dotnet test`. -### Design Overview -The 'Microsoft.Common.Test.targets' file contains a stub test target. -``` - - - -``` -This target serves a placeholder and entry point for test target implementations. - -#### Conditional Import -* This stub target is conditionally imported, determined by a condition named -`$(UseMSBuildTestInfrastructure)`. -* This condition allows for users to opt-in to this test target, which helps to prevent breaking changes, with respect the the target name, since there are likely 'Test' targets that exist in the wild already. - -#### Extensibility for Test Runners -* Test runner implemenations can hook into the provided stub using the `AfterTargets` property. -* This approach enables different test runners to extend the basic funcionarlity of the test target. - -For instance, an implementation for running VSTest would look like: -``` - - - -``` - -#### Usage Scenario -* Users who wish to utilize this target will set the `$(UseMSBuildTestInfrastructure)` condition in their project file, rsp or via the command line. -* By executing `msbuild /t:Test`, the MSBuild engine will envoke the `Test` taget, which in turn triggers any test runner targets defined to run after it. - -### Default Task Implementation -See: [MSBuild Test Task](https://github.com/microsoft/MSBuildSdks/pull/473) - -#### Nuget package for default implementaion -* The default implementation will be provided through a nuget package -* This package will contain an MSBuild Task deigned to execute `vstest.console.exe` - -#### MSBuild Task Functionality -* The core of this implemenation is an MSBUild task that interfaces with `vstest.console.exe`. -* This task will accept arguments as properties and pass them directly into the command line test runner. - -* This task is functionally equivalent to the implementation of `dotnet test`, modified for use within MSBuild. - -* This ensures consistency in user experience and functionality between `dotnet test` and `msbuild /t:Test`. - -#### Using The Default Implementation -* Users would install the provided Nuget Package to incorporate it into their projects -* Add the package to their GlobalPackageReferences or specific projects -* Once integrated, executing `msbuild /t:Test` would trigger the MSBuild Task, ultimately executing `vstest.console.exe` - -<<<<<<< HEAD -## Usage in `Directory.Packages.Props` - -======= ## For projects that cannot use package references such as vcxproj. Usage in `Directory.Packages.Props` In your global.json add the following: ```json @@ -76,7 +18,6 @@ In your ..vcxproj file ... ``` ->>>>>>> f180ca40132333a66cf79cd11dcc265c3f4dddab ## For projects that use packages references. In your `Directory.Packages.props`: ```xml @@ -124,10 +65,6 @@ For sln project it is sufficent to simply add the package reference to your test ## Example To run tests ``` -<<<<<<< HEAD -$env:=1 -======= ->>>>>>> f180ca40132333a66cf79cd11dcc265c3f4dddab msbuild /nodereuse:false /t:Test ``` @@ -135,9 +72,6 @@ To build and run tests ``` $env:MSBUILDENSURESTDOUTFORTASKPROCESSES=1 msbuild /nodereuse:false /t:Build;Test -<<<<<<< HEAD -``` -======= ``` ## Microsoft.TestPlatform version @@ -145,5 +79,4 @@ A default version is set for Microsoft.TestPlatform. To use a version other than override the VSTestRunnerVersion property. ``` msbuild /nodereuse:false /t:Test /p:VSTestRunnerVersion=17.7.2 -``` ->>>>>>> f180ca40132333a66cf79cd11dcc265c3f4dddab +``` \ No newline at end of file diff --git a/src/RunTests/RunVSTestTask.cs b/src/RunTests/RunVSTestTask.cs index 47fe2aec..04b15f84 100644 --- a/src/RunTests/RunVSTestTask.cs +++ b/src/RunTests/RunVSTestTask.cs @@ -2,30 +2,14 @@ // // Licensed under the MIT license. -<<<<<<< HEAD -using Microsoft.Build.Utilities; -using System; -using System.Collections.Generic; - -namespace Microsoft.Build -{ - public class RunVSTestTask : ToolTask -======= using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using System; using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Threading; namespace Microsoft.Build { - /// - /// Runs tests with vstest. - /// - public class RunVSTestTask : Task ->>>>>>> f180ca40132333a66cf79cd11dcc265c3f4dddab + public class RunVSTestTask : ToolTask { private const string CodeCoverageString = "Code Coverage"; private static readonly HashSet NormalTestLogging = new (new[] { "n", "normal", "d", "detailed", "diag", "diagnostic" }, StringComparer.OrdinalIgnoreCase); @@ -176,72 +160,32 @@ public class RunVSTestTask : Task [Required] public string NugetPath { get; set; } -<<<<<<< HEAD - protected override string ToolName + protected override string ToolName => "vstest.console.exe"; + + protected override string GenerateFullPathToTool() { - get - { #if NET6_0_OR_GREATER - return $@"{NugetPath}\packages\microsoft.testplatform\{VSTestRunnerVersion}\tools\net6.0\Common7\IDE\Extensions\TestPlatform\"; + return $@"{NugetPath}microsoft.testplatform\{VSTestRunnerVersion}\tools\net6.0\Common7\IDE\Extensions\TestPlatform\vstest.console.exe"; #else - return $@"{NugetPath}\packages\microsoft.testplatform\{VSTestRunnerVersion}\tools\net462\Common7\IDE\Extensions\TestPlatform\"; + return $@"{NugetPath}microsoft.testplatform\{VSTestRunnerVersion}\tools\net462\Common7\IDE\Extensions\TestPlatform\vstest.console.exe"; #endif -======= - /// - /// Executes the test. - /// - /// Returns true if the test was executed, otherwise false. - public override bool Execute() - { - var debugEnabled = Environment.GetEnvironmentVariable("VSTEST_BUILD_DEBUG"); - if (!string.IsNullOrEmpty(debugEnabled) && debugEnabled.Equals("1", StringComparison.Ordinal)) - { - Log.LogMessage("Waiting for debugger attach..."); - - var currentProcess = Process.GetCurrentProcess(); - Log.LogMessage($"Process Id: {currentProcess.Id}, Name: {currentProcess.ProcessName}"); - - while (!Debugger.IsAttached) - { - Thread.Sleep(1000); - } - - Debugger.Break(); - } - - return ExecuteTest() == 0; } - internal IEnumerable CreateArguments() - { - var allArgs = AddArgs(); + protected override MessageImportance StandardOutputLoggingImportance => MessageImportance.High; - // VSTestCLIRunSettings should be last argument in allArgs as vstest.console ignore options after "--"(CLIRunSettings option). - AddCliRunSettingsArgs(allArgs); + protected override MessageImportance StandardErrorLoggingImportance => MessageImportance.High; - return allArgs; - } - - private void AddCliRunSettingsArgs(List allArgs) + protected override void LogEventsFromTextOutput(string singleLine, MessageImportance messageImportance) { - if (VSTestCLIRunSettings != null && VSTestCLIRunSettings.Length > 0) - { - allArgs.Add("--"); - foreach (var arg in VSTestCLIRunSettings) - { - allArgs.Add(ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(arg)); - } ->>>>>>> f180ca40132333a66cf79cd11dcc265c3f4dddab - } + base.LogEventsFromTextOutput(singleLine, messageImportance); } - protected override string GenerateFullPathToTool() + protected override string GenerateCommandLineCommands() { CommandLineBuilder commandLineBuilder = new CommandLineBuilder(); var isConsoleLoggerSpecifiedByUser = false; var isCollectCodeCoverageEnabled = false; var isRunSettingsEnabled = false; - var allArgs = new List(); // TODO log arguments in task if (!string.IsNullOrEmpty(VSTestSetting)) @@ -308,18 +252,19 @@ protected override string GenerateFullPathToTool() } else { - commandLineBuilder.AppendSwitch(TestFileFullPath); + commandLineBuilder.AppendTextUnquoted(" "); + commandLineBuilder.AppendTextUnquoted(TestFileFullPath); } // Console logger was not specified by user, but verbosity was, hence add default console logger with verbosity as specified if (!string.IsNullOrEmpty(VSTestVerbosity) && !isConsoleLoggerSpecifiedByUser) { string vsTestVerbosity = "minimal"; - if (NormalTestLogging.Contains(VSTestVerbosity)) + if (NormalTestLogging.Contains(VSTestVerbosity.ToLowerInvariant())) { vsTestVerbosity = "normal"; } - else if (QuietTestLogging.Contains(VSTestVerbosity)) + else if (QuietTestLogging.Contains(VSTestVerbosity.ToLowerInvariant())) { vsTestVerbosity = "quiet"; } @@ -422,36 +367,7 @@ protected override string GenerateFullPathToTool() commandLineBuilder.AppendSwitchIfNotNull("--testSessionCorrelationId:", VSTestSessionCorrelationId); } -<<<<<<< HEAD return commandLineBuilder.ToString(); -======= - return allArgs; - } - - private int ExecuteTest() - { - string packagePath = $@"{NugetPath}\microsoft.testplatform\{VSTestRunnerVersion}\tools\net462\Common7\IDE\Extensions\TestPlatform\"; - - var processInfo = new ProcessStartInfo - { - FileName = $"{packagePath}vstest.console.exe", - Arguments = string.Join(" ", CreateArguments()), - UseShellExecute = false, - RedirectStandardError = true, - RedirectStandardOutput = true, - }; - - using var activeProcess = new Process { StartInfo = processInfo }; - activeProcess.Start(); - using StreamReader errReader = activeProcess.StandardError; - _ = Log.LogMessagesFromStream(errReader, MessageImportance.Normal); - - using StreamReader outReader = activeProcess.StandardOutput; - _ = Log.LogMessagesFromStream(outReader, MessageImportance.Normal); - activeProcess.WaitForExit(); - - return activeProcess.ExitCode; ->>>>>>> f180ca40132333a66cf79cd11dcc265c3f4dddab } } } \ No newline at end of file From 5f60f5955d6ef2c1929ad648d159495e05a0c7d4 Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Tue, 2 Jan 2024 14:42:50 -0800 Subject: [PATCH 15/23] update csproj --- src/RunTests/Microsoft.Build.RunVSTest.csproj | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/RunTests/Microsoft.Build.RunVSTest.csproj b/src/RunTests/Microsoft.Build.RunVSTest.csproj index 4a57d3b6..32bc2528 100644 --- a/src/RunTests/Microsoft.Build.RunVSTest.csproj +++ b/src/RunTests/Microsoft.Build.RunVSTest.csproj @@ -27,7 +27,18 @@ Never - + + + Sdk\ + true + Never + + + Sdk\ + true + Never + + From d90a35a97b8f1a6f54930c976a0fefaa78fb70e6 Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Tue, 2 Jan 2024 15:01:36 -0800 Subject: [PATCH 16/23] private assets --- src/RunTests/Microsoft.Build.RunVSTest.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RunTests/Microsoft.Build.RunVSTest.csproj b/src/RunTests/Microsoft.Build.RunVSTest.csproj index 32bc2528..5a5ca6f1 100644 --- a/src/RunTests/Microsoft.Build.RunVSTest.csproj +++ b/src/RunTests/Microsoft.Build.RunVSTest.csproj @@ -12,8 +12,8 @@ snupkg - - + + From b1c487c9860a04f5bfd7351240222d98668c23f5 Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Tue, 2 Jan 2024 15:10:58 -0800 Subject: [PATCH 17/23] refactor --- src/RunTests/RunVSTestTask.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/RunTests/RunVSTestTask.cs b/src/RunTests/RunVSTestTask.cs index af918f97..544b2f9e 100644 --- a/src/RunTests/RunVSTestTask.cs +++ b/src/RunTests/RunVSTestTask.cs @@ -162,24 +162,27 @@ public class RunVSTestTask : ToolTask protected override string ToolName => "vstest.console.exe"; + protected override MessageImportance StandardOutputLoggingImportance => MessageImportance.High; + + protected override MessageImportance StandardErrorLoggingImportance => MessageImportance.High; + + /// protected override string GenerateFullPathToTool() { #if NET6_0_OR_GREATER - return $@"{NugetPath}microsoft.testplatform\{VSTestRunnerVersion}\tools\net6.0\Common7\IDE\Extensions\TestPlatform\vstest.console.exe"; + return $@"{NugetPath}microsoft.testplatform\{VSTestRunnerVersion}\tools\net6.0\Common7\IDE\Extensions\TestPlatform\vstest.console.exe"; #else return $@"{NugetPath}microsoft.testplatform\{VSTestRunnerVersion}\tools\net462\Common7\IDE\Extensions\TestPlatform\vstest.console.exe"; #endif } - protected override MessageImportance StandardOutputLoggingImportance => MessageImportance.High; - - protected override MessageImportance StandardErrorLoggingImportance => MessageImportance.High; - + /// protected override void LogEventsFromTextOutput(string singleLine, MessageImportance messageImportance) { base.LogEventsFromTextOutput(singleLine, messageImportance); } + /// protected override string GenerateCommandLineCommands() { CommandLineBuilder commandLineBuilder = new CommandLineBuilder(); From dce43424d98913563d721f827f241d2dc348b4ff Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Tue, 2 Jan 2024 15:21:43 -0800 Subject: [PATCH 18/23] update readme --- src/RunTests/README.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/RunTests/README.md b/src/RunTests/README.md index 68af109d..79b43b1d 100644 --- a/src/RunTests/README.md +++ b/src/RunTests/README.md @@ -28,9 +28,12 @@ In your ..vcxproj file ``` -in your .csproj file +in your .csproj file opt-in to using 'UseMSBuildTestInfrastructure' and add the package reference to your test project csproj files ``` + + true + @@ -53,9 +56,13 @@ Use with traversal project ``` ## Sln -For sln project it is sufficent to simply add the package reference to your test project csproj files +For sln project it is sufficent to simply opt-in to using 'UseMSBuildTestInfrastructure' and add the package reference to your test project csproj files ``` + + true + +true @@ -65,18 +72,17 @@ For sln project it is sufficent to simply add the package reference to your test ## Example To run tests ``` -msbuild /nodereuse:false /t:Test +msbuild /t:Test ``` To build and run tests ``` -$env:MSBUILDENSURESTDOUTFORTASKPROCESSES=1 -msbuild /nodereuse:false /t:Build;Test +msbuild /t:Build;Test ``` ## Microsoft.TestPlatform version A default version is set for Microsoft.TestPlatform. To use a version other than the one included with this task, override the VSTestRunnerVersion property. ``` -msbuild /nodereuse:false /t:Test /p:VSTestRunnerVersion=17.7.2 +msbuild /t:Test /p:VSTestRunnerVersion=17.7.2 ``` From 262b89eba5e776289b2741d84d7fe9857b967245 Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Fri, 5 Jan 2024 13:00:53 -0800 Subject: [PATCH 19/23] updates --- src/RunTests/README.md | 5 ++--- src/RunTests/build/Microsoft.Build.RunVSTest.props | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/RunTests/README.md b/src/RunTests/README.md index 79b43b1d..77dc6999 100644 --- a/src/RunTests/README.md +++ b/src/RunTests/README.md @@ -59,10 +59,9 @@ Use with traversal project For sln project it is sufficent to simply opt-in to using 'UseMSBuildTestInfrastructure' and add the package reference to your test project csproj files ``` - + true - -true + diff --git a/src/RunTests/build/Microsoft.Build.RunVSTest.props b/src/RunTests/build/Microsoft.Build.RunVSTest.props index 49cd06cc..bd0fb14b 100644 --- a/src/RunTests/build/Microsoft.Build.RunVSTest.props +++ b/src/RunTests/build/Microsoft.Build.RunVSTest.props @@ -10,5 +10,6 @@ $(NUGET_PACKAGES) false 17.8.0 + true \ No newline at end of file From 3e42f42dc1169b73eb5ca13362c6631dee006e9d Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Mon, 5 Feb 2024 13:14:09 -0800 Subject: [PATCH 20/23] use package target --- Directory.Packages.props | 2 + src/RunTests/Microsoft.Build.RunVSTest.csproj | 14 +- src/RunTests/README.md | 3 - src/RunTests/RunVSTestTask.cs | 376 ------------------ .../build/Microsoft.Build.RunVSTest.props | 2 + .../build/Microsoft.Build.RunVSTest.targets | 31 +- 6 files changed, 10 insertions(+), 418 deletions(-) delete mode 100644 src/RunTests/RunVSTestTask.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index a4ff21e8..11ba28b5 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -10,6 +10,8 @@ + + diff --git a/src/RunTests/Microsoft.Build.RunVSTest.csproj b/src/RunTests/Microsoft.Build.RunVSTest.csproj index 5a5ca6f1..24d35ff6 100644 --- a/src/RunTests/Microsoft.Build.RunVSTest.csproj +++ b/src/RunTests/Microsoft.Build.RunVSTest.csproj @@ -14,6 +14,8 @@ + + @@ -27,18 +29,6 @@ Never - - - Sdk\ - true - Never - - - Sdk\ - true - Never - - diff --git a/src/RunTests/README.md b/src/RunTests/README.md index 77dc6999..6ce004bf 100644 --- a/src/RunTests/README.md +++ b/src/RunTests/README.md @@ -59,9 +59,6 @@ Use with traversal project For sln project it is sufficent to simply opt-in to using 'UseMSBuildTestInfrastructure' and add the package reference to your test project csproj files ``` - - true - diff --git a/src/RunTests/RunVSTestTask.cs b/src/RunTests/RunVSTestTask.cs deleted file mode 100644 index 544b2f9e..00000000 --- a/src/RunTests/RunVSTestTask.cs +++ /dev/null @@ -1,376 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// Licensed under the MIT license. - -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using System; -using System.Collections.Generic; - -namespace Microsoft.Build -{ - public class RunVSTestTask : ToolTask - { - private const string CodeCoverageString = "Code Coverage"; - private static readonly HashSet NormalTestLogging = new (new[] { "n", "normal", "d", "detailed", "diag", "diagnostic" }, StringComparer.OrdinalIgnoreCase); - private static readonly HashSet QuietTestLogging = new (new[] { "q", "quiet" }, StringComparer.OrdinalIgnoreCase); - - /// - /// Gets or Sets Full path to the test file. - /// - public string IsTestProject { get; set; } - - /// - /// Gets or Sets Full path to the test file. - /// - public string TestFileFullPath { get; set; } - - /// - /// Gets or Sets Settings for VSTest. - /// - public string VSTestSetting { get; set; } - - /// - /// Gets or Sets Paths to test adapter DLLs. - /// - public string[] VSTestTestAdapterPath { get; set; } - - /// - /// Gets or Sets Framework for VSTest. - /// - public string VSTestFramework { get; set; } - - /// - /// Gets or Sets Platform for VSTest. - /// - public string VSTestPlatform { get; set; } - - /// - /// Gets or Sets Filter used to select test cases. - /// - public string VSTestTestCaseFilter { get; set; } - - /// - /// Gets or Sets Logger used for VSTest. - /// - public string[] VSTestLogger { get; set; } - - /// - /// Gets or Sets Indicates whether to list test cases. - /// - public string VSTestListTests { get; set; } - - /// - /// Gets or Sets Diagnostic data for VSTest. - /// - public string VSTestDiag { get; set; } - - /// - /// Gets or Sets Command line options for VSTest. - /// - public string[] VSTestCLIRunSettings { get; set; } - - // Initialized to empty string to allow declaring as non-nullable, the property is marked as - // required so we can ensure that the property is set to non-null before the task is executed. - - /// - /// Gets or Sets Path to VSTest console executable. - /// - public string VSTestConsolePath { get; set; } = string.Empty; - - /// - /// Gets or Sets Directory where VSTest results are saved. - /// - public string VSTestResultsDirectory { get; set; } - - /// - /// Gets or Sets Verbosity level of VSTest output. - /// - public string VSTestVerbosity { get; set; } - - /// - /// Gets or Sets Collectors for VSTest run. - /// - public string[] VSTestCollect { get; set; } - - /// - /// Gets or Sets source blame on test failure. - /// - public string VSTestBlame { get; set; } - - /// - /// Gets or Sets source blame on test crash. - /// - public string VSTestBlameCrash { get; set; } - - /// - /// Gets or Sets Dumptype used for crash source blame. - /// - public string VSTestBlameCrashDumpType { get; set; } - - /// - /// Gets or Sets source blame on test crash even if test pass. - /// - public string VSTestBlameCrashCollectAlways { get; set; } - - /// - /// Gets or Sets source blame on test hang. - /// - public string VSTestBlameHang { get; set; } - - /// - /// Gets or Sets Dumptype used for hang source blame. - /// - public string VSTestBlameHangDumpType { get; set; } - - /// - /// Gets or Sets Time out for hang source blame. - /// - public string VSTestBlameHangTimeout { get; set; } - - /// - /// Gets or Sets The directory path where trace data collector is. - /// - public string VSTestTraceDataCollectorDirectoryPath { get; set; } - - /// - /// Gets or Sets disabling Microsoft logo while running test through VSTest. - /// - public string VSTestNoLogo { get; set; } - - /// - /// Gets or Sets Test artifacts processing mode which is applicable for .NET 5.0 or later versions. - /// - public string VSTestArtifactsProcessingMode { get; set; } - - /// - /// Gets or Sets Correlation Id of test session. - /// - public string VSTestSessionCorrelationId { get; set; } - - /// - /// Gets or Sets Runner version of VSTest. - /// - [Required] - public string VSTestRunnerVersion { get; set; } - - /// - /// Gets or Sets Path to nuget package cache. - /// - [Required] - public string NugetPath { get; set; } - - protected override string ToolName => "vstest.console.exe"; - - protected override MessageImportance StandardOutputLoggingImportance => MessageImportance.High; - - protected override MessageImportance StandardErrorLoggingImportance => MessageImportance.High; - - /// - protected override string GenerateFullPathToTool() - { -#if NET6_0_OR_GREATER - return $@"{NugetPath}microsoft.testplatform\{VSTestRunnerVersion}\tools\net6.0\Common7\IDE\Extensions\TestPlatform\vstest.console.exe"; -#else - return $@"{NugetPath}microsoft.testplatform\{VSTestRunnerVersion}\tools\net462\Common7\IDE\Extensions\TestPlatform\vstest.console.exe"; -#endif - } - - /// - protected override void LogEventsFromTextOutput(string singleLine, MessageImportance messageImportance) - { - base.LogEventsFromTextOutput(singleLine, messageImportance); - } - - /// - protected override string GenerateCommandLineCommands() - { - CommandLineBuilder commandLineBuilder = new CommandLineBuilder(); - var isConsoleLoggerSpecifiedByUser = false; - var isCollectCodeCoverageEnabled = false; - var isRunSettingsEnabled = false; - - // TODO log arguments in task - if (!string.IsNullOrEmpty(VSTestSetting)) - { - isRunSettingsEnabled = true; - commandLineBuilder.AppendSwitchIfNotNull("--settings", VSTestSetting); - } - - if (VSTestTestAdapterPath != null && VSTestTestAdapterPath.Length > 0) - { - foreach (var arg in VSTestTestAdapterPath) - { - commandLineBuilder.AppendSwitchIfNotNull("--testAdapterPath:", arg); - } - } - - if (!string.IsNullOrEmpty(VSTestFramework)) - { - commandLineBuilder.AppendSwitchIfNotNull("--framework:", VSTestFramework); - } - - // vstest.console only support x86 and x64 for argument platform - if (!string.IsNullOrEmpty(VSTestPlatform) && !VSTestPlatform.Contains("AnyCPU")) - { - commandLineBuilder.AppendSwitchIfNotNull("--platform:", VSTestPlatform); - } - - if (!string.IsNullOrEmpty(VSTestTestCaseFilter)) - { - commandLineBuilder.AppendSwitchIfNotNull("--testCaseFilter:", VSTestTestCaseFilter); - } - - if (VSTestLogger != null && VSTestLogger.Length > 0) - { - foreach (var arg in VSTestLogger) - { - commandLineBuilder.AppendSwitchIfNotNull("--logger:", arg); - - if (arg.StartsWith("console", StringComparison.OrdinalIgnoreCase)) - { - isConsoleLoggerSpecifiedByUser = true; - } - } - } - - if (!string.IsNullOrEmpty(VSTestResultsDirectory)) - { - commandLineBuilder.AppendSwitchIfNotNull("--resultsDirectory:", VSTestResultsDirectory); - } - - if (!string.IsNullOrEmpty(VSTestListTests)) - { - commandLineBuilder.AppendSwitchIfNotNull("--listTests", VSTestListTests); - } - - if (!string.IsNullOrEmpty(VSTestDiag)) - { - commandLineBuilder.AppendSwitchIfNotNull("--Diag:", VSTestDiag); - } - - if (string.IsNullOrEmpty(TestFileFullPath)) - { - Log.LogError("Test file path cannot be empty or null."); - } - else - { - commandLineBuilder.AppendTextUnquoted(" "); - commandLineBuilder.AppendTextUnquoted(TestFileFullPath); - } - - // Console logger was not specified by user, but verbosity was, hence add default console logger with verbosity as specified - if (!string.IsNullOrEmpty(VSTestVerbosity) && !isConsoleLoggerSpecifiedByUser) - { - string vsTestVerbosity = "minimal"; - if (NormalTestLogging.Contains(VSTestVerbosity)) - { - vsTestVerbosity = "normal"; - } - else if (QuietTestLogging.Contains(VSTestVerbosity)) - { - vsTestVerbosity = "quiet"; - } - - commandLineBuilder.AppendSwitchIfNotNull("--logger:Console;Verbosity=", vsTestVerbosity); - } - - var blameCrash = !string.IsNullOrEmpty(VSTestBlameCrash); - var blameHang = !string.IsNullOrEmpty(VSTestBlameHang); - if (!string.IsNullOrEmpty(VSTestBlame) || blameCrash || blameHang) - { - var blameArgs = "--Blame"; - - var dumpArgs = new List(); - if (blameCrash || blameHang) - { - if (blameCrash) - { - dumpArgs.Add("CollectDump"); - if (!string.IsNullOrEmpty(VSTestBlameCrashCollectAlways)) - { - dumpArgs.Add($"CollectAlways={string.IsNullOrEmpty(VSTestBlameCrashCollectAlways)}"); - } - - if (!string.IsNullOrEmpty(VSTestBlameCrashDumpType)) - { - dumpArgs.Add($"DumpType={VSTestBlameCrashDumpType}"); - } - } - - if (blameHang) - { - dumpArgs.Add("CollectHangDump"); - - if (!string.IsNullOrEmpty(VSTestBlameHangDumpType)) - { - dumpArgs.Add($"HangDumpType={VSTestBlameHangDumpType}"); - } - - if (!string.IsNullOrEmpty(VSTestBlameHangTimeout)) - { - dumpArgs.Add($"TestTimeout={VSTestBlameHangTimeout}"); - } - } - - if (dumpArgs.Count != 0) - { - blameArgs += $":\"{string.Join(";", dumpArgs)}\""; - } - } - - commandLineBuilder.AppendSwitch(blameArgs); - } - - if (VSTestCollect != null && VSTestCollect.Length > 0) - { - foreach (var arg in VSTestCollect) - { - // For collecting code coverage, argument value can be either "Code Coverage" or "Code Coverage;a=b;c=d". - // Split the argument with ';' and compare first token value. - var tokens = arg.Split(';'); - - if (arg.Equals(CodeCoverageString, StringComparison.OrdinalIgnoreCase) || - tokens[0].Equals(CodeCoverageString, StringComparison.OrdinalIgnoreCase)) - { - isCollectCodeCoverageEnabled = true; - } - - commandLineBuilder.AppendSwitchIfNotNull("--collect:", arg); - } - } - - if (isCollectCodeCoverageEnabled || isRunSettingsEnabled) - { - // Pass TraceDataCollector path to vstest.console as TestAdapterPath if --collect "Code Coverage" - // or --settings (User can enable code coverage from runsettings) option given. - // Not parsing the runsettings for two reason: - // 1. To keep no knowledge of runsettings structure in VSTestTask. - // 2. Impact of adding adapter path always is minimal. (worst case: loads additional data collector assembly in datacollector process.) - // This is required due to currently trace datacollector not ships with dotnet sdk, can be remove once we have - // go code coverage x-plat. - if (!string.IsNullOrEmpty(VSTestTraceDataCollectorDirectoryPath)) - { - commandLineBuilder.AppendSwitchIfNotNull("--testAdapterPath:", VSTestTraceDataCollectorDirectoryPath); - } - } - - if (!string.IsNullOrEmpty(VSTestNoLogo)) - { - commandLineBuilder.AppendSwitch("--nologo"); - } - - if (!string.IsNullOrEmpty(VSTestArtifactsProcessingMode) && VSTestArtifactsProcessingMode.Equals("collect", StringComparison.OrdinalIgnoreCase)) - { - commandLineBuilder.AppendSwitch("--artifactsProcessingMode-collect"); - } - - if (!string.IsNullOrEmpty(VSTestSessionCorrelationId)) - { - commandLineBuilder.AppendSwitchIfNotNull("--testSessionCorrelationId:", VSTestSessionCorrelationId); - } - - return commandLineBuilder.ToString(); - } - } -} \ No newline at end of file diff --git a/src/RunTests/build/Microsoft.Build.RunVSTest.props b/src/RunTests/build/Microsoft.Build.RunVSTest.props index bd0fb14b..a34e3fa4 100644 --- a/src/RunTests/build/Microsoft.Build.RunVSTest.props +++ b/src/RunTests/build/Microsoft.Build.RunVSTest.props @@ -11,5 +11,7 @@ false 17.8.0 true + $(NugetPath)microsoft.testplatform.build\$(VSTestRunnerVersion)\lib\netstandard2.0\Microsoft.TestPlatform.Build.dll + $(NugetPath)microsoft.testplatform.portable\$(VSTestRunnerVersion)\tools\netcoreapp3.1\vstest.console.dll \ No newline at end of file diff --git a/src/RunTests/build/Microsoft.Build.RunVSTest.targets b/src/RunTests/build/Microsoft.Build.RunVSTest.targets index 09c8d2c8..4aa6bde0 100644 --- a/src/RunTests/build/Microsoft.Build.RunVSTest.targets +++ b/src/RunTests/build/Microsoft.Build.RunVSTest.targets @@ -5,31 +5,8 @@ Licensed under the MIT license. --> - - - - + + + + From 1415cecc000e557c5b9090f41181df56afa398e9 Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Thu, 8 Feb 2024 13:20:14 -0800 Subject: [PATCH 21/23] use vs installs vstest for msbuild and existing target for dotnet --- src/RunTests/Microsoft.Build.RunVSTest.csproj | 13 ----- src/RunTests/RunVSTestTask.cs | 6 +- .../build/Microsoft.Build.RunVSTest.targets | 56 ++++++++++--------- 3 files changed, 31 insertions(+), 44 deletions(-) diff --git a/src/RunTests/Microsoft.Build.RunVSTest.csproj b/src/RunTests/Microsoft.Build.RunVSTest.csproj index 5a5ca6f1..4a1b5644 100644 --- a/src/RunTests/Microsoft.Build.RunVSTest.csproj +++ b/src/RunTests/Microsoft.Build.RunVSTest.csproj @@ -13,7 +13,6 @@ - @@ -27,18 +26,6 @@ Never - - - Sdk\ - true - Never - - - Sdk\ - true - Never - - diff --git a/src/RunTests/RunVSTestTask.cs b/src/RunTests/RunVSTestTask.cs index 544b2f9e..19e551c9 100644 --- a/src/RunTests/RunVSTestTask.cs +++ b/src/RunTests/RunVSTestTask.cs @@ -169,11 +169,7 @@ public class RunVSTestTask : ToolTask /// protected override string GenerateFullPathToTool() { -#if NET6_0_OR_GREATER - return $@"{NugetPath}microsoft.testplatform\{VSTestRunnerVersion}\tools\net6.0\Common7\IDE\Extensions\TestPlatform\vstest.console.exe"; -#else - return $@"{NugetPath}microsoft.testplatform\{VSTestRunnerVersion}\tools\net462\Common7\IDE\Extensions\TestPlatform\vstest.console.exe"; -#endif + return $@"{Environment.GetEnvironmentVariable("VSINSTALLDIR")}\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe"; } /// diff --git a/src/RunTests/build/Microsoft.Build.RunVSTest.targets b/src/RunTests/build/Microsoft.Build.RunVSTest.targets index 09c8d2c8..56729ee6 100644 --- a/src/RunTests/build/Microsoft.Build.RunVSTest.targets +++ b/src/RunTests/build/Microsoft.Build.RunVSTest.targets @@ -5,31 +5,35 @@ Licensed under the MIT license. --> - - - + + - + + + + + From bacb2d4778c99fde5fc4acde4035e71afba93113 Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Thu, 8 Feb 2024 13:37:44 -0800 Subject: [PATCH 22/23] fix merge --- src/RunTests/README.md | 13 +------------ src/RunTests/RunVSTestTask.cs | 6 ------ src/RunTests/build/Microsoft.Build.RunVSTest.props | 6 ------ .../build/Microsoft.Build.RunVSTest.targets | 5 ----- 4 files changed, 1 insertion(+), 29 deletions(-) diff --git a/src/RunTests/README.md b/src/RunTests/README.md index 6ce004bf..e3d54d91 100644 --- a/src/RunTests/README.md +++ b/src/RunTests/README.md @@ -28,12 +28,9 @@ In your ..vcxproj file ``` -in your .csproj file opt-in to using 'UseMSBuildTestInfrastructure' and add the package reference to your test project csproj files + ``` - - true - @@ -56,7 +53,6 @@ Use with traversal project ``` ## Sln -For sln project it is sufficent to simply opt-in to using 'UseMSBuildTestInfrastructure' and add the package reference to your test project csproj files ``` @@ -75,10 +71,3 @@ To build and run tests ``` msbuild /t:Build;Test ``` - -## Microsoft.TestPlatform version -A default version is set for Microsoft.TestPlatform. To use a version other than the one included with this task, -override the VSTestRunnerVersion property. -``` -msbuild /t:Test /p:VSTestRunnerVersion=17.7.2 -``` diff --git a/src/RunTests/RunVSTestTask.cs b/src/RunTests/RunVSTestTask.cs index 19e551c9..ca2ee358 100644 --- a/src/RunTests/RunVSTestTask.cs +++ b/src/RunTests/RunVSTestTask.cs @@ -154,12 +154,6 @@ public class RunVSTestTask : ToolTask [Required] public string VSTestRunnerVersion { get; set; } - /// - /// Gets or Sets Path to nuget package cache. - /// - [Required] - public string NugetPath { get; set; } - protected override string ToolName => "vstest.console.exe"; protected override MessageImportance StandardOutputLoggingImportance => MessageImportance.High; diff --git a/src/RunTests/build/Microsoft.Build.RunVSTest.props b/src/RunTests/build/Microsoft.Build.RunVSTest.props index a34e3fa4..9d8b5011 100644 --- a/src/RunTests/build/Microsoft.Build.RunVSTest.props +++ b/src/RunTests/build/Microsoft.Build.RunVSTest.props @@ -6,12 +6,6 @@ --> - $(NuGetPackageRoot) - $(NUGET_PACKAGES) - false - 17.8.0 true - $(NugetPath)microsoft.testplatform.build\$(VSTestRunnerVersion)\lib\netstandard2.0\Microsoft.TestPlatform.Build.dll - $(NugetPath)microsoft.testplatform.portable\$(VSTestRunnerVersion)\tools\netcoreapp3.1\vstest.console.dll \ No newline at end of file diff --git a/src/RunTests/build/Microsoft.Build.RunVSTest.targets b/src/RunTests/build/Microsoft.Build.RunVSTest.targets index f758ec92..50478d56 100644 --- a/src/RunTests/build/Microsoft.Build.RunVSTest.targets +++ b/src/RunTests/build/Microsoft.Build.RunVSTest.targets @@ -5,10 +5,6 @@ Licensed under the MIT license. --> - - - - From 4ed055236767db812486521228ea9ae639084372 Mon Sep 17 00:00:00 2001 From: Cole Carter Date: Thu, 8 Feb 2024 17:00:19 -0800 Subject: [PATCH 23/23] remove unnecessary import --- src/RunTests/build/Microsoft.Build.RunVSTest.targets | 1 - 1 file changed, 1 deletion(-) diff --git a/src/RunTests/build/Microsoft.Build.RunVSTest.targets b/src/RunTests/build/Microsoft.Build.RunVSTest.targets index 50478d56..d66ec22a 100644 --- a/src/RunTests/build/Microsoft.Build.RunVSTest.targets +++ b/src/RunTests/build/Microsoft.Build.RunVSTest.targets @@ -31,7 +31,6 @@ VSTestSessionCorrelationId="$(VSTestSessionCorrelationId)" /> -