Serenity Operating System
1/*
2 * Copyright (c) 2021, the SerenityOS developers.
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/Function.h>
8#include <AK/Vector.h>
9#include <LibCore/ArgsParser.h>
10#include <LibTest/TestCase.h>
11#include <string.h>
12
13class ParserResult {
14public:
15 ParserResult(Vector<StringView> arguments)
16 : arguments(move(arguments))
17 {
18 }
19
20 ParserResult(ParserResult&& other)
21 {
22 arguments = move(other.arguments);
23 result = other.result;
24 }
25
26 ParserResult& operator=(ParserResult&& other)
27 {
28 if (this != &other) {
29 arguments = move(other.arguments);
30 result = other.result;
31 }
32 return *this;
33 }
34
35 Vector<StringView> arguments;
36 bool result { false };
37};
38
39static ParserResult run_parser(Vector<StringView> arguments, Function<void(Core::ArgsParser&)> parser_initialization = {})
40{
41 Core::ArgsParser parser;
42 if (parser_initialization)
43 parser_initialization(parser);
44
45 auto parse_result = ParserResult { move(arguments) };
46 parse_result.result = parser.parse(parse_result.arguments, Core::ArgsParser::FailureBehavior::Ignore);
47 return parse_result;
48}
49
50TEST_CASE(no_arguments)
51{
52 auto parser_result = run_parser({ "app"sv });
53 EXPECT_EQ(parser_result.result, true);
54}
55
56TEST_CASE(bool_option)
57{
58 // Short option
59 bool force = false;
60 auto parser_result = run_parser({ "app"sv, "-f"sv }, [&](auto& parser) {
61 parser.add_option(force, "force", nullptr, 'f');
62 });
63 EXPECT_EQ(parser_result.result, true);
64 EXPECT_EQ(force, true);
65
66 // Short option, not given
67 force = false;
68 parser_result = run_parser({ "app"sv }, [&](auto& parser) {
69 parser.add_option(force, "force", nullptr, 'f');
70 });
71 EXPECT_EQ(parser_result.result, true);
72 EXPECT_EQ(force, false);
73
74 // Long option
75 force = false;
76 parser_result = run_parser({ "app"sv, "--force"sv }, [&](auto& parser) {
77 parser.add_option(force, "force", "force", '\0');
78 });
79 EXPECT_EQ(parser_result.result, true);
80 EXPECT_EQ(force, true);
81
82 // Long option, not given
83 force = false;
84 parser_result = run_parser({ "app"sv }, [&](auto& parser) {
85 parser.add_option(force, "force", "force", '\0');
86 });
87 EXPECT_EQ(parser_result.result, true);
88 EXPECT_EQ(force, false);
89
90 // Allow both short and long option, provide short
91 force = false;
92 parser_result = run_parser({ "app"sv, "-f"sv }, [&](auto& parser) {
93 parser.add_option(force, "force", "force", 'f');
94 });
95 EXPECT_EQ(parser_result.result, true);
96 EXPECT_EQ(force, true);
97
98 // Allow both short and long option, provide long
99 force = false;
100 parser_result = run_parser({ "app"sv, "--force"sv }, [&](auto& parser) {
101 parser.add_option(force, "force", "force", 'f');
102 });
103 EXPECT_EQ(parser_result.result, true);
104 EXPECT_EQ(force, true);
105
106 // Allow both short and long option, provide both
107 force = false;
108 parser_result = run_parser({ "app"sv, "--force"sv, "-f"sv }, [&](auto& parser) {
109 parser.add_option(force, "force", "force", 'f');
110 });
111 EXPECT_EQ(parser_result.result, true);
112 EXPECT_EQ(force, true);
113}
114
115TEST_CASE(positional_string_argument)
116{
117 // Single required string argument
118 DeprecatedString name = "";
119 auto parser_result = run_parser({ "app"sv, "buggie"sv }, [&](auto& parser) {
120 parser.add_positional_argument(name, "name", "name", Core::ArgsParser::Required::Yes);
121 });
122 EXPECT_EQ(parser_result.result, true);
123 EXPECT_EQ(name, "buggie");
124
125 // Single required string argument, not given
126 name = "";
127 parser_result = run_parser({ "app"sv }, [&](auto& parser) {
128 parser.add_positional_argument(name, "name", "name", Core::ArgsParser::Required::Yes);
129 });
130 EXPECT_EQ(parser_result.result, false);
131 EXPECT_EQ(name, "");
132
133 // Single optional string argument
134 name = "";
135 parser_result = run_parser({ "app"sv, "buggie"sv }, [&](auto& parser) {
136 parser.add_positional_argument(name, "name", "name", Core::ArgsParser::Required::No);
137 });
138 EXPECT_EQ(parser_result.result, true);
139 EXPECT_EQ(name, "buggie");
140
141 // Single optional string argument, not given
142 name = "";
143 parser_result = run_parser({ "app"sv }, [&](auto& parser) {
144 parser.add_positional_argument(name, "name", "name", Core::ArgsParser::Required::No);
145 });
146 EXPECT_EQ(parser_result.result, true);
147 EXPECT_EQ(name, "");
148}
149
150TEST_CASE(positional_vector_string_argument)
151{
152 Vector<StringView> values;
153
154 // Zero or more positional arguments, zero given
155 auto parser_result = run_parser({ "app"sv }, [&](auto& parser) {
156 parser.add_positional_argument(values, "values", "values", Core::ArgsParser::Required::No);
157 });
158 EXPECT_EQ(parser_result.result, true);
159 EXPECT_EQ(values.size(), 0u);
160
161 // Zero or more positional arguments, one given
162 values = {};
163 parser_result = run_parser({ "app"sv, "one"sv }, [&](auto& parser) {
164 parser.add_positional_argument(values, "values", "values", Core::ArgsParser::Required::No);
165 });
166 EXPECT_EQ(parser_result.result, true);
167 EXPECT_EQ(values.size(), 1u);
168 if (values.size() == 1u)
169 EXPECT_EQ(values[0], "one");
170
171 // Zero or more positional arguments, two given
172 values = {};
173 parser_result = run_parser({ "app"sv, "one"sv, "two"sv }, [&](auto& parser) {
174 parser.add_positional_argument(values, "values", "values", Core::ArgsParser::Required::No);
175 });
176 EXPECT_EQ(parser_result.result, true);
177 EXPECT_EQ(values.size(), 2u);
178 if (values.size() == 2u) {
179 EXPECT_EQ(values[0], "one");
180 EXPECT_EQ(values[1], "two");
181 }
182
183 // One or more positional arguments, zero given
184 values = {};
185 parser_result = run_parser({ "app"sv }, [&](auto& parser) {
186 parser.add_positional_argument(values, "values", "values", Core::ArgsParser::Required::Yes);
187 });
188 EXPECT_EQ(parser_result.result, false);
189 EXPECT_EQ(values.size(), 0u);
190
191 // One or more positional arguments, one given
192 values = {};
193 parser_result = run_parser({ "app"sv, "one"sv }, [&](auto& parser) {
194 parser.add_positional_argument(values, "values", "values", Core::ArgsParser::Required::Yes);
195 });
196 EXPECT_EQ(parser_result.result, true);
197 EXPECT_EQ(values.size(), 1u);
198 if (values.size() == 1u)
199 EXPECT_EQ(values[0], "one");
200
201 // One or more positional arguments, two given
202 values = {};
203 parser_result = run_parser({ "app"sv, "one"sv, "two"sv }, [&](auto& parser) {
204 parser.add_positional_argument(values, "values", "values", Core::ArgsParser::Required::Yes);
205 });
206 EXPECT_EQ(parser_result.result, true);
207 EXPECT_EQ(values.size(), 2u);
208 if (values.size() == 2u) {
209 EXPECT_EQ(values[0], "one");
210 EXPECT_EQ(values[1], "two");
211 }
212}
213
214TEST_CASE(combination_of_bool_options_with_positional_vector_string)
215{
216 Vector<StringView> positionals;
217 // Bool options (given) and positional arguments (given)
218 // Expected: all arguments fill as given
219 bool bool_opt1 = false;
220 bool bool_opt2 = false;
221 auto parser_result = run_parser({ "app"sv, "-b"sv, "-c"sv, "one"sv, "two"sv }, [&](auto& parser) {
222 parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b');
223 parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c');
224 parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::No);
225 });
226 EXPECT_EQ(parser_result.result, true);
227 EXPECT_EQ(bool_opt1, true);
228 EXPECT_EQ(bool_opt2, true);
229 EXPECT_EQ(positionals.size(), 2u);
230 if (positionals.size() == 2u) {
231 EXPECT_EQ(positionals[0], "one");
232 EXPECT_EQ(positionals[1], "two");
233 }
234
235 // Bool options (missing) and positional arguments (given)
236 // Expected: only the positional arguments are filled
237 bool_opt1 = false;
238 bool_opt2 = false;
239 positionals = {};
240 parser_result = run_parser({ "app"sv, "one"sv, "two"sv }, [&](auto& parser) {
241 parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b');
242 parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c');
243 parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::No);
244 });
245 EXPECT_EQ(parser_result.result, true);
246 EXPECT_EQ(bool_opt1, false);
247 EXPECT_EQ(bool_opt2, false);
248 EXPECT_EQ(positionals.size(), 2u);
249 if (positionals.size() == 2u) {
250 EXPECT_EQ(positionals[0], "one");
251 EXPECT_EQ(positionals[1], "two");
252 }
253
254 // Bool options (given) and positional arguments (missing)
255 // Expected: only the bool options are filled
256 bool_opt1 = false;
257 bool_opt2 = false;
258 positionals = {};
259 parser_result = run_parser({ "app"sv, "-b"sv, "-c"sv }, [&](auto& parser) {
260 parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b');
261 parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c');
262 parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::No);
263 });
264 EXPECT_EQ(parser_result.result, true);
265 EXPECT_EQ(bool_opt1, true);
266 EXPECT_EQ(bool_opt2, true);
267 EXPECT_EQ(positionals.size(), 0u);
268
269 // Bool options (missing) and positional arguments (given) using double dash
270 // Expected: the bool options are interpreted as positional arguments
271 bool_opt1 = false;
272 bool_opt2 = false;
273 positionals = {};
274 parser_result = run_parser({ "app"sv, "--"sv, "-b"sv, "-c"sv }, [&](auto& parser) {
275 parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b');
276 parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c');
277 parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::No);
278 });
279 EXPECT_EQ(parser_result.result, true);
280 EXPECT_EQ(bool_opt1, false);
281 EXPECT_EQ(bool_opt2, false);
282 EXPECT_EQ(positionals.size(), 2u);
283 if (positionals.size() == 2u) {
284 EXPECT_EQ(positionals[0], "-b");
285 EXPECT_EQ(positionals[1], "-c");
286 }
287
288 // Bool options (one given) and positional arguments (one given) using double dash
289 // Expected: bool_opt1 is set, one positional is added
290 bool_opt1 = false;
291 bool_opt2 = false;
292 positionals = {};
293 parser_result = run_parser({ "app"sv, "-b"sv, "--"sv, "-c"sv }, [&](auto& parser) {
294 parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b');
295 parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c');
296 parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::No);
297 });
298 EXPECT_EQ(parser_result.result, true);
299 EXPECT_EQ(bool_opt1, true);
300 EXPECT_EQ(bool_opt2, false);
301 EXPECT_EQ(positionals.size(), 1u);
302 if (positionals.size() == 1u) {
303 EXPECT_EQ(positionals[0], "-c");
304 }
305
306 // Bool options (three given, one incorrect) and positional arguments (missing)
307 // Expected: parser fails
308 bool_opt1 = false;
309 bool_opt2 = false;
310 positionals = {};
311 parser_result = run_parser({ "app"sv, "-b"sv, "-d"sv, "-c"sv }, [&](auto& parser) {
312 parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b');
313 parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c');
314 parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::No);
315 });
316 EXPECT_EQ(parser_result.result, false);
317};
318
319TEST_CASE(stop_on_first_non_option)
320{
321 // Do not stop on first non-option; arguments in correct order
322 // Expected: bool options are set and one positional argument is filled
323 bool bool_opt1 = false;
324 bool bool_opt2 = false;
325 Vector<StringView> positionals;
326 auto parser_result = run_parser({ "app"sv, "-b"sv, "-c"sv, "one"sv }, [&](auto& parser) {
327 parser.set_stop_on_first_non_option(false);
328 parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b');
329 parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c');
330 parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::Yes);
331 });
332 EXPECT_EQ(parser_result.result, true);
333 EXPECT_EQ(bool_opt1, true);
334 EXPECT_EQ(bool_opt2, true);
335 EXPECT_EQ(positionals.size(), 1u);
336 if (positionals.size() == 1u)
337 EXPECT_EQ(positionals[0], "one");
338
339 // Do not stop on first non-option; arguments in wrong order
340 // Expected: bool options are set and one positional argument is filled
341 bool_opt1 = false;
342 bool_opt2 = false;
343 positionals = {};
344 parser_result = run_parser({ "app"sv, "-b"sv, "one"sv, "-c"sv }, [&](auto& parser) {
345 parser.set_stop_on_first_non_option(false);
346 parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b');
347 parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c');
348 parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::Yes);
349 });
350 EXPECT_EQ(parser_result.result, true);
351 EXPECT_EQ(bool_opt1, true);
352 EXPECT_EQ(bool_opt2, true);
353 EXPECT_EQ(positionals.size(), 1u);
354 if (positionals.size() == 1u)
355 EXPECT_EQ(positionals[0], "one");
356
357 // Stop on first non-option; arguments in correct order
358 // Expected: bool options are set and one positional argument is filled
359 bool_opt1 = false;
360 bool_opt2 = false;
361 positionals = {};
362 parser_result = run_parser({ "app"sv, "-b"sv, "-c"sv, "one"sv }, [&](auto& parser) {
363 parser.set_stop_on_first_non_option(true);
364 parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b');
365 parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c');
366 parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::Yes);
367 });
368 EXPECT_EQ(parser_result.result, true);
369 EXPECT_EQ(bool_opt1, true);
370 EXPECT_EQ(bool_opt2, true);
371 EXPECT_EQ(positionals.size(), 1u);
372 if (positionals.size() == 1u)
373 EXPECT_EQ(positionals[0], "one");
374
375 // Stop on first non-option; arguments in wrong order
376 // Expected: bool_opt1 is set, other arguments are filled as positional arguments
377 bool_opt1 = false;
378 bool_opt2 = false;
379 positionals = {};
380 parser_result = run_parser({ "app"sv, "-b"sv, "one"sv, "-c"sv }, [&](auto& parser) {
381 parser.set_stop_on_first_non_option(true);
382 parser.add_option(bool_opt1, "bool_opt1", nullptr, 'b');
383 parser.add_option(bool_opt2, "bool_opt2", nullptr, 'c');
384 parser.add_positional_argument(positionals, "pos", "pos", Core::ArgsParser::Required::Yes);
385 });
386 EXPECT_EQ(parser_result.result, true);
387 EXPECT_EQ(bool_opt1, true);
388 EXPECT_EQ(bool_opt2, false);
389 EXPECT_EQ(positionals.size(), 2u);
390 if (positionals.size() == 2u) {
391 EXPECT_EQ(positionals[0], "one");
392 EXPECT_EQ(positionals[1], "-c");
393 }
394}