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.
27 #include "argpar/argpar.h"
30 * Formats `item` and appends the resulting string to `res_str` to
31 * incrementally build an expected command line string.
35 * * Prefers the `--long-opt=arg` style over the `-s arg` style.
37 * * Uses the `arg<A,B>` form for non-option arguments, where `A` is the
38 * original argument index and `B` is the non-option argument index.
41 void append_to_res_str(GString
* const res_str
,
42 const struct argpar_item
* const item
)
44 if (res_str
->len
> 0) {
45 g_string_append_c(res_str
, ' ');
48 switch (argpar_item_type(item
)) {
49 case ARGPAR_ITEM_TYPE_OPT
:
51 const struct argpar_opt_descr
* const descr
=
52 argpar_item_opt_descr(item
);
53 const char * const arg
= argpar_item_opt_arg(item
);
55 if (descr
->long_name
) {
56 g_string_append_printf(res_str
, "--%s",
60 g_string_append_printf(res_str
, "=%s", arg
);
62 } else if (descr
->short_name
) {
63 g_string_append_printf(res_str
, "-%c",
67 g_string_append_printf(res_str
, " %s", arg
);
73 case ARGPAR_ITEM_TYPE_NON_OPT
:
75 const char * const arg
= argpar_item_non_opt_arg(item
);
76 const unsigned int orig_index
=
77 argpar_item_non_opt_orig_index(item
);
78 const unsigned int non_opt_index
=
79 argpar_item_non_opt_non_opt_index(item
);
81 g_string_append_printf(res_str
, "%s<%u,%u>", arg
, orig_index
,
91 * Parses `cmdline` with argpar_parse() using the option descriptors
92 * `descrs`, and ensures that the resulting effective command line is
93 * `expected_cmd_line` and that the number of ingested original
94 * arguments is `expected_ingested_orig_args`.
96 * This function splits `cmdline` on spaces to create an original
99 * This function builds the resulting command line from parsing items
100 * by space-separating each formatted item (see append_to_res_str()).
103 void test_succeed_argpar_parse(const char * const cmdline
,
104 const char * const expected_cmd_line
,
105 const struct argpar_opt_descr
* const descrs
,
106 const unsigned int expected_ingested_orig_args
)
108 struct argpar_parse_ret parse_ret
;
109 GString
* const res_str
= g_string_new(NULL
);
110 gchar
** const argv
= g_strsplit(cmdline
, " ", 0);
115 parse_ret
= argpar_parse(g_strv_length(argv
),
116 (const char * const *) argv
, descrs
, false);
118 "argpar_parse() succeeds for command line `%s`", cmdline
);
120 "argpar_parse() doesn't set an error for command line `%s`",
122 ok(parse_ret
.ingested_orig_args
== expected_ingested_orig_args
,
123 "argpar_parse() returns the correct number of ingested "
124 "original arguments for command line `%s`", cmdline
);
126 if (parse_ret
.ingested_orig_args
!= expected_ingested_orig_args
) {
127 diag("Expected: %u Got: %u", expected_ingested_orig_args
,
128 parse_ret
.ingested_orig_args
);
131 if (!parse_ret
.items
) {
132 fail("argpar_parse() returns the expected parsing items "
133 "for command line `%s`", cmdline
);
137 for (i
= 0; i
< parse_ret
.items
->n_items
; i
++) {
138 append_to_res_str(res_str
, parse_ret
.items
->items
[i
]);
141 ok(strcmp(expected_cmd_line
, res_str
->str
) == 0,
142 "argpar_parse() returns the expected parsed arguments "
143 "for command line `%s`", cmdline
);
145 if (strcmp(expected_cmd_line
, res_str
->str
) != 0) {
146 diag("Expected: `%s`", expected_cmd_line
);
147 diag("Got: `%s`", res_str
->str
);
151 argpar_parse_ret_fini(&parse_ret
);
152 g_string_free(res_str
, TRUE
);
157 * Parses `cmdline` with the iterator API using the option descriptors
158 * `descrs`, and ensures that the resulting effective command line is
159 * `expected_cmd_line` and that the number of ingested original
160 * arguments is `expected_ingested_orig_args`.
162 * This function splits `cmdline` on spaces to create an original
165 * This function builds the resulting command line from parsing items
166 * by space-separating each formatted item (see append_to_res_str()).
169 void test_succeed_argpar_iter(const char * const cmdline
,
170 const char * const expected_cmd_line
,
171 const struct argpar_opt_descr
* const descrs
,
172 const unsigned int expected_ingested_orig_args
)
174 struct argpar_iter
*iter
= NULL
;
175 const struct argpar_item
*item
= NULL
;
177 GString
* const res_str
= g_string_new(NULL
);
178 gchar
** const argv
= g_strsplit(cmdline
, " ", 0);
179 unsigned int i
, actual_ingested_orig_args
;
183 iter
= argpar_iter_create(g_strv_length(argv
),
184 (const char * const *) argv
, descrs
);
188 enum argpar_iter_next_status status
;
190 ARGPAR_ITEM_DESTROY_AND_RESET(item
);
191 status
= argpar_iter_next(iter
, &item
, &error
);
193 ok(status
== ARGPAR_ITER_NEXT_STATUS_OK
||
194 status
== ARGPAR_ITER_NEXT_STATUS_END
||
195 status
== ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT
,
196 "argpar_iter_next() returns the expected status "
197 "(%d) for command line `%s` (call %u)",
198 status
, cmdline
, i
+ 1);
200 if (status
== ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT
) {
202 "argpar_iter_next() sets an error for "
203 "status `ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT` "
204 "and command line `%s` (call %u)",
208 "argpar_iter_next() doesn't set an error "
209 "for other status than "
210 "`ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT` "
211 "and command line `%s` (call %u)",
215 if (status
== ARGPAR_ITER_NEXT_STATUS_END
||
216 status
== ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT
) {
218 "argpar_iter_next() doesn't set an item "
219 "for status `ARGPAR_ITER_NEXT_STATUS_END` "
220 "or `ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT` "
221 "and command line `%s` (call %u)",
226 append_to_res_str(res_str
, item
);
229 actual_ingested_orig_args
= argpar_iter_ingested_orig_args(iter
);
230 ok(actual_ingested_orig_args
== expected_ingested_orig_args
,
231 "argpar_iter_ingested_orig_args() returns the expected "
232 "number of ingested original arguments for command line `%s`",
235 if (actual_ingested_orig_args
!= expected_ingested_orig_args
) {
236 diag("Expected: %u Got: %u", expected_ingested_orig_args
,
237 actual_ingested_orig_args
);
240 ok(strcmp(expected_cmd_line
, res_str
->str
) == 0,
241 "argpar_iter_next() returns the expected parsing items "
242 "for command line `%s`", cmdline
);
244 if (strcmp(expected_cmd_line
, res_str
->str
) != 0) {
245 diag("Expected: `%s`", expected_cmd_line
);
246 diag("Got: `%s`", res_str
->str
);
249 argpar_item_destroy(item
);
250 argpar_iter_destroy(iter
);
251 g_string_free(res_str
, TRUE
);
257 * Calls test_succeed_argpar_parse() and test_succeed_argpar_iter()
258 * with the provided parameters.
261 void test_succeed(const char * const cmdline
,
262 const char * const expected_cmd_line
,
263 const struct argpar_opt_descr
* const descrs
,
264 const unsigned int expected_ingested_orig_args
)
266 test_succeed_argpar_parse(cmdline
, expected_cmd_line
, descrs
,
267 expected_ingested_orig_args
);
268 test_succeed_argpar_iter(cmdline
, expected_cmd_line
, descrs
,
269 expected_ingested_orig_args
);
273 void succeed_tests(void)
277 const struct argpar_opt_descr descrs
[] = {
278 ARGPAR_OPT_DESCR_SENTINEL
287 /* Single long option */
289 const struct argpar_opt_descr descrs
[] = {
290 { 0, '\0', "salut", false },
291 ARGPAR_OPT_DESCR_SENTINEL
300 /* Single short option */
302 const struct argpar_opt_descr descrs
[] = {
303 { 0, 'f', NULL
, false },
304 ARGPAR_OPT_DESCR_SENTINEL
313 /* Short and long option (aliases) */
315 const struct argpar_opt_descr descrs
[] = {
316 { 0, 'f', "flaw", false },
317 ARGPAR_OPT_DESCR_SENTINEL
326 /* Long option with argument (space form) */
328 const struct argpar_opt_descr descrs
[] = {
329 { 0, '\0', "tooth", true },
330 ARGPAR_OPT_DESCR_SENTINEL
339 /* Long option with argument (equal form) */
341 const struct argpar_opt_descr descrs
[] = {
342 { 0, '\0', "polish", true },
343 ARGPAR_OPT_DESCR_SENTINEL
352 /* Short option with argument (space form) */
354 const struct argpar_opt_descr descrs
[] = {
355 { 0, 'c', NULL
, true },
356 ARGPAR_OPT_DESCR_SENTINEL
365 /* Short option with argument (glued form) */
367 const struct argpar_opt_descr descrs
[] = {
368 { 0, 'c', NULL
, true },
369 ARGPAR_OPT_DESCR_SENTINEL
378 /* Short and long option (aliases) with argument (all forms) */
380 const struct argpar_opt_descr descrs
[] = {
381 { 0, 'd', "dry", true },
382 ARGPAR_OPT_DESCR_SENTINEL
386 "--dry=rate -dthing --dry street --dry=shape",
387 "--dry=rate --dry=thing --dry=street --dry=shape",
391 /* Many short options, last one with argument (glued form) */
393 const struct argpar_opt_descr descrs
[] = {
394 { 0, 'd', NULL
, false },
395 { 0, 'e', NULL
, false },
396 { 0, 'f', NULL
, true },
397 ARGPAR_OPT_DESCR_SENTINEL
408 const struct argpar_opt_descr descrs
[] = {
409 { 0, 'd', NULL
, false },
410 { 0, 'e', "east", true },
411 { 0, '\0', "mind", false },
412 ARGPAR_OPT_DESCR_SENTINEL
416 "-d --mind -destart --mind --east cough -d --east=itch",
417 "-d --mind -d --east=start --mind --east=cough -d --east=itch",
421 /* Single non-option argument */
423 const struct argpar_opt_descr descrs
[] = {
424 ARGPAR_OPT_DESCR_SENTINEL
433 /* Two non-option arguments */
435 const struct argpar_opt_descr descrs
[] = {
436 ARGPAR_OPT_DESCR_SENTINEL
441 "kilojoule<0,0> mitaine<1,1>",
445 /* Single non-option argument mixed with options */
447 const struct argpar_opt_descr descrs
[] = {
448 { 0, 'd', NULL
, false },
449 { 0, '\0', "squeeze", true },
450 ARGPAR_OPT_DESCR_SENTINEL
454 "-d sprout yes --squeeze little bag -d",
455 "-d sprout<1,0> yes<2,1> --squeeze=little bag<5,2> -d",
459 /* Unknown short option (space form) */
461 const struct argpar_opt_descr descrs
[] = {
462 { 0, 'd', NULL
, true },
463 ARGPAR_OPT_DESCR_SENTINEL
467 "-d salut -e -d meow",
472 /* Unknown short option (glued form) */
474 const struct argpar_opt_descr descrs
[] = {
475 { 0, 'd', NULL
, true },
476 ARGPAR_OPT_DESCR_SENTINEL
480 "-dsalut -e -d meow",
485 /* Unknown long option (space form) */
487 const struct argpar_opt_descr descrs
[] = {
488 { 0, '\0', "sink", true },
489 ARGPAR_OPT_DESCR_SENTINEL
493 "--sink party --food --sink impulse",
498 /* Unknown long option (equal form) */
500 const struct argpar_opt_descr descrs
[] = {
501 { 0, '\0', "sink", true },
502 ARGPAR_OPT_DESCR_SENTINEL
506 "--sink=party --food --sink=impulse",
511 /* Unknown option before non-option argument */
513 const struct argpar_opt_descr descrs
[] = {
514 { 0, '\0', "thumb", true },
515 ARGPAR_OPT_DESCR_SENTINEL
519 "--thumb=party --food bateau --thumb waves",
524 /* Unknown option after non-option argument */
526 const struct argpar_opt_descr descrs
[] = {
527 { 0, '\0', "thumb", true },
528 ARGPAR_OPT_DESCR_SENTINEL
532 "--thumb=party wound --food --thumb waves",
533 "--thumb=party wound<1,0>",
539 const struct argpar_opt_descr descrs
[] = {
540 { 0, '\0', "-fuel", true },
541 ARGPAR_OPT_DESCR_SENTINEL
550 /* Long option containing `=` in argument (equal form) */
552 const struct argpar_opt_descr descrs
[] = {
553 { 0, '\0', "zebra", true },
554 ARGPAR_OPT_DESCR_SENTINEL
563 /* Short option's argument starting with `-` (glued form) */
565 const struct argpar_opt_descr descrs
[] = {
566 { 0, 'z', NULL
, true },
567 ARGPAR_OPT_DESCR_SENTINEL
576 /* Short option's argument starting with `-` (space form) */
578 const struct argpar_opt_descr descrs
[] = {
579 { 0, 'z', NULL
, true },
580 ARGPAR_OPT_DESCR_SENTINEL
589 /* Long option's argument starting with `-` (space form) */
591 const struct argpar_opt_descr descrs
[] = {
592 { 0, '\0', "janine", true },
593 ARGPAR_OPT_DESCR_SENTINEL
602 /* Long option's argument starting with `-` (equal form) */
604 const struct argpar_opt_descr descrs
[] = {
605 { 0, '\0', "janine", true },
606 ARGPAR_OPT_DESCR_SENTINEL
615 /* Long option's empty argument (equal form) */
617 const struct argpar_opt_descr descrs
[] = {
618 { 0, 'f', NULL
, false },
619 { 0, '\0', "yeah", true },
620 ARGPAR_OPT_DESCR_SENTINEL
629 /* `-` non-option argument */
631 const struct argpar_opt_descr descrs
[] = {
632 { 0, 'f', NULL
, false },
633 ARGPAR_OPT_DESCR_SENTINEL
642 /* `--` non-option argument */
644 const struct argpar_opt_descr descrs
[] = {
645 { 0, 'f', NULL
, false },
646 ARGPAR_OPT_DESCR_SENTINEL
655 /* Very long name of long option */
657 const char opt_name
[] =
658 "kale-chips-waistcoat-yr-bicycle-rights-gochujang-"
659 "woke-tumeric-flexitarian-biodiesel-chillwave-cliche-"
660 "ethical-cardigan-listicle-pok-pok-sustainable-live-"
661 "edge-jianbing-gochujang-butcher-disrupt-tattooed-"
662 "tumeric-prism-photo-booth-vape-kogi-jean-shorts-"
663 "blog-williamsburg-fingerstache-palo-santo-artisan-"
664 "affogato-occupy-skateboard-adaptogen-neutra-celiac-"
665 "put-a-bird-on-it-kombucha-everyday-carry-hot-chicken-"
666 "craft-beer-subway-tile-tote-bag-disrupt-selvage-"
667 "raclette-art-party-readymade-paleo-heirloom-trust-"
668 "fund-small-batch-kinfolk-woke-cardigan-prism-"
669 "chambray-la-croix-hashtag-unicorn-edison-bulb-tbh-"
670 "cornhole-cliche-tattooed-green-juice-adaptogen-"
671 "kitsch-lo-fi-vexillologist-migas-gentrify-"
673 const struct argpar_opt_descr descrs
[] = {
674 { 0, '\0', opt_name
, true },
675 ARGPAR_OPT_DESCR_SENTINEL
679 sprintf(cmdline
, "--%s=23", opt_name
);
680 test_succeed(cmdline
, cmdline
, descrs
, 1);
685 * Parses `cmdline` with argpar_parse() using the option descriptors
686 * `descrs`, and ensures that the function fails and that it sets an
687 * error which is equal to `expected_error`.
689 * This function splits `cmdline` on spaces to create an original
693 void test_fail_argpar_parse(const char * const cmdline
,
694 const char * const expected_error
,
695 const struct argpar_opt_descr
* const descrs
)
697 struct argpar_parse_ret parse_ret
;
698 gchar
** const argv
= g_strsplit(cmdline
, " ", 0);
700 parse_ret
= argpar_parse(g_strv_length(argv
),
701 (const char * const *) argv
, descrs
, true);
703 "argpar_parse() fails for command line `%s`", cmdline
);
705 "argpar_parse() sets an error string for command line `%s`",
708 if (parse_ret
.items
) {
709 fail("argpar_parse() sets the expected error string");
713 ok(strcmp(expected_error
, parse_ret
.error
) == 0,
714 "argpar_parse() sets the expected error string "
715 "for command line `%s`", cmdline
);
717 if (strcmp(expected_error
, parse_ret
.error
) != 0) {
718 diag("Expected: `%s`", expected_error
);
719 diag("Got: `%s`", parse_ret
.error
);
723 argpar_parse_ret_fini(&parse_ret
);
728 * Parses `cmdline` with the iterator API using the option descriptors
729 * `descrs`, and ensures that argpar_iter_next() fails with status
730 * `expected_status` and that it sets an error which is equal to
733 * This function splits `cmdline` on spaces to create an original
737 void test_fail_argpar_iter(const char * const cmdline
,
738 const char * const expected_error
,
739 const enum argpar_iter_next_status expected_status
,
740 const struct argpar_opt_descr
* const descrs
)
742 struct argpar_iter
*iter
= NULL
;
743 const struct argpar_item
*item
= NULL
;
744 gchar
** const argv
= g_strsplit(cmdline
, " ", 0);
748 iter
= argpar_iter_create(g_strv_length(argv
),
749 (const char * const *) argv
, descrs
);
753 enum argpar_iter_next_status status
;
755 ARGPAR_ITEM_DESTROY_AND_RESET(item
);
756 status
= argpar_iter_next(iter
, &item
, &error
);
757 ok(status
== ARGPAR_ITER_NEXT_STATUS_OK
||
758 status
== expected_status
,
759 "argpar_iter_next() returns the expected status "
760 "(%d) for command line `%s` (call %u)",
761 status
, cmdline
, i
+ 1);
763 if (status
!= ARGPAR_ITER_NEXT_STATUS_OK
) {
765 "argpar_iter_next() doesn't set an item "
766 "for other status than "
767 "`ARGPAR_ITER_NEXT_STATUS_OK` "
768 "and command line `%s` (call %u)",
771 "argpar_iter_next() sets an error for "
773 " `ARGPAR_ITER_NEXT_STATUS_OK` "
774 "and command line `%s` (call %u)",
780 "argpar_iter_next() sets an item for status "
781 "`ARGPAR_ITER_NEXT_STATUS_OK` "
782 "and command line `%s` (call %u)",
785 "argpar_iter_next() doesn't set an error for status "
786 "`ARGPAR_ITER_NEXT_STATUS_OK` "
787 "and command line `%s` (call %u)",
791 ok(strcmp(expected_error
, error
) == 0,
792 "argpar_iter_next() sets the expected error string "
793 "for command line `%s`", cmdline
);
795 if (strcmp(expected_error
, error
) != 0) {
796 diag("Expected: `%s`", expected_error
);
797 diag("Got: `%s`", error
);
800 argpar_item_destroy(item
);
801 argpar_iter_destroy(iter
);
807 * Calls test_fail_argpar_parse() and test_fail_argpar_iter() with the
808 * provided parameters.
811 void test_fail(const char * const cmdline
, const char * const expected_error
,
812 const enum argpar_iter_next_status expected_iter_next_status
,
813 const struct argpar_opt_descr
* const descrs
)
815 test_fail_argpar_parse(cmdline
, expected_error
, descrs
);
816 test_fail_argpar_iter(cmdline
, expected_error
,
817 expected_iter_next_status
, descrs
);
821 void fail_tests(void)
823 /* Unknown long option */
825 const struct argpar_opt_descr descrs
[] = {
826 { 0, '\0', "thumb", true },
827 ARGPAR_OPT_DESCR_SENTINEL
831 "--thumb=party --meow",
832 "While parsing argument #2 (`--meow`): Unknown option `--meow`",
833 ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT
,
837 /* Unknown short option */
839 const struct argpar_opt_descr descrs
[] = {
840 { 0, '\0', "thumb", true },
841 ARGPAR_OPT_DESCR_SENTINEL
846 "While parsing argument #2 (`-x`): Unknown option `-x`",
847 ARGPAR_ITER_NEXT_STATUS_ERROR_UNKNOWN_OPT
,
851 /* Missing long option argument */
853 const struct argpar_opt_descr descrs
[] = {
854 { 0, '\0', "thumb", true },
855 ARGPAR_OPT_DESCR_SENTINEL
860 "While parsing argument #1 (`--thumb`): Missing required argument for option `--thumb`",
861 ARGPAR_ITER_NEXT_STATUS_ERROR_MISSING_OPT_ARG
,
865 /* Missing short option argument */
867 const struct argpar_opt_descr descrs
[] = {
868 { 0, 'k', NULL
, true },
869 ARGPAR_OPT_DESCR_SENTINEL
874 "While parsing argument #1 (`-k`): Missing required argument for option `-k`",
875 ARGPAR_ITER_NEXT_STATUS_ERROR_MISSING_OPT_ARG
,
879 /* Missing short option argument (multiple glued) */
881 const struct argpar_opt_descr descrs
[] = {
882 { 0, 'a', NULL
, false },
883 { 0, 'b', NULL
, false },
884 { 0, 'c', NULL
, true },
885 ARGPAR_OPT_DESCR_SENTINEL
890 "While parsing argument #1 (`-abc`): Missing required argument for option `-c`",
891 ARGPAR_ITER_NEXT_STATUS_ERROR_MISSING_OPT_ARG
,
895 /* Unexpected long option argument */
897 const struct argpar_opt_descr descrs
[] = {
898 { 0, 'c', "chevre", false },
899 ARGPAR_OPT_DESCR_SENTINEL
904 "While parsing argument #1 (`--chevre=fromage`): Unexpected argument for option `--chevre`",
905 ARGPAR_ITER_NEXT_STATUS_ERROR_UNEXPECTED_OPT_ARG
,
915 return exit_status();