diff --git a/src/Microsoft.TestPlatform.Build/ArgumentEscaper.cs b/src/Microsoft.TestPlatform.Build/ArgumentEscaper.cs deleted file mode 100644 index 46f64427e6..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..eaf6791b0c 100644 --- a/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.targets +++ b/src/Microsoft.TestPlatform.Build/Microsoft.TestPlatform.targets @@ -25,7 +25,7 @@ Copyright (c) .NET Foundation. All rights reserved. ============================================================ --> - + @@ -53,7 +53,8 @@ Copyright (c) .NET Foundation. All rights reserved. VSTestBlameHangTimeout="$(VSTestBlameHangTimeout)" VSTestTraceDataCollectorDirectoryPath="$(TraceDataCollectorDirectoryPath)" VSTestNoLogo="$(VSTestNoLogo)" - Condition="'$(IsTestProject)' == 'true'" + YieldDuringToolExecution="True" + Condition="$(IsTestProject)" /> @@ -68,37 +69,37 @@ Copyright (c) .NET Foundation. All rights reserved. - + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + 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/VSTestTask.cs b/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask.cs index df681503ed..14f4c670a1 100644 --- a/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask.cs +++ b/src/Microsoft.TestPlatform.Build/Tasks/VSTestTask.cs @@ -5,21 +5,19 @@ namespace Microsoft.TestPlatform.Build.Tasks { using System; using System.Collections.Generic; + using System.IO; using System.Linq; + 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 { - // The process which is invoking vstest.console - private VSTestForwardingApp vsTestForwardingApp; - private const string vsTestAppName = "vstest.console.dll"; - - public string TestFileFullPath + [Required] + //TODO: rename, as relative paths are allowed? + public ITaskItem TestFileFullPath { get; set; @@ -31,7 +29,7 @@ public string VSTestSetting set; } - public string[] VSTestTestAdapterPath + public ITaskItem[] VSTestTestAdapterPath { get; set; @@ -60,7 +58,7 @@ public string[] VSTestLogger set; } - public string VSTestListTests + public bool VSTestListTests { get; set; @@ -79,13 +77,13 @@ public string[] VSTestCLIRunSettings } [Required] - public string VSTestConsolePath + public ITaskItem VSTestConsolePath { get; set; } - public string VSTestResultsDirectory + public ITaskItem VSTestResultsDirectory { get; set; @@ -103,13 +101,13 @@ public string[] VSTestCollect set; } - public string VSTestBlame + public bool VSTestBlame { get; set; } - public string VSTestBlameCrash + public bool VSTestBlameCrash { get; set; @@ -121,13 +119,13 @@ public string VSTestBlameCrashDumpType set; } - public string VSTestBlameCrashCollectAlways + public bool VSTestBlameCrashCollectAlways { get; set; } - public string VSTestBlameHang + public bool VSTestBlameHang { get; set; @@ -138,110 +136,87 @@ public string VSTestBlameHangDumpType get; set; } + public string VSTestBlameHangTimeout { get; set; } - public string VSTestTraceDataCollectorDirectoryPath + public ITaskItem VSTestTraceDataCollectorDirectoryPath { get; set; } - public string VSTestNoLogo + public bool VSTestNoLogo { get; set; } - public override bool Execute() + protected override string ToolName { - var traceEnabledValue = Environment.GetEnvironmentVariable("VSTEST_BUILD_TRACE"); - Tracing.traceEnabled = !string.IsNullOrEmpty(traceEnabledValue) && traceEnabledValue.Equals("1", StringComparison.OrdinalIgnoreCase); - - vsTestForwardingApp = new VSTestForwardingApp(this.VSTestConsolePath, this.CreateArgument()); - if (!string.IsNullOrEmpty(this.VSTestFramework)) + get { - Console.WriteLine(Resources.TestRunningSummary, this.TestFileFullPath, this.VSTestFramework); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return "dotnet.exe"; + else + return "dotnet"; } - - return vsTestForwardingApp.Execute() == 0; - } - - public void Cancel() - { - Tracing.Trace("VSTest: Killing the process..."); - vsTestForwardingApp.Cancel(); - } - - internal IEnumerable CreateArgument() - { - var allArgs = this.AddArgs(); - - // VSTestCLIRunSettings should be last argument in allArgs as vstest.console ignore options after "--"(CLIRunSettings option). - this.AddCLIRunSettingsArgs(allArgs); - - return allArgs; } - private void AddCLIRunSettingsArgs(List allArgs) + public VSTestTask() { - if (this.VSTestCLIRunSettings != null && this.VSTestCLIRunSettings.Length > 0) - { - allArgs.Add("--"); - foreach (var arg in this.VSTestCLIRunSettings) - { - allArgs.Add(ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(arg)); - } - } + this.LogStandardErrorAsError = true; + this.StandardOutputImportance = "Normal"; } - private List AddArgs() + protected override string GenerateCommandLineCommands() { var isConsoleLoggerSpecifiedByUser = false; var isCollectCodeCoverageEnabled = false; var isRunSettingsEnabled = false; - var allArgs = new List(); - // TODO log arguments in task - if (!string.IsNullOrEmpty(this.VSTestSetting)) + var builder = new CommandLineBuilder(); + builder.AppendSwitch("exec"); + builder.AppendSwitchIfNotNull("", this.VSTestConsolePath); + + if (this.VSTestSetting != null) { isRunSettingsEnabled = true; - allArgs.Add("--settings:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(this.VSTestSetting)); + builder.AppendSwitchIfNotNull("--settings:", this.VSTestSetting); } - if (this.VSTestTestAdapterPath != null && this.VSTestTestAdapterPath.Length > 0) + if (this.VSTestTestAdapterPath != null && this.VSTestTestAdapterPath.Any()) { foreach (var arg in this.VSTestTestAdapterPath) { - allArgs.Add("--testAdapterPath:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(arg)); + builder.AppendSwitchIfNotNull("--testAdapterPath:", arg); } } if (!string.IsNullOrEmpty(this.VSTestFramework)) { - allArgs.Add("--framework:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(this.VSTestFramework)); + builder.AppendSwitchIfNotNull("--framework:", 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)); + builder.AppendSwitchIfNotNull("--platform:", this.VSTestPlatform); } if (!string.IsNullOrEmpty(this.VSTestTestCaseFilter)) { - allArgs.Add("--testCaseFilter:" + - ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(this.VSTestTestCaseFilter)); + builder.AppendSwitchIfNotNull("--testCaseFilter:", this.VSTestTestCaseFilter); } if (this.VSTestLogger != null && this.VSTestLogger.Length > 0) { foreach (var arg in this.VSTestLogger) { - allArgs.Add("--logger:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(arg)); + builder.AppendSwitchIfNotNull("--logger:", arg); if (arg.StartsWith("console", StringComparison.OrdinalIgnoreCase)) { @@ -250,29 +225,24 @@ private List AddArgs() } } - if (!string.IsNullOrEmpty(this.VSTestResultsDirectory)) + if (this.VSTestResultsDirectory != null && !string.IsNullOrEmpty(this.VSTestResultsDirectory.ItemSpec)) { - allArgs.Add("--resultsDirectory:" + - ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(this.VSTestResultsDirectory)); + builder.AppendSwitchIfNotNull("--resultsDirectory:", this.VSTestResultsDirectory); } - if (!string.IsNullOrEmpty(this.VSTestListTests)) + if (this.VSTestListTests) { - allArgs.Add("--listTests"); + builder.AppendSwitch("--listTests"); } if (!string.IsNullOrEmpty(this.VSTestDiag)) { - allArgs.Add("--Diag:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(this.VSTestDiag)); + builder.AppendSwitchIfNotNull("--Diag:", this.VSTestDiag); } - if (string.IsNullOrEmpty(this.TestFileFullPath)) + if (this.TestFileFullPath != null) { - this.Log.LogError("Test file path cannot be empty or null."); - } - else - { - allArgs.Add(ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(this.TestFileFullPath)); + builder.AppendFileNameIfNotNull(this.TestFileFullPath); } // Console logger was not specified by user, but verbosity was, hence add default console logger with verbosity as specified @@ -282,31 +252,28 @@ private List AddArgs() var quietTestLogging = new List() { "q", "quiet" }; string vsTestVerbosity = "minimal"; - if (normalTestLogging.Contains(this.VSTestVerbosity.ToLowerInvariant())) + if (normalTestLogging.Contains(this.VSTestVerbosity, StringComparer.InvariantCultureIgnoreCase)) { vsTestVerbosity = "normal"; } - else if (quietTestLogging.Contains(this.VSTestVerbosity.ToLowerInvariant())) + else if (quietTestLogging.Contains(this.VSTestVerbosity, StringComparer.InvariantCultureIgnoreCase)) { vsTestVerbosity = "quiet"; } - allArgs.Add("--logger:Console;Verbosity=" + vsTestVerbosity); + builder.AppendSwitchUnquotedIfNotNull("--logger:", $"Console;Verbosity={vsTestVerbosity}"); } - var blameCrash = !string.IsNullOrEmpty(this.VSTestBlameCrash); - var blameHang = !string.IsNullOrEmpty(this.VSTestBlameHang); - if (!string.IsNullOrEmpty(this.VSTestBlame) || blameCrash || blameHang) + if (this.VSTestBlame) { - var blameArgs = "--Blame"; - var dumpArgs = new List(); - if (blameCrash || blameHang) + if (this.VSTestBlameCrash || this.VSTestBlameHang) { - if (blameCrash) + if (this.VSTestBlameCrash) { dumpArgs.Add("CollectDump"); - if (!string.IsNullOrEmpty(this.VSTestBlameCrashCollectAlways)) + + if (this.VSTestBlameCrashCollectAlways) { dumpArgs.Add($"CollectAlways={this.VSTestBlameCrashCollectAlways}"); } @@ -317,7 +284,7 @@ private List AddArgs() } } - if (blameHang) + if (this.VSTestBlameHang) { dumpArgs.Add("CollectHangDump"); @@ -331,14 +298,15 @@ private List AddArgs() dumpArgs.Add($"TestTimeout={this.VSTestBlameHangTimeout}"); } } - - if (dumpArgs.Any()) - { - blameArgs += $":\"{string.Join(";", dumpArgs)}\""; - } } - allArgs.Add(blameArgs); + if (dumpArgs.Any()) + { + builder.AppendSwitchIfNotNull("--Blame:", string.Join(";", dumpArgs)); + } else + { + builder.AppendSwitch("--Blame"); + } } if (this.VSTestCollect != null && this.VSTestCollect.Length > 0) @@ -350,7 +318,7 @@ private List AddArgs() isCollectCodeCoverageEnabled = true; } - allArgs.Add("--collect:" + ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(arg)); + builder.AppendSwitchIfNotNull("--collect:", arg); } } @@ -363,29 +331,71 @@ private List AddArgs() // 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)) + if (this.VSTestTraceDataCollectorDirectoryPath != null && !string.IsNullOrEmpty(this.VSTestTraceDataCollectorDirectoryPath.ItemSpec)) { - allArgs.Add("--testAdapterPath:" + - ArgumentEscaper.HandleEscapeSequenceInArgForProcessStart(this - .VSTestTraceDataCollectorDirectoryPath)); - } - else + builder.AppendSwitchIfNotNull("--testAdapterPath:", 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); + this.Log.LogWarning(Resources.UpdateTestSdkForCollectingCodeCoverage); } } } - if (!string.IsNullOrWhiteSpace(this.VSTestNoLogo)) + if (this.VSTestNoLogo) + { + builder.AppendSwitch("--nologo"); + } + + // VSTestCLIRunSettings should be last argument as vstest.console ignore options after "--"(CLIRunSettings option). + if (this.VSTestCLIRunSettings != null && this.VSTestCLIRunSettings.Any()) + { + builder.AppendSwitch("--"); + + foreach (var arg in this.VSTestCLIRunSettings) + { + builder.AppendSwitchIfNotNull(string.Empty, arg); + } + } + + return builder.ToString(); + } + + protected override string GenerateFullPathToTool() + { + string path = null; + + if (!string.IsNullOrEmpty(ToolPath)) + { + path = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(ToolPath)), ToolExe); + } else { - allArgs.Add("--nologo"); + //TODO: https://github.com/dotnet/sdk/issues/20 Need to get the dotnet path from MSBuild + if (File.Exists(ToolExe)) + { + path = Path.GetFullPath(ToolExe); + } else + { + var values = Environment.GetEnvironmentVariable("PATH"); + foreach (var p in values.Split(Path.PathSeparator)) + { + var fullPath = Path.Combine(p, ToolExe); + if (File.Exists(fullPath)) + path = fullPath; + } + } } - return allArgs; + return path; + } + + /// To be used by unit tests only + internal protected string CreateCommandLineArguments() + { + return this.GenerateCommandLineCommands(); } } } diff --git a/test/Microsoft.TestPlatform.Build.UnitTests/ArgumentEscaperTests.cs b/test/Microsoft.TestPlatform.Build.UnitTests/ArgumentEscaperTests.cs deleted file mode 100644 index 31bfa52afc..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..f6b2421e1b --- /dev/null +++ b/test/Microsoft.TestPlatform.Build.UnitTests/FakeBuildEngine.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.TestPlatform.Build.UnitTests +{ + using System; + using System.Collections; + using System.Collections.Generic; + 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) + { + } + } +} diff --git a/test/Microsoft.TestPlatform.Build.UnitTests/VsTestTaskTests.cs b/test/Microsoft.TestPlatform.Build.UnitTests/VsTestTaskTests.cs index 8bdc6d282d..6307c31122 100644 --- a/test/Microsoft.TestPlatform.Build.UnitTests/VsTestTaskTests.cs +++ b/test/Microsoft.TestPlatform.Build.UnitTests/VsTestTaskTests.cs @@ -2,9 +2,10 @@ namespace Microsoft.TestPlatform.Build.UnitTests { - using System; - using System.Linq; - + using System.Collections; + using System.Text.RegularExpressions; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; using Microsoft.TestPlatform.Build.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -17,7 +18,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 +34,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 +47,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 +56,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:\"{resultsDirectoryValue}\""); } [TestMethod] @@ -82,10 +80,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(allArguments.FirstOrDefault(arg => arg.Contains("--logger:Console;Verbosity=normal"))); - Assert.IsNotNull(allArguments.FirstOrDefault(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 +91,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger { this.vsTestTask.VSTestVerbosity = "n"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(allArguments.FirstOrDefault(arg => arg.Contains("--logger:Console;Verbosity=normal"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); } [TestMethod] @@ -103,9 +101,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger { this.vsTestTask.VSTestVerbosity = "normal"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(allArguments.FirstOrDefault(arg => arg.Contains("--logger:Console;Verbosity=normal"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); } [TestMethod] @@ -113,9 +111,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger { this.vsTestTask.VSTestVerbosity = "d"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(allArguments.FirstOrDefault(arg => arg.Contains("--logger:Console;Verbosity=normal"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); } [TestMethod] @@ -123,9 +121,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger { this.vsTestTask.VSTestVerbosity = "detailed"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(allArguments.FirstOrDefault(arg => arg.Contains("--logger:Console;Verbosity=normal"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); } [TestMethod] @@ -133,9 +131,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger { this.vsTestTask.VSTestVerbosity = "diag"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(allArguments.FirstOrDefault(arg => arg.Contains("--logger:Console;Verbosity=normal"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); } [TestMethod] @@ -143,9 +141,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger { this.vsTestTask.VSTestVerbosity = "diagnostic"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(allArguments.FirstOrDefault(arg => arg.Contains("--logger:Console;Verbosity=normal"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); } [TestMethod] @@ -153,9 +151,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToQuietIfConsoleLoggerI { this.vsTestTask.VSTestVerbosity = "q"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(allArguments.FirstOrDefault(arg => arg.Contains("--logger:Console;Verbosity=quiet"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=quiet"); } [TestMethod] @@ -163,9 +161,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToQuietIfConsoleLoggerI { this.vsTestTask.VSTestVerbosity = "quiet"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(allArguments.FirstOrDefault(arg => arg.Contains("--logger:Console;Verbosity=quiet"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=quiet"); } [TestMethod] @@ -173,9 +171,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToMinimalIfConsoleLogge { this.vsTestTask.VSTestVerbosity = "m"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(allArguments.FirstOrDefault(arg => arg.Contains("--logger:Console;Verbosity=minimal"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=minimal"); } [TestMethod] @@ -183,9 +181,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToMinimalIfConsoleLogge { this.vsTestTask.VSTestVerbosity = "minimal"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(allArguments.FirstOrDefault(arg => arg.Contains("--logger:Console;Verbosity=minimal"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=minimal"); } [TestMethod] @@ -193,9 +191,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToNormalIfConsoleLogger { this.vsTestTask.VSTestVerbosity = "Normal"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(allArguments.FirstOrDefault(arg => arg.Contains("--logger:Console;Verbosity=normal"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=normal"); } [TestMethod] @@ -203,9 +201,9 @@ public void CreateArgumentShouldSetConsoleLoggerVerbosityToQuietIfConsoleLoggerI { this.vsTestTask.VSTestVerbosity = "Quiet"; - var allArguments = this.vsTestTask.CreateArgument().ToArray(); + var commandline = this.vsTestTask.CreateCommandLineArguments(); - Assert.IsNotNull(allArguments.FirstOrDefault(arg => arg.Contains("--logger:Console;Verbosity=quiet"))); + StringAssert.Contains(commandline, "--logger:Console;Verbosity=quiet"); } [TestMethod] @@ -213,9 +211,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(allArguments.FirstOrDefault(arg => arg.Contains("--logger:\"trx;LogFileName=foo bar.trx\""))); + StringAssert.Contains(commandline, "--logger:\"trx;LogFileName=foo bar.trx\""); } [TestMethod] @@ -226,91 +224,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(allArguments.FirstOrDefault(arg => arg.Contains("--collect:name1"))); - Assert.IsNotNull(allArguments.FirstOrDefault(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(allArguments.FirstOrDefault(arg => arg.Contains("--testAdapterPath:path1"))); - Assert.IsNotNull(allArguments.FirstOrDefault(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(allArguments.FirstOrDefault(arg => arg.Contains("--logger:\"trx;LogFileName=foo bar.trx\""))); - Assert.IsNotNull(allArguments.FirstOrDefault(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)}]"); + 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)}]"); + 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)}]"); + 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(allArguments.FirstOrDefault(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(allArguments.FirstOrDefault(arg => arg.Contains("--nologo"))); + StringAssert.Contains(commandline, "--nologo"); } } }