mirror of
https://github.com/microsoft/WSL.git
synced 2026-04-27 06:53:05 -05:00
CLI: Fix forwarded args beginning with '-' from being a parser error (#40131)
This commit is contained in:
@@ -173,7 +173,10 @@ ParseArgumentsStateMachine::State ParseArgumentsStateMachine::ProcessAnchoredPos
|
||||
if ((m_executionArgs.Count(m_anchorPositional.value().Type()) < m_anchorPositional.value().Limit()) ||
|
||||
(m_anchorPositional.value().Limit() == NO_LIMIT))
|
||||
{
|
||||
// validate that we dont have any invalid argument specifiers.
|
||||
// Validate that we don't have any invalid argument specifiers.
|
||||
// Anchor positionals with multiple values should be order-independent, which means a
|
||||
// '-' at the start of the first one would be invalid, so it should also be invalid for
|
||||
// all other anchor positionals of the same type.
|
||||
if (!currArg.empty() && currArg[0] == WSLC_CLI_ARG_ID_CHAR)
|
||||
{
|
||||
return ArgumentException(Localization::WSLCCLI_InvalidArgumentSpecifierError(currArg));
|
||||
@@ -192,12 +195,6 @@ ParseArgumentsStateMachine::State ParseArgumentsStateMachine::ProcessAnchoredPos
|
||||
const Argument* nextPositional = NextPositional();
|
||||
if (nextPositional)
|
||||
{
|
||||
// validate that we dont have any invalid argument specifiers.
|
||||
if (!currArg.empty() && currArg[0] == WSLC_CLI_ARG_ID_CHAR)
|
||||
{
|
||||
return ArgumentException(Localization::WSLCCLI_InvalidArgumentSpecifierError(currArg));
|
||||
}
|
||||
|
||||
m_executionArgs.Add(nextPositional->Type(), std::wstring{currArg});
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -55,6 +55,19 @@ COMMAND_LINE_TEST_CASE(L"container list --format json", L"list", true)
|
||||
COMMAND_LINE_TEST_CASE(L"container list --format table", L"list", true)
|
||||
COMMAND_LINE_TEST_CASE(L"container list --format badformat", L"list", false)
|
||||
COMMAND_LINE_TEST_CASE(L"run ubuntu", L"run", true)
|
||||
COMMAND_LINE_TEST_CASE(L"run --rm -it --entrypoint bash archlinux:latest -c \"echo 123\"", L"run", true)
|
||||
COMMAND_LINE_TEST_CASE(L"run --rm --entrypoint /bin/bash debian:latest -c ls", L"run", true)
|
||||
COMMAND_LINE_TEST_CASE(L"run jrottenberg/ffmpeg:4.4-alpine -i http://url/to/media.mp4 -stats", L"run", true)
|
||||
COMMAND_LINE_TEST_CASE(
|
||||
L"run -v ${PWD}:/data jrottenberg/ffmpeg:4.4-scratch -stats -i http://www.hevc-10bit.mkv -c:v libx265 -pix_fmt yuv420p10 -t "
|
||||
L"5 -f mp4 test.mp4",
|
||||
L"run",
|
||||
true)
|
||||
COMMAND_LINE_TEST_CASE(
|
||||
L"run -v ${PWD}:/data -it jrottenberg/ffmpeg:4.4-scratch -stats -i https://file-examples/file_example_MP4_480_1_5MG.mp4 -c:v "
|
||||
L"libx265 -pix_fmt yuv420p10 -t 5 -f mp4 /dataout.mp4",
|
||||
L"run",
|
||||
true)
|
||||
COMMAND_LINE_TEST_CASE(L"container run ubuntu bash -c 'echo Hello World'", L"run", true)
|
||||
COMMAND_LINE_TEST_CASE(L"container run ubuntu", L"run", true)
|
||||
COMMAND_LINE_TEST_CASE(L"container run -it --name foo ubuntu", L"run", true)
|
||||
@@ -77,7 +90,6 @@ COMMAND_LINE_TEST_CASE(L"exec --workdir /app cont1 echo Hello", L"exec", true)
|
||||
COMMAND_LINE_TEST_CASE(L"exec -w /app cont1 echo Hello", L"exec", true)
|
||||
COMMAND_LINE_TEST_CASE(L"container exec --workdir /app cont1 sh", L"exec", true)
|
||||
COMMAND_LINE_TEST_CASE(L"exec --workdir", L"exec", false) // Missing value for --workdir
|
||||
COMMAND_LINE_TEST_CASE(L"exec cont1 --workdir", L"exec", false) // Invalid argument specifier after container id
|
||||
COMMAND_LINE_TEST_CASE(L"exec --workdir \"\" cont1 echo Hello", L"exec", false) // Empty working directory
|
||||
COMMAND_LINE_TEST_CASE(L"kill cont1 --signal sigkill", L"kill", true)
|
||||
COMMAND_LINE_TEST_CASE(L"container kill cont1 -s KILL", L"kill", true)
|
||||
|
||||
@@ -44,8 +44,8 @@ inline std::vector<wsl::windows::wslc::Argument> GetArgumentsForSet(ArgumentSet
|
||||
{
|
||||
case ArgumentSet::Run:
|
||||
return {
|
||||
Argument::Create(ArgType::ContainerId, true), // Required positional argument
|
||||
Argument::Create(ArgType::Command, false), // Optional positional argument
|
||||
Argument::Create(ArgType::ImageId, true), // Required positional argument
|
||||
Argument::Create(ArgType::Command, false), // Optional positional argument
|
||||
Argument::Create(ArgType::ForwardArgs, false),
|
||||
Argument::Create(ArgType::Help),
|
||||
Argument::Create(ArgType::Interactive),
|
||||
@@ -76,42 +76,50 @@ inline std::vector<wsl::windows::wslc::Argument> GetArgumentsForSet(ArgumentSet
|
||||
#define WSLC_PARSER_TEST_CASES \
|
||||
/* Simple case with required arg and simple other args */ \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -h)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc cont1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --verbose cont1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc image1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --verbose image1)") \
|
||||
\
|
||||
/* Value tests, flag and non-flag, multi-value */ \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --publish=80:80 cont1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --publish 80:80 cont1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -p=80:80 cont1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -p 80:80 cont1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -p 80:80 -p 443:443 cont1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -p=80:80 -p=443:443 cont1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc --verbose --verbose cont1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --publish=80:80 image1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --publish 80:80 image1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -p=80:80 image1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -p 80:80 image1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -p 80:80 -p 443:443 image1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -p=80:80 -p=443:443 image1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc --verbose --verbose image1)") \
|
||||
\
|
||||
/* Flag parse tests */ \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -h cont1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -hi cont1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc -ihp- cont1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc -pih cont1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc -pih=80:80 cont1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc -pih 80:80 cont1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -ihp 80:80 cont1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -ihp=80:80 cont1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -h image1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -hi image1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc -ihp- image1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc -pih image1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc -pih=80:80 image1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc -pih 80:80 image1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -ihp 80:80 image1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -ihp=80:80 image1)") \
|
||||
\
|
||||
/* Validation tests */ \
|
||||
WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc --signal FOO cont1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --signal 9 cont1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc -t blah)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -t 5)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc --signal FOO image1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --signal 9 image1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc -t blah image1)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -t 5 image1)") \
|
||||
\
|
||||
/* Multi-positional tests */ \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc cont1 command)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc cont1 command --f -z forward hello world)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc cont1 command forward hello world)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc cont1 command forward"hello world")") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc cont1 command f="hello world" forward echo)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc cont1 -v command f="hello world" forward echo)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc cont1 \\command\\?"" --f -z forward hello world)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc image1 command)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc image1 command --f -z forward hello world)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc image1 command forward hello world)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc image1 command forward"hello world")") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc image1 command f="hello world" forward echo)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --verbose image1 command f="hello world" forward echo)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc image1 \\command\\?"" --f -z forward hello world)") \
|
||||
\
|
||||
/* Once the image name is parsed, the next token becomes the optional <command> positional \
|
||||
* and everything after that goes into ForwardArgs. Neither <command> nor ForwardArgs are \
|
||||
* interpreted as wslc options. The second case uses '\' + newline between tokens, which \
|
||||
* CommandLineToArgvW passes through as literal '\' tokens that the container shell \
|
||||
* handles correctly. */ \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc jrottenberg/ffmpeg:4.4-alpine ffmpeg -i http://url/to/media.mp4 -stats)") \
|
||||
WSLC_PARSER_TEST_CASE(Run, true, L"wslc jrottenberg/ffmpeg:4.4-alpine \\\nffmpeg \\\n-i http://url/to/media.mp4 \\\n-stats") \
|
||||
\
|
||||
/* List cases with multiple args and flags that can come after the optional multi-positional. */ \
|
||||
WSLC_PARSER_TEST_CASE(List, true, LR"(wslc)") \
|
||||
|
||||
@@ -58,6 +58,8 @@ class WSLCCLIParserUnitTests
|
||||
|
||||
for (const auto& testCase : testCases)
|
||||
{
|
||||
bool succeeded = false;
|
||||
|
||||
try
|
||||
{
|
||||
Log::Comment(String().Format(L"Testing: %ls", testCase.commandLine.c_str()));
|
||||
@@ -73,16 +75,48 @@ class WSLCCLIParserUnitTests
|
||||
stateMachine.ThrowIfError();
|
||||
}
|
||||
|
||||
if (testCase.commandLine.find(L"cont1") != std::wstring::npos)
|
||||
// Validate count limits and required arguments, mirroring Command::ValidateArguments.
|
||||
// Skip all validation if --help is present, as Command::ValidateArguments does.
|
||||
if (!args.Contains(ArgType::Help))
|
||||
{
|
||||
for (const auto& arg : GetArgumentsForSet(testCase.argumentSet))
|
||||
{
|
||||
if (arg.Required() && !args.Contains(arg.Type()))
|
||||
{
|
||||
throw ArgumentException(std::wstring(L"Required argument missing: ") + arg.Name());
|
||||
}
|
||||
|
||||
if ((arg.Limit() > 0) && (arg.Limit() < args.Count(arg.Type())))
|
||||
{
|
||||
throw ArgumentException(std::wstring(L"Too many values for argument: ") + arg.Name());
|
||||
}
|
||||
|
||||
if (args.Contains(arg.Type()))
|
||||
{
|
||||
arg.Validate(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
succeeded = true;
|
||||
|
||||
if (testCase.commandLine.find(L"image1") != std::wstring::npos && testCase.argumentSet == ArgumentSet::Run)
|
||||
{
|
||||
VERIFY_IS_TRUE(args.Contains(ArgType::ImageId));
|
||||
auto imageId = args.Get<ArgType::ImageId>();
|
||||
VERIFY_ARE_EQUAL(L"image1", imageId);
|
||||
}
|
||||
|
||||
if (testCase.commandLine.find(L"cont1") != std::wstring::npos && testCase.argumentSet == ArgumentSet::List)
|
||||
{
|
||||
VERIFY_IS_TRUE(args.Contains(ArgType::ContainerId));
|
||||
auto containerId = args.Get<ArgType::ContainerId>();
|
||||
VERIFY_ARE_EQUAL(L"cont1", containerId);
|
||||
}
|
||||
|
||||
if (testCase.commandLine.find(L"rm") != std::wstring::npos)
|
||||
if (testCase.commandLine.find(L"--rm") != std::wstring::npos)
|
||||
{
|
||||
// Ensure 'rm' was parsed wherever it was found.
|
||||
// Ensure '--rm' was parsed wherever it was found.
|
||||
VERIFY_IS_TRUE(args.Contains(ArgType::Remove));
|
||||
}
|
||||
|
||||
@@ -99,7 +133,7 @@ class WSLCCLIParserUnitTests
|
||||
auto forwardArgs = args.Get<ArgType::ForwardArgs>();
|
||||
std::wstring forwardArgsConcat = wsl::shared::string::Join(forwardArgs, L' ');
|
||||
VERIFY_IS_TRUE(forwardArgsConcat.find(L"hello world") != std::wstring::npos); // Forward args should contain hello world
|
||||
VERIFY_IS_TRUE(forwardArgsConcat.find(L"cont1") == std::wstring::npos); // Forward args should not contain the containerId
|
||||
VERIFY_IS_TRUE(forwardArgsConcat.find(L"image1") == std::wstring::npos); // Forward args should not contain the imageId
|
||||
VERIFY_IS_TRUE(forwardArgsConcat.find(L"command") == std::wstring::npos); // Forward args should not contain the command
|
||||
LogComment(L"Forwarded Args: " + forwardArgsConcat);
|
||||
}
|
||||
@@ -134,6 +168,8 @@ class WSLCCLIParserUnitTests
|
||||
Log::Comment(String().Format(L"Test case threw expected exception: %hs", ex.what()));
|
||||
}
|
||||
}
|
||||
|
||||
VERIFY_ARE_EQUAL(testCase.expectedResult, succeeded, String().Format(L"Command line: %ls", testCase.commandLine.c_str()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user