2016-01-22 14:02:08 -08:00
// 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 ;
2016-04-07 21:54:59 -05:00
using System.Linq ;
2016-01-22 14:02:08 -08:00
using System.Runtime.InteropServices ;
using System.Text ;
using Xunit ;
using Microsoft.DotNet.Cli.Utils ;
using Microsoft.DotNet.Tools.Test.Utilities ;
using System.Diagnostics ;
using FluentAssertions ;
namespace Microsoft.DotNet.Tests.ArgumentForwarding
{
public class ArgumentForwardingTests : TestBase
{
2016-04-07 21:54:59 -05:00
private static readonly string s_reflectorDllName = "ArgumentsReflector.dll" ;
2016-01-22 14:02:08 -08:00
private static readonly string s_reflectorCmdName = "reflector_cmd" ;
private string ReflectorPath { get ; set ; }
private string ReflectorCmdPath { get ; set ; }
public ArgumentForwardingTests ( )
{
// This test has a dependency on an argument reflector
// Make sure it's been binplaced properly
FindAndEnsureReflectorPresent ( ) ;
}
private void FindAndEnsureReflectorPresent ( )
{
2016-04-07 21:54:59 -05:00
ReflectorPath = Path . Combine ( AppContext . BaseDirectory , s_reflectorDllName ) ;
2016-01-22 14:02:08 -08:00
ReflectorCmdPath = Path . Combine ( AppContext . BaseDirectory , s_reflectorCmdName ) ;
File . Exists ( ReflectorPath ) . Should ( ) . BeTrue ( ) ;
}
/// <summary>
/// Tests argument forwarding in Command.Create
/// This is a critical scenario for the driver.
/// </summary>
/// <param name="testUserArgument"></param>
[Theory]
[InlineData(@"""abc"" d e")]
2016-04-20 12:43:34 -07:00
[InlineData(@"""ábc"" d é")]
2016-01-22 14:02:08 -08:00
[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 < rawEvaluatedArgument . Length ; + + i )
{
var rawArg = rawEvaluatedArgument [ i ] ;
var escapedArg = escapedEvaluatedRawArgument [ i ] ;
rawArg . Should ( ) . Be ( escapedArg ) ;
}
}
/// <summary>
/// Tests argument forwarding in Command.Create to a cmd file
/// This is a critical scenario for the driver.
/// </summary>
/// <param name="testUserArgument"></param>
2017-02-07 10:08:14 -08:00
[WindowsOnlyTheory]
2016-01-22 14:02:08 -08:00
[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 e")]
[InlineData(@"a""b c""d e""f g""h i""j k""l")]
[InlineData(@"a b c""def")]
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 ] ;
2016-02-11 16:57:32 -08:00
2016-01-22 14:02:08 -08:00
try
{
rawArg . Should ( ) . Be ( escapedArg ) ;
}
catch ( Exception e )
{
Console . WriteLine ( $"Expected: {rawArg}" ) ;
Console . WriteLine ( $"Actual: {escapedArg}" ) ;
throw e ;
}
}
}
2017-02-07 10:08:14 -08:00
[WindowsOnlyTheory]
2016-02-09 15:30:04 -08:00
[InlineData(@"a\""b c d")]
[InlineData(@"a\\\""b c d")]
[InlineData(@"""\a\"" \\""\\\ b c")]
[InlineData(@"a\""b \\ cd ""\e f\"" \\""\\\")]
public void TestArgumentForwardingCmdFailsWithUnbalancedQuote ( string testArgString )
{
// 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 ( testArgString ) ;
// Escape and Re-Evaluate the rawEvaluatedArgument
var escapedEvaluatedRawArgument = EscapeAndEvaluateArgumentStringCmd ( rawEvaluatedArgument ) ;
rawEvaluatedArgument . Length . Should ( ) . NotBe ( escapedEvaluatedRawArgument . Length ) ;
}
2016-01-22 14:02:08 -08:00
/// <summary>
/// EscapeAndEvaluateArgumentString returns a representation of string[] args
/// when rawEvaluatedArgument is passed as an argument to a process using
2016-02-11 16:57:32 -08:00
/// Command.Create(). Ideally this should escape the argument such that
2016-01-22 14:02:08 -08:00
/// the output is == rawEvaluatedArgument.
/// </summary>
/// <param name="rawEvaluatedArgument">A string[] representing string[] args as already evaluated by a process</param>
/// <returns></returns>
private string [ ] EscapeAndEvaluateArgumentString ( string [ ] rawEvaluatedArgument )
{
2016-04-07 21:54:59 -05:00
var commandResult = Command . Create ( "dotnet" , new [ ] { ReflectorPath } . Concat ( rawEvaluatedArgument ) )
2016-01-22 14:02:08 -08:00
. CaptureStdErr ( )
. CaptureStdOut ( )
. Execute ( ) ;
2017-03-15 15:17:37 -07:00
Console . WriteLine ( $"STDOUT: {commandResult.StdOut}" ) ;
Console . WriteLine ( $"STDERR: {commandResult.StdErr}" ) ;
2016-01-22 14:02:08 -08:00
commandResult . ExitCode . Should ( ) . Be ( 0 ) ;
return ParseReflectorOutput ( commandResult . StdOut ) ;
}
/// <summary>
/// EscapeAndEvaluateArgumentString returns a representation of string[] args
/// when rawEvaluatedArgument is passed as an argument to a process using
2016-02-11 16:57:32 -08:00
/// Command.Create(). Ideally this should escape the argument such that
2016-01-22 14:02:08 -08:00
/// the output is == rawEvaluatedArgument.
/// </summary>
/// <param name="rawEvaluatedArgument">A string[] representing string[] args as already evaluated by a process</param>
/// <returns></returns>
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 ) ;
}
/// <summary>
/// Parse the output of the reflector into a string array.
2016-02-11 16:57:32 -08:00
/// Reflector output is simply string[] args written to
2016-01-22 14:02:08 -08:00
/// one string separated by commas.
/// </summary>
/// <param name="reflectorOutput"></param>
/// <returns></returns>
private string [ ] ParseReflectorOutput ( string reflectorOutput )
{
2016-02-05 12:29:16 -08:00
return reflectorOutput . TrimEnd ( '\r' , '\n' ) . Split ( ',' ) ;
2016-01-22 14:02:08 -08:00
}
/// <summary>
/// Parse the output of the reflector into a string array.
2016-02-11 16:57:32 -08:00
/// Reflector output is simply string[] args written to
2016-01-22 14:02:08 -08:00
/// one string separated by commas.
/// </summary>
/// <param name="reflectorOutput"></param>
/// <returns></returns>
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 ;
}
/// <summary>
/// RawEvaluateArgumentString returns a representation of string[] args
/// when testUserArgument is provided (unmodified) as arguments to a c#
/// process.
/// </summary>
/// <param name="testUserArgument">A test argument representing what a "user" would provide to a process</param>
/// <returns>A string[] representing string[] args with the provided testUserArgument</returns>
private string [ ] RawEvaluateArgumentString ( string testUserArgument )
{
var proc = new Process
{
StartInfo = new ProcessStartInfo
{
2017-03-19 19:16:27 -07:00
FileName = new Muxer ( ) . MuxerPath ,
2016-04-07 21:54:59 -05:00
Arguments = $"{ReflectorPath} {testUserArgument}" ,
2016-01-22 14:02:08 -08:00
UseShellExecute = false ,
RedirectStandardOutput = true ,
CreateNoWindow = true
}
} ;
proc . Start ( ) ;
2016-01-22 16:31:35 -08:00
proc . WaitForExit ( ) ;
2016-01-22 14:02:08 -08:00
var stdOut = proc . StandardOutput . ReadToEnd ( ) ;
Assert . Equal ( 0 , proc . ExitCode ) ;
return ParseReflectorOutput ( stdOut ) ;
}
}
2017-03-02 20:35:20 -08:00
}