master
h 267 lines 13.6 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 ParserTestCases.h
8
9 Abstract:
10
11 X-macro definitions for WSLC CLI parser test cases.
12
13 --*/
14
15 #pragma once
16
17 #include "Argument.h"
18 #include "ArgumentTypes.h"
19 #include <string>
20 #include <vector>
21
22 // ArgumentSet enum - defines which set of arguments to use for parsing
23 enum class ArgumentSet
24 {
25 Run,
26 List,
27 Build,
28 // RootCommand globals; parsed in optionsOnly mode (stops at first positional).
29 Globals,
30 };
31
32 // ParserTestCase - represents a single test case
33 struct ParserTestCase
34 {
35 ArgumentSet argumentSet;
36 bool expectedResult;
37 std::wstring commandLine;
38 };
39
40 // True for argument sets that mirror the root-level "global options" parsing
41 // pass in Main.cpp, which uses optionsOnly=true so the parser stops at the
42 // first positional / subcommand token without consuming it.
43 inline bool IsOptionsOnlySet(ArgumentSet argumentSet)
44 {
45 return argumentSet == ArgumentSet::Globals;
46 }
47
48 // Function to get the argument definitions for a given ArgumentSet
49 inline std::vector<wsl::windows::wslc::Argument> GetArgumentsForSet(ArgumentSet argumentSet)
50 {
51 using namespace wsl::windows::wslc;
52 using namespace wsl::windows::wslc::argument;
53 using Argument = wsl::windows::wslc::Argument;
54
55 switch (argumentSet)
56 {
57 case ArgumentSet::Run:
58 return {
59 Argument::Create(ArgType::ImageId, {.Required = true}), // Required positional argument
60 Argument::Create(ArgType::Command), // Optional positional argument
61 Argument::Create(ArgType::ForwardArgs),
62 Argument::Create(ArgType::Help),
63 Argument::Create(ArgType::Interactive),
64 Argument::Create(ArgType::Verbose),
65 Argument::Create(ArgType::Remove),
66 Argument::Create(ArgType::Signal),
67 Argument::Create(ArgType::Time),
68 Argument::Create(ArgType::Publish, {.Limit = Limit::Unlimited}), // Not required, unlimited.
69 };
70
71 case ArgumentSet::List:
72 return {
73 Argument::Create(ArgType::ContainerId, {.Limit = Limit::Unlimited}), // Optional positional
74 Argument::Create(ArgType::Help),
75 Argument::Create(ArgType::Verbose),
76 };
77
78 case ArgumentSet::Build:
79 // Mirrors ImageBuildCommand::GetArguments() so the parser tests exercise the
80 // real `wslc build` option set (notably the --progress value option).
81 return {
82 Argument::Create(ArgType::Path, {.Required = true}), // Required positional (build context path)
83 Argument::Create(ArgType::BuildArg, {.Limit = Limit::Unlimited}),
84 Argument::Create(ArgType::BuildPull),
85 Argument::Create(ArgType::BuildTarget),
86 Argument::Create(ArgType::File),
87 Argument::Create(ArgType::Label, {.Limit = Limit::Unlimited}),
88 Argument::Create(ArgType::NoCache),
89 Argument::Create(ArgType::Output),
90 Argument::Create(ArgType::Progress),
91 Argument::Create(ArgType::Secret, {.Limit = Limit::Unlimited}),
92 Argument::Create(ArgType::Tag, {.Limit = Limit::Unlimited}),
93 Argument::Create(ArgType::Verbose),
94 Argument::Create(ArgType::Help),
95 };
96
97 case ArgumentSet::Globals:
98 // Synthetic stand-in for what Main.cpp passes as cliGlobals to the
99 // first (optionsOnly) parse pass. Decoupled from RootCommand so the
100 // parser tests stay stable as the production global set evolves.
101 // Quiet (Flag with alias) and Session (Value, no alias) are convenient
102 // existing ArgTypes that together exercise both kinds in the global
103 // parsing path; the cases below treat them as "the global options".
104 return {
105 Argument::Create(ArgType::Quiet),
106 Argument::Create(ArgType::Session),
107 };
108
109 default:
110 return {};
111 }
112 }
113 // X-macro format: WSLC_PARSER_TEST_CASE(ArgumentSetValue, ExpectedResult, CommandLine)
114 // ArgumentSetValue: Just the enum value name (e.g., Run), without ArgumentSet:: prefix
115 // ExpectedResult: true if test should succeed, false if it should fail
116 // CommandLine: The command line string to test
117
118 // clang-format off
119 #define WSLC_PARSER_TEST_CASES \
120 /* Simple case with required arg and simple other args */ \
121 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -?)") \
122 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc image1)") \
123 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --verbose image1)") \
124 \
125 /* Value tests, flag and non-flag, multi-value */ \
126 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --publish=80:80 image1)") \
127 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --publish 80:80 image1)") \
128 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -p=80:80 image1)") \
129 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -p 80:80 image1)") \
130 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -p 80:80 -p 443:443 image1)") \
131 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -p=80:80 -p=443:443 image1)") \
132 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --verbose --verbose image1)") \
133 \
134 /* Single-value args are last-wins (docker-style): repeating a single-value arg is legal \
135 * (no longer a "too many" error); the final occurrence overwrites earlier ones. Contrast \
136 * with --publish above, which is unlimited and accumulates. Named, adjoined, alias, and \
137 * interleaved forms all collapse to one value. */ \
138 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --signal 9 --signal 1 image1)") \
139 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --signal=9 --signal=1 image1)") \
140 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -t 5 -t 10 image1)") \
141 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --signal 9 -t 5 --signal 1 image1)") \
142 \
143 /* Flag parse tests */ \
144 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -? image1)") \
145 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -?i image1)") \
146 WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc -i?p- image1)") \
147 WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc -pi? image1)") \
148 WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc -pi?=80:80 image1)") \
149 WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc -pi? 80:80 image1)") \
150 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -i?p 80:80 image1)") \
151 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -i?p=80:80 image1)") \
152 \
153 /* Validation tests */ \
154 WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc --signal FOO image1)") \
155 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --signal 9 image1)") \
156 WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc -t blah image1)") \
157 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -t 5 image1)") \
158 \
159 /* Multi-positional tests */ \
160 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc image1 command)") \
161 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc image1 command --f -z forward hello world)") \
162 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc image1 command forward hello world)") \
163 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc image1 command forward"hello world")") \
164 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc image1 command f="hello world" forward echo)") \
165 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --verbose image1 command f="hello world" forward echo)") \
166 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc image1 \\command\\?"" --f -z forward hello world)") \
167 \
168 /* Once the image name is parsed, the next token becomes the optional <command> positional \
169 * and everything after that goes into ForwardArgs. Neither <command> nor ForwardArgs are \
170 * interpreted as wslc options. The second case uses '\' + newline between tokens, which \
171 * CommandLineToArgvW passes through as literal '\' tokens that the container shell \
172 * handles correctly. */ \
173 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc jrottenberg/ffmpeg:4.4-alpine ffmpeg -i http://url/to/media.mp4 -stats)") \
174 WSLC_PARSER_TEST_CASE(Run, true, L"wslc jrottenberg/ffmpeg:4.4-alpine \\\nffmpeg \\\n-i http://url/to/media.mp4 \\\n-stats") \
175 \
176 /* Stdin dash ('-') as a positional value. A lone '-' conventionally means stdin and must \
177 * be accepted as a valid positional rather than treated as an invalid flag specifier. */ \
178 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -)") \
179 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc - command)") \
180 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --verbose -)") \
181 \
182 /* List cases with multiple args and flags that can come after the optional multi-positional. */ \
183 WSLC_PARSER_TEST_CASE(List, true, LR"(wslc)") \
184 WSLC_PARSER_TEST_CASE(List, true, LR"(wslc cont1)") \
185 WSLC_PARSER_TEST_CASE(List, true, LR"(wslc cont1 cont2)") \
186 WSLC_PARSER_TEST_CASE(List, true, LR"(wslc --verbose cont1)") \
187 WSLC_PARSER_TEST_CASE(List, true, LR"(wslc --verbose cont1 cont2)") \
188 WSLC_PARSER_TEST_CASE(List, true, LR"(wslc cont1 --verbose cont2)") \
189 WSLC_PARSER_TEST_CASE(List, true, LR"(wslc cont1 cont2 --verbose)") \
190 \
191 /* Failure List cases */ \
192 WSLC_PARSER_TEST_CASE(List, false, LR"(wslc --invalidarg)") \
193 WSLC_PARSER_TEST_CASE(List, false, LR"(wslc --invalidarg cont1)") \
194 WSLC_PARSER_TEST_CASE(List, false, LR"(wslc -i cont1 cont2)") \
195 WSLC_PARSER_TEST_CASE(List, false, LR"(wslc -vp cont1)") \
196 WSLC_PARSER_TEST_CASE(List, false, LR"(wslc cont1 -v cont2 -12)") \
197 WSLC_PARSER_TEST_CASE(List, true, LR"(wslc cont1 --verbose=false cont2)") \
198 WSLC_PARSER_TEST_CASE(List, false, LR"(wslc cont1 --verbose=invalid cont2)") \
199 WSLC_PARSER_TEST_CASE(List, false, LR"(wslc cont1 cont2 --invalidarg)") \
200 \
201 /* Boolean flag value tests: named and alias forms accept true/false/1/0 (case-insensitive), \
202 * and reject non-boolean tokens. Adjoined false forms store the flag with an explicit false \
203 * value (so it reads back via GetValue) and parsing still succeeds. Avoid --rm / changing the \
204 * image1 positional so the harness spot-checks below stay valid. */ \
205 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --interactive=false image1)") \
206 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --interactive=true image1)") \
207 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --interactive=0 image1)") \
208 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --interactive=1 image1)") \
209 WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc --interactive=maybe image1)") \
210 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -i=false image1)") \
211 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -i=true image1)") \
212 WSLC_PARSER_TEST_CASE(Run, false, LR"(wslc -i=nope image1)") \
213 /* Docker parity: a boolean flag never consumes the following token, so "true" here is the \
214 * image positional rather than the flag's value. */ \
215 WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --interactive true)") \
216 WSLC_PARSER_TEST_CASE(List, true, LR"(wslc --verbose=0 cont1)") \
217 WSLC_PARSER_TEST_CASE(List, true, LR"(wslc --verbose=1 cont1)") \
218 WSLC_PARSER_TEST_CASE(List, true, LR"(wslc cont1 --verbose=FALSE cont2)") \
219 \
220 /* Root-level globals: strict optionsOnly parsing. Stops cleanly at the first \
221 * non-option token; recognized globals before that are consumed. Production \
222 * uses an additional stopOnUnknown bool that is covered separately by \
223 * OptionsOnly_StopOnUnknown_LeavesTokenForCaller. The Globals set is a \
224 * synthetic mix of Quiet (Flag) and Session (Value) — see GetArgumentsForSet \
225 * — so these cases test the parser, not RootCommand's current global set. */ \
226 WSLC_PARSER_TEST_CASE(Globals, true, LR"(wslc)") \
227 /* Flag-kind global: long, short, and stops at positional/subcommand. */ \
228 WSLC_PARSER_TEST_CASE(Globals, true, LR"(wslc --quiet)") \
229 WSLC_PARSER_TEST_CASE(Globals, true, LR"(wslc -q)") \
230 WSLC_PARSER_TEST_CASE(Globals, true, LR"(wslc --quiet image1)") \
231 WSLC_PARSER_TEST_CASE(Globals, true, LR"(wslc -q image1)") \
232 WSLC_PARSER_TEST_CASE(Globals, true, LR"(wslc image1)") \
233 WSLC_PARSER_TEST_CASE(Globals, true, LR"(wslc --quiet system list)") \
234 WSLC_PARSER_TEST_CASE(Globals, true, LR"(wslc system --verbose)") \
235 /* Value-kind global: separated and adjoined value forms, then positional. */ \
236 WSLC_PARSER_TEST_CASE(Globals, true, LR"(wslc --session foo)") \
237 WSLC_PARSER_TEST_CASE(Globals, true, LR"(wslc --session=foo)") \
238 WSLC_PARSER_TEST_CASE(Globals, true, LR"(wslc --session foo image1)") \
239 WSLC_PARSER_TEST_CASE(Globals, true, LR"(wslc --session=foo image1)") \
240 WSLC_PARSER_TEST_CASE(Globals, true, LR"(wslc --session foo system list)") \
241 /* Mixed Flag + Value globals before the first positional. */ \
242 WSLC_PARSER_TEST_CASE(Globals, true, LR"(wslc --quiet --session foo image1)") \
243 WSLC_PARSER_TEST_CASE(Globals, true, LR"(wslc --session foo -q image1)") \
244 /* Docker-style idempotency: duplicate global flags collapse to a single entry. */ \
245 WSLC_PARSER_TEST_CASE(Globals, true, LR"(wslc --quiet --quiet)") \
246 WSLC_PARSER_TEST_CASE(Globals, true, LR"(wslc -q -q system list)") \
247 \
248 /* `wslc build` --progress option (Build set mirrors ImageBuildCommand). The build \
249 * context path is the required positional; --progress takes one of auto/tty/plain/ \
250 * quiet and is validated by Argument::Validate via GetProgressModeFromString. */ \
251 /* Valid modes, separated and adjoined value forms, and case-sensitivity. */ \
252 WSLC_PARSER_TEST_CASE(Build, true, LR"(wslc . --progress=auto)") \
253 WSLC_PARSER_TEST_CASE(Build, true, LR"(wslc . --progress auto)") \
254 WSLC_PARSER_TEST_CASE(Build, true, LR"(wslc --progress=tty .)") \
255 WSLC_PARSER_TEST_CASE(Build, true, LR"(wslc . --progress=plain)") \
256 WSLC_PARSER_TEST_CASE(Build, true, LR"(wslc . --progress quiet)") \
257 /* Values are case-sensitive (lowercase only), matching Docker and --format. */ \
258 WSLC_PARSER_TEST_CASE(Build, false, LR"(wslc . --progress=TTY)") \
259 /* A build with no --progress at all is valid (the option is optional). */ \
260 WSLC_PARSER_TEST_CASE(Build, true, LR"(wslc .)") \
261 /* Invalid / unrecognized modes, empty value, and missing value at end of input. */ \
262 WSLC_PARSER_TEST_CASE(Build, false, LR"(wslc . --progress=fancy)") \
263 WSLC_PARSER_TEST_CASE(Build, false, LR"(wslc . --progress bogus)") \
264 WSLC_PARSER_TEST_CASE(Build, false, LR"(wslc . --progress=json)") \
265 WSLC_PARSER_TEST_CASE(Build, false, LR"(wslc . --progress=)") \
266 WSLC_PARSER_TEST_CASE(Build, false, LR"(wslc . --progress)")
267 // clang-format on