зеркало из https://github.com/microsoft/testfx.git
Support rsp in MTP (#4072)
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
This commit is contained in:
Родитель
981c70fc1c
Коммит
01609e7871
|
@ -2,14 +2,16 @@
|
|||
// 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;
|
||||
|
||||
|
@ -17,8 +19,6 @@ internal sealed class CommandLineParseResult(string? toolName, IReadOnlyList<Opt
|
|||
|
||||
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;
|
||||
|
@ -122,4 +122,42 @@ internal sealed class CommandLineParseResult(string? toolName, IReadOnlyList<Opt
|
|||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = [];
|
||||
|
@ -41,8 +44,14 @@ internal static class CommandLineParser
|
|||
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];
|
||||
|
||||
|
@ -118,7 +127,7 @@ internal static class CommandLineParser
|
|||
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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -655,4 +655,11 @@ Takes one argument as string in the format <value>[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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -117,6 +117,16 @@
|
|||
<target state="translated">Obiekt ICommandLineOptions nie został jeszcze skompilowany.</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">Nieoczekiwany argument {0}</target>
|
||||
|
|
|
@ -117,6 +117,16 @@
|
|||
<target state="translated">O ICommandLineOptions ainda não foi criado.</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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -117,6 +117,16 @@
|
|||
<target state="translated">ICommandLineOptions henüz derlenmedi.</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} bağımsız değişkeni beklenmiyordu</target>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -243,7 +243,7 @@ public class CommandLineHandlerTests : TestBase
|
|||
// Arrange
|
||||
OptionRecord optionRecord = new("name", ["value1", "value2"]);
|
||||
CommandLineHandler commandLineHandler = new(
|
||||
new CommandLineParseResult(string.Empty, [optionRecord], [], []), _extensionCommandLineOptionsProviders,
|
||||
new CommandLineParseResult(string.Empty, [optionRecord], []), _extensionCommandLineOptionsProviders,
|
||||
_systemCommandLineOptionsProviders, _testApplicationModuleInfoMock.Object, _runtimeFeatureMock.Object);
|
||||
|
||||
// Act
|
||||
|
|
|
@ -10,65 +10,198 @@ namespace Microsoft.Testing.Platform.UnitTests;
|
|||
[TestGroup]
|
||||
public sealed class CommandLineTests : TestBase
|
||||
{
|
||||
// The test method ParserTests is parameterized and one of the parameter needs to be CommandLineParseResult.
|
||||
// The test method has to be public to be run, but CommandLineParseResult is internal.
|
||||
// So, we introduce this wrapper to be used instead so that the test method can be made public.
|
||||
public class CommandLineParseResultWrapper
|
||||
{
|
||||
internal CommandLineParseResultWrapper(string? toolName, IReadOnlyList<OptionRecord> options, IReadOnlyList<string> errors)
|
||||
=> Result = new CommandLineParseResult(toolName, options, errors);
|
||||
|
||||
internal CommandLineParseResult Result { get; }
|
||||
}
|
||||
|
||||
public CommandLineTests(ITestExecutionContext testExecutionContext)
|
||||
: base(testExecutionContext)
|
||||
{
|
||||
}
|
||||
|
||||
[ArgumentsProvider(nameof(ParserTestsData), TestArgumentsEntryProviderMethodName = nameof(ParserTestDataFormat))]
|
||||
internal void ParserTests(int testNum, string[] args, CommandLineParseResult parseResult)
|
||||
public void ParserTests(int testNum, string[] args, (string RspFileName, string RspFileContent)[]? rspFiles, CommandLineParseResultWrapper parseResultWrapper)
|
||||
{
|
||||
int? runTestNumber = null;
|
||||
|
||||
if (runTestNumber is null || runTestNumber == testNum)
|
||||
try
|
||||
{
|
||||
if (rspFiles is not null)
|
||||
{
|
||||
foreach ((string rspFileName, string rspFileContent) in rspFiles)
|
||||
{
|
||||
File.WriteAllText(rspFileName, rspFileContent);
|
||||
}
|
||||
}
|
||||
|
||||
CommandLineParseResult result = CommandLineParser.Parse(args, new SystemEnvironment());
|
||||
Assert.AreEqual(parseResult, result, $"Test num '{testNum}' failed");
|
||||
Assert.AreEqual(parseResultWrapper.Result, result, $"Test num '{testNum}' failed");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (rspFiles is not null)
|
||||
{
|
||||
foreach ((string rspFileName, _) in rspFiles)
|
||||
{
|
||||
File.Delete(rspFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static TestArgumentsEntry<(int TestNum, string[] Args, CommandLineParseResult ParseResult)> ParserTestDataFormat(TestArgumentsContext ctx)
|
||||
internal static TestArgumentsEntry<(int TestNum, string[] Args, (string RspFileName, string RspFileContent)[]? RspFiles, CommandLineParseResultWrapper ParseResult)> ParserTestDataFormat(TestArgumentsContext ctx)
|
||||
{
|
||||
(int TestNum, string[] Args, CommandLineParseResult ParseResult) item = ((int, string[], CommandLineParseResult))ctx.Arguments;
|
||||
(int TestNum, string[] Args, (string RspFileName, string RspFileContent)[]? RspFiles, CommandLineParseResultWrapper ParseResult) item = ((int, string[], (string, string)[], CommandLineParseResultWrapper))ctx.Arguments;
|
||||
|
||||
return item.TestNum == 13
|
||||
? new(item, $"\"--option1\", $@\" \"\" \\{{Environment.NewLine}} \"\" \" {item.TestNum}")
|
||||
: new(item, $"{item.Args.Aggregate((a, b) => $"{a} {b}")} {item.TestNum}");
|
||||
}
|
||||
|
||||
internal static IEnumerable<(int TestNum, string[] Args, CommandLineParseResult ParseResult)> ParserTestsData()
|
||||
internal static IEnumerable<(int TestNum, string[] Args, (string RspFileName, string RspFileContent)[]? RspFiles, CommandLineParseResultWrapper ParseResult)> ParserTestsData()
|
||||
{
|
||||
yield return (1, ["--option1", "a"], new CommandLineParseResult(null, new List<OptionRecord> { new("option1", ["a"]) }.ToArray(), [], []));
|
||||
yield return (2, ["--option1", "a", "b"], new CommandLineParseResult(null, new List<OptionRecord> { new("option1", ["a", "b"]) }.ToArray(), [], []));
|
||||
yield return (3, ["-option1", "a"], new CommandLineParseResult(null, new List<OptionRecord> { new("option1", ["a"]) }.ToArray(), [], []));
|
||||
yield return (4, ["--option1", "a", "-option2", "c"], new CommandLineParseResult(null, new List<OptionRecord>
|
||||
yield return (1, ["--option1", "a"], null, new CommandLineParseResultWrapper(null, new List<OptionRecord> { new("option1", ["a"]) }.ToArray(), []));
|
||||
yield return (2, ["--option1", "a", "b"], null, new CommandLineParseResultWrapper(null, new List<OptionRecord> { new("option1", ["a", "b"]) }.ToArray(), []));
|
||||
yield return (3, ["-option1", "a"], null, new CommandLineParseResultWrapper(null, new List<OptionRecord> { new("option1", ["a"]) }.ToArray(), []));
|
||||
yield return (4, ["--option1", "a", "-option2", "c"], null, new CommandLineParseResultWrapper(null, new List<OptionRecord>
|
||||
{
|
||||
new("option1", ["a"]),
|
||||
new("option2", ["c"]),
|
||||
}.ToArray(), [], []));
|
||||
yield return (5, ["---option1", "a"], new CommandLineParseResult(null, new List<OptionRecord>().ToArray(), ["Unexpected argument ---option1", "Unexpected argument a"], []));
|
||||
yield return (6, ["--option1", "'a'"], new CommandLineParseResult(null, new List<OptionRecord> { new("option1", ["a"]) }.ToArray(), [], []));
|
||||
yield return (7, ["--option1", "'a'", "--option2", "'hello'"], new CommandLineParseResult(null, new List<OptionRecord>
|
||||
}.ToArray(), []));
|
||||
yield return (5, ["---option1", "a"], null, new CommandLineParseResultWrapper(null, new List<OptionRecord>().ToArray(), ["Unexpected argument ---option1", "Unexpected argument a"]));
|
||||
yield return (6, ["--option1", "'a'"], null, new CommandLineParseResultWrapper(null, new List<OptionRecord> { new("option1", ["a"]) }.ToArray(), []));
|
||||
yield return (7, ["--option1", "'a'", "--option2", "'hello'"], null, new CommandLineParseResultWrapper(null, new List<OptionRecord>
|
||||
{
|
||||
new("option1", ["a"]),
|
||||
new("option2", ["hello"]),
|
||||
}.ToArray(), [], []));
|
||||
yield return (8, ["--option1", "'a'b'"], new CommandLineParseResult(null, new List<OptionRecord> { new("option1", []) }.ToArray(), ["Unexpected single quote in argument: 'a'b' for option option1"], []));
|
||||
yield return (9, ["option1", "--option1"], new CommandLineParseResult("option1", new List<OptionRecord> { new("option1", []) }.ToArray(), [], []));
|
||||
yield return (10, ["--option1", @"""\\"""], new CommandLineParseResult(null, new List<OptionRecord> { new("option1", ["\\"]) }.ToArray(), [], []));
|
||||
yield return (11, ["--option1", @" "" \"" "" "], new CommandLineParseResult(null, new List<OptionRecord> { new("option1", [" \" "]) }.ToArray(), [], []));
|
||||
yield return (12, ["--option1", @" "" \$ "" "], new CommandLineParseResult(null, new List<OptionRecord> { new("option1", [" $ "]) }.ToArray(), [], []));
|
||||
yield return (13, ["--option1", $@" "" \{Environment.NewLine} "" "], new CommandLineParseResult(null, new List<OptionRecord> { new("option1", [$" {Environment.NewLine} "]) }.ToArray(), [], []));
|
||||
yield return (14, ["--option1", "a"], new CommandLineParseResult(null, new List<OptionRecord> { new("option1", ["a"]) }.ToArray(), [], []));
|
||||
yield return (15, ["--option1:a"], new CommandLineParseResult(null, new List<OptionRecord> { new("option1", ["a"]) }.ToArray(), [], []));
|
||||
yield return (16, ["--option1=a"], new CommandLineParseResult(null, new List<OptionRecord> { new("option1", ["a"]) }.ToArray(), [], []));
|
||||
yield return (17, ["--option1=a", "--option1=b"], new CommandLineParseResult(null, new List<OptionRecord> { new("option1", ["a"]), new("option1", ["b"]) }.ToArray(), [], []));
|
||||
yield return (18, ["--option1=a", "--option1 b"], new CommandLineParseResult(null, new List<OptionRecord> { new("option1", ["a"]), new("option1", ["b"]) }.ToArray(), [], []));
|
||||
yield return (19, ["--option1=a=a"], new CommandLineParseResult(null, new List<OptionRecord> { new("option1", ["a=a"]) }.ToArray(), [], []));
|
||||
yield return (20, ["--option1=a:a"], new CommandLineParseResult(null, new List<OptionRecord> { new("option1", ["a:a"]) }.ToArray(), [], []));
|
||||
yield return (21, ["--option1:a=a"], new CommandLineParseResult(null, new List<OptionRecord> { new("option1", ["a=a"]) }.ToArray(), [], []));
|
||||
yield return (22, ["--option1:a:a"], new CommandLineParseResult(null, new List<OptionRecord> { new("option1", ["a:a"]) }.ToArray(), [], []));
|
||||
yield return (23, ["--option1:a:a", "--option1:a=a"], new CommandLineParseResult(null, new List<OptionRecord> { new("option1", ["a:a"]), new("option1", ["a=a"]) }.ToArray(), [], []));
|
||||
}.ToArray(), []));
|
||||
yield return (8, ["--option1", "'a'b'"], null, new CommandLineParseResultWrapper(null, new List<OptionRecord> { new("option1", []) }.ToArray(), ["Unexpected single quote in argument: 'a'b' for option '--option1'"]));
|
||||
yield return (9, ["option1", "--option1"], null, new CommandLineParseResultWrapper("option1", new List<OptionRecord> { new("option1", []) }.ToArray(), []));
|
||||
yield return (10, ["--option1", @"""\\"""], null, new CommandLineParseResultWrapper(null, new List<OptionRecord> { new("option1", ["\\"]) }.ToArray(), []));
|
||||
yield return (11, ["--option1", @" "" \"" "" "], null, new CommandLineParseResultWrapper(null, new List<OptionRecord> { new("option1", [" \" "]) }.ToArray(), []));
|
||||
yield return (12, ["--option1", @" "" \$ "" "], null, new CommandLineParseResultWrapper(null, new List<OptionRecord> { new("option1", [" $ "]) }.ToArray(), []));
|
||||
yield return (13, ["--option1", $@" "" \{Environment.NewLine} "" "], null, new CommandLineParseResultWrapper(null, new List<OptionRecord> { new("option1", [$" {Environment.NewLine} "]) }.ToArray(), []));
|
||||
yield return (14, ["--option1", "a"], null, new CommandLineParseResultWrapper(null, new List<OptionRecord> { new("option1", ["a"]) }.ToArray(), []));
|
||||
yield return (15, ["--option1:a"], null, new CommandLineParseResultWrapper(null, new List<OptionRecord> { new("option1", ["a"]) }.ToArray(), []));
|
||||
yield return (16, ["--option1=a"], null, new CommandLineParseResultWrapper(null, new List<OptionRecord> { new("option1", ["a"]) }.ToArray(), []));
|
||||
yield return (17, ["--option1=a", "--option1=b"], null, new CommandLineParseResultWrapper(null, new List<OptionRecord> { new("option1", ["a"]), new("option1", ["b"]) }.ToArray(), []));
|
||||
yield return (18, ["--option1=a", "--option1 b"], null, new CommandLineParseResultWrapper(null, new List<OptionRecord> { new("option1", ["a"]), new("option1", ["b"]) }.ToArray(), []));
|
||||
yield return (19, ["--option1=a=a"], null, new CommandLineParseResultWrapper(null, new List<OptionRecord> { new("option1", ["a=a"]) }.ToArray(), []));
|
||||
yield return (20, ["--option1=a:a"], null, new CommandLineParseResultWrapper(null, new List<OptionRecord> { new("option1", ["a:a"]) }.ToArray(), []));
|
||||
yield return (21, ["--option1:a=a"], null, new CommandLineParseResultWrapper(null, new List<OptionRecord> { new("option1", ["a=a"]) }.ToArray(), []));
|
||||
yield return (22, ["--option1:a:a"], null, new CommandLineParseResultWrapper(null, new List<OptionRecord> { new("option1", ["a:a"]) }.ToArray(), []));
|
||||
yield return (23, ["--option1:a:a", "--option1:a=a"], null, new CommandLineParseResultWrapper(null, new List<OptionRecord> { new("option1", ["a:a"]), new("option1", ["a=a"]) }.ToArray(), []));
|
||||
yield return (24, ["--option1", "a", "@test.rsp", "--option5", "e"], [("test.rsp",
|
||||
"""
|
||||
--option2 b
|
||||
--option3 c
|
||||
--option4 d
|
||||
""")], new CommandLineParseResultWrapper(null, new List<OptionRecord>
|
||||
{
|
||||
new("option1", ["a"]),
|
||||
new("option2", ["b"]),
|
||||
new("option3", ["c"]),
|
||||
new("option4", ["d"]),
|
||||
new("option5", ["e"]),
|
||||
}.ToArray(), []));
|
||||
yield return (25, ["--option1", "a", "@25_test1.rsp", "--option6", "f"], [
|
||||
("25_test1.rsp",
|
||||
"""
|
||||
--option2 b
|
||||
@25_test2.rsp
|
||||
--option5 e
|
||||
"""),
|
||||
("25_test2.rsp",
|
||||
"""
|
||||
--option3 c
|
||||
--option4 d
|
||||
""")], new CommandLineParseResultWrapper(null, new List<OptionRecord>
|
||||
{
|
||||
new("option1", ["a"]),
|
||||
new("option2", ["b"]),
|
||||
new("option3", ["c"]),
|
||||
new("option4", ["d"]),
|
||||
new("option5", ["e"]),
|
||||
new("option6", ["f"]),
|
||||
}.ToArray(), []));
|
||||
yield return (26, ["@26_test.rsp", "--option3", "c", "--option4", "d"], [
|
||||
("26_test.rsp",
|
||||
"""
|
||||
--option1 a
|
||||
--option2 b
|
||||
""")], new CommandLineParseResultWrapper(null, new List<OptionRecord>
|
||||
{
|
||||
new("option1", ["a"]),
|
||||
new("option2", ["b"]),
|
||||
new("option3", ["c"]),
|
||||
new("option4", ["d"]),
|
||||
}.ToArray(), []));
|
||||
yield return (27, ["--option1", "a", "--option2", "b", "@27_test.rsp"], [
|
||||
("27_test.rsp",
|
||||
"""
|
||||
--option3 c
|
||||
--option4 d
|
||||
""")], new CommandLineParseResultWrapper(null, new List<OptionRecord>
|
||||
{
|
||||
new("option1", ["a"]),
|
||||
new("option2", ["b"]),
|
||||
new("option3", ["c"]),
|
||||
new("option4", ["d"]),
|
||||
}.ToArray(), []));
|
||||
yield return (28, ["@28_test.rsp"], [
|
||||
("28_test.rsp",
|
||||
"""
|
||||
--option1 a
|
||||
--option2 b
|
||||
--option3 c
|
||||
--option4 d
|
||||
""")], new CommandLineParseResultWrapper(null, new List<OptionRecord>
|
||||
{
|
||||
new("option1", ["a"]),
|
||||
new("option2", ["b"]),
|
||||
new("option3", ["c"]),
|
||||
new("option4", ["d"]),
|
||||
}.ToArray(), []));
|
||||
yield return (29, ["@29_test1.rsp", "@29_test2.rsp"], [
|
||||
("29_test1.rsp",
|
||||
"""
|
||||
--option1 a
|
||||
--option2 b
|
||||
"""),
|
||||
("29_test2.rsp",
|
||||
"""
|
||||
--option3 c
|
||||
--option4 d
|
||||
""")], new CommandLineParseResultWrapper(null, new List<OptionRecord>
|
||||
{
|
||||
new("option1", ["a"]),
|
||||
new("option2", ["b"]),
|
||||
new("option3", ["c"]),
|
||||
new("option4", ["d"]),
|
||||
}.ToArray(), []));
|
||||
yield return (30, ["@30_test1.rsp"], [
|
||||
("30_test1.rsp",
|
||||
"""
|
||||
--option1 a
|
||||
--option2 b
|
||||
@30_test2.rsp
|
||||
"""),
|
||||
("30_test2.rsp",
|
||||
"""
|
||||
--option3 c
|
||||
--option4 d
|
||||
""")], new CommandLineParseResultWrapper(null, new List<OptionRecord>
|
||||
{
|
||||
new("option1", ["a"]),
|
||||
new("option2", ["b"]),
|
||||
new("option3", ["c"]),
|
||||
new("option4", ["d"]),
|
||||
}.ToArray(), []));
|
||||
}
|
||||
|
||||
public void CommandLineOptionWithNumber_IsSupported()
|
||||
|
|
|
@ -35,7 +35,7 @@ public class ConfigurationManagerTests : TestBase
|
|||
CurrentTestApplicationModuleInfo testApplicationModuleInfo = new(new SystemEnvironment(), new SystemProcessHandler());
|
||||
ConfigurationManager configurationManager = new(fileSystem.Object, testApplicationModuleInfo);
|
||||
configurationManager.AddConfigurationSource(() => new JsonConfigurationSource(testApplicationModuleInfo, fileSystem.Object, null));
|
||||
IConfiguration configuration = await configurationManager.BuildAsync(null, new CommandLineParseResult(null, new List<OptionRecord>(), Array.Empty<string>(), Array.Empty<string>()));
|
||||
IConfiguration configuration = await configurationManager.BuildAsync(null, new CommandLineParseResult(null, new List<OptionRecord>(), Array.Empty<string>()));
|
||||
Assert.AreEqual(result, configuration[key], $"Expected '{result}' found '{configuration[key]}'");
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,7 @@ public class ConfigurationManagerTests : TestBase
|
|||
ConfigurationManager configurationManager = new(fileSystem.Object, testApplicationModuleInfo);
|
||||
configurationManager.AddConfigurationSource(() =>
|
||||
new JsonConfigurationSource(testApplicationModuleInfo, fileSystem.Object, null));
|
||||
await Assert.ThrowsAsync<Exception>(() => configurationManager.BuildAsync(null, new CommandLineParseResult(null, new List<OptionRecord>(), Array.Empty<string>(), Array.Empty<string>())));
|
||||
await Assert.ThrowsAsync<Exception>(() => configurationManager.BuildAsync(null, new CommandLineParseResult(null, new List<OptionRecord>(), Array.Empty<string>())));
|
||||
}
|
||||
|
||||
[ArgumentsProvider(nameof(GetConfigurationValueFromJsonData))]
|
||||
|
@ -91,7 +91,7 @@ public class ConfigurationManagerTests : TestBase
|
|||
configurationManager.AddConfigurationSource(() =>
|
||||
new JsonConfigurationSource(testApplicationModuleInfo, fileSystem.Object, null));
|
||||
|
||||
IConfiguration configuration = await configurationManager.BuildAsync(loggerProviderMock.Object, new CommandLineParseResult(null, new List<OptionRecord>(), Array.Empty<string>(), Array.Empty<string>()));
|
||||
IConfiguration configuration = await configurationManager.BuildAsync(loggerProviderMock.Object, new CommandLineParseResult(null, new List<OptionRecord>(), Array.Empty<string>()));
|
||||
Assert.AreEqual(result, configuration[key], $"Expected '{result}' found '{configuration[key]}'");
|
||||
|
||||
loggerMock.Verify(x => x.LogAsync(LogLevel.Trace, It.IsAny<string>(), null, LoggingExtensions.Formatter), Times.Once);
|
||||
|
@ -101,7 +101,7 @@ public class ConfigurationManagerTests : TestBase
|
|||
{
|
||||
CurrentTestApplicationModuleInfo testApplicationModuleInfo = new(new SystemEnvironment(), new SystemProcessHandler());
|
||||
ConfigurationManager configurationManager = new(new SystemFileSystem(), testApplicationModuleInfo);
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => configurationManager.BuildAsync(null, new CommandLineParseResult(null, new List<OptionRecord>(), Array.Empty<string>(), Array.Empty<string>())));
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => configurationManager.BuildAsync(null, new CommandLineParseResult(null, new List<OptionRecord>(), Array.Empty<string>())));
|
||||
}
|
||||
|
||||
public async ValueTask BuildAsync_ConfigurationSourcesNotEnabledAsync_ThrowsException()
|
||||
|
@ -113,7 +113,7 @@ public class ConfigurationManagerTests : TestBase
|
|||
ConfigurationManager configurationManager = new(new SystemFileSystem(), testApplicationModuleInfo);
|
||||
configurationManager.AddConfigurationSource(() => mockConfigurationSource.Object);
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => configurationManager.BuildAsync(null, new CommandLineParseResult(null, new List<OptionRecord>(), Array.Empty<string>(), Array.Empty<string>())));
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => configurationManager.BuildAsync(null, new CommandLineParseResult(null, new List<OptionRecord>(), Array.Empty<string>())));
|
||||
|
||||
mockConfigurationSource.Verify(x => x.IsEnabledAsync(), Times.Once);
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ public class ConfigurationManagerTests : TestBase
|
|||
ConfigurationManager configurationManager = new(new SystemFileSystem(), testApplicationModuleInfo);
|
||||
configurationManager.AddConfigurationSource(() => fakeConfigurationSource);
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => configurationManager.BuildAsync(null, new CommandLineParseResult(null, new List<OptionRecord>(), Array.Empty<string>(), Array.Empty<string>())));
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => configurationManager.BuildAsync(null, new CommandLineParseResult(null, new List<OptionRecord>(), Array.Empty<string>())));
|
||||
}
|
||||
|
||||
private class FakeConfigurationSource : IConfigurationSource, IAsyncInitializableExtension
|
||||
|
|
Загрузка…
Ссылка в новой задаче