Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 39 additions & 15 deletions src/Microsoft.TestPlatform.Utilities/CommandLineUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
Expand All @@ -33,49 +33,73 @@ 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;
index++;
}
else
{
// Collect all other characters.
currentArg.Append(args[index]);
index++;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"]);
}
}