diff --git a/scripts/test/argument-forwarding-tests.ps1 b/scripts/test/argument-forwarding-tests.ps1
new file mode 100644
index 000000000..8316aa200
--- /dev/null
+++ b/scripts/test/argument-forwarding-tests.ps1
@@ -0,0 +1,37 @@
+#
+# Copyright (c) .NET Foundation and contributors. All rights reserved.
+# Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#
+
+. "$PSScriptRoot\..\common\_common.ps1"
+
+$TestPackagesPath = "$RepoRoot\tests\packages"
+
+$ArgTestRoot = "$RepoRoot\test\ArgumentForwardingTests"
+$ArgTestBinRoot = "$RepoRoot\artifacts\tests\arg-forwarding"
+
+dotnet publish --framework "dnxcore50" --runtime "$Rid" --output "$ArgTestBinRoot" --configuration "$Configuration" "$ArgTestRoot\Reflector"
+if (!$?) {
+ Write-Host Command failed: dotnet publish --framework "dnxcore50" --runtime "$Rid" --output "$ArgTestBinRoot" --configuration "$Configuration" "$ArgTestRoot\Reflector"
+ Exit 1
+}
+
+dotnet publish --framework "dnxcore50" --runtime "$Rid" --output "$ArgTestBinRoot" --configuration "$Configuration" "$ArgTestRoot\ArgumentForwardingTests"
+if (!$?) {
+ Write-Host Command failed: dotnet publish --framework "dnxcore50" --runtime "$Rid" --output "$ArgTestBinRoot" --configuration "$Configuration" "$ArgTestRoot\ArgumentForwardingTests"
+ Exit 1
+}
+
+cp "$ArgTestRoot\Reflector\reflector_cmd.cmd" "$ArgTestBinRoot"
+
+pushd $ArgTestBinRoot
+
+& ".\corerun" "xunit.console.netcore.exe" "ArgumentForwardingTests.dll" -xml "$_-testResults.xml" -notrait category=failing
+$exitCode = $LastExitCode
+
+popd
+
+# No need to output here, we'll get test results
+if ($exitCode -ne 0) {
+ Exit 1
+}
\ No newline at end of file
diff --git a/scripts/test/argument-forwarding-tests.sh b/scripts/test/argument-forwarding-tests.sh
new file mode 100644
index 000000000..008caee92
--- /dev/null
+++ b/scripts/test/argument-forwarding-tests.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) .NET Foundation and contributors. All rights reserved.
+# Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#
+
+set -e
+
+SOURCE="${BASH_SOURCE[0]}"
+while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
+ DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
+ SOURCE="$(readlink "$SOURCE")"
+ [[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
+done
+
+DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
+
+source "$DIR/../common/_common.sh"
+
+ArgTestRoot = "$REPOROOT/test/ArgumentForwardingTests"
+ArgTestBinRoot="$REPOROOT/artifacts/tests/arg-forwarding"
+
+dotnet publish --framework "dnxcore50" --runtime "$RID" --output "$ArgTestBinRoot" --configuration "$CONFIGURATION" "$ArgTestRoot/Reflector"
+dotnet publish --framework "dnxcore50" --runtime "$RID" --output "$ArgTestBinRoot" --configuration "$CONFIGURATION" "$ArgTestRoot/ArgumentForwardingTests"
+
+cp "$ArgTestRoot/Reflector/reflector_cmd.cmd" "$ArgTestBinRoot"
+
+pushd $TestBinRoot
+./corerun "xunit.console.netcore.exe" "ArgumentForwardingTests.dll" -xml "ArgumentForwardingTests-testResults.xml" -notrait category=failing
+popd
\ No newline at end of file
diff --git a/scripts/test/runtests.ps1 b/scripts/test/runtests.ps1
index 3c6ca293b..84d91b76a 100644
--- a/scripts/test/runtests.ps1
+++ b/scripts/test/runtests.ps1
@@ -17,6 +17,11 @@ $TestProjects = @(
"Microsoft.DotNet.Tools.Builder.Tests"
)
+$TestScripts = @(
+ "package-command-test.ps1",
+ "argument-forwarding-tests.ps1"
+)
+
# Publish each test project
$TestProjects | ForEach-Object {
dotnet publish --framework "dnxcore50" --runtime "$Rid" --output "$TestBinRoot" --configuration "$Configuration" "$RepoRoot\test\$_"
@@ -64,11 +69,14 @@ $TestProjects | ForEach-Object {
popd
-& $RepoRoot\scripts\test\package-command-test.ps1
-$exitCode = $LastExitCode
-if ($exitCode -ne 0) {
+$TestScripts | ForEach-Object {
+ & "$RepoRoot\scripts\test\$_"
+ $exitCode = $LastExitCode
+ if ($exitCode -ne 0) {
+ $failingTests += "$_"
+ }
+
$failCount += 1
- $failingTests += "package-command-test"
}
if ($failCount -ne 0) {
diff --git a/scripts/test/runtests.sh b/scripts/test/runtests.sh
index c8f5d8af4..d4e8ab718 100755
--- a/scripts/test/runtests.sh
+++ b/scripts/test/runtests.sh
@@ -27,6 +27,11 @@ TestProjects=( \
Microsoft.DotNet.Tools.Builder.Tests \
)
+TestScripts=( \
+ "package-command-test.sh" \
+ "argument-forwarding-tests.sh" \
+)
+
for project in ${TestProjects[@]}
do
dotnet publish --framework "dnxcore50" --output "$TestBinRoot" --configuration "$CONFIGURATION" "$REPOROOT/test/$project"
@@ -58,11 +63,15 @@ do
fi
done
-"$REPOROOT/scripts/test/package-command-test.sh"
-if [ $? -ne 0 ]; then
+for script in ${TestScripts[@]}
+do
+ "$REPOROOT/scripts/test/$script"
+ exitCode=$?
failCount+=1
- failedTests+=("package-command-test.sh")
-fi
+ if [ $exitCode -ne 0 ]; then
+ failedTests+=($script)
+ fi
+done
for test in ${failedTests[@]}
do
diff --git a/test/ArgumentForwardingTests/ArgumentForwardingTests/ArgumentForwardingTests.cs b/test/ArgumentForwardingTests/ArgumentForwardingTests/ArgumentForwardingTests.cs
new file mode 100644
index 000000000..aba6e17de
--- /dev/null
+++ b/test/ArgumentForwardingTests/ArgumentForwardingTests/ArgumentForwardingTests.cs
@@ -0,0 +1,257 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+using Xunit;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.ProjectModel;
+using Microsoft.DotNet.Tools.Test.Utilities;
+using Microsoft.Extensions.PlatformAbstractions;
+using System.Diagnostics;
+using FluentAssertions;
+
+namespace Microsoft.DotNet.Tests.ArgumentForwarding
+{
+ public class ArgumentForwardingTests : TestBase
+ {
+ private static readonly string s_reflectorExeName = "reflector" + Constants.ExeSuffix;
+ private static readonly string s_reflectorCmdName = "reflector_cmd";
+
+ private string ReflectorPath { get; set; }
+ private string ReflectorCmdPath { get; set; }
+
+ public static void Main()
+ {
+ Console.WriteLine("Dummy Entrypoint.");
+ }
+
+ public ArgumentForwardingTests()
+ {
+ // This test has a dependency on an argument reflector
+ // Make sure it's been binplaced properly
+ FindAndEnsureReflectorPresent();
+ }
+
+ private void FindAndEnsureReflectorPresent()
+ {
+ ReflectorPath = Path.Combine(AppContext.BaseDirectory, s_reflectorExeName);
+ ReflectorCmdPath = Path.Combine(AppContext.BaseDirectory, s_reflectorCmdName);
+ File.Exists(ReflectorPath).Should().BeTrue();
+ }
+
+ ///
+ /// Tests argument forwarding in Command.Create
+ /// This is a critical scenario for the driver.
+ ///
+ ///
+ [Theory]
+ [InlineData(@"""abc"" d e")]
+ [InlineData(@"""abc"" d e")]
+ [InlineData("\"abc\"\t\td\te")]
+ [InlineData(@"a\\b d""e f""g h")]
+ [InlineData(@"\ \\ \\\")]
+ [InlineData(@"a\""b c d")]
+ [InlineData(@"a\\""b c d")]
+ [InlineData(@"a\\\""b c d")]
+ [InlineData(@"a\\\\""b c d")]
+ [InlineData(@"a\\\\""b c d")]
+ [InlineData(@"a\\\\""b c"" d e")]
+ [InlineData(@"a""b c""d e""f g""h i""j k""l")]
+ [InlineData(@"a b c""def")]
+ [InlineData(@"""\a\"" \\""\\\ b c")]
+ [InlineData(@"a\""b \\ cd ""\e f\"" \\""\\\")]
+ public void TestArgumentForwarding(string testUserArgument)
+ {
+ // Get Baseline Argument Evaluation via Reflector
+ var rawEvaluatedArgument = RawEvaluateArgumentString(testUserArgument);
+
+ // Escape and Re-Evaluate the rawEvaluatedArgument
+ var escapedEvaluatedRawArgument = EscapeAndEvaluateArgumentString(rawEvaluatedArgument);
+
+ rawEvaluatedArgument.Length.Should().Be(escapedEvaluatedRawArgument.Length);
+
+ for (int i=0; i
+ /// Tests argument forwarding in Command.Create to a cmd file
+ /// This is a critical scenario for the driver.
+ ///
+ ///
+ [Theory]
+ [InlineData(@"""abc"" d e")]
+ [InlineData(@"""abc"" d e")]
+ [InlineData("\"abc\"\t\td\te")]
+ [InlineData(@"a\\b d""e f""g h")]
+ [InlineData(@"\ \\ \\\")]
+ [InlineData(@"a\""b c d")]
+ [InlineData(@"a\\""b c d")]
+ [InlineData(@"a\\\""b c d")]
+ [InlineData(@"a\\\\""b c d")]
+ [InlineData(@"a\\\\""b c d")]
+ [InlineData(@"a\\\\""b c"" d e")]
+ [InlineData(@"a""b c""d e""f g""h i""j k""l")]
+ [InlineData(@"a b c""def")]
+ [InlineData(@"""\a\"" \\""\\\ b c")]
+ [InlineData(@"a\""b \\ cd ""\e f\"" \\""\\\")]
+ public void TestArgumentForwardingCmd(string testUserArgument)
+ {
+ // Get Baseline Argument Evaluation via Reflector
+ // This does not need to be different for cmd because
+ // it only establishes what the string[] args should be
+ var rawEvaluatedArgument = RawEvaluateArgumentString(testUserArgument);
+
+ // Escape and Re-Evaluate the rawEvaluatedArgument
+ var escapedEvaluatedRawArgument = EscapeAndEvaluateArgumentStringCmd(rawEvaluatedArgument);
+
+ try
+ {
+ rawEvaluatedArgument.Length.Should().Be(escapedEvaluatedRawArgument.Length);
+ }
+ catch(Exception e)
+ {
+ Console.WriteLine("Argument Lists differ in length.");
+
+ var expected = string.Join(",", rawEvaluatedArgument);
+ var actual = string.Join(",", escapedEvaluatedRawArgument);
+ Console.WriteLine($"Expected: {expected}");
+ Console.WriteLine($"Actual: {actual}");
+
+ throw e;
+ }
+
+ for (int i = 0; i < rawEvaluatedArgument.Length; ++i)
+ {
+ var rawArg = rawEvaluatedArgument[i];
+ var escapedArg = escapedEvaluatedRawArgument[i];
+
+ try
+ {
+ rawArg.Should().Be(escapedArg);
+ }
+ catch(Exception e)
+ {
+ Console.WriteLine($"Expected: {rawArg}");
+ Console.WriteLine($"Actual: {escapedArg}");
+ throw e;
+ }
+ }
+ }
+
+ ///
+ /// EscapeAndEvaluateArgumentString returns a representation of string[] args
+ /// when rawEvaluatedArgument is passed as an argument to a process using
+ /// Command.Create(). Ideally this should escape the argument such that
+ /// the output is == rawEvaluatedArgument.
+ ///
+ /// A string[] representing string[] args as already evaluated by a process
+ ///
+ private string[] EscapeAndEvaluateArgumentString(string[] rawEvaluatedArgument)
+ {
+ var commandResult = Command.Create(ReflectorPath, rawEvaluatedArgument)
+ .CaptureStdErr()
+ .CaptureStdOut()
+ .Execute();
+
+ commandResult.ExitCode.Should().Be(0);
+
+ return ParseReflectorOutput(commandResult.StdOut);
+ }
+
+ ///
+ /// EscapeAndEvaluateArgumentString returns a representation of string[] args
+ /// when rawEvaluatedArgument is passed as an argument to a process using
+ /// Command.Create(). Ideally this should escape the argument such that
+ /// the output is == rawEvaluatedArgument.
+ ///
+ /// A string[] representing string[] args as already evaluated by a process
+ ///
+ private string[] EscapeAndEvaluateArgumentStringCmd(string[] rawEvaluatedArgument)
+ {
+ var cmd = Command.Create(s_reflectorCmdName, rawEvaluatedArgument);
+ var commandResult = cmd
+ .CaptureStdErr()
+ .CaptureStdOut()
+ .Execute();
+
+ Console.WriteLine(commandResult.StdOut);
+ Console.WriteLine(commandResult.StdErr);
+
+ commandResult.ExitCode.Should().Be(0);
+
+ return ParseReflectorCmdOutput(commandResult.StdOut);
+ }
+
+ ///
+ /// Parse the output of the reflector into a string array.
+ /// Reflector output is simply string[] args written to
+ /// one string separated by commas.
+ ///
+ ///
+ ///
+ private string[] ParseReflectorOutput(string reflectorOutput)
+ {
+ return reflectorOutput.Split(',');
+ }
+
+ ///
+ /// Parse the output of the reflector into a string array.
+ /// Reflector output is simply string[] args written to
+ /// one string separated by commas.
+ ///
+ ///
+ ///
+ private string[] ParseReflectorCmdOutput(string reflectorOutput)
+ {
+ var args = reflectorOutput.Split(new string[] { "," }, StringSplitOptions.None);
+ args[args.Length-1] = args[args.Length-1].TrimEnd('\r', '\n');
+
+ // To properly pass args to cmd, quotes inside a parameter are doubled
+ // Count them as a single quote for our comparison.
+ for (int i=0; i < args.Length; ++i)
+ {
+ args[i] = args[i].Replace(@"""""", @"""");
+ }
+ return args;
+ }
+
+ ///
+ /// RawEvaluateArgumentString returns a representation of string[] args
+ /// when testUserArgument is provided (unmodified) as arguments to a c#
+ /// process.
+ ///
+ /// A test argument representing what a "user" would provide to a process
+ /// A string[] representing string[] args with the provided testUserArgument
+ private string[] RawEvaluateArgumentString(string testUserArgument)
+ {
+ var proc = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = s_reflectorExeName,
+ Arguments = testUserArgument,
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ CreateNoWindow = true
+ }
+ };
+
+ proc.Start();
+ var stdOut = proc.StandardOutput.ReadToEnd();
+
+ Assert.Equal(0, proc.ExitCode);
+
+ return ParseReflectorOutput(stdOut);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/ArgumentForwardingTests/ArgumentForwardingTests/ArgumentForwardingTests.xproj b/test/ArgumentForwardingTests/ArgumentForwardingTests/ArgumentForwardingTests.xproj
new file mode 100644
index 000000000..c3bee218e
--- /dev/null
+++ b/test/ArgumentForwardingTests/ArgumentForwardingTests/ArgumentForwardingTests.xproj
@@ -0,0 +1,18 @@
+
+
+
+ 14.0.24720
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ 6973e08d-11ec-49dc-82ef-d5effec7c6e9
+ Microsoft.DotNet.Tests.ArgumentForwarding
+ ..\..\artifacts\obj\$(MSBuildProjectName)
+ ..\..\artifacts\bin\$(MSBuildProjectName)\
+
+
+ 2.0
+
+
+
\ No newline at end of file
diff --git a/test/ArgumentForwardingTests/ArgumentForwardingTests/project.json b/test/ArgumentForwardingTests/ArgumentForwardingTests/project.json
new file mode 100644
index 000000000..432e463a9
--- /dev/null
+++ b/test/ArgumentForwardingTests/ArgumentForwardingTests/project.json
@@ -0,0 +1,24 @@
+{
+ "version": "1.0.0-*",
+ "compilationOptions": {
+ "emitEntryPoint": true
+ },
+
+ "dependencies": {
+ "NETStandard.Library" : "1.0.0-rc2-23706",
+ "Microsoft.NETCore.TestHost": "1.0.0-rc2-23706",
+
+ "xunit": "2.1.0",
+ "xunit.console.netcore": "1.0.2-prerelease-00101",
+ "xunit.netcore.extensions": "1.0.0-prerelease-*",
+ "xunit.runner.utility": "2.1.0",
+
+ "Microsoft.DotNet.ProjectModel": { "target": "project" },
+ "Microsoft.DotNet.Cli.Utils": { "target": "project" },
+ "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }
+ },
+
+ "frameworks": {
+ "dnxcore50": { }
+ }
+}
diff --git a/test/ArgumentForwardingTests/Reflector/Reflector.cs b/test/ArgumentForwardingTests/Reflector/Reflector.cs
new file mode 100644
index 000000000..1af26f716
--- /dev/null
+++ b/test/ArgumentForwardingTests/Reflector/Reflector.cs
@@ -0,0 +1,38 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+using Xunit;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.ProjectModel;
+using Microsoft.DotNet.Tools.Test.Utilities;
+using Microsoft.Extensions.PlatformAbstractions;
+using System.Diagnostics;
+using FluentAssertions;
+
+namespace Microsoft.DotNet.Tests.ArgumentForwarding
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ bool first=true;
+ foreach (var arg in args)
+ {
+ if (first)
+ {
+ first=false;
+ }
+ else
+ {
+ Console.Write(",");
+ }
+ Console.Write(arg);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/ArgumentForwardingTests/Reflector/Reflector.xproj b/test/ArgumentForwardingTests/Reflector/Reflector.xproj
new file mode 100644
index 000000000..24635bf0b
--- /dev/null
+++ b/test/ArgumentForwardingTests/Reflector/Reflector.xproj
@@ -0,0 +1,18 @@
+
+
+
+ 14.0.24720
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ 6f2e6f25-b43b-495d-9ca5-7f207d1dd604
+ Microsoft.DotNet.Tests.ArgumentForwarding
+ ..\..\artifacts\obj\$(MSBuildProjectName)
+ ..\..\artifacts\bin\$(MSBuildProjectName)\
+
+
+ 2.0
+
+
+
\ No newline at end of file
diff --git a/test/ArgumentForwardingTests/Reflector/project.json b/test/ArgumentForwardingTests/Reflector/project.json
new file mode 100644
index 000000000..432e463a9
--- /dev/null
+++ b/test/ArgumentForwardingTests/Reflector/project.json
@@ -0,0 +1,24 @@
+{
+ "version": "1.0.0-*",
+ "compilationOptions": {
+ "emitEntryPoint": true
+ },
+
+ "dependencies": {
+ "NETStandard.Library" : "1.0.0-rc2-23706",
+ "Microsoft.NETCore.TestHost": "1.0.0-rc2-23706",
+
+ "xunit": "2.1.0",
+ "xunit.console.netcore": "1.0.2-prerelease-00101",
+ "xunit.netcore.extensions": "1.0.0-prerelease-*",
+ "xunit.runner.utility": "2.1.0",
+
+ "Microsoft.DotNet.ProjectModel": { "target": "project" },
+ "Microsoft.DotNet.Cli.Utils": { "target": "project" },
+ "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }
+ },
+
+ "frameworks": {
+ "dnxcore50": { }
+ }
+}
diff --git a/test/ArgumentForwardingTests/Reflector/reflector_cmd.cmd b/test/ArgumentForwardingTests/Reflector/reflector_cmd.cmd
new file mode 100644
index 000000000..2d4ab84c9
--- /dev/null
+++ b/test/ArgumentForwardingTests/Reflector/reflector_cmd.cmd
@@ -0,0 +1,19 @@
+@echo off
+set ALL_ARGS=
+set a=%1
+if not defined a goto :doneSetArgs
+
+set ALL_ARGS=%~1%
+shift
+
+:setArgs
+set a=%1
+if not defined a goto :doneSetArgs
+
+set ALL_ARGS=%ALL_ARGS%,%~1%
+shift
+goto setArgs
+
+:doneSetArgs
+echo %ALL_ARGS%
+goto :EOF