diff --git a/src/Microsoft.TestPlatform.Build/ArgumentEscaper.cs b/src/Microsoft.TestPlatform.Build/ArgumentEscaper.cs deleted file mode 100644 index 86178efb95..0000000000 --- a/src/Microsoft.TestPlatform.Build/ArgumentEscaper.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Text; - -namespace Microsoft.TestPlatform.Build.Utils -{ - 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) - { - return argument.StartsWith("\"", StringComparison.Ordinal) && - argument.EndsWith("\"", StringComparison.Ordinal); - } - - internal static bool ArgumentContainsWhitespace(string argument) - { - return argument.Contains(" ") || argument.Contains("\t") || argument.Contains("\n"); - } - } -} diff --git a/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.targets b/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.targets index 8f9d5c4c20..cffc0eca2b 100644 --- a/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.targets +++ b/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.targets @@ -12,10 +12,14 @@ Copyright (c) .NET Foundation. All rights reserved. - Microsoft.TestPlatform.Build.dll - $([System.IO.Path]::Combine($(MSBuildThisFileDirectory),"vstest.console.dll")) + Microsoft.TestPlatform.Build.dll + $([System.IO.Path]::Combine($(MSBuildThisFileDirectory),"vstest.console.dll")) + False + False + <_VSTestMSBuildDependsOn Condition="$(_VSTestMSBuildDependsOn) == '' And !$(VSTestNoBuild)">$(MSBuildProjectDefaultTargets) + - + + + + + + + + + + + + + + - + - @@ -68,37 +118,36 @@ Copyright (c) .NET Foundation. All rights reserved. - + - - - - - - + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + - diff --git a/src/Microsoft.TestPlatform.Build/Tasks/ITestTask.cs b/src/Microsoft.TestPlatform.Build/Tasks/ITestTask.cs new file mode 100644 index 0000000000..adf7791f36 --- /dev/null +++ b/src/Microsoft.TestPlatform.Build/Tasks/ITestTask.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.Build.Tasks +{ + + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + public interface ITestTask: + ITask, + ICancelableTask + { + + ITaskItem TestFileFullPath { get; set; } + string VSTestSetting { get; set; } + ITaskItem[] VSTestTestAdapterPath { get; set; } + string VSTestFramework { get; set; } + string VSTestPlatform { get; set; } + string VSTestTestCaseFilter { get; set; } + string[] VSTestLogger { get; set; } + bool VSTestListTests { get; set; } + string VSTestDiag { get; set; } + string[] VSTestCLIRunSettings { get; set; } + ITaskItem VSTestConsolePath { get; set; } + ITaskItem VSTestResultsDirectory { get; set; } + string VSTestVerbosity { get; set; } + string[] VSTestCollect { get; set; } + bool VSTestBlame { get; set; } + bool VSTestBlameCrash { get; set; } + string VSTestBlameCrashDumpType { get; set; } + bool VSTestBlameCrashCollectAlways { get; set; } + bool VSTestBlameHang { get; set; } + string VSTestBlameHangDumpType { get; set; } + string VSTestBlameHangTimeout { get; set; } + ITaskItem VSTestTraceDataCollectorDirectoryPath { get; set; } + bool VSTestNoLogo { get; set; } + + TaskLoggingHelper Log { get; } + } +} diff --git a/src/Microsoft.TestPlatform.Build/Tasks/TestTaskExtensions.cs b/src/Microsoft.TestPlatform.Build/Tasks/TestTaskExtensions.cs new file mode 100644 index 0000000000..eb2f3169d9 --- /dev/null +++ b/src/Microsoft.TestPlatform.Build/Tasks/TestTaskExtensions.cs @@ -0,0 +1,217 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.Build.Tasks +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.Build.Utilities; + using Microsoft.TestPlatform.Build.Resources; + + public static class TestTaskExtensions + { + + public static string CreateCommandLineArguments(this ITestTask task) + { + var isConsoleLoggerSpecifiedByUser = false; + var isCollectCodeCoverageEnabled = false; + var isRunSettingsEnabled = false; + + var builder = new CommandLineBuilder(); + builder.AppendSwitch("exec"); + if (task.VSTestConsolePath != null && !string.IsNullOrEmpty(task.VSTestConsolePath.ItemSpec)) + { + builder.AppendSwitchIfNotNull("", task.VSTestConsolePath); + } + else + { + builder.AppendSwitch("vstest.console.dll"); + } + + // TODO log arguments in task + if (!string.IsNullOrEmpty(task.VSTestSetting)) + { + isRunSettingsEnabled = true; + builder.AppendSwitchIfNotNull("--settings:", task.VSTestSetting); + } + + if (task.VSTestTestAdapterPath != null && task.VSTestTestAdapterPath.Any()) + { + foreach (var arg in task.VSTestTestAdapterPath) + { + builder.AppendSwitchIfNotNull("--testAdapterPath:", arg); + } + } + + if (!string.IsNullOrEmpty(task.VSTestFramework)) + { + builder.AppendSwitchIfNotNull("--framework:", task.VSTestFramework); + } + + // vstest.console only support x86 and x64 for argument platform + if (!string.IsNullOrEmpty(task.VSTestPlatform) && !task.VSTestPlatform.Contains("AnyCPU")) + { + builder.AppendSwitchIfNotNull("--platform:", task.VSTestPlatform); + } + + if (!string.IsNullOrEmpty(task.VSTestTestCaseFilter)) + { + builder.AppendSwitchIfNotNull("--testCaseFilter:", task.VSTestTestCaseFilter); + } + + if (task.VSTestLogger != null && task.VSTestLogger.Any()) + { + foreach (var arg in task.VSTestLogger) + { + builder.AppendSwitchIfNotNull("--logger:", arg); + + if (arg.StartsWith("console", StringComparison.OrdinalIgnoreCase)) + { + isConsoleLoggerSpecifiedByUser = true; + } + } + } + + if (task.VSTestResultsDirectory != null && !string.IsNullOrEmpty(task.VSTestResultsDirectory.ItemSpec)) + { + builder.AppendSwitchIfNotNull("--resultsDirectory:", task.VSTestResultsDirectory); + } + + if (task.VSTestListTests) + { + builder.AppendSwitch("--listTests"); + } + + if (!string.IsNullOrEmpty(task.VSTestDiag)) + { + builder.AppendSwitchIfNotNull("--Diag:", task.VSTestDiag); + } + + if (task.TestFileFullPath != null) + { + builder.AppendFileNameIfNotNull(task.TestFileFullPath); + } + + // Console logger was not specified by user, but verbosity was, hence add default console logger with verbosity as specified + if (!string.IsNullOrWhiteSpace(task.VSTestVerbosity) && !isConsoleLoggerSpecifiedByUser) + { + var normalTestLogging = new List() { "n", "normal", "d", "detailed", "diag", "diagnostic" }; + var quietTestLogging = new List() { "q", "quiet" }; + + string vsTestVerbosity = "minimal"; + if (normalTestLogging.Contains(task.VSTestVerbosity, StringComparer.InvariantCultureIgnoreCase)) + { + vsTestVerbosity = "normal"; + } + else if (quietTestLogging.Contains(task.VSTestVerbosity, StringComparer.InvariantCultureIgnoreCase)) + { + vsTestVerbosity = "quiet"; + } + + builder.AppendSwitchUnquotedIfNotNull("--logger:", $"Console;Verbosity={vsTestVerbosity}"); + } + + if (task.VSTestBlame) + { + var dumpArgs = new List(); + if (task.VSTestBlameCrash || task.VSTestBlameHang) + { + if (task.VSTestBlameCrash) + { + dumpArgs.Add("CollectDump"); + + if (task.VSTestBlameCrashCollectAlways) + { + dumpArgs.Add($"CollectAlways={task.VSTestBlameCrashCollectAlways}"); + } + + if (!string.IsNullOrEmpty(task.VSTestBlameCrashDumpType)) + { + dumpArgs.Add($"DumpType={task.VSTestBlameCrashDumpType}"); + } + } + + if (task.VSTestBlameHang) + { + dumpArgs.Add("CollectHangDump"); + + if (!string.IsNullOrEmpty(task.VSTestBlameHangDumpType)) + { + dumpArgs.Add($"HangDumpType={task.VSTestBlameHangDumpType}"); + } + + if (!string.IsNullOrEmpty(task.VSTestBlameHangTimeout)) + { + dumpArgs.Add($"TestTimeout={task.VSTestBlameHangTimeout}"); + } + } + } + + if (dumpArgs.Any()) + { + builder.AppendSwitchIfNotNull("--Blame:", string.Join(";", dumpArgs)); + } + else + { + builder.AppendSwitch("--Blame"); + } + } + + if (task.VSTestCollect != null && task.VSTestCollect.Any()) + { + foreach (var arg in task.VSTestCollect) + { + if (arg.Equals("Code Coverage", StringComparison.OrdinalIgnoreCase)) + { + isCollectCodeCoverageEnabled = true; + } + + builder.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 (task.VSTestTraceDataCollectorDirectoryPath != null && !string.IsNullOrEmpty(task.VSTestTraceDataCollectorDirectoryPath.ItemSpec)) + { + builder.AppendSwitchIfNotNull("--testAdapterPath:", task.VSTestTraceDataCollectorDirectoryPath); + } + else + { + if (isCollectCodeCoverageEnabled) + { + // Not showing message in runsettings scenario, because we are not sure that code coverage is enabled. + // User might be using older Microsoft.NET.Test.Sdk which don't have CodeCoverage infra. + task.Log.LogWarning(Resources.UpdateTestSdkForCollectingCodeCoverage); + } + } + } + + if (task.VSTestNoLogo) + { + builder.AppendSwitch("--nologo"); + } + + // VSTestCLIRunSettings should be last argument as vstest.console ignore options after "--"(CLIRunSettings option). + if (task.VSTestCLIRunSettings != null && task.VSTestCLIRunSettings.Any()) + { + builder.AppendSwitch("--"); + + foreach (var arg in task.VSTestCLIRunSettings) + { + builder.AppendSwitchIfNotNull(string.Empty, arg); + } + } + + return builder.ToString(); + } + } +} diff --git a/src/Microsoft.TestPlatform.Build/Tasks/VSTestForwardingApp.cs b/src/Microsoft.TestPlatform.Build/Tasks/VSTestForwardingApp.cs deleted file mode 100644 index 0131b222d5..0000000000 --- a/src/Microsoft.TestPlatform.Build/Tasks/VSTestForwardingApp.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.TestPlatform.Build.Tasks -{ - using Microsoft.TestPlatform.Build.Utils; - using System; - using System.Collections.Generic; - using System.Diagnostics; - using Trace; - - public class VSTestForwardingApp - { - private const string hostExe = "dotnet"; - private readonly List allArgs = new List(); - private int activeProcessId; - - public VSTestForwardingApp(string vsTestExePath, IEnumerable argsToForward) - { - this.allArgs.Add("exec"); - - // Ensure that path to vstest.console is whitespace friendly. User may install - // dotnet-cli to any folder containing whitespace (e.g. VS installs to program files). - // Arguments are already whitespace friendly. - this.allArgs.Add(ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(vsTestExePath)); - this.allArgs.AddRange(argsToForward); - } - - public int Execute() - { - var processInfo = new ProcessStartInfo - { - FileName = hostExe, - Arguments = string.Join(" ", this.allArgs), - UseShellExecute = false, - }; - - Tracing.Trace("VSTest: Starting vstest.console..."); - Tracing.Trace("VSTest: Arguments: " + processInfo.FileName + " " + processInfo.Arguments); - - using (var activeProcess = new Process { StartInfo = processInfo }) - { - activeProcess.Start(); - this.activeProcessId = activeProcess.Id; - - activeProcess.WaitForExit(); - Tracing.Trace("VSTest: Exit code: " + activeProcess.ExitCode); - return activeProcess.ExitCode; - } - } - - public void Cancel() - { - try - { - Process.GetProcessById(activeProcessId).Kill(); - } - catch(ArgumentException ex) - { - Tracing.Trace(string.Format("VSTest: Killing process throws ArgumentException with the following message {0}. It may be that process is not running", ex)); - } - } - } -} diff --git a/src/Microsoft.TestPlatform.Build/Tasks/VSTestForwardingTask.cs b/src/Microsoft.TestPlatform.Build/Tasks/VSTestForwardingTask.cs new file mode 100644 index 0000000000..52c3bea16a --- /dev/null +++ b/src/Microsoft.TestPlatform.Build/Tasks/VSTestForwardingTask.cs @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.TestPlatform.Build.Tasks +{ + using System; + using System.Diagnostics; + using System.Threading; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + using Microsoft.TestPlatform.Build.Resources; + using Microsoft.TestPlatform.Build.Trace; + + public class VSTestForwardingTask : Task, ITestTask + { + private const string toolExe = "dotnet"; + private int activeProcessId; + + public ITaskItem TestFileFullPath { get; set; } + public string VSTestSetting { get; set; } + public ITaskItem[] VSTestTestAdapterPath { get; set; } + public string VSTestFramework { get; set; } + public string VSTestPlatform { get; set; } + public string VSTestTestCaseFilter { get; set; } + public string[] VSTestLogger { get; set; } + public bool VSTestListTests { get; set; } + public string VSTestDiag { get; set; } + public string[] VSTestCLIRunSettings { get; set; } + [Required] + public ITaskItem VSTestConsolePath { get; set; } + public ITaskItem VSTestResultsDirectory { get; set; } + public string VSTestVerbosity { get; set; } + public string[] VSTestCollect { get; set; } + public bool VSTestBlame { get; set; } + public bool VSTestBlameCrash { get; set; } + public string VSTestBlameCrashDumpType { get; set; } + public bool VSTestBlameCrashCollectAlways { get; set; } + public bool VSTestBlameHang { get; set; } + public string VSTestBlameHangDumpType { get; set; } + public string VSTestBlameHangTimeout { get; set; } + public ITaskItem VSTestTraceDataCollectorDirectoryPath { get; set; } + public bool VSTestNoLogo { get; set; } + + public override bool Execute() + { + var traceEnabledValue = Environment.GetEnvironmentVariable("VSTEST_BUILD_TRACE"); + Tracing.traceEnabled = !string.IsNullOrEmpty(traceEnabledValue) && traceEnabledValue.Equals("1", StringComparison.OrdinalIgnoreCase); + + var debugEnabled = Environment.GetEnvironmentVariable("VSTEST_BUILD_DEBUG"); + if (!string.IsNullOrEmpty(debugEnabled) && debugEnabled.Equals("1", StringComparison.Ordinal)) + { + Console.WriteLine("Waiting for debugger attach..."); + + var currentProcess = Process.GetCurrentProcess(); + Console.WriteLine(string.Format("Process Id: {0}, Name: {1}", currentProcess.Id, 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); + + var processInfo = new ProcessStartInfo + { + FileName = toolExe, + Arguments = this.CreateCommandLineArguments(), + UseShellExecute = false, + }; + + if (!string.IsNullOrEmpty(this.VSTestFramework)) + { + Console.WriteLine(Resources.TestRunningSummary, this.TestFileFullPath, this.VSTestFramework); + } + + Tracing.Trace("VSTest: Starting vstest.console..."); + Tracing.Trace("VSTest: Arguments: " + processInfo.FileName + " " + processInfo.Arguments); + + using (var activeProcess = new Process { StartInfo = processInfo }) + { + activeProcess.Start(); + this.activeProcessId = activeProcess.Id; + + activeProcess.WaitForExit(); + Tracing.Trace("VSTest: Exit code: " + activeProcess.ExitCode); + return activeProcess.ExitCode == 0; + } + } + public void Cancel() + { + Tracing.Trace("VSTest: Killing the process..."); + try + { + Process.GetProcessById(activeProcessId).Kill(); + } + catch (ArgumentException ex) + { + Tracing.Trace(string.Format("VSTest: Killing process throws ArgumentException with the following message {0}. It may be that process is not running", ex)); + } + } + } +} diff --git a/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask.cs b/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask.cs index 2d151decb3..4f9336ea65 100644 --- a/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask.cs +++ b/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask.cs @@ -4,411 +4,94 @@ namespace Microsoft.TestPlatform.Build.Tasks { using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using System.Threading; + using System.IO; + using System.Runtime.InteropServices; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; - using Microsoft.TestPlatform.Build.Resources; - using Microsoft.TestPlatform.Build.Utils; - using Trace; - public class VSTestTask : Task, ICancelableTask + public class VSTestTask : ToolTask, ITestTask { - // The process which is invoking vstest.console - private VSTestForwardingApp vsTestForwardingApp; - - private const string vsTestAppName = "vstest.console.dll"; - - 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; - } + public ITaskItem TestFileFullPath { get; set; } + public string VSTestSetting { get; set; } + public ITaskItem[] VSTestTestAdapterPath { get; set; } + public string VSTestFramework { get; set; } + public string VSTestPlatform { get; set; } + public string VSTestTestCaseFilter { get; set; } + public string[] VSTestLogger { get; set; } + public bool VSTestListTests { get; set; } + public string VSTestDiag { get; set; } + public string[] VSTestCLIRunSettings { get; set; } [Required] - public string VSTestConsolePath - { - get; - set; - } - - 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 override bool Execute() - { - var traceEnabledValue = Environment.GetEnvironmentVariable("VSTEST_BUILD_TRACE"); - Tracing.traceEnabled = !string.IsNullOrEmpty(traceEnabledValue) && traceEnabledValue.Equals("1", StringComparison.OrdinalIgnoreCase); - - var debugEnabled = Environment.GetEnvironmentVariable("VSTEST_BUILD_DEBUG"); - if (!string.IsNullOrEmpty(debugEnabled) && debugEnabled.Equals("1", StringComparison.Ordinal)) - { - Console.WriteLine("Waiting for debugger attach..."); - - var currentProcess = Process.GetCurrentProcess(); - Console.WriteLine(string.Format("Process Id: {0}, Name: {1}", currentProcess.Id, 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); - - vsTestForwardingApp = new VSTestForwardingApp(this.VSTestConsolePath, this.CreateArgument()); - if (!string.IsNullOrEmpty(this.VSTestFramework)) - { - Console.WriteLine(Resources.TestRunningSummary, this.TestFileFullPath, this.VSTestFramework); + public ITaskItem VSTestConsolePath { get; set; } + public ITaskItem VSTestResultsDirectory { get; set; } + public string VSTestVerbosity { get; set; } + public string[] VSTestCollect { get; set; } + public bool VSTestBlame { get; set; } + public bool VSTestBlameCrash { get; set; } + public string VSTestBlameCrashDumpType { get; set; } + public bool VSTestBlameCrashCollectAlways { get; set; } + public bool VSTestBlameHang { get; set; } + public string VSTestBlameHangDumpType { get; set; } + public string VSTestBlameHangTimeout { get; set; } + public ITaskItem VSTestTraceDataCollectorDirectoryPath { get; set; } + public bool VSTestNoLogo { get; set; } + + protected override string ToolName + { + get + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return "dotnet.exe"; + else + return "dotnet"; } - - return vsTestForwardingApp.Execute() == 0; } - public void Cancel() + public VSTestTask() { - Tracing.Trace("VSTest: Killing the process..."); - vsTestForwardingApp.Cancel(); + this.LogStandardErrorAsError = true; + this.StandardOutputImportance = "Normal"; } - internal IEnumerable CreateArgument() + protected override string GenerateCommandLineCommands() { - var allArgs = this.AddArgs(); - - // VSTestCLIRunSettings should be last argument in allArgs as vstest.console ignore options after "--"(CLIRunSettings option). - this.AddCLIRunSettingsArgs(allArgs); - - return allArgs; + return this.CreateCommandLineArguments(); } - private void AddCLIRunSettingsArgs(List allArgs) + protected override string GenerateFullPathToTool() { - if (this.VSTestCLIRunSettings != null && this.VSTestCLIRunSettings.Length > 0) - { - allArgs.Add("--"); - foreach (var arg in this.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(this.VSTestSetting)) - { - isRunSettingsEnabled = true; - allArgs.Add("--settings:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(this.VSTestSetting)); - } - - if (this.VSTestTestAdapterPath != null && this.VSTestTestAdapterPath.Length > 0) - { - foreach (var arg in this.VSTestTestAdapterPath) - { - allArgs.Add("--testAdapterPath:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(arg)); - } - } - - if (!string.IsNullOrEmpty(this.VSTestFramework)) - { - allArgs.Add("--framework:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(this.VSTestFramework)); - } - - // vstest.console only support x86 and x64 for argument platform - if (!string.IsNullOrEmpty(this.VSTestPlatform) && !this.VSTestPlatform.Contains("AnyCPU")) - { - allArgs.Add("--platform:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(this.VSTestPlatform)); - } - - if (!string.IsNullOrEmpty(this.VSTestTestCaseFilter)) - { - allArgs.Add("--testCaseFilter:" + - ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(this.VSTestTestCaseFilter)); - } - - if (this.VSTestLogger != null && this.VSTestLogger.Length > 0) - { - foreach (var arg in this.VSTestLogger) - { - allArgs.Add("--logger:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(arg)); - - if (arg.StartsWith("console", StringComparison.OrdinalIgnoreCase)) - { - isConsoleLoggerSpecifiedByUser = true; - } - } - } - - if (!string.IsNullOrEmpty(this.VSTestResultsDirectory)) - { - allArgs.Add("--resultsDirectory:" + - ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(this.VSTestResultsDirectory)); - } - - if (!string.IsNullOrEmpty(this.VSTestListTests)) - { - allArgs.Add("--listTests"); - } - - if (!string.IsNullOrEmpty(this.VSTestDiag)) - { - allArgs.Add("--Diag:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(this.VSTestDiag)); - } - - if (string.IsNullOrEmpty(this.TestFileFullPath)) + if (!string.IsNullOrEmpty(ToolPath)) { - this.Log.LogError("Test file path cannot be empty or null."); - } - else - { - allArgs.Add(ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(this.TestFileFullPath)); - } - - // Console logger was not specified by user, but verbosity was, hence add default console logger with verbosity as specified - if (!string.IsNullOrWhiteSpace(this.VSTestVerbosity) && !isConsoleLoggerSpecifiedByUser) - { - var normalTestLogging = new List() { "n", "normal", "d", "detailed", "diag", "diagnostic" }; - var quietTestLogging = new List() { "q", "quiet" }; - - string vsTestVerbosity = "minimal"; - if (normalTestLogging.Contains(this.VSTestVerbosity.ToLowerInvariant())) - { - vsTestVerbosity = "normal"; - } - else if (quietTestLogging.Contains(this.VSTestVerbosity.ToLowerInvariant())) - { - vsTestVerbosity = "quiet"; - } - - allArgs.Add("--logger:Console;Verbosity=" + vsTestVerbosity); + return Path.Combine(Path.GetDirectoryName(Path.GetFullPath(ToolPath)), ToolExe); } - var blameCrash = !string.IsNullOrEmpty(this.VSTestBlameCrash); - var blameHang = !string.IsNullOrEmpty(this.VSTestBlameHang); - if (!string.IsNullOrEmpty(this.VSTestBlame) || blameCrash || blameHang) - { - var blameArgs = "--Blame"; - - var dumpArgs = new List(); - if (blameCrash || blameHang) - { - if (blameCrash) - { - dumpArgs.Add("CollectDump"); - if (!string.IsNullOrEmpty(this.VSTestBlameCrashCollectAlways)) - { - dumpArgs.Add($"CollectAlways={this.VSTestBlameCrashCollectAlways}"); - } - - if (!string.IsNullOrEmpty(this.VSTestBlameCrashDumpType)) - { - dumpArgs.Add($"DumpType={this.VSTestBlameCrashDumpType}"); - } - } - - if (blameHang) - { - dumpArgs.Add("CollectHangDump"); - - if (!string.IsNullOrEmpty(this.VSTestBlameHangDumpType)) - { - dumpArgs.Add($"HangDumpType={this.VSTestBlameHangDumpType}"); - } + //TODO: https://github.com/dotnet/sdk/issues/20 Need to get the dotnet path from MSBuild? - if (!string.IsNullOrEmpty(this.VSTestBlameHangTimeout)) - { - dumpArgs.Add($"TestTimeout={this.VSTestBlameHangTimeout}"); - } - } - - if (dumpArgs.Any()) - { - blameArgs += $":\"{string.Join(";", dumpArgs)}\""; - } - } - - allArgs.Add(blameArgs); - } - - if (this.VSTestCollect != null && this.VSTestCollect.Length > 0) + var dhp = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH"); + if (!string.IsNullOrEmpty(dhp)) { - foreach (var arg in this.VSTestCollect) + var path = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(dhp)), ToolExe); + if (File.Exists(path)) { - if (arg.Equals("Code Coverage", StringComparison.OrdinalIgnoreCase)) - { - isCollectCodeCoverageEnabled = true; - } - - allArgs.Add("--collect:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(arg)); + return path; } } - if (isCollectCodeCoverageEnabled || isRunSettingsEnabled) + if (File.Exists(ToolExe)) { - // 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(this.VSTestTraceDataCollectorDirectoryPath)) - { - allArgs.Add("--testAdapterPath:" + - ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(this - .VSTestTraceDataCollectorDirectoryPath)); - } - else - { - if (isCollectCodeCoverageEnabled) - { - // Not showing message in runsettings scenario, because we are not sure that code coverage is enabled. - // User might be using older Microsoft.NET.Test.Sdk which don't have CodeCoverage infra. - Console.WriteLine(Resources.UpdateTestSdkForCollectingCodeCoverage); - } - } + return Path.GetFullPath(ToolExe); } - if (!string.IsNullOrWhiteSpace(this.VSTestNoLogo)) + var values = Environment.GetEnvironmentVariable("PATH"); + foreach (var p in values.Split(Path.PathSeparator)) { - allArgs.Add("--nologo"); + var fullPath = Path.Combine(p, ToolExe); + if (File.Exists(fullPath)) + return fullPath; } - return allArgs; + return null; } } } diff --git a/test/Microsoft.TestPlatform.Build.UnitTests/ArgumentEscaperTests.cs b/test/Microsoft.TestPlatform.Build.UnitTests/ArgumentEscaperTests.cs deleted file mode 100644 index e71cb67c55..0000000000 --- a/test/Microsoft.TestPlatform.Build.UnitTests/ArgumentEscaperTests.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.TestPlatform.Build.Utils.UnitTests -{ - using Microsoft.VisualStudio.TestTools.UnitTesting; - - [TestClass] - public class ArgumentEscaperTests - { - [TestMethod] - public void EscapeArgForProcessStartShouldAddDoubleQuoteIfThereIsSpace() - { - string stringWithSpace = "Some string"; - - string expected = "\"Some string\""; - string result = ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(stringWithSpace); - - Assert.AreEqual(expected, result); - } - - [TestMethod] - public void EscapeArgForProcessStartShouldAddDoubleQuoteIfThereIsSpaceAtEnd() - { - string stringWithSpaceAtEnd = "Some string "; - - string expected = "\"Some string \""; - string result = ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(stringWithSpaceAtEnd); - - Assert.AreEqual(expected, result); - } - - [TestMethod] - public void EscapeArgForProcessStartShouldHandleForwardSlash() - { - string stringWithForwardSlash = "Some/string"; - - string expected = "Some/string"; - string result = ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(stringWithForwardSlash); - - Assert.AreEqual(expected, result); - } - - [TestMethod] - public void EscapeArgForProcessStartShouldPreserveDoubleQuote() - { - string stringWithDoubleQuote = "Some\"string"; - - string expected = "Some\\\"string"; - string result = ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(stringWithDoubleQuote); - - Assert.AreEqual(expected, result); - } - - [TestMethod] - public void EscapeArgForProcessStartShouldPreserveSingleQuote() - { - string stringWithSingleQuote = "Some'string"; - - string expected = "Some'string"; - string result = ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(stringWithSingleQuote); - - Assert.AreEqual(expected, result); - } - - [TestMethod] - public void EscapeArgForProcessStartShouldPreserveBackSlash() - { - string stringWithBackSlash = @"Some\\string"; - - string expected = "Some\\\\string"; - string result = ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(stringWithBackSlash); - - Assert.AreEqual(expected, result); - } - - [TestMethod] - public void EscapeArgForProcessStartShouldPreserveBackSlashIfStringHasWhiteSpace() - { - string stringWithBackSlash = @"Some string With Space\\"; - - string expected = @"""Some string With Space\\\\"""; - string result = ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(stringWithBackSlash); - - Assert.AreEqual(expected, result); - } - - [TestMethod] - public void ShouldSurroundWithQuotesShouldReturnFalseIfAlreadySurroundWithQuotes() - { - string stringSurroundWithQuotes = "\"some string\""; - - Assert.IsFalse(ArgumentEscaper.ShouldSurroundWithQuotes(stringSurroundWithQuotes)); - } - - [TestMethod] - public void ShouldSurroundWithQuotesShouldReturnFalseIfItIsNotSurroundWithQuotesAndHasNoWhiteSpace() - { - string stringWithoutSpace = "someStringWithNoWhiteSpace"; - - Assert.IsFalse(ArgumentEscaper.ShouldSurroundWithQuotes(stringWithoutSpace)); - } - - [TestMethod] - public void ShouldSurroundWithQuotesShouldReturnTrueIfItIsNotSurroundWithQuotesAndHasWhiteSpace() - { - string stringWithSpace = "some String With WhiteSpace"; - - Assert.IsTrue(ArgumentEscaper.ShouldSurroundWithQuotes(stringWithSpace)); - } - - [TestMethod] - public void IsSurroundedWithQuotesShouldReturnTrueIfStringIsSurrondedByQuotes() - { - string stringSurroundWithQuotes = "\"some string\""; - - Assert.IsTrue(ArgumentEscaper.IsSurroundedWithQuotes(stringSurroundWithQuotes)); - } - - [TestMethod] - public void IsSurroundedWithQuotesShouldReturnFalseIfStringIsNotSurrondedByQuotes() - { - string stringNotSurroundWithQuotes = "some string"; - - Assert.IsFalse(ArgumentEscaper.IsSurroundedWithQuotes(stringNotSurroundWithQuotes)); - } - } -} diff --git a/test/Microsoft.TestPlatform.Build.UnitTests/FakeBuildEngine.cs b/test/Microsoft.TestPlatform.Build.UnitTests/FakeBuildEngine.cs new file mode 100644 index 0000000000..fd94e651c6 --- /dev/null +++ b/test/Microsoft.TestPlatform.Build.UnitTests/FakeBuildEngine.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Build.UnitTests +{ + using System.Collections; + using Microsoft.Build.Framework; + + public class FakeBuildEngine : IBuildEngine + { + public bool ContinueOnError => false; + + public int LineNumberOfTaskNode => 0; + + public int ColumnNumberOfTaskNode => 0; + + public string ProjectFileOfTaskNode => string.Empty; + + public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs) + { + return false; + } + + public void LogCustomEvent(CustomBuildEventArgs e) + { + } + + public void LogErrorEvent(BuildErrorEventArgs e) + { + } + + public void LogMessageEvent(BuildMessageEventArgs e) + { + } + + public void LogWarningEvent(BuildWarningEventArgs e) + { + } + } +} \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.Build.UnitTests/VsTestTaskTests.cs b/test/Microsoft.TestPlatform.Build.UnitTests/VsTestTaskTests.cs index e3ddc98587..d8e7989d4a 100644 --- a/test/Microsoft.TestPlatform.Build.UnitTests/VsTestTaskTests.cs +++ b/test/Microsoft.TestPlatform.Build.UnitTests/VsTestTaskTests.cs @@ -2,9 +2,9 @@ namespace Microsoft.TestPlatform.Build.UnitTests { - using System; - using System.Linq; - + using System.Text.RegularExpressions; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; using Microsoft.TestPlatform.Build.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -17,7 +17,8 @@ public VSTestTaskTests() { this.vsTestTask = new VSTestTask { - TestFileFullPath = @"C:\path\to\test-assembly.dll", + BuildEngine = new FakeBuildEngine(), + TestFileFullPath = new TaskItem(@"C:\path\to\test-assembly.dll"), VSTestFramework = ".NETCoreapp,Version2.0" }; } @@ -32,13 +33,11 @@ public void CreateArgumentShouldAddOneEntryForCLIRunSettings() this.vsTestTask.VSTestCLIRunSettings[0] = arg1; this.vsTestTask.VSTestCLIRunSettings[1] = arg2; - var result = this.vsTestTask.CreateArgument().ToArray(); - - Assert.AreEqual(5, result.Length); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - // First, second and third args would be framework:".NETCoreapp,Version2.0", testfilepath and -- respectively. - Assert.AreEqual($"\"{arg1}\"", result[3]); - Assert.AreEqual($"{arg2}", result[4]); + StringAssert.Contains(commandline, " -- "); + StringAssert.Contains(commandline, $"\"{arg1}\""); + StringAssert.Contains(commandline, $"{arg2}"); } [TestMethod] @@ -47,7 +46,7 @@ public void CreateArgumentShouldAddCLIRunSettingsArgAtEnd() const string codeCoverageOption = "Code Coverage"; this.vsTestTask.VSTestCollect = new string[] { codeCoverageOption }; - this.vsTestTask.VSTestBlame = "Blame"; + this.vsTestTask.VSTestBlame = true; const string arg1 = "RunConfiguration.ResultsDirectory=Path having Space"; const string arg2 = "MSTest.DeploymentEnabled"; @@ -56,24 +55,22 @@ public void CreateArgumentShouldAddCLIRunSettingsArgAtEnd() this.vsTestTask.VSTestCLIRunSettings[0] = arg1; this.vsTestTask.VSTestCLIRunSettings[1] = arg2; - var result = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.AreEqual(7, result.Length); - - // Following are expected --framework:".NETCoreapp,Version2.0", testfilepath, blame, collect:"Code coverage" -- respectively. - Assert.AreEqual($"\"{arg1}\"", result[5]); - Assert.AreEqual($"{arg2}", result[6]); + StringAssert.Contains(commandline, " -- "); + StringAssert.Contains(commandline, $"\"{arg1}\""); + StringAssert.Contains(commandline, $"{arg2}"); } [TestMethod] public void CreateArgumentShouldPassResultsDirectoryCorrectly() { const string resultsDirectoryValue = @"C:\tmp\Results Directory"; - this.vsTestTask.VSTestResultsDirectory = resultsDirectoryValue; + this.vsTestTask.VSTestResultsDirectory = new TaskItem(resultsDirectoryValue); - var result = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.AreEqual($"--resultsDirectory:\"{resultsDirectoryValue}\"", result[1]); + StringAssert.Contains(commandline, $"--resultsDirectory:\"{this.vsTestTask.VSTestResultsDirectory.ItemSpec}\""); } [TestMethod] @@ -82,10 +79,10 @@ public void CreateArgumentShouldNotSetConsoleLoggerVerbosityIfConsoleLoggerIsGiv this.vsTestTask.VSTestVerbosity = "diag"; this.vsTestTask.VSTestLogger = new string[] { "Console;Verbosity=quiet" }; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNull(Array.Find(allArguments, arg => arg.Contains("--logger:Console;Verbosity=normal"))); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--logger:Console;Verbosity=quiet"))); + StringAssert.DoesNotMatch(commandline, new Regex("(--logger:\"Console;Verbosity=normal\")")); + StringAssert.Contains(commandline, "--logger:\"Console;Verbosity=quiet\""); } [TestMethod] @@ -93,9 +90,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger { this.vsTestTask.VSTestVerbosity = "n"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--logger:Console;Verbosity=normal"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); } [TestMethod] @@ -103,9 +100,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger { this.vsTestTask.VSTestVerbosity = "normal"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--logger:Console;Verbosity=normal"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); } [TestMethod] @@ -113,9 +110,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger { this.vsTestTask.VSTestVerbosity = "d"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--logger:Console;Verbosity=normal"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); } [TestMethod] @@ -123,9 +120,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger { this.vsTestTask.VSTestVerbosity = "detailed"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--logger:Console;Verbosity=normal"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); } [TestMethod] @@ -133,9 +130,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger { this.vsTestTask.VSTestVerbosity = "diag"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--logger:Console;Verbosity=normal"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); } [TestMethod] @@ -143,9 +140,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger { this.vsTestTask.VSTestVerbosity = "diagnostic"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--logger:Console;Verbosity=normal"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); } [TestMethod] @@ -153,9 +150,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToQuietIfConsoleLoggerI { this.vsTestTask.VSTestVerbosity = "q"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--logger:Console;Verbosity=quiet"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=quiet"); } [TestMethod] @@ -163,9 +160,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToQuietIfConsoleLoggerI { this.vsTestTask.VSTestVerbosity = "quiet"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--logger:Console;Verbosity=quiet"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=quiet"); } [TestMethod] @@ -173,9 +170,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToMinimalIfConsoleLogge { this.vsTestTask.VSTestVerbosity = "m"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--logger:Console;Verbosity=minimal"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=minimal"); } [TestMethod] @@ -183,9 +180,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToMinimalIfConsoleLogge { this.vsTestTask.VSTestVerbosity = "minimal"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--logger:Console;Verbosity=minimal"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=minimal"); } [TestMethod] @@ -193,9 +190,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger { this.vsTestTask.VSTestVerbosity = "Normal"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--logger:Console;Verbosity=normal"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); } [TestMethod] @@ -203,9 +200,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToQuietIfConsoleLoggerI { this.vsTestTask.VSTestVerbosity = "Quiet"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--logger:Console;Verbosity=quiet"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=quiet"); } [TestMethod] @@ -213,9 +210,9 @@ public void CreateArgumentShouldPreserveWhiteSpaceInLogger() { this.vsTestTask.VSTestLogger = new string[] { "trx;LogFileName=foo bar.trx" }; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--logger:\"trx;LogFileName=foo bar.trx\""))); + StringAssert.Contains(commandline, "--logger:\"trx;LogFileName=foo bar.trx\""); } [TestMethod] @@ -226,91 +223,92 @@ public void CreateArgumentShouldAddOneCollectArgumentForEachCollect() this.vsTestTask.VSTestCollect[0] = "name1"; this.vsTestTask.VSTestCollect[1] = "name 2"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--collect:name1"))); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--collect:\"name 2\""))); + StringAssert.Contains(commandline, "--collect:name1"); + StringAssert.Contains(commandline, "--collect:\"name 2\""); } [TestMethod] public void CreateArgumentShouldAddMultipleTestAdapterPaths() { - this.vsTestTask.VSTestTestAdapterPath = new string[] { "path1", "path2" }; + this.vsTestTask.VSTestTestAdapterPath = new ITaskItem[] { new TaskItem("path1"), new TaskItem("path2") }; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--testAdapterPath:path1"))); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--testAdapterPath:path2"))); + StringAssert.Contains(commandline, "--testAdapterPath:path1"); + StringAssert.Contains(commandline, "--testAdapterPath:path2"); } [TestMethod] public void CreateArgumentShouldAddMultipleLoggers() { this.vsTestTask.VSTestLogger = new string[] { "trx;LogFileName=foo bar.trx", "console" }; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--logger:\"trx;LogFileName=foo bar.trx\""))); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--logger:console"))); + StringAssert.Contains(commandline, "--logger:\"trx;LogFileName=foo bar.trx\""); + StringAssert.Contains(commandline, "--logger:console"); } [TestMethod] public void CreateArgumentShouldAddTraceCollectorDirectoryPathAsTestAdapterForCodeCoverageCollect() { const string traceDataCollectorDirectoryPath = @"c:\path\to\tracedata collector"; - this.vsTestTask.VSTestTraceDataCollectorDirectoryPath = traceDataCollectorDirectoryPath; + this.vsTestTask.VSTestTraceDataCollectorDirectoryPath = new TaskItem(traceDataCollectorDirectoryPath); this.vsTestTask.VSTestCollect = new string[] { "code coverage" }; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - const string expectedArg = "--testAdapterPath:\"c:\\path\\to\\tracedata collector\""; - CollectionAssert.Contains(allArguments, expectedArg, $"Expected argument: '''{expectedArg}''' not present in [{string.Join(", ", allArguments)}]"); + string expectedArg = $"--testAdapterPath:\"{this.vsTestTask.VSTestTraceDataCollectorDirectoryPath.ItemSpec}\""; + StringAssert.Contains(commandline, expectedArg); } [TestMethod] public void CreateArgumentShouldNotAddTraceCollectorDirectoryPathAsTestAdapterForNonCodeCoverageCollect() { const string traceDataCollectorDirectoryPath = @"c:\path\to\tracedata collector"; - this.vsTestTask.VSTestTraceDataCollectorDirectoryPath = traceDataCollectorDirectoryPath; + this.vsTestTask.VSTestTraceDataCollectorDirectoryPath = new TaskItem(traceDataCollectorDirectoryPath); this.vsTestTask.VSTestCollect = new string[] { "not code coverage" }; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - const string notExpectedArg = "--testAdapterPath:\"c:\\path\\to\\tracedata collector\""; - CollectionAssert.DoesNotContain(allArguments, notExpectedArg, $"Not expected argument: '''{notExpectedArg}''' present in [{string.Join(", ", allArguments)}]"); + string notExpectedArg = $"--testAdapterPath:\"{this.vsTestTask.VSTestTraceDataCollectorDirectoryPath.ItemSpec}\""; + StringAssert.DoesNotMatch(commandline, new Regex(Regex.Escape(notExpectedArg))); } [TestMethod] public void CreateArgumentShouldAddTraceCollectorDirectoryPathAsTestAdapterIfSettingsGiven() { const string traceDataCollectorDirectoryPath = @"c:\path\to\tracedatacollector\"; - this.vsTestTask.VSTestTraceDataCollectorDirectoryPath = traceDataCollectorDirectoryPath; + this.vsTestTask.VSTestTraceDataCollectorDirectoryPath = new TaskItem(traceDataCollectorDirectoryPath); this.vsTestTask.VSTestSetting = @"c:\path\to\sample.runsettings"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - const string expectedArg = "--testAdapterPath:c:\\path\\to\\tracedatacollector\\"; - CollectionAssert.Contains(allArguments, expectedArg, $"Expected argument: '''{expectedArg}''' not present in [{string.Join(", ", allArguments)}]"); + string expectedArg = $"--testAdapterPath:{this.vsTestTask.VSTestTraceDataCollectorDirectoryPath.ItemSpec}"; + StringAssert.Contains(commandline, expectedArg); } [TestMethod] public void CreateArgumentShouldNotAddTestAdapterPathIfVSTestTraceDataCollectorDirectoryPathIsEmpty() { - this.vsTestTask.VSTestTraceDataCollectorDirectoryPath = string.Empty; + this.vsTestTask.VSTestTraceDataCollectorDirectoryPath = null; this.vsTestTask.VSTestSetting = @"c:\path\to\sample.runsettings"; this.vsTestTask.VSTestCollect = new string[] { "code coverage" }; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNull(Array.Find(allArguments, arg => arg.Contains("--testAdapterPath:"))); + StringAssert.DoesNotMatch(commandline, new Regex(@"(--testAdapterPath:)")); } [TestMethod] public void CreateArgumentShouldAddNoLogoOptionIfSpecifiedByUser() { - this.vsTestTask.VSTestNoLogo = "--nologo"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + this.vsTestTask.VSTestNoLogo = true; + + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(Array.Find(allArguments, arg => arg.Contains("--nologo"))); + StringAssert.Contains(commandline, "--nologo"); } } -} +} \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs b/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs index b7181a2fe6..b38d5a4cd6 100644 --- a/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs +++ b/test/Microsoft.TestPlatform.TestUtilities/IntegrationTestBase.cs @@ -139,7 +139,7 @@ public void InvokeDotnetTest(string arguments) Environment.SetEnvironmentVariable(env, vstestConsolePath); if (arguments.Contains(".csproj")) { - arguments = $@"-p:VsTestConsolePath=""{vstestConsolePath}"" " + arguments; + arguments = $@"-p:VsTestConsolePath=""{vstestConsolePath}"" -p:VSTestUseConsole=True " + arguments; } this.ExecutePatchedDotnet("test", arguments, out this.standardTestOutput, out this.standardTestError, out this.runnerExitCode);