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.
26 #include "argpar/argpar.h"
29 * Formats `item` and appends the resulting string to `res_str` to
30 * incrementally build an expected command line string.
34 * * Prefers the `--long-opt=arg` style over the `-s arg` style.
36 * * Uses the `arg<A,B>` form for non-option arguments, where `A` is the
37 * original argument index and `B` is the non-option argument index.
40 void append_to_res_str(GString
* const res_str
,
41 const struct argpar_item
* const item
)
43 if (res_str
->len
> 0) {
44 g_string_append_c(res_str
, ' ');
48 case ARGPAR_ITEM_TYPE_OPT
:
50 const struct argpar_item_opt
*const item_opt
=
53 if (item_opt
->descr
->long_name
) {
54 g_string_append_printf(res_str
, "--%s",
55 item_opt
->descr
->long_name
);
58 g_string_append_printf(res_str
, "=%s",
61 } else if (item_opt
->descr
->short_name
) {
62 g_string_append_printf(res_str
, "-%c",
63 item_opt
->descr
->short_name
);
66 g_string_append_printf(res_str
, " %s",
73 case ARGPAR_ITEM_TYPE_NON_OPT
:
75 const struct argpar_item_non_opt
* const item_non_opt
=
78 g_string_append_printf(res_str
, "%s<%u,%u>",
79 item_non_opt
->arg
, item_non_opt
->orig_index
,
80 item_non_opt
->non_opt_index
);
89 * Parses `cmdline` with argpar_parse() using the option descriptors
90 * `descrs`, and ensures that the resulting effective command line is
91 * `expected_cmd_line` and that the number of ingested original
92 * arguments is `expected_ingested_orig_args`.
94 * This function splits `cmdline` on spaces to create an original
97 * This function builds the resulting command line from parsing items
98 * by space-separating each formatted item (see append_to_res_str()).
101 void test_succeed_argpar_parse(const char * const cmdline
,
102 const char * const expected_cmd_line
,
103 const struct argpar_opt_descr
* const descrs
,
104 const unsigned int expected_ingested_orig_args
)
106 struct argpar_parse_ret parse_ret
;
107 GString
* const res_str
= g_string_new(NULL
);
108 gchar
** const argv
= g_strsplit(cmdline
, " ", 0);
113 parse_ret
= argpar_parse(g_strv_length(argv
),
114 (const char * const *) argv
, descrs
, false);
116 "argpar_parse() succeeds for command line `%s`", cmdline
);
118 "argpar_parse() doesn't set an error for command line `%s`",
120 ok(parse_ret
.ingested_orig_args
== expected_ingested_orig_args
,
121 "argpar_parse() returns the correct number of ingested "
122 "original arguments for command line `%s`", cmdline
);
124 if (parse_ret
.ingested_orig_args
!= expected_ingested_orig_args
) {
125 diag("Expected: %u Got: %u", expected_ingested_orig_args
,
126 parse_ret
.ingested_orig_args
);
129 if (!parse_ret
.items
) {
130 fail("argpar_parse() returns the expected parsing items "
131 "for command line `%s`", cmdline
);
135 for (i
= 0; i
< parse_ret
.items
->n_items
; i
++) {
136 append_to_res_str(res_str
, parse_ret
.items
->items
[i
]);
139 ok(strcmp(expected_cmd_line
, res_str
->str
) == 0,
140 "argpar_parse() returns the expected parsed arguments "
141 "for command line `%s`", cmdline
);
143 if (strcmp(expected_cmd_line
, res_str
->str
) != 0) {
144 diag("Expected: `%s`", expected_cmd_line
);
145 diag("Got: `%s`", res_str
->str
);
149 argpar_parse_ret_fini(&parse_ret
);
150 g_string_free(res_str
, TRUE
);
155 * Parses `cmdline` with the iterator API using the option descriptors
156 * `descrs`, and ensures that the resulting effective command line is
157 * `expected_cmd_line` and that the number of ingested original
158 * arguments is `expected_ingested_orig_args`.
160 * This function splits `cmdline` on spaces to create an original
163 * This function builds the resulting command line from parsing items
164 * by space-separating each formatted item (see append_to_res_str()).
167 void test_succeed_argpar_iter(const char * const cmdline
,
168 const char * const expected_cmd_line
,
169 const struct argpar_opt_descr
* const descrs
,
170 const unsigned int expected_ingested_orig_args
)
172 struct argpar_iter
*iter
= NULL
;
173 const struct argpar_item
*item
= NULL
;
175 GString
* const res_str
= g_string_new(NULL
);
176 gchar
** const argv
= g_strsplit(cmdline
, " ", 0);
177 unsigned int i
, actual_ingested_orig_args
;
181 iter
= argpar_iter_create(g_strv_length(argv
),
182 (const char * const *) argv
, descrs
);
186 enum argpar_iter_parse_next_status status
;
188 ARGPAR_ITEM_DESTROY_AND_RESET(item
);
189 status
= argpar_iter_parse_next(iter
, &item
, &error
);
191 ok(status
== ARGPAR_ITER_PARSE_NEXT_STATUS_OK
||
192 status
== ARGPAR_ITER_PARSE_NEXT_STATUS_END
||
193 status
== ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT
,
194 "argpar_iter_parse_next() returns the expected status "
195 "(%d) for command line `%s` (call %u)",
196 status
, cmdline
, i
+ 1);
198 if (status
== ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT
) {
200 "argpar_iter_parse_next() sets an error for "
201 "status `ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT` "
202 "and command line `%s` (call %u)",
206 "argpar_iter_parse_next() doesn't set an error "
207 "for other status than "
208 "`ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT` "
209 "and command line `%s` (call %u)",
213 if (status
== ARGPAR_ITER_PARSE_NEXT_STATUS_END
||
214 status
== ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT
) {
216 "argpar_iter_parse_next() doesn't set an item "
217 "for status `ARGPAR_ITER_PARSE_NEXT_STATUS_END` "
218 "or `ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT` "
219 "and command line `%s` (call %u)",
224 append_to_res_str(res_str
, item
);
227 actual_ingested_orig_args
= argpar_iter_get_ingested_orig_args(iter
);
228 ok(actual_ingested_orig_args
== expected_ingested_orig_args
,
229 "argpar_iter_get_ingested_orig_args() returns the expected "
230 "number of ingested original arguments for command line `%s`",
233 if (actual_ingested_orig_args
!= expected_ingested_orig_args
) {
234 diag("Expected: %u Got: %u", expected_ingested_orig_args
,
235 actual_ingested_orig_args
);
238 ok(strcmp(expected_cmd_line
, res_str
->str
) == 0,
239 "argpar_iter_parse_next() returns the expected parsing items "
240 "for command line `%s`", cmdline
);
242 if (strcmp(expected_cmd_line
, res_str
->str
) != 0) {
243 diag("Expected: `%s`", expected_cmd_line
);
244 diag("Got: `%s`", res_str
->str
);
247 argpar_item_destroy(item
);
248 argpar_iter_destroy(iter
);
249 g_string_free(res_str
, TRUE
);
255 * Calls test_succeed_argpar_parse() and test_succeed_argpar_iter()
256 * with the provided parameters.
259 void test_succeed(const char * const cmdline
,
260 const char * const expected_cmd_line
,
261 const struct argpar_opt_descr
* const descrs
,
262 const unsigned int expected_ingested_orig_args
)
264 test_succeed_argpar_parse(cmdline
, expected_cmd_line
, descrs
,
265 expected_ingested_orig_args
);
266 test_succeed_argpar_iter(cmdline
, expected_cmd_line
, descrs
,
267 expected_ingested_orig_args
);
271 void succeed_tests(void)
275 const struct argpar_opt_descr descrs
[] = {
276 ARGPAR_OPT_DESCR_SENTINEL
285 /* Single long option */
287 const struct argpar_opt_descr descrs
[] = {
288 { 0, '\0', "salut", false },
289 ARGPAR_OPT_DESCR_SENTINEL
298 /* Single short option */
300 const struct argpar_opt_descr descrs
[] = {
301 { 0, 'f', NULL
, false },
302 ARGPAR_OPT_DESCR_SENTINEL
311 /* Short and long option (aliases) */
313 const struct argpar_opt_descr descrs
[] = {
314 { 0, 'f', "flaw", false },
315 ARGPAR_OPT_DESCR_SENTINEL
324 /* Long option with argument (space form) */
326 const struct argpar_opt_descr descrs
[] = {
327 { 0, '\0', "tooth", true },
328 ARGPAR_OPT_DESCR_SENTINEL
337 /* Long option with argument (equal form) */
339 const struct argpar_opt_descr descrs
[] = {
340 { 0, '\0', "polish", true },
341 ARGPAR_OPT_DESCR_SENTINEL
350 /* Short option with argument (space form) */
352 const struct argpar_opt_descr descrs
[] = {
353 { 0, 'c', NULL
, true },
354 ARGPAR_OPT_DESCR_SENTINEL
363 /* Short option with argument (glued form) */
365 const struct argpar_opt_descr descrs
[] = {
366 { 0, 'c', NULL
, true },
367 ARGPAR_OPT_DESCR_SENTINEL
376 /* Short and long option (aliases) with argument (all forms) */
378 const struct argpar_opt_descr descrs
[] = {
379 { 0, 'd', "dry", true },
380 ARGPAR_OPT_DESCR_SENTINEL
384 "--dry=rate -dthing --dry street --dry=shape",
385 "--dry=rate --dry=thing --dry=street --dry=shape",
389 /* Many short options, last one with argument (glued form) */
391 const struct argpar_opt_descr descrs
[] = {
392 { 0, 'd', NULL
, false },
393 { 0, 'e', NULL
, false },
394 { 0, 'f', NULL
, true },
395 ARGPAR_OPT_DESCR_SENTINEL
406 const struct argpar_opt_descr descrs
[] = {
407 { 0, 'd', NULL
, false },
408 { 0, 'e', "east", true },
409 { 0, '\0', "mind", false },
410 ARGPAR_OPT_DESCR_SENTINEL
414 "-d --mind -destart --mind --east cough -d --east=itch",
415 "-d --mind -d --east=start --mind --east=cough -d --east=itch",
419 /* Single non-option argument */
421 const struct argpar_opt_descr descrs
[] = {
422 ARGPAR_OPT_DESCR_SENTINEL
431 /* Two non-option arguments */
433 const struct argpar_opt_descr descrs
[] = {
434 ARGPAR_OPT_DESCR_SENTINEL
439 "kilojoule<0,0> mitaine<1,1>",
443 /* Single non-option argument mixed with options */
445 const struct argpar_opt_descr descrs
[] = {
446 { 0, 'd', NULL
, false },
447 { 0, '\0', "squeeze", true },
448 ARGPAR_OPT_DESCR_SENTINEL
452 "-d sprout yes --squeeze little bag -d",
453 "-d sprout<1,0> yes<2,1> --squeeze=little bag<5,2> -d",
457 /* Unknown short option (space form) */
459 const struct argpar_opt_descr descrs
[] = {
460 { 0, 'd', NULL
, true },
461 ARGPAR_OPT_DESCR_SENTINEL
465 "-d salut -e -d meow",
470 /* Unknown short option (glued form) */
472 const struct argpar_opt_descr descrs
[] = {
473 { 0, 'd', NULL
, true },
474 ARGPAR_OPT_DESCR_SENTINEL
478 "-dsalut -e -d meow",
483 /* Unknown long option (space form) */
485 const struct argpar_opt_descr descrs
[] = {
486 { 0, '\0', "sink", true },
487 ARGPAR_OPT_DESCR_SENTINEL
491 "--sink party --food --sink impulse",
496 /* Unknown long option (equal form) */
498 const struct argpar_opt_descr descrs
[] = {
499 { 0, '\0', "sink", true },
500 ARGPAR_OPT_DESCR_SENTINEL
504 "--sink=party --food --sink=impulse",
509 /* Unknown option before non-option argument */
511 const struct argpar_opt_descr descrs
[] = {
512 { 0, '\0', "thumb", true },
513 ARGPAR_OPT_DESCR_SENTINEL
517 "--thumb=party --food bateau --thumb waves",
522 /* Unknown option after non-option argument */
524 const struct argpar_opt_descr descrs
[] = {
525 { 0, '\0', "thumb", true },
526 ARGPAR_OPT_DESCR_SENTINEL
530 "--thumb=party wound --food --thumb waves",
531 "--thumb=party wound<1,0>",
537 const struct argpar_opt_descr descrs
[] = {
538 { 0, '\0', "-fuel", true },
539 ARGPAR_OPT_DESCR_SENTINEL
548 /* Long option containing `=` in argument (equal form) */
550 const struct argpar_opt_descr descrs
[] = {
551 { 0, '\0', "zebra", true },
552 ARGPAR_OPT_DESCR_SENTINEL
561 /* Short option's argument starting with `-` (glued form) */
563 const struct argpar_opt_descr descrs
[] = {
564 { 0, 'z', NULL
, true },
565 ARGPAR_OPT_DESCR_SENTINEL
574 /* Short option's argument starting with `-` (space form) */
576 const struct argpar_opt_descr descrs
[] = {
577 { 0, 'z', NULL
, true },
578 ARGPAR_OPT_DESCR_SENTINEL
587 /* Long option's argument starting with `-` (space form) */
589 const struct argpar_opt_descr descrs
[] = {
590 { 0, '\0', "janine", true },
591 ARGPAR_OPT_DESCR_SENTINEL
600 /* Long option's argument starting with `-` (equal form) */
602 const struct argpar_opt_descr descrs
[] = {
603 { 0, '\0', "janine", true },
604 ARGPAR_OPT_DESCR_SENTINEL
613 /* Long option's empty argument (equal form) */
615 const struct argpar_opt_descr descrs
[] = {
616 { 0, 'f', NULL
, false },
617 { 0, '\0', "yeah", true },
618 ARGPAR_OPT_DESCR_SENTINEL
629 * Parses `cmdline` with argpar_parse() using the option descriptors
630 * `descrs`, and ensures that the function fails and that it sets an
631 * error which is equal to `expected_error`.
633 * This function splits `cmdline` on spaces to create an original
637 void test_fail_argpar_parse(const char * const cmdline
,
638 const char * const expected_error
,
639 const struct argpar_opt_descr
* const descrs
)
641 struct argpar_parse_ret parse_ret
;
642 gchar
** const argv
= g_strsplit(cmdline
, " ", 0);
644 parse_ret
= argpar_parse(g_strv_length(argv
),
645 (const char * const *) argv
, descrs
, true);
647 "argpar_parse() fails for command line `%s`", cmdline
);
649 "argpar_parse() sets an error string for command line `%s`",
652 if (parse_ret
.items
) {
653 fail("argpar_parse() sets the expected error string");
657 ok(strcmp(expected_error
, parse_ret
.error
) == 0,
658 "argpar_parse() sets the expected error string "
659 "for command line `%s`", cmdline
);
661 if (strcmp(expected_error
, parse_ret
.error
) != 0) {
662 diag("Expected: `%s`", expected_error
);
663 diag("Got: `%s`", parse_ret
.error
);
667 argpar_parse_ret_fini(&parse_ret
);
672 * Parses `cmdline` with the iterator API using the option descriptors
673 * `descrs`, and ensures that argpar_iter_parse_next() fails and that it
674 * sets an error which is equal to `expected_error`.
676 * This function splits `cmdline` on spaces to create an original
680 void test_fail_argpar_iter(const char * const cmdline
,
681 const char * const expected_error
,
682 const struct argpar_opt_descr
* const descrs
)
684 struct argpar_iter
*iter
= NULL
;
685 const struct argpar_item
*item
= NULL
;
686 gchar
** const argv
= g_strsplit(cmdline
, " ", 0);
690 iter
= argpar_iter_create(g_strv_length(argv
),
691 (const char * const *) argv
, descrs
);
695 enum argpar_iter_parse_next_status status
;
697 ARGPAR_ITEM_DESTROY_AND_RESET(item
);
698 status
= argpar_iter_parse_next(iter
, &item
, &error
);
700 ok(status
== ARGPAR_ITER_PARSE_NEXT_STATUS_OK
||
701 status
== ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR
||
702 status
== ARGPAR_ITER_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT
,
703 "argpar_iter_parse_next() returns the expected status "
704 "(%d) for command line `%s` (call %u)",
705 status
, cmdline
, i
+ 1);
707 if (status
!= ARGPAR_ITER_PARSE_NEXT_STATUS_OK
) {
709 "argpar_iter_parse_next() doesn't set an item "
710 "for other status than "
711 "`ARGPAR_ITER_PARSE_NEXT_STATUS_OK` "
712 "and command line `%s` (call %u)",
715 "argpar_iter_parse_next() sets an error for "
717 " `ARGPAR_ITER_PARSE_NEXT_STATUS_OK` "
718 "and command line `%s` (call %u)",
724 "argpar_iter_parse_next() sets an item for status "
725 "`ARGPAR_ITER_PARSE_NEXT_STATUS_OK` "
726 "and command line `%s` (call %u)",
729 "argpar_iter_parse_next() doesn't set an error for status "
730 "`ARGPAR_ITER_PARSE_NEXT_STATUS_OK` "
731 "and command line `%s` (call %u)",
735 ok(strcmp(expected_error
, error
) == 0,
736 "argpar_iter_parse_next() sets the expected error string "
737 "for command line `%s`", cmdline
);
739 if (strcmp(expected_error
, error
) != 0) {
740 diag("Expected: `%s`", expected_error
);
741 diag("Got: `%s`", error
);
744 argpar_item_destroy(item
);
745 argpar_iter_destroy(iter
);
751 * Calls test_fail_argpar_parse() and test_fail_argpar_iter() with the
752 * provided parameters.
755 void test_fail(const char * const cmdline
, const char * const expected_error
,
756 const struct argpar_opt_descr
* const descrs
)
758 test_fail_argpar_parse(cmdline
, expected_error
, descrs
);
759 test_fail_argpar_iter(cmdline
, expected_error
, descrs
);
763 void fail_tests(void)
765 /* Unknown long option */
767 const struct argpar_opt_descr descrs
[] = {
768 { 0, '\0', "thumb", true },
769 ARGPAR_OPT_DESCR_SENTINEL
773 "--thumb=party --meow",
774 "While parsing argument #2 (`--meow`): Unknown option `--meow`",
778 /* Unknown short option */
780 const struct argpar_opt_descr descrs
[] = {
781 { 0, '\0', "thumb", true },
782 ARGPAR_OPT_DESCR_SENTINEL
787 "While parsing argument #2 (`-x`): Unknown option `-x`",
791 /* Missing long option argument */
793 const struct argpar_opt_descr descrs
[] = {
794 { 0, '\0', "thumb", true },
795 ARGPAR_OPT_DESCR_SENTINEL
800 "While parsing argument #1 (`--thumb`): Missing required argument for option `--thumb`",
804 /* Missing short option argument */
806 const struct argpar_opt_descr descrs
[] = {
807 { 0, 'k', NULL
, true },
808 ARGPAR_OPT_DESCR_SENTINEL
813 "While parsing argument #1 (`-k`): Missing required argument for option `-k`",
817 /* Missing short option argument (multiple glued) */
819 const struct argpar_opt_descr descrs
[] = {
820 { 0, 'a', NULL
, false },
821 { 0, 'b', NULL
, false },
822 { 0, 'c', NULL
, true },
823 ARGPAR_OPT_DESCR_SENTINEL
828 "While parsing argument #1 (`-abc`): Missing required argument for option `-c`",
834 const struct argpar_opt_descr descrs
[] = {
835 { 0, 'a', NULL
, false },
836 { 0, 'b', NULL
, false },
837 { 0, 'c', NULL
, true },
838 ARGPAR_OPT_DESCR_SENTINEL
843 "While parsing argument #2 (`-`): Invalid argument",
849 const struct argpar_opt_descr descrs
[] = {
850 { 0, 'a', NULL
, false },
851 { 0, 'b', NULL
, false },
852 { 0, 'c', NULL
, true },
853 ARGPAR_OPT_DESCR_SENTINEL
858 "While parsing argument #2 (`--`): Invalid argument",
863 const struct argpar_opt_descr descrs
[] = {
864 { 0, 'c', "chevre", false },
865 ARGPAR_OPT_DESCR_SENTINEL
870 "While parsing argument #1 (`--chevre=fromage`): Unexpected argument for option `--chevre`",
880 return exit_status();