diff --git a/src/Microsoft.TestPlatform.Utilities/CommandLineUtilities.cs b/src/Microsoft.TestPlatform.Utilities/CommandLineUtilities.cs
index db5b9676d7..93aed86fd9 100644
--- a/src/Microsoft.TestPlatform.Utilities/CommandLineUtilities.cs
+++ b/src/Microsoft.TestPlatform.Utilities/CommandLineUtilities.cs
@@ -1,157 +1,108 @@
-// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-// Code taken from: https://github.com/dotnet/roslyn/blob/d00363d8f892f4f3c514718a964ea37783d21de5/src/Compilers/Core/Portable/InternalUtilities/CommandLineUtilities.cs
-
namespace Microsoft.VisualStudio.TestPlatform.Utilities
{
+ using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+ using System;
using System.Collections.Generic;
+ using System.Globalization;
+ using System.IO;
using System.Text;
public static class CommandLineUtilities
{
- ///
- /// Split a command line by the same rules as Main would get the commands except the original
- /// state of backslashes and quotes are preserved. For example in normal Windows command line
- /// parsing the following command lines would produce equivalent Main arguments:
- ///
- /// - /r:a,b
- /// - /r:"a,b"
- ///
- /// This method will differ as the latter will have the quotes preserved. The only case where
- /// quotes are removed is when the entire argument is surrounded by quotes without any inner
- /// quotes.
- ///
- ///
- /// Rules for command line parsing, according to MSDN:
- ///
- /// Arguments are delimited by white space, which is either a space or a tab.
- ///
- /// A string surrounded by double quotation marks ("string") is interpreted
- /// as a single argument, regardless of white space contained within.
- /// A quoted string can be embedded in an argument.
- ///
- /// A double quotation mark preceded by a backslash (\") is interpreted as a
- /// literal double quotation mark character (").
- ///
- /// Backslashes are interpreted literally, unless they immediately precede a
- /// double quotation mark.
- ///
- /// If an even number of backslashes is followed by a double quotation mark,
- /// one backslash is placed in the argv array for every pair of backslashes,
- /// and the double quotation mark is interpreted as a string delimiter.
- ///
- /// If an odd number of backslashes is followed by a double quotation mark,
- /// one backslash is placed in the argv array for every pair of backslashes,
- /// and the double quotation mark is "escaped" by the remaining backslash,
- /// causing a literal double quotation mark (") to be placed in argv.
- ///
- public static IEnumerable SplitCommandLineIntoArguments(string commandLine, bool removeHashComments)
- {
- char? unused;
- return SplitCommandLineIntoArguments(commandLine, removeHashComments, out unused);
- }
-
- public static IEnumerable SplitCommandLineIntoArguments(string commandLine, bool removeHashComments, out char? illegalChar)
+ public static bool SplitCommandLineIntoArguments(string args, out string[] arguments)
{
- var builder = new StringBuilder(commandLine.Length);
- var list = new List();
- var i = 0;
+ bool hadError = false;
+ var argArray = new List();
+ var currentArg = new StringBuilder();
+ bool inQuotes = false;
+ int index = 0;
- illegalChar = null;
- while (i < commandLine.Length)
+ try
{
- while (i < commandLine.Length && char.IsWhiteSpace(commandLine[i]))
+ while (true)
{
- i++;
- }
-
- if (i == commandLine.Length)
- {
- break;
- }
+ // skip whitespace
+ while (char.IsWhiteSpace(args[index]))
+ {
+ index += 1;
+ }
- if (commandLine[i] == '#' && removeHashComments)
- {
- break;
- }
+ // # - comment to end of line
+ if (args[index] == '#')
+ {
+ index += 1;
+ while (args[index] != '\n')
+ {
+ index += 1;
+ }
+ continue;
+ }
- var quoteCount = 0;
- builder.Length = 0;
- while (i < commandLine.Length && (!char.IsWhiteSpace(commandLine[i]) || (quoteCount % 2 != 0)))
- {
- var current = commandLine[i];
- switch (current)
+ // do one argument
+ do
{
- case '\\':
+ if (args[index] == '\\')
+ {
+ int cSlashes = 1;
+ index += 1;
+ while (index == args.Length && args[index] == '\\')
{
- var slashCount = 0;
- do
- {
- builder.Append(commandLine[i]);
- i++;
- slashCount++;
- } while (i < commandLine.Length && commandLine[i] == '\\');
-
- // Slashes not followed by a quote character can be ignored for now
- if (i >= commandLine.Length || commandLine[i] != '"')
- {
- break;
- }
-
- // If there is an odd number of slashes then it is escaping the quote
- // otherwise it is just a quote.
- if (slashCount % 2 == 0)
- {
- quoteCount++;
- }
-
- builder.Append('"');
- i++;
- break;
+ cSlashes += 1;
}
- case '"':
- builder.Append(current);
- quoteCount++;
- i++;
- break;
-
- default:
- if ((current >= 0x1 && current <= 0x1f) || current == '|')
+ if (index == args.Length || args[index] != '"')
{
- if (illegalChar == null)
- {
- illegalChar = current;
- }
+ currentArg.Append('\\', cSlashes);
}
else
{
- builder.Append(current);
+ currentArg.Append('\\', (cSlashes >> 1));
+ if (0 != (cSlashes & 1))
+ {
+ currentArg.Append('"');
+ }
+ else
+ {
+ inQuotes = !inQuotes;
+ }
}
-
- i++;
- break;
- }
+ }
+ else if (args[index] == '"')
+ {
+ inQuotes = !inQuotes;
+ index += 1;
+ }
+ else
+ {
+ currentArg.Append(args[index]);
+ index += 1;
+ }
+ } while (!char.IsWhiteSpace(args[index]) || inQuotes);
+ argArray.Add(currentArg.ToString());
+ currentArg.Clear();
}
-
- // If the quote string is surrounded by quotes with no interior quotes then
- // remove the quotes here.
- if (quoteCount == 2 && builder[0] == '"' && builder[builder.Length - 1] == '"')
+ }
+ catch (IndexOutOfRangeException)
+ {
+ // got EOF
+ if (inQuotes)
{
- builder.Remove(0, length: 1);
- builder.Remove(builder.Length - 1, length: 1);
+ EqtTrace.Verbose("Executor.Execute: Exiting with exit code of {0}", 1);
+ EqtTrace.Error(string.Format(CultureInfo.InvariantCulture, "Error: Unbalanced '\"' in command line argument file"));
+ hadError = true;
}
-
- if (builder.Length > 0)
+ else if (currentArg.Length > 0)
{
- list.Add(builder.ToString());
+ // valid argument can be terminated by EOF
+ argArray.Add(currentArg.ToString());
}
}
- return list;
+ arguments = argArray.ToArray();
+ return hadError;
}
}
}
diff --git a/src/vstest.console/CommandLine/Executor.cs b/src/vstest.console/CommandLine/Executor.cs
index f7e0d55b6f..6787a8cf61 100644
--- a/src/vstest.console/CommandLine/Executor.cs
+++ b/src/vstest.console/CommandLine/Executor.cs
@@ -230,7 +230,7 @@ private int GetArgumentProcessors(string[] args, out List pr
this.Output.Error(false, ex.Message);
result = 1;
}
- else if(ex is SettingsException)
+ else if (ex is SettingsException)
{
this.Output.Error(false, ex.Message);
result = 1;
@@ -246,7 +246,7 @@ private int GetArgumentProcessors(string[] args, out List pr
}
// If some argument was invalid, add help argument processor in beginning(i.e. at highest priority)
- if(result == 1 && this.showHelp && processors.First().Metadata.Value.CommandName != HelpArgumentProcessor.CommandName)
+ if (result == 1 && this.showHelp && processors.First().Metadata.Value.CommandName != HelpArgumentProcessor.CommandName)
{
processors.Insert(0, processorFactory.CreateArgumentProcessor(HelpArgumentProcessor.CommandName));
}
@@ -381,7 +381,6 @@ private void PrintSplashScreen()
/// Arguments provided to perform execution with.
/// Array of flattened arguments.
/// 0 if successful and 1 otherwise.
- ///
private int FlattenArguments(IEnumerable arguments, out string[] flattenedArguments)
{
List outputArguments = new List();
@@ -393,8 +392,17 @@ private int FlattenArguments(IEnumerable arguments, out string[] flatten
{
// response file:
string path = arg.Substring(1).TrimEnd(null);
- result |= ParseResponseFile(path, out var responseFileArguments);
- outputArguments.AddRange(responseFileArguments.Reverse());
+ var hadError = this.ReadArgumentsAndSanitize(path, out var responseFileArgs, out var nestedArgs);
+
+ if (hadError)
+ {
+ result |= 1;
+ }
+ else
+ {
+ this.Output.WriteLine(string.Format("vstest.console.exe {0}", responseFileArgs), OutputLevel.Information);
+ outputArguments.AddRange(nestedArgs);
+ }
}
else
{
@@ -407,58 +415,46 @@ private int FlattenArguments(IEnumerable arguments, out string[] flatten
}
///
- /// Parse a response file into a set of arguments. Errors opening the response file are output as errors.
+ /// Read and sanitize the arguments.
///
- /// Full path to the response file.
- /// Enumeration of the response file arguments.
+ /// File provided by user.
+ /// argument in the file as string.
+ /// Modified argument after sanitizing the contents of the file.
/// 0 if successful and 1 otherwise.
- ///
- private int ParseResponseFile(string fullPath, out IEnumerable responseFileArguments)
+ public bool ReadArgumentsAndSanitize(string fileName, out string args, out string[] arguments)
{
- int result = 0;
- List lines = new List();
- try
+ arguments = null;
+ if (GetContentUsingFile(fileName, out args))
{
- using (var reader = new StreamReader(
- new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read),
- detectEncodingFromByteOrderMarks: true))
- {
- string str;
- while ((str = reader.ReadLine()) != null)
- {
- lines.Add(str);
- }
- }
-
- responseFileArguments = ParseResponseLines(lines);
+ return true;
}
- catch (Exception)
+
+ if (string.IsNullOrEmpty(args))
{
- this.Output.Error(false, string.Format(CultureInfo.CurrentCulture, CommandLineResources.OpenResponseFileError, fullPath));
- responseFileArguments = new string[0];
- result = 1;
+ return false;
}
- return result;
+ return CommandLineUtilities.SplitCommandLineIntoArguments(args, out arguments);
}
- ///
- /// Take a string of lines from a response file, remove comments,
- /// and split into a set of command line arguments.
- ///
- ///
- private static IEnumerable ParseResponseLines(IEnumerable lines)
+ private bool GetContentUsingFile(string fileName, out string contents)
{
- List arguments = new List();
-
- foreach (string line in lines)
+ contents = null;
+ try
+ {
+ contents = File.ReadAllText(fileName);
+ }
+ catch (Exception e)
{
- arguments.AddRange(CommandLineUtilities.SplitCommandLineIntoArguments(line, removeHashComments: true));
+ EqtTrace.Verbose("Executor.Execute: Exiting with exit code of {0}", 1);
+ EqtTrace.Error(string.Format(CultureInfo.InvariantCulture, "Error: Can't open command line argument file '{0}' : '{1}'", fileName, e.Message));
+ this.Output.Error(false, string.Format(CultureInfo.CurrentCulture, CommandLineResources.OpenResponseFileError, fileName));
+ return true;
}
- return arguments;
+ return false;
}
#endregion
}
-}
+}
\ No newline at end of file
diff --git a/test/Microsoft.TestPlatform.Utilities.UnitTests/CommandLineUtilitiesTest.cs b/test/Microsoft.TestPlatform.Utilities.UnitTests/CommandLineUtilitiesTest.cs
index 5af7b6cd26..f63ec574b6 100644
--- a/test/Microsoft.TestPlatform.Utilities.UnitTests/CommandLineUtilitiesTest.cs
+++ b/test/Microsoft.TestPlatform.Utilities.UnitTests/CommandLineUtilitiesTest.cs
@@ -3,18 +3,15 @@
namespace Microsoft.TestPlatform.Utilities.Tests
{
- using System.Linq;
-
using Microsoft.VisualStudio.TestPlatform.Utilities;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class CommandLineUtilitiesTest
{
- ///
- private void VerifyCommandLineSplitter(string commandLine, string[] expected, bool removeHashComments = false)
+ private void VerifyCommandLineSplitter(string commandLine, string[] expected)
{
- var actual = CommandLineUtilities.SplitCommandLineIntoArguments(commandLine, removeHashComments).ToArray();
+ CommandLineUtilities.SplitCommandLineIntoArguments(commandLine, out var actual);
Assert.AreEqual(expected.Length, actual.Length);
for (int i = 0; i < actual.Length; ++i)
@@ -23,31 +20,13 @@ private void VerifyCommandLineSplitter(string commandLine, string[] expected, bo
}
}
- ///
[TestMethod]
public void TestCommandLineSplitter()
{
VerifyCommandLineSplitter("", new string[0]);
- VerifyCommandLineSplitter(" \t ", new string[0]);
- VerifyCommandLineSplitter(" abc\tdef baz quuz ", new[] { "abc", "def", "baz", "quuz" });
- VerifyCommandLineSplitter(@" ""abc def"" fi""ddle dee de""e ""hi there ""dude he""llo there"" ",
- new string[] { @"abc def", @"fi""ddle dee de""e", @"""hi there ""dude", @"he""llo there""" });
- VerifyCommandLineSplitter(@" ""abc def \"" baz quuz"" ""\""straw berry"" fi\""zz \""buzz fizzbuzz",
- new string[] { @"abc def \"" baz quuz", @"\""straw berry", @"fi\""zz", @"\""buzz", @"fizzbuzz" });
- VerifyCommandLineSplitter(@" \\""abc def"" \\\""abc def"" ",
- new string[] { @"\\""abc def""", @"\\\""abc", @"def"" " });
- VerifyCommandLineSplitter(@" \\\\""abc def"" \\\\\""abc def"" ",
- new string[] { @"\\\\""abc def""", @"\\\\\""abc", @"def"" " });
- VerifyCommandLineSplitter(@" \\\\""abc def"" \\\\\""abc def"" q a r ",
- new string[] { @"\\\\""abc def""", @"\\\\\""abc", @"def"" q a r " });
- VerifyCommandLineSplitter(@"abc #Comment ignored",
- new string[] { @"abc" }, removeHashComments: true);
- VerifyCommandLineSplitter(@"""foo bar"";""baz"" ""tree""",
- new string[] { @"""foo bar"";""baz""", "tree" });
- VerifyCommandLineSplitter(@"/reference:""a, b"" ""test""",
- new string[] { @"/reference:""a, b""", "test" });
- VerifyCommandLineSplitter(@"fo""o ba""r",
- new string[] { @"fo""o ba""r" });
+ VerifyCommandLineSplitter("/testadapterpath:\"c:\\Path\"", new[] { @"/testadapterpath:c:\Path" });
+ VerifyCommandLineSplitter("/testadapterpath:\"c:\\Path\" /logger:\"trx\"", new[] { @"/testadapterpath:c:\Path", "/logger:trx" });
+ VerifyCommandLineSplitter("/testadapterpath:\"c:\\Path\" /logger:\"trx\" /diag:\"log.txt\"", new[] { @"/testadapterpath:c:\Path", "/logger:trx", "/diag:log.txt" });
}
}
}