tap: import some changes
[argpar.git] / tests / test-argpar.c
1 /*
2 * SPDX-License-Identifier: GPL-2.0-only
3 * SPDX-FileCopyrightText: 2019-2024 Philippe Proulx <pproulx@efficios.com>
4 * SPDX-FileCopyrightText: 2020-2024 Simon Marchi <simon.marchi@efficios.com>
5 */
6
7 #include <assert.h>
8 #include <glib.h>
9 #include <stdbool.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13
14 #include "argpar/argpar.h"
15 #include "tap/tap.h"
16
17 /*
18 * Formats `item` and appends the resulting string to `res_str` to
19 * incrementally build an expected command line string.
20 *
21 * This function:
22 *
23 * ‣ Prefers the `--long-opt=arg` style over the `-s arg` style.
24 *
25 * ‣ Uses the `arg<A,B>` form for non-option arguments, where `A` is the
26 * original argument index and `B` is the non-option argument index.
27 */
28 static void append_to_res_str(GString * const res_str, const argpar_item_t * const item)
29 {
30 if (res_str->len > 0) {
31 g_string_append_c(res_str, ' ');
32 }
33
34 switch (argpar_item_type(item)) {
35 case ARGPAR_ITEM_TYPE_OPT:
36 {
37 const argpar_opt_descr_t * const descr = argpar_item_opt_descr(item);
38 const char * const arg = argpar_item_opt_arg(item);
39
40 if (descr->long_name) {
41 g_string_append_printf(res_str, "--%s", descr->long_name);
42
43 if (arg) {
44 g_string_append_printf(res_str, "=%s", arg);
45 }
46 } else if (descr->short_name) {
47 g_string_append_printf(res_str, "-%c", descr->short_name);
48
49 if (arg) {
50 g_string_append_printf(res_str, " %s", arg);
51 }
52 }
53
54 break;
55 }
56 case ARGPAR_ITEM_TYPE_NON_OPT:
57 {
58 const char * const arg = argpar_item_non_opt_arg(item);
59 const unsigned int orig_index = argpar_item_non_opt_orig_index(item);
60 const unsigned int non_opt_index = argpar_item_non_opt_non_opt_index(item);
61
62 g_string_append_printf(res_str, "%s<%u,%u>", arg, orig_index, non_opt_index);
63 break;
64 }
65 default:
66 abort();
67 }
68 }
69
70 /*
71 * Parses `cmdline` with the argpar API using the option descriptors
72 * `descrs`, and ensures that the resulting effective command line is
73 * `expected_cmd_line` and that the number of ingested original
74 * arguments is `expected_ingested_orig_args`.
75 *
76 * This function splits `cmdline` on spaces to create an original
77 * argument array.
78 *
79 * This function builds the resulting command line from parsing items
80 * by space-separating each formatted item (see append_to_res_str()).
81 */
82 static void test_succeed(const char * const cmdline, const char * const expected_cmd_line,
83 const argpar_opt_descr_t * const descrs,
84 const unsigned int expected_ingested_orig_args)
85 {
86 argpar_iter_t *iter = NULL;
87 const argpar_item_t *item = NULL;
88 const argpar_error_t *error = NULL;
89 GString * const res_str = g_string_new(NULL);
90 gchar ** const argv = g_strsplit(cmdline, " ", 0);
91 unsigned int i, actual_ingested_orig_args;
92
93 assert(argv);
94 assert(res_str);
95 iter = argpar_iter_create(g_strv_length(argv), (const char * const *) argv, descrs);
96 assert(iter);
97
98 for (i = 0;; i++) {
99 argpar_iter_next_status_t status;
100
101 ARGPAR_ITEM_DESTROY_AND_RESET(item);
102 status = argpar_iter_next(iter, &item, &error);
103
104 ok(status == ARGPAR_ITER_NEXT_STATUS_OK || status == ARGPAR_ITER_NEXT_STATUS_END,
105 "argpar_iter_next() returns the expected status (%d) for command line `%s` (call %u)",
106 status, cmdline, i + 1);
107 ok(!error, "argpar_iter_next() doesn't set an error for command line `%s` (call %u)",
108 cmdline, i + 1);
109
110 if (status == ARGPAR_ITER_NEXT_STATUS_END) {
111 ok(!item,
112 "argpar_iter_next() doesn't set an item for status `ARGPAR_ITER_NEXT_STATUS_END` "
113 "and command line `%s` (call %u)",
114 cmdline, i + 1);
115 break;
116 }
117
118 append_to_res_str(res_str, item);
119 }
120
121 actual_ingested_orig_args = argpar_iter_ingested_orig_args(iter);
122 ok(actual_ingested_orig_args == expected_ingested_orig_args,
123 "argpar_iter_ingested_orig_args() returns the expected number of ingested original "
124 "arguments for command line `%s`",
125 cmdline);
126
127 if (actual_ingested_orig_args != expected_ingested_orig_args) {
128 diag("Expected: %u Got: %u", expected_ingested_orig_args, actual_ingested_orig_args);
129 }
130
131 ok(strcmp(expected_cmd_line, res_str->str) == 0,
132 "argpar_iter_next() returns the expected parsing items for command line `%s`", cmdline);
133
134 if (strcmp(expected_cmd_line, res_str->str) != 0) {
135 diag("Expected: `%s`", expected_cmd_line);
136 diag("Got: `%s`", res_str->str);
137 }
138
139 argpar_item_destroy(item);
140 argpar_iter_destroy(iter);
141 assert(!error);
142 g_string_free(res_str, TRUE);
143 g_strfreev(argv);
144 }
145
146 static void succeed_tests(void)
147 {
148 /* No arguments */
149 {
150 const argpar_opt_descr_t descrs[] = {ARGPAR_OPT_DESCR_SENTINEL};
151
152 test_succeed("", "", descrs, 0);
153 }
154
155 /* Single long option */
156 {
157 const argpar_opt_descr_t descrs[] = {{0, '\0', "salut", false}, ARGPAR_OPT_DESCR_SENTINEL};
158
159 test_succeed("--salut", "--salut", descrs, 1);
160 }
161
162 /* Single short option */
163 {
164 const argpar_opt_descr_t descrs[] = {{0, 'f', NULL, false}, ARGPAR_OPT_DESCR_SENTINEL};
165
166 test_succeed("-f", "-f", descrs, 1);
167 }
168
169 /* Short and long option (aliases) */
170 {
171 const argpar_opt_descr_t descrs[] = {{0, 'f', "flaw", false}, ARGPAR_OPT_DESCR_SENTINEL};
172
173 test_succeed("-f --flaw", "--flaw --flaw", descrs, 2);
174 }
175
176 /* Long option with argument (space form) */
177 {
178 const argpar_opt_descr_t descrs[] = {{0, '\0', "tooth", true}, ARGPAR_OPT_DESCR_SENTINEL};
179
180 test_succeed("--tooth 67", "--tooth=67", descrs, 2);
181 }
182
183 /* Long option with argument (equal form) */
184 {
185 const argpar_opt_descr_t descrs[] = {{0, '\0', "polish", true}, ARGPAR_OPT_DESCR_SENTINEL};
186
187 test_succeed("--polish=brick", "--polish=brick", descrs, 1);
188 }
189
190 /* Short option with argument (space form) */
191 {
192 const argpar_opt_descr_t descrs[] = {{0, 'c', NULL, true}, ARGPAR_OPT_DESCR_SENTINEL};
193
194 test_succeed("-c chilly", "-c chilly", descrs, 2);
195 }
196
197 /* Short option with argument (glued form) */
198 {
199 const argpar_opt_descr_t descrs[] = {{0, 'c', NULL, true}, ARGPAR_OPT_DESCR_SENTINEL};
200
201 test_succeed("-cchilly", "-c chilly", descrs, 1);
202 }
203
204 /* Short and long option (aliases) with argument (all forms) */
205 {
206 const argpar_opt_descr_t descrs[] = {{0, 'd', "dry", true}, ARGPAR_OPT_DESCR_SENTINEL};
207
208 test_succeed("--dry=rate -dthing --dry street --dry=shape",
209 "--dry=rate --dry=thing --dry=street --dry=shape", descrs, 5);
210 }
211
212 /* Many short options, last one with argument (glued form) */
213 {
214 const argpar_opt_descr_t descrs[] = {{0, 'd', NULL, false},
215 {0, 'e', NULL, false},
216 {0, 'f', NULL, true},
217 ARGPAR_OPT_DESCR_SENTINEL};
218
219 test_succeed("-defmeow", "-d -e -f meow", descrs, 1);
220 }
221
222 /* Many options */
223 {
224 const argpar_opt_descr_t descrs[] = {{0, 'd', NULL, false},
225 {0, 'e', "east", true},
226 {0, '\0', "mind", false},
227 ARGPAR_OPT_DESCR_SENTINEL};
228
229 test_succeed("-d --mind -destart --mind --east cough -d --east=itch",
230 "-d --mind -d --east=start --mind --east=cough -d --east=itch", descrs, 8);
231 }
232
233 /* Single non-option argument */
234 {
235 const argpar_opt_descr_t descrs[] = {ARGPAR_OPT_DESCR_SENTINEL};
236
237 test_succeed("kilojoule", "kilojoule<0,0>", descrs, 1);
238 }
239
240 /* Two non-option arguments */
241 {
242 const argpar_opt_descr_t descrs[] = {ARGPAR_OPT_DESCR_SENTINEL};
243
244 test_succeed("kilojoule mitaine", "kilojoule<0,0> mitaine<1,1>", descrs, 2);
245 }
246
247 /* Single non-option argument mixed with options */
248 {
249 const argpar_opt_descr_t descrs[] = {{0, 'd', NULL, false},
250 {0, '\0', "squeeze", true},
251 ARGPAR_OPT_DESCR_SENTINEL};
252
253 test_succeed("-d sprout yes --squeeze little bag -d",
254 "-d sprout<1,0> yes<2,1> --squeeze=little bag<5,2> -d", descrs, 7);
255 }
256
257 /* Valid `---opt` */
258 {
259 const argpar_opt_descr_t descrs[] = {{0, '\0', "-fuel", true}, ARGPAR_OPT_DESCR_SENTINEL};
260
261 test_succeed("---fuel=three", "---fuel=three", descrs, 1);
262 }
263
264 /* Long option containing `=` in argument (equal form) */
265 {
266 const argpar_opt_descr_t descrs[] = {{0, '\0', "zebra", true}, ARGPAR_OPT_DESCR_SENTINEL};
267
268 test_succeed("--zebra=three=yes", "--zebra=three=yes", descrs, 1);
269 }
270
271 /* Short option's argument starting with `-` (glued form) */
272 {
273 const argpar_opt_descr_t descrs[] = {{0, 'z', NULL, true}, ARGPAR_OPT_DESCR_SENTINEL};
274
275 test_succeed("-z-will", "-z -will", descrs, 1);
276 }
277
278 /* Short option's argument starting with `-` (space form) */
279 {
280 const argpar_opt_descr_t descrs[] = {{0, 'z', NULL, true}, ARGPAR_OPT_DESCR_SENTINEL};
281
282 test_succeed("-z -will", "-z -will", descrs, 2);
283 }
284
285 /* Long option's argument starting with `-` (space form) */
286 {
287 const argpar_opt_descr_t descrs[] = {{0, '\0', "janine", true}, ARGPAR_OPT_DESCR_SENTINEL};
288
289 test_succeed("--janine -sutto", "--janine=-sutto", descrs, 2);
290 }
291
292 /* Long option's argument starting with `-` (equal form) */
293 {
294 const argpar_opt_descr_t descrs[] = {{0, '\0', "janine", true}, ARGPAR_OPT_DESCR_SENTINEL};
295
296 test_succeed("--janine=-sutto", "--janine=-sutto", descrs, 1);
297 }
298
299 /* Long option's empty argument (equal form) */
300 {
301 const argpar_opt_descr_t descrs[] = {{0, 'f', NULL, false},
302 {0, '\0', "yeah", true},
303 ARGPAR_OPT_DESCR_SENTINEL};
304
305 test_succeed("-f --yeah= -f", "-f --yeah= -f", descrs, 3);
306 }
307
308 /* `-` non-option argument */
309 {
310 const argpar_opt_descr_t descrs[] = {{0, 'f', NULL, false}, ARGPAR_OPT_DESCR_SENTINEL};
311
312 test_succeed("-f - -f", "-f -<1,0> -f", descrs, 3);
313 }
314
315 /* `--` non-option argument */
316 {
317 const argpar_opt_descr_t descrs[] = {{0, 'f', NULL, false}, ARGPAR_OPT_DESCR_SENTINEL};
318
319 test_succeed("-f -- -f", "-f --<1,0> -f", descrs, 3);
320 }
321
322 /* Very long name of long option */
323 {
324 const char opt_name[] = "kale-chips-waistcoat-yr-bicycle-rights-gochujang-"
325 "woke-tumeric-flexitarian-biodiesel-chillwave-cliche-"
326 "ethical-cardigan-listicle-pok-pok-sustainable-live-"
327 "edge-jianbing-gochujang-butcher-disrupt-tattooed-"
328 "tumeric-prism-photo-booth-vape-kogi-jean-shorts-"
329 "blog-williamsburg-fingerstache-palo-santo-artisan-"
330 "affogato-occupy-skateboard-adaptogen-neutra-celiac-"
331 "put-a-bird-on-it-kombucha-everyday-carry-hot-chicken-"
332 "craft-beer-subway-tile-tote-bag-disrupt-selvage-"
333 "raclette-art-party-readymade-paleo-heirloom-trust-"
334 "fund-small-batch-kinfolk-woke-cardigan-prism-"
335 "chambray-la-croix-hashtag-unicorn-edison-bulb-tbh-"
336 "cornhole-cliche-tattooed-green-juice-adaptogen-"
337 "kitsch-lo-fi-vexillologist-migas-gentrify-"
338 "viral-raw-denim";
339 const argpar_opt_descr_t descrs[] = {{0, '\0', opt_name, true}, ARGPAR_OPT_DESCR_SENTINEL};
340 char cmdline[1024];
341
342 sprintf(cmdline, "--%s=23", opt_name);
343 test_succeed(cmdline, cmdline, descrs, 1);
344 }
345 }
346
347 /*
348 * Parses `cmdline` with the argpar API using the option descriptors
349 * `descrs`, and ensures that argpar_iter_next() fails with status
350 * `expected_status` and that it sets an error having:
351 *
352 * ‣ The original argument index `expected_orig_index`.
353 *
354 * ‣ If applicable:
355 *
356 * • The unknown option name `expected_unknown_opt_name`.
357 *
358 * • The option descriptor at index `expected_opt_descr_index` of
359 * `descrs`.
360 *
361 * • The option type `expected_is_short`.
362 *
363 * This function splits `cmdline` on spaces to create an original
364 * argument array.
365 */
366 static void test_fail(const char * const cmdline, const argpar_error_type_t expected_error_type,
367 const unsigned int expected_orig_index,
368 const char * const expected_unknown_opt_name,
369 const unsigned int expected_opt_descr_index, const bool expected_is_short,
370 const argpar_opt_descr_t * const descrs)
371 {
372 argpar_iter_t *iter = NULL;
373 const argpar_item_t *item = NULL;
374 gchar ** const argv = g_strsplit(cmdline, " ", 0);
375 unsigned int i;
376 const argpar_error_t *error = NULL;
377
378 iter = argpar_iter_create(g_strv_length(argv), (const char * const *) argv, descrs);
379 assert(iter);
380
381 for (i = 0;; i++) {
382 argpar_iter_next_status_t status;
383
384 ARGPAR_ITEM_DESTROY_AND_RESET(item);
385 status = argpar_iter_next(iter, &item, &error);
386 ok(status == ARGPAR_ITER_NEXT_STATUS_OK ||
387 (status == ARGPAR_ITER_NEXT_STATUS_ERROR &&
388 argpar_error_type(error) == expected_error_type),
389 "argpar_iter_next() returns the expected status and error type (%d) "
390 "for command line `%s` (call %u)",
391 expected_error_type, cmdline, i + 1);
392
393 if (status != ARGPAR_ITER_NEXT_STATUS_OK) {
394 ok(!item,
395 "argpar_iter_next() doesn't set an item for other status than "
396 "`ARGPAR_ITER_NEXT_STATUS_OK` and command line `%s` (call %u)",
397 cmdline, i + 1);
398 ok(error,
399 "argpar_iter_next() sets an error for other status than "
400 "`ARGPAR_ITER_NEXT_STATUS_OK` and command line `%s` (call %u)",
401 cmdline, i + 1);
402 ok(argpar_error_orig_index(error) == expected_orig_index,
403 "argpar_iter_next() sets an error with the expected original argument index "
404 "for command line `%s` (call %u)",
405 cmdline, i + 1);
406
407 if (argpar_error_type(error) == ARGPAR_ERROR_TYPE_UNKNOWN_OPT) {
408 ok(strcmp(argpar_error_unknown_opt_name(error), expected_unknown_opt_name) == 0,
409 "argpar_iter_next() sets an error with the expected unknown option name "
410 "for command line `%s` (call %u)",
411 cmdline, i + 1);
412 } else {
413 bool is_short;
414
415 ok(argpar_error_opt_descr(error, &is_short) == &descrs[expected_opt_descr_index],
416 "argpar_iter_next() sets an error with the expected option descriptor "
417 "for command line `%s` (call %u)",
418 cmdline, i + 1);
419 ok(is_short == expected_is_short,
420 "argpar_iter_next() sets an error with the expected option type "
421 "for command line `%s` (call %u)",
422 cmdline, i + 1);
423 }
424 break;
425 }
426
427 ok(item,
428 "argpar_iter_next() sets an item for status `ARGPAR_ITER_NEXT_STATUS_OK` "
429 "and command line `%s` (call %u)",
430 cmdline, i + 1);
431 ok(!error,
432 "argpar_iter_next() doesn't set an error for status `ARGPAR_ITER_NEXT_STATUS_OK` "
433 "and command line `%s` (call %u)",
434 cmdline, i + 1);
435 }
436
437 /*
438 ok(strcmp(expected_error, error) == 0,
439 "argpar_iter_next() sets the expected error string "
440 "for command line `%s`", cmdline);
441
442 if (strcmp(expected_error, error) != 0) {
443 diag("Expected: `%s`", expected_error);
444 diag("Got: `%s`", error);
445 }
446 */
447
448 argpar_item_destroy(item);
449 argpar_iter_destroy(iter);
450 argpar_error_destroy(error);
451 g_strfreev(argv);
452 }
453
454 static void fail_tests(void)
455 {
456 /* Unknown short option (space form) */
457 {
458 const argpar_opt_descr_t descrs[] = {{0, 'd', NULL, true}, ARGPAR_OPT_DESCR_SENTINEL};
459
460 test_fail("-d salut -e -d meow", ARGPAR_ERROR_TYPE_UNKNOWN_OPT, 2, "-e", 0, false, descrs);
461 }
462
463 /* Unknown short option (glued form) */
464 {
465 const argpar_opt_descr_t descrs[] = {{0, 'd', 0, true}, ARGPAR_OPT_DESCR_SENTINEL};
466
467 test_fail("-dsalut -e -d meow", ARGPAR_ERROR_TYPE_UNKNOWN_OPT, 1, "-e", 0, false, descrs);
468 }
469
470 /* Unknown long option (space form) */
471 {
472 const argpar_opt_descr_t descrs[] = {{0, '\0', "sink", true}, ARGPAR_OPT_DESCR_SENTINEL};
473
474 test_fail("--sink party --food --sink impulse", ARGPAR_ERROR_TYPE_UNKNOWN_OPT, 2, "--food",
475 0, false, descrs);
476 }
477
478 /* Unknown long option (equal form) */
479 {
480 const argpar_opt_descr_t descrs[] = {{0, '\0', "sink", true}, ARGPAR_OPT_DESCR_SENTINEL};
481
482 test_fail("--sink=party --food --sink=impulse", ARGPAR_ERROR_TYPE_UNKNOWN_OPT, 1, "--food",
483 0, false, descrs);
484 }
485
486 /* Unknown option before non-option argument */
487 {
488 const argpar_opt_descr_t descrs[] = {{0, '\0', "thumb", true}, ARGPAR_OPT_DESCR_SENTINEL};
489
490 test_fail("--thumb=party --food=18 bateau --thumb waves", ARGPAR_ERROR_TYPE_UNKNOWN_OPT, 1,
491 "--food", 0, false, descrs);
492 }
493
494 /* Unknown option after non-option argument */
495 {
496 const argpar_opt_descr_t descrs[] = {{0, '\0', "thumb", true}, ARGPAR_OPT_DESCR_SENTINEL};
497
498 test_fail("--thumb=party wound --food --thumb waves", ARGPAR_ERROR_TYPE_UNKNOWN_OPT, 2,
499 "--food", 0, false, descrs);
500 }
501
502 /* Missing long option argument */
503 {
504 const argpar_opt_descr_t descrs[] = {{0, '\0', "thumb", true}, ARGPAR_OPT_DESCR_SENTINEL};
505
506 test_fail("allo --thumb", ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, 1, NULL, 0, false, descrs);
507 }
508
509 /* Missing short option argument */
510 {
511 const argpar_opt_descr_t descrs[] = {{0, 'k', NULL, true}, ARGPAR_OPT_DESCR_SENTINEL};
512
513 test_fail("zoom heille -k", ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, 2, NULL, 0, true, descrs);
514 }
515
516 /* Missing short option argument (multiple glued) */
517 {
518 const argpar_opt_descr_t descrs[] = {{0, 'a', NULL, false},
519 {0, 'b', NULL, false},
520 {0, 'c', NULL, true},
521 ARGPAR_OPT_DESCR_SENTINEL};
522
523 test_fail("-abc", ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, 0, NULL, 2, true, descrs);
524 }
525
526 /* Unexpected long option argument */
527 {
528 const argpar_opt_descr_t descrs[] = {{0, 'c', "chevre", false}, ARGPAR_OPT_DESCR_SENTINEL};
529
530 test_fail("ambulance --chevre=fromage tar -cjv", ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG, 1,
531 NULL, 0, false, descrs);
532 }
533 }
534
535 int main(void)
536 {
537 plan_tests(309);
538 succeed_tests();
539 fail_tests();
540 return exit_status();
541 }
This page took 0.03899 seconds and 4 git commands to generate.