2 * Copyright (c) 2019-2021 Philippe Proulx <pproulx@efficios.com>
3 * Copyright (c) 2020-2021 Simon Marchi <simon.marchi@efficios.com>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; under version 2 of the License.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #include "argpar/argpar.h"
28 * Append `item` to `res_str` to incrementally build an expected command line string.
31 void append_to_res_str(GString
*res_str
, const struct argpar_item
*item
)
33 if (res_str
->len
> 0) {
34 g_string_append_c(res_str
, ' ');
38 case ARGPAR_ITEM_TYPE_OPT
:
40 const struct argpar_item_opt
*item_opt
=
43 if (item_opt
->descr
->long_name
) {
44 g_string_append_printf(res_str
, "--%s",
45 item_opt
->descr
->long_name
);
48 g_string_append_printf(res_str
, "=%s",
51 } else if (item_opt
->descr
->short_name
) {
52 g_string_append_printf(res_str
, "-%c",
53 item_opt
->descr
->short_name
);
56 g_string_append_printf(res_str
, " %s",
63 case ARGPAR_ITEM_TYPE_NON_OPT
:
65 const struct argpar_item_non_opt
*item_non_opt
=
68 g_string_append_printf(res_str
, "%s<%u,%u>",
69 item_non_opt
->arg
, item_non_opt
->orig_index
,
70 item_non_opt
->non_opt_index
);
78 /* Test using the all-at-once argpar_parse function. */
81 void test_succeed_argpar_parse(const char *cmdline
,
82 const char *expected_cmd_line
,
83 const struct argpar_opt_descr
*descrs
,
84 unsigned int expected_ingested_orig_args
)
86 struct argpar_parse_ret parse_ret
;
87 GString
*res_str
= g_string_new(NULL
);
88 gchar
**argv
= g_strsplit(cmdline
, " ", 0);
93 parse_ret
= argpar_parse(g_strv_length(argv
),
94 (const char * const *) argv
, descrs
, false);
96 "argpar_parse() succeeds for command line `%s`", cmdline
);
98 "argpar_parse() does not write an error for command line `%s`", cmdline
);
99 ok(parse_ret
.ingested_orig_args
== expected_ingested_orig_args
,
100 "argpar_parse() returns the correct number of ingested "
101 "original arguments for command line `%s`", cmdline
);
102 if (parse_ret
.ingested_orig_args
!= expected_ingested_orig_args
) {
103 diag("Expected: %u Got: %u", expected_ingested_orig_args
,
104 parse_ret
.ingested_orig_args
);
107 if (!parse_ret
.items
) {
108 fail("argpar_parse() returns the expected parsed arguments "
109 "for command line `%s`", cmdline
);
113 for (i
= 0; i
< parse_ret
.items
->n_items
; i
++) {
114 const struct argpar_item
*item
= parse_ret
.items
->items
[i
];
116 append_to_res_str(res_str
, item
);
119 ok(strcmp(expected_cmd_line
, res_str
->str
) == 0,
120 "argpar_parse() returns the expected parsed arguments "
121 "for command line `%s`", cmdline
);
122 if (strcmp(expected_cmd_line
, res_str
->str
) != 0) {
123 diag("Expected: `%s`", expected_cmd_line
);
124 diag("Got: `%s`", res_str
->str
);
128 argpar_parse_ret_fini(&parse_ret
);
129 g_string_free(res_str
, TRUE
);
133 /* Test using the iterator API. */
136 void test_succeed_argpar_iter(const char *cmdline
,
137 const char *expected_cmd_line
,
138 const struct argpar_opt_descr
*descrs
,
139 unsigned int expected_ingested_orig_args
)
141 struct argpar_iter
*iter
= NULL
;
142 const struct argpar_item
*item
= NULL
;
144 GString
*res_str
= g_string_new(NULL
);
145 gchar
**argv
= g_strsplit(cmdline
, " ", 0);
146 unsigned int i
, actual_ingested_orig_args
;
151 iter
= argpar_iter_create(g_strv_length(argv
),
152 (const char * const *) argv
, descrs
);
156 enum argpar_iter_parse_next_status status
;
158 ARGPAR_ITEM_DESTROY_AND_RESET(item
);
159 status
= argpar_iter_parse_next(iter
, &item
, &error
);
161 ok(status
== ARGPAR_ITER_PARSE_NEXT_STATUS_OK
||
162 status
== ARGPAR_ITER_PARSE_NEXT_STATUS_END
||
163 status
== ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT
,
164 "argpar_iter_parse_next() for command line `%s` call #%u: status", cmdline
, i
);
166 if (status
== ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT
) {
167 ok(error
, "argpar_iter_parse_next() for command line `%s` call #%u: error returned", cmdline
, i
);
169 ok(!error
, "argpar_iter_parse_next() for command line `%s` call #%u: no error returned", cmdline
, i
);
172 if (status
== ARGPAR_ITER_PARSE_NEXT_STATUS_END
||
173 status
== ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT
) {
174 ok(!item
, "argpar_iter_parse_next() for command line `%s` call #%u: no item returned", cmdline
, i
);
178 append_to_res_str(res_str
, item
);
181 actual_ingested_orig_args
= argpar_iter_get_ingested_orig_args(iter
);
182 ok(actual_ingested_orig_args
== expected_ingested_orig_args
,
183 "argpar_iter_get_ingested_orig_args() returns the correct number of ingested "
184 "original arguments for command line `%s`", cmdline
);
185 if (actual_ingested_orig_args
!= expected_ingested_orig_args
) {
186 diag("Expected: %u Got: %u", expected_ingested_orig_args
,
187 actual_ingested_orig_args
);
190 ok(strcmp(expected_cmd_line
, res_str
->str
) == 0,
191 "argpar_iter_parse_next() returns the expected parsed arguments "
192 "for command line `%s`", cmdline
);
193 if (strcmp(expected_cmd_line
, res_str
->str
) != 0) {
194 diag("Expected: `%s`", expected_cmd_line
);
195 diag("Got: `%s`", res_str
->str
);
198 argpar_item_destroy(item
);
199 argpar_iter_destroy(iter
);
200 g_string_free(res_str
, TRUE
);
206 * Tests that the command line `cmdline`, with non-quoted
207 * space-delimited arguments, once parsed given the option descriptors
208 * `descrs`, succeeds and gives the expected command line `expected_cmd_line`
209 * and number of ingested original arguments `expected_ingested_orig_args`.
211 * The resulting command-line is built from the resulting arguments,
212 * space-delimiting each argument, preferring the `--long-opt=arg` style
213 * over the `-s arg` style, and using the `arg<A,B>` form for non-option
214 * arguments where `A` is the original argument index and `B` is the
215 * non-option argument index.
217 * Test with both the parse-all-at-once and iterator-style APIs.
220 void test_succeed(const char *cmdline
,
221 const char *expected_cmd_line
,
222 const struct argpar_opt_descr
*descrs
,
223 unsigned int expected_ingested_orig_args
)
225 test_succeed_argpar_parse(cmdline
, expected_cmd_line
, descrs
,
226 expected_ingested_orig_args
);
227 test_succeed_argpar_iter(cmdline
, expected_cmd_line
, descrs
,
228 expected_ingested_orig_args
);
232 void succeed_tests(void)
236 const struct argpar_opt_descr descrs
[] = {
237 ARGPAR_OPT_DESCR_SENTINEL
246 /* Single long option */
248 const struct argpar_opt_descr descrs
[] = {
249 { 0, '\0', "salut", false },
250 ARGPAR_OPT_DESCR_SENTINEL
259 /* Single short option */
261 const struct argpar_opt_descr descrs
[] = {
262 { 0, 'f', NULL
, false },
263 ARGPAR_OPT_DESCR_SENTINEL
272 /* Short and long option (aliases) */
274 const struct argpar_opt_descr descrs
[] = {
275 { 0, 'f', "flaw", false },
276 ARGPAR_OPT_DESCR_SENTINEL
285 /* Long option with argument (space form) */
287 const struct argpar_opt_descr descrs
[] = {
288 { 0, '\0', "tooth", true },
289 ARGPAR_OPT_DESCR_SENTINEL
298 /* Long option with argument (equal form) */
300 const struct argpar_opt_descr descrs
[] = {
301 { 0, '\0', "polish", true },
302 ARGPAR_OPT_DESCR_SENTINEL
311 /* Short option with argument (space form) */
313 const struct argpar_opt_descr descrs
[] = {
314 { 0, 'c', NULL
, true },
315 ARGPAR_OPT_DESCR_SENTINEL
324 /* Short option with argument (glued form) */
326 const struct argpar_opt_descr descrs
[] = {
327 { 0, 'c', NULL
, true },
328 ARGPAR_OPT_DESCR_SENTINEL
337 /* Short and long option (aliases) with argument (all forms) */
339 const struct argpar_opt_descr descrs
[] = {
340 { 0, 'd', "dry", true },
341 ARGPAR_OPT_DESCR_SENTINEL
345 "--dry=rate -dthing --dry street --dry=shape",
346 "--dry=rate --dry=thing --dry=street --dry=shape",
350 /* Many short options, last one with argument (glued form) */
352 const struct argpar_opt_descr descrs
[] = {
353 { 0, 'd', NULL
, false },
354 { 0, 'e', NULL
, false },
355 { 0, 'f', NULL
, true },
356 ARGPAR_OPT_DESCR_SENTINEL
367 const struct argpar_opt_descr descrs
[] = {
368 { 0, 'd', NULL
, false },
369 { 0, 'e', "east", true },
370 { 0, '\0', "mind", false },
371 ARGPAR_OPT_DESCR_SENTINEL
375 "-d --mind -destart --mind --east cough -d --east=itch",
376 "-d --mind -d --east=start --mind --east=cough -d --east=itch",
380 /* Single non-option argument */
382 const struct argpar_opt_descr descrs
[] = {
383 ARGPAR_OPT_DESCR_SENTINEL
392 /* Two non-option arguments */
394 const struct argpar_opt_descr descrs
[] = {
395 ARGPAR_OPT_DESCR_SENTINEL
400 "kilojoule<0,0> mitaine<1,1>",
404 /* Single non-option argument mixed with options */
406 const struct argpar_opt_descr descrs
[] = {
407 { 0, 'd', NULL
, false },
408 { 0, '\0', "squeeze", true },
409 ARGPAR_OPT_DESCR_SENTINEL
413 "-d sprout yes --squeeze little bag -d",
414 "-d sprout<1,0> yes<2,1> --squeeze=little bag<5,2> -d",
418 /* Unknown short option (space form) */
420 const struct argpar_opt_descr descrs
[] = {
421 { 0, 'd', NULL
, true },
422 ARGPAR_OPT_DESCR_SENTINEL
426 "-d salut -e -d meow",
431 /* Unknown short option (glued form) */
433 const struct argpar_opt_descr descrs
[] = {
434 { 0, 'd', NULL
, true },
435 ARGPAR_OPT_DESCR_SENTINEL
439 "-dsalut -e -d meow",
444 /* Unknown long option (space form) */
446 const struct argpar_opt_descr descrs
[] = {
447 { 0, '\0', "sink", true },
448 ARGPAR_OPT_DESCR_SENTINEL
452 "--sink party --food --sink impulse",
457 /* Unknown long option (equal form) */
459 const struct argpar_opt_descr descrs
[] = {
460 { 0, '\0', "sink", true },
461 ARGPAR_OPT_DESCR_SENTINEL
465 "--sink=party --food --sink=impulse",
470 /* Unknown option before non-option argument */
472 const struct argpar_opt_descr descrs
[] = {
473 { 0, '\0', "thumb", true },
474 ARGPAR_OPT_DESCR_SENTINEL
478 "--thumb=party --food bateau --thumb waves",
483 /* Unknown option after non-option argument */
485 const struct argpar_opt_descr descrs
[] = {
486 { 0, '\0', "thumb", true },
487 ARGPAR_OPT_DESCR_SENTINEL
491 "--thumb=party wound --food --thumb waves",
492 "--thumb=party wound<1,0>",
498 const struct argpar_opt_descr descrs
[] = {
499 { 0, '\0', "-fuel", true },
500 ARGPAR_OPT_DESCR_SENTINEL
509 /* Long option containing `=` in argument (equal form) */
511 const struct argpar_opt_descr descrs
[] = {
512 { 0, '\0', "zebra", true },
513 ARGPAR_OPT_DESCR_SENTINEL
522 /* Short option's argument starting with `-` (glued form) */
524 const struct argpar_opt_descr descrs
[] = {
525 { 0, 'z', NULL
, true },
526 ARGPAR_OPT_DESCR_SENTINEL
535 /* Short option's argument starting with `-` (space form) */
537 const struct argpar_opt_descr descrs
[] = {
538 { 0, 'z', NULL
, true },
539 ARGPAR_OPT_DESCR_SENTINEL
548 /* Long option's argument starting with `-` (space form) */
550 const struct argpar_opt_descr descrs
[] = {
551 { 0, '\0', "janine", true },
552 ARGPAR_OPT_DESCR_SENTINEL
561 /* Long option's argument starting with `-` (equal form) */
563 const struct argpar_opt_descr descrs
[] = {
564 { 0, '\0', "janine", true },
565 ARGPAR_OPT_DESCR_SENTINEL
574 /* Long option's empty argument (equal form) */
576 const struct argpar_opt_descr descrs
[] = {
577 { 0, 'f', NULL
, false },
578 { 0, '\0', "yeah", true },
579 ARGPAR_OPT_DESCR_SENTINEL
589 /* Test using the all-at-once argpar_parse function. */
592 void test_fail_argpar_parse(const char *cmdline
, const char *expected_error
,
593 const struct argpar_opt_descr
*descrs
)
595 struct argpar_parse_ret parse_ret
;
596 gchar
**argv
= g_strsplit(cmdline
, " ", 0);
598 parse_ret
= argpar_parse(g_strv_length(argv
),
599 (const char * const *) argv
, descrs
, true);
601 "argpar_parse() fails for command line `%s`", cmdline
);
603 "argpar_parse() writes an error string for command line `%s`",
605 if (parse_ret
.items
) {
606 fail("argpar_parse() writes the expected error string");
610 ok(strcmp(expected_error
, parse_ret
.error
) == 0,
611 "argpar_parse() writes the expected error string "
612 "for command line `%s`", cmdline
);
613 if (strcmp(expected_error
, parse_ret
.error
) != 0) {
614 diag("Expected: `%s`", expected_error
);
615 diag("Got: `%s`", parse_ret
.error
);
619 argpar_parse_ret_fini(&parse_ret
);
623 /* Test using the iterator API. */
626 void test_fail_argpar_iter(const char *cmdline
, const char *expected_error
,
627 const struct argpar_opt_descr
*descrs
)
629 struct argpar_iter
*iter
= NULL
;
630 const struct argpar_item
*item
= NULL
;
631 gchar
**argv
= g_strsplit(cmdline
, " ", 0);
635 iter
= argpar_iter_create(g_strv_length(argv
),
636 (const char * const *) argv
, descrs
);
640 enum argpar_iter_parse_next_status status
;
642 ARGPAR_ITEM_DESTROY_AND_RESET(item
);
643 status
= argpar_iter_parse_next(iter
, &item
, &error
);
645 ok(status
== ARGPAR_ITER_PARSE_NEXT_STATUS_OK
||
646 status
== ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR
||
647 status
== ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT
,
648 "argpar_iter_parse_next() for command line `%s` call #%u: status", cmdline
, i
);
650 if (status
!= ARGPAR_ITER_PARSE_NEXT_STATUS_OK
) {
651 ok(!item
, "argpar_iter_parse_next() for command line `%s` call #%u: no item returned", cmdline
, i
);
652 ok(error
, "argpar_iter_parse_next() for command line `%s` call #%u: error returned", cmdline
, i
);
656 ok(item
, "argpar_iter_parse_next() for command line `%s` call #%u: item returned", cmdline
, i
);
657 ok(!error
, "argpar_iter_parse_next() for command line `%s` call #%u: no error returned", cmdline
, i
);
660 ok(strcmp(expected_error
, error
) == 0,
661 "argpar_iter_parse_next() writes the expected error string "
662 "for command line `%s`", cmdline
);
663 if (strcmp(expected_error
, error
) != 0) {
664 diag("Expected: `%s`", expected_error
);
665 diag("Got: `%s`", error
);
668 argpar_item_destroy(item
);
669 argpar_iter_destroy(iter
);
675 * Tests that the command line `cmdline`, with non-quoted
676 * space-delimited arguments, once parsed given the option descriptors
677 * `descrs`, fails and gives the expected error `expected_error`.
679 * Test with both the parse-all-at-once and iterator-style APIs.
683 void test_fail(const char *cmdline
, const char *expected_error
,
684 const struct argpar_opt_descr
*descrs
)
686 test_fail_argpar_parse(cmdline
, expected_error
, descrs
);
687 test_fail_argpar_iter(cmdline
, expected_error
, descrs
);
691 void fail_tests(void)
693 /* Unknown long option */
695 const struct argpar_opt_descr descrs
[] = {
696 { 0, '\0', "thumb", true },
697 ARGPAR_OPT_DESCR_SENTINEL
701 "--thumb=party --meow",
702 "While parsing argument #2 (`--meow`): Unknown option `--meow`",
706 /* Unknown short option */
708 const struct argpar_opt_descr descrs
[] = {
709 { 0, '\0', "thumb", true },
710 ARGPAR_OPT_DESCR_SENTINEL
715 "While parsing argument #2 (`-x`): Unknown option `-x`",
719 /* Missing long option argument */
721 const struct argpar_opt_descr descrs
[] = {
722 { 0, '\0', "thumb", true },
723 ARGPAR_OPT_DESCR_SENTINEL
728 "While parsing argument #1 (`--thumb`): Missing required argument for option `--thumb`",
732 /* Missing short option argument */
734 const struct argpar_opt_descr descrs
[] = {
735 { 0, 'k', NULL
, true },
736 ARGPAR_OPT_DESCR_SENTINEL
741 "While parsing argument #1 (`-k`): Missing required argument for option `-k`",
745 /* Missing short option argument (multiple glued) */
747 const struct argpar_opt_descr descrs
[] = {
748 { 0, 'a', NULL
, false },
749 { 0, 'b', NULL
, false },
750 { 0, 'c', NULL
, true },
751 ARGPAR_OPT_DESCR_SENTINEL
756 "While parsing argument #1 (`-abc`): Missing required argument for option `-c`",
762 const struct argpar_opt_descr descrs
[] = {
763 { 0, 'a', NULL
, false },
764 { 0, 'b', NULL
, false },
765 { 0, 'c', NULL
, true },
766 ARGPAR_OPT_DESCR_SENTINEL
771 "While parsing argument #2 (`-`): Invalid argument",
777 const struct argpar_opt_descr descrs
[] = {
778 { 0, 'a', NULL
, false },
779 { 0, 'b', NULL
, false },
780 { 0, 'c', NULL
, true },
781 ARGPAR_OPT_DESCR_SENTINEL
786 "While parsing argument #2 (`--`): Invalid argument",
791 const struct argpar_opt_descr descrs
[] = {
792 { 0, 'c', "chevre", false },
793 ARGPAR_OPT_DESCR_SENTINEL
798 "While parsing argument #1 (`--chevre=fromage`): Unexpected argument for option `--chevre`",
808 return exit_status();