Skip to content

Commit

Permalink
Support rsp in MTP (#4072)
Browse files Browse the repository at this point in the history
Co-authored-by: Amaury Levé <[email protected]>
  • Loading branch information
Youssef1313 and Evangelink authored Nov 20, 2024
1 parent 981c70f commit 01609e7
Show file tree
Hide file tree
Showing 20 changed files with 528 additions and 46 deletions.
46 changes: 42 additions & 4 deletions src/Platform/Microsoft.Testing.Platform/CommandLine/ParseResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text;

namespace Microsoft.Testing.Platform.CommandLine;

internal sealed class CommandLineParseResult(string? toolName, IReadOnlyList<OptionRecord> options, IReadOnlyList<string> errors, IReadOnlyList<string> originalArguments) : IEquatable<CommandLineParseResult>
internal sealed class CommandLineParseResult(string? toolName, IReadOnlyList<OptionRecord> options, IReadOnlyList<string> errors) : IEquatable<CommandLineParseResult>
{
public const char OptionPrefix = '-';

public static CommandLineParseResult Empty => new(null, [], [], []);
public static CommandLineParseResult Empty => new(null, [], []);

public string? ToolName { get; } = toolName;

public IReadOnlyList<OptionRecord> Options { get; } = options;

public IReadOnlyList<string> Errors { get; } = errors;

public IReadOnlyList<string> OriginalArguments { get; } = originalArguments;

public bool HasError => Errors.Count > 0;

public bool HasTool => ToolName is not null;
Expand Down Expand Up @@ -122,4 +122,42 @@ public override int GetHashCode()

return hashCode.ToHashCode();
}

public override string ToString()
{
var builder = new StringBuilder();
builder.AppendLine(CultureInfo.InvariantCulture, $"ToolName: {ToolName}");

builder.AppendLine("Errors:");
if (Errors.Count == 0)
{
builder.AppendLine(" None");
}
else
{
foreach (string error in Errors)
{
builder.AppendLine(CultureInfo.InvariantCulture, $" {error}");
}
}

builder.AppendLine("Options:");
if (Options.Count == 0)
{
builder.AppendLine(" None");
}
else
{
foreach (OptionRecord option in Options)
{
builder.AppendLine(CultureInfo.InvariantCulture, $" {option.Option}");
foreach (string arg in option.Arguments)
{
builder.AppendLine(CultureInfo.InvariantCulture, $" {arg}");
}
}
}

return builder.ToString();
}
}
13 changes: 11 additions & 2 deletions src/Platform/Microsoft.Testing.Platform/CommandLine/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ internal static class CommandLineParser
/// * A POSIX convention lets you omit the delimiter when you are specifying a single-character option alias, i.e. myapp -vquiet.
/// </summary>
public static CommandLineParseResult Parse(string[] args, IEnvironment environment)
=> Parse(args.ToList(), environment);

private static CommandLineParseResult Parse(List<string> args, IEnvironment environment)
{
List<OptionRecord> options = [];
List<string> errors = [];
Expand All @@ -41,8 +44,14 @@ public static CommandLineParseResult Parse(string[] args, IEnvironment environme
string? currentArg = null;
string? toolName = null;
List<string> currentOptionArguments = [];
for (int i = 0; i < args.Length; i++)
for (int i = 0; i < args.Count; i++)
{
if (args[i].StartsWith('@') && ResponseFileHelper.TryReadResponseFile(args[i].Substring(1), errors, out string[]? newArguments))
{
args.InsertRange(i + 1, newArguments);
continue;
}

bool argumentHandled = false;
currentArg = args[i];

Expand Down Expand Up @@ -118,7 +127,7 @@ public static CommandLineParseResult Parse(string[] args, IEnvironment environme
options.Add(new(currentOption, currentOptionArguments.ToArray()));
}

return new CommandLineParseResult(toolName, options, errors, args);
return new CommandLineParseResult(toolName, options, errors);

static void ParseOptionAndSeparators(string arg, out string? currentOption, out string? currentArg)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Diagnostics.CodeAnalysis;
using System.Globalization;

using Microsoft.Testing.Platform.Resources;

// Most of the core logic is from:
// - https://github.com/dotnet/command-line-api/blob/feb61c7f328a2401d74f4317b39d02126cfdfe24/src/System.CommandLine/Parsing/CliParser.cs#L40
// - https://github.com/dotnet/command-line-api/blob/feb61c7f328a2401d74f4317b39d02126cfdfe24/src/System.CommandLine/Parsing/StringExtensions.cs#L349
internal static class ResponseFileHelper
{
internal static bool TryReadResponseFile(string rspFilePath, ICollection<string> errors, [NotNullWhen(true)] out string[]? newArguments)
{
try
{
newArguments = ExpandResponseFile(rspFilePath).ToArray();
return true;
}
catch (FileNotFoundException)
{
errors.Add(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineParserResponseFileNotFound, rspFilePath));
}
catch (IOException e)
{
errors.Add(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineParserFailedToReadResponseFile, rspFilePath, e.ToString()));
}

newArguments = null;
return false;

// Local functions
static IEnumerable<string> ExpandResponseFile(string filePath)
{
string[] lines = File.ReadAllLines(filePath);

for (int i = 0; i < lines.Length; i++)
{
string line = lines[i];

foreach (string p in SplitLine(line))
{
yield return p;
}
}
}

static IEnumerable<string> SplitLine(string line)
{
string arg = line.Trim();

if (arg.Length == 0 || arg[0] == '#')
{
yield break;
}

foreach (string word in SplitCommandLine(arg))
{
yield return word;
}
}
}

private enum Boundary
{
TokenStart,
WordEnd,
QuoteStart,
QuoteEnd,
}

public static IEnumerable<string> SplitCommandLine(string commandLine)
{
int startTokenIndex = 0;

int pos = 0;

Boundary seeking = Boundary.TokenStart;
Boundary seekingQuote = Boundary.QuoteStart;

while (pos < commandLine.Length)
{
char c = commandLine[pos];

if (char.IsWhiteSpace(c))
{
if (seekingQuote == Boundary.QuoteStart)
{
switch (seeking)
{
case Boundary.WordEnd:
yield return CurrentToken();
startTokenIndex = pos;
seeking = Boundary.TokenStart;
break;

case Boundary.TokenStart:
startTokenIndex = pos;
break;
}
}
}
else if (c == '\"')
{
if (seeking == Boundary.TokenStart)
{
switch (seekingQuote)
{
case Boundary.QuoteEnd:
yield return CurrentToken();
startTokenIndex = pos;
seekingQuote = Boundary.QuoteStart;
break;

case Boundary.QuoteStart:
startTokenIndex = pos + 1;
seekingQuote = Boundary.QuoteEnd;
break;
}
}
else
{
switch (seekingQuote)
{
case Boundary.QuoteEnd:
seekingQuote = Boundary.QuoteStart;
break;

case Boundary.QuoteStart:
seekingQuote = Boundary.QuoteEnd;
break;
}
}
}
else if (seeking == Boundary.TokenStart && seekingQuote == Boundary.QuoteStart)
{
seeking = Boundary.WordEnd;
startTokenIndex = pos;
}

Advance();

if (IsAtEndOfInput())
{
switch (seeking)
{
case Boundary.TokenStart:
break;
default:
yield return CurrentToken();
break;
}
}
}

void Advance() => pos++;

string CurrentToken() => commandLine.Substring(startTokenIndex, IndexOfEndOfToken()).ToString().Replace("\"", string.Empty);

int IndexOfEndOfToken() => pos - startTokenIndex;

bool IsAtEndOfInput() => pos == commandLine.Length;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -655,4 +655,11 @@ Takes one argument as string in the format &lt;value&gt;[h|m|s] where 'value' is
<data name="ExitCode" xml:space="preserve">
<value>Exit code</value>
</data>
<data name="CommandLineParserResponseFileNotFound" xml:space="preserve">
<value>The response file '{0}' was not found</value>
</data>
<data name="CommandLineParserFailedToReadResponseFile" xml:space="preserve">
<value>Failed to read response file '{0}'. {1}.</value>
<comment>{1} is the exception</comment>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@
<target state="translated">Rozhraní ICommandLineOptions ještě není sestavené.</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserFailedToReadResponseFile">
<source>Failed to read response file '{0}'. {1}.</source>
<target state="new">Failed to read response file '{0}'. {1}.</target>
<note>{1} is the exception</note>
</trans-unit>
<trans-unit id="CommandLineParserResponseFileNotFound">
<source>The response file '{0}' was not found</source>
<target state="new">The response file '{0}' was not found</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedArgument">
<source>Unexpected argument {0}</source>
<target state="translated">Neočekávaný argument {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@
<target state="translated">ICommandLineOptions wurde noch nicht erstellt.</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserFailedToReadResponseFile">
<source>Failed to read response file '{0}'. {1}.</source>
<target state="new">Failed to read response file '{0}'. {1}.</target>
<note>{1} is the exception</note>
</trans-unit>
<trans-unit id="CommandLineParserResponseFileNotFound">
<source>The response file '{0}' was not found</source>
<target state="new">The response file '{0}' was not found</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedArgument">
<source>Unexpected argument {0}</source>
<target state="translated">Unerwartetes Argument {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@
<target state="translated">ICommandLineOptions aún no se ha compilado.</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserFailedToReadResponseFile">
<source>Failed to read response file '{0}'. {1}.</source>
<target state="new">Failed to read response file '{0}'. {1}.</target>
<note>{1} is the exception</note>
</trans-unit>
<trans-unit id="CommandLineParserResponseFileNotFound">
<source>The response file '{0}' was not found</source>
<target state="new">The response file '{0}' was not found</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedArgument">
<source>Unexpected argument {0}</source>
<target state="translated">Argumento inesperado {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@
<target state="translated">ICommandLineOptions n’a pas encore été généré.</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserFailedToReadResponseFile">
<source>Failed to read response file '{0}'. {1}.</source>
<target state="new">Failed to read response file '{0}'. {1}.</target>
<note>{1} is the exception</note>
</trans-unit>
<trans-unit id="CommandLineParserResponseFileNotFound">
<source>The response file '{0}' was not found</source>
<target state="new">The response file '{0}' was not found</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedArgument">
<source>Unexpected argument {0}</source>
<target state="translated">Arguments inattendue {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@
<target state="translated">ICommandLineOptions non è stato ancora compilato.</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserFailedToReadResponseFile">
<source>Failed to read response file '{0}'. {1}.</source>
<target state="new">Failed to read response file '{0}'. {1}.</target>
<note>{1} is the exception</note>
</trans-unit>
<trans-unit id="CommandLineParserResponseFileNotFound">
<source>The response file '{0}' was not found</source>
<target state="new">The response file '{0}' was not found</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedArgument">
<source>Unexpected argument {0}</source>
<target state="translated">Argomento imprevisto {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@
<target state="translated">ICommandLineOptions はまだ構築されていません。</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserFailedToReadResponseFile">
<source>Failed to read response file '{0}'. {1}.</source>
<target state="new">Failed to read response file '{0}'. {1}.</target>
<note>{1} is the exception</note>
</trans-unit>
<trans-unit id="CommandLineParserResponseFileNotFound">
<source>The response file '{0}' was not found</source>
<target state="new">The response file '{0}' was not found</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedArgument">
<source>Unexpected argument {0}</source>
<target state="translated">予期しない引数 {0}</target>
Expand Down
Loading

0 comments on commit 01609e7

Please sign in to comment.