diff --git a/src/Microsoft.TestPlatform.Utilities/CommandLineUtilities.cs b/src/Microsoft.TestPlatform.Utilities/CommandLineUtilities.cs index b7c7fc0b66..0a51a550de 100644 --- a/src/Microsoft.TestPlatform.Utilities/CommandLineUtilities.cs +++ b/src/Microsoft.TestPlatform.Utilities/CommandLineUtilities.cs @@ -21,9 +21,9 @@ public static bool SplitCommandLineIntoArguments(string args, out string[] argum try { - while (true) + while (index < args.Length) { - // skip whitespace + // Skip whitespace. while (char.IsWhiteSpace(args[index])) { index++; @@ -33,42 +33,65 @@ public static bool SplitCommandLineIntoArguments(string args, out string[] argum if (args[index] == '#') { index++; - while (args[index] != '\n') + while (index < args.Length && args[index] != '\n') { index++; } + + // We are done processing comment move to next statement. continue; } - // do one argument + // Read argument until next whitespace (not in quotes). do { if (args[index] == '\\') { - int cSlashes = 1; + // Move to next char. index++; - while (index == args.Length && args[index] == '\\') - { - cSlashes++; - } - if (index == args.Length || args[index] != '"') + // If this was the last char then output the slash. + if (index == args.Length) { - currentArg.Append('\\', cSlashes); + currentArg.Append('\\'); + + index++; + continue; } else { - currentArg.Append('\\', (cSlashes >> 1)); - if (0 != (cSlashes & 1)) + // If the char after '\' is also a '\', output the second '\' and skip over to the next char. + if (args[index] == '\\') + { + currentArg.Append('\\'); + + // We processed the escaped \, move to next char. + index++; + continue; + } + + // If the char after '\' is a '"', output '"' and skip over to the next char. + if (index <= args.Length && args[index] == '"') { currentArg.Append('"'); + + // We processed the escaped " move to next char. + index++; + continue; } - else + + // If the char after '\' is anything else, output the slash. And continue processing the next char. + if (index <= args.Length) { - inQuotes = !inQuotes; + currentArg.Append('\\'); + + // Don't skip to the next char. We outputted the \ because it was not escaping \ or ". Let the next character to be processed by the loop. + // index++; + continue; } } } + // Unescaped quote enters and leaves quoted mode. else if (args[index] == '"') { inQuotes = !inQuotes; @@ -76,6 +99,7 @@ public static bool SplitCommandLineIntoArguments(string args, out string[] argum } else { + // Collect all other characters. currentArg.Append(args[index]); index++; } diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/CommandLineUtilitiesTest.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/CommandLineUtilitiesTest.cs index c1106695a7..1cc190cc45 100644 --- a/test/Microsoft.TestPlatform.Utilities.UnitTests/CommandLineUtilitiesTest.cs +++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/CommandLineUtilitiesTest.cs @@ -10,23 +10,28 @@ namespace Microsoft.TestPlatform.Utilities.Tests; [TestClass] public class CommandLineUtilitiesTest { - private static void VerifyCommandLineSplitter(string commandLine, string[] expected) + [TestMethod] + [DataRow("", new string[] { })] + [DataRow(" /a:b ", new string[] { "/a:b" })] + [DataRow(""" + /param1 + /param2:value2 + /param3:"value with spaces" + """, new string[] { "/param1", "/param2:value2", "/param3:value with spaces" })] + [DataRow("""/param3 #comment""", new string[] { "/param3" })] + [DataRow(""" + /param3 #comment ends with newline \" \\ + /param4 + """, new string[] { "/param3", "/param4" })] + [DataRow("""/testadapterpath:"c:\Path" """, new string[] { @"/testadapterpath:c:\Path" })] + [DataRow("""/testadapterpath:"c:\Path" /logger:"trx" """, new string[] { @"/testadapterpath:c:\Path", "/logger:trx" })] + [DataRow("""/testadapterpath:"c:\Path" /logger:"trx" /diag:"log.txt" """, new string[] { @"/testadapterpath:c:\Path", "/logger:trx", "/diag:log.txt" })] + [DataRow("""/Tests:"Test(\"iCT 256\")" """, new string[] { """/Tests:Test("iCT 256")""" })] + public void VerifyCommandLineSplitter(string input, string[] expected) { - CommandLineUtilities.SplitCommandLineIntoArguments(commandLine, out var actual); + CommandLineUtilities.SplitCommandLineIntoArguments(input, out var actual); - Assert.AreEqual(expected.Length, actual.Length); - for (int i = 0; i < actual.Length; ++i) - { - Assert.AreEqual(expected[i], actual[i]); - } + CollectionAssert.AreEqual(expected, actual); } - [TestMethod] - public void TestCommandLineSplitter() - { - VerifyCommandLineSplitter("", []); - VerifyCommandLineSplitter("/testadapterpath:\"c:\\Path\"", [@"/testadapterpath:c:\Path"]); - VerifyCommandLineSplitter("/testadapterpath:\"c:\\Path\" /logger:\"trx\"", [@"/testadapterpath:c:\Path", "/logger:trx"]); - VerifyCommandLineSplitter("/testadapterpath:\"c:\\Path\" /logger:\"trx\" /diag:\"log.txt\"", [@"/testadapterpath:c:\Path", "/logger:trx", "/diag:log.txt"]); - } }