Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
This commit is contained in:
Youssef Victor 2024-11-20 09:17:46 +01:00 коммит произвёл GitHub
Родитель 981c70fc1c
Коммит 01609e7871
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
20 изменённых файлов: 528 добавлений и 46 удалений

Просмотреть файл

@ -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 &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>

Просмотреть файл

@ -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 na 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