Increase REUSE compliance
[argpar.git] / tests / test-argpar.c
CommitLineData
903a5b8a 1/*
9cc0acc6
PP
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>
903a5b8a
SM
5 */
6
7ac57709 7#include <assert.h>
45ad74d3
SM
8#include <glib.h>
9#include <stdbool.h>
d1f7bbdb 10#include <stdio.h>
45ad74d3 11#include <stdlib.h>
903a5b8a 12#include <string.h>
903a5b8a 13
903a5b8a 14#include "argpar/argpar.h"
45ad74d3 15#include "tap/tap.h"
903a5b8a
SM
16
17/*
11003cd5
PP
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 *
8a47e37d 23 * ‣ Prefers the `--long-opt=arg` style over the `-s arg` style.
11003cd5 24 *
8a47e37d 25 * ‣ Uses the `arg<A,B>` form for non-option arguments, where `A` is the
11003cd5 26 * original argument index and `B` is the non-option argument index.
903a5b8a 27 */
45ad74d3 28static void append_to_res_str(GString * const res_str, const argpar_item_t * const item)
fc07e526 29{
45ad74d3
SM
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 }
fc07e526
SM
68}
69
11003cd5 70/*
4d6198b5 71 * Parses `cmdline` with the argpar API using the option descriptors
11003cd5
PP
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 */
45ad74d3
SM
82static 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)
fc07e526 85{
45ad74d3
SM
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);
fc07e526
SM
144}
145
45ad74d3 146static void succeed_tests(void)
903a5b8a 147{
45ad74d3
SM
148 /* No arguments */
149 {
150 const argpar_opt_descr_t descrs[] = {ARGPAR_OPT_DESCR_SENTINEL};
903a5b8a 151
45ad74d3
SM
152 test_succeed("", "", descrs, 0);
153 }
903a5b8a 154
45ad74d3
SM
155 /* Single long option */
156 {
157 const argpar_opt_descr_t descrs[] = {{0, '\0', "salut", false}, ARGPAR_OPT_DESCR_SENTINEL};
903a5b8a 158
45ad74d3
SM
159 test_succeed("--salut", "--salut", descrs, 1);
160 }
903a5b8a 161
45ad74d3
SM
162 /* Single short option */
163 {
164 const argpar_opt_descr_t descrs[] = {{0, 'f', NULL, false}, ARGPAR_OPT_DESCR_SENTINEL};
903a5b8a 165
45ad74d3
SM
166 test_succeed("-f", "-f", descrs, 1);
167 }
903a5b8a 168
45ad74d3
SM
169 /* Short and long option (aliases) */
170 {
171 const argpar_opt_descr_t descrs[] = {{0, 'f', "flaw", false}, ARGPAR_OPT_DESCR_SENTINEL};
903a5b8a 172
45ad74d3
SM
173 test_succeed("-f --flaw", "--flaw --flaw", descrs, 2);
174 }
903a5b8a 175
45ad74d3
SM
176 /* Long option with argument (space form) */
177 {
178 const argpar_opt_descr_t descrs[] = {{0, '\0', "tooth", true}, ARGPAR_OPT_DESCR_SENTINEL};
903a5b8a 179
45ad74d3
SM
180 test_succeed("--tooth 67", "--tooth=67", descrs, 2);
181 }
903a5b8a 182
45ad74d3
SM
183 /* Long option with argument (equal form) */
184 {
185 const argpar_opt_descr_t descrs[] = {{0, '\0', "polish", true}, ARGPAR_OPT_DESCR_SENTINEL};
903a5b8a 186
45ad74d3
SM
187 test_succeed("--polish=brick", "--polish=brick", descrs, 1);
188 }
903a5b8a 189
45ad74d3
SM
190 /* Short option with argument (space form) */
191 {
192 const argpar_opt_descr_t descrs[] = {{0, 'c', NULL, true}, ARGPAR_OPT_DESCR_SENTINEL};
903a5b8a 193
45ad74d3
SM
194 test_succeed("-c chilly", "-c chilly", descrs, 2);
195 }
903a5b8a 196
45ad74d3
SM
197 /* Short option with argument (glued form) */
198 {
199 const argpar_opt_descr_t descrs[] = {{0, 'c', NULL, true}, ARGPAR_OPT_DESCR_SENTINEL};
903a5b8a 200
45ad74d3
SM
201 test_succeed("-cchilly", "-c chilly", descrs, 1);
202 }
903a5b8a 203
45ad74d3
SM
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};
903a5b8a 207
45ad74d3
SM
208 test_succeed("--dry=rate -dthing --dry street --dry=shape",
209 "--dry=rate --dry=thing --dry=street --dry=shape", descrs, 5);
210 }
903a5b8a 211
45ad74d3
SM
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};
903a5b8a 218
45ad74d3
SM
219 test_succeed("-defmeow", "-d -e -f meow", descrs, 1);
220 }
dd757a65 221
45ad74d3
SM
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};
dd757a65 228
45ad74d3
SM
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 }
d1f7bbdb 232
45ad74d3
SM
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 }
903a5b8a
SM
345}
346
11003cd5 347/*
4d6198b5 348 * Parses `cmdline` with the argpar API using the option descriptors
2af370d0 349 * `descrs`, and ensures that argpar_iter_next() fails with status
8b95d883
PP
350 * `expected_status` and that it sets an error having:
351 *
8a47e37d 352 * ‣ The original argument index `expected_orig_index`.
8b95d883 353 *
8a47e37d 354 * ‣ If applicable:
8b95d883 355 *
8a47e37d 356 * • The unknown option name `expected_unknown_opt_name`.
8b95d883 357 *
8a47e37d 358 * • The option descriptor at index `expected_opt_descr_index` of
8b95d883
PP
359 * `descrs`.
360 *
8a47e37d 361 * • The option type `expected_is_short`.
11003cd5
PP
362 *
363 * This function splits `cmdline` on spaces to create an original
364 * argument array.
365 */
45ad74d3
SM
366static 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)
fc07e526 371{
45ad74d3
SM
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 /*
fc07e526 438 ok(strcmp(expected_error, error) == 0,
2af370d0 439 "argpar_iter_next() sets the expected error string "
fc07e526 440 "for command line `%s`", cmdline);
11003cd5 441
fc07e526
SM
442 if (strcmp(expected_error, error) != 0) {
443 diag("Expected: `%s`", expected_error);
444 diag("Got: `%s`", error);
445 }
8b95d883 446 */
fc07e526 447
45ad74d3
SM
448 argpar_item_destroy(item);
449 argpar_iter_destroy(iter);
450 argpar_error_destroy(error);
451 g_strfreev(argv);
fc07e526
SM
452}
453
45ad74d3 454static void fail_tests(void)
903a5b8a 455{
45ad74d3
SM
456 /* Unknown short option (space form) */
457 {
458 const argpar_opt_descr_t descrs[] = {{0, 'd', NULL, true}, ARGPAR_OPT_DESCR_SENTINEL};
8b95d883 459
45ad74d3
SM
460 test_fail("-d salut -e -d meow", ARGPAR_ERROR_TYPE_UNKNOWN_OPT, 2, "-e", 0, false, descrs);
461 }
8b95d883 462
45ad74d3
SM
463 /* Unknown short option (glued form) */
464 {
465 const argpar_opt_descr_t descrs[] = {{0, 'd', 0, true}, ARGPAR_OPT_DESCR_SENTINEL};
8b95d883 466
45ad74d3
SM
467 test_fail("-dsalut -e -d meow", ARGPAR_ERROR_TYPE_UNKNOWN_OPT, 1, "-e", 0, false, descrs);
468 }
8b95d883 469
45ad74d3
SM
470 /* Unknown long option (space form) */
471 {
472 const argpar_opt_descr_t descrs[] = {{0, '\0', "sink", true}, ARGPAR_OPT_DESCR_SENTINEL};
8b95d883 473
45ad74d3
SM
474 test_fail("--sink party --food --sink impulse", ARGPAR_ERROR_TYPE_UNKNOWN_OPT, 2, "--food",
475 0, false, descrs);
476 }
903a5b8a 477
45ad74d3
SM
478 /* Unknown long option (equal form) */
479 {
480 const argpar_opt_descr_t descrs[] = {{0, '\0', "sink", true}, ARGPAR_OPT_DESCR_SENTINEL};
903a5b8a 481
45ad74d3
SM
482 test_fail("--sink=party --food --sink=impulse", ARGPAR_ERROR_TYPE_UNKNOWN_OPT, 1, "--food",
483 0, false, descrs);
484 }
903a5b8a 485
45ad74d3
SM
486 /* Unknown option before non-option argument */
487 {
488 const argpar_opt_descr_t descrs[] = {{0, '\0', "thumb", true}, ARGPAR_OPT_DESCR_SENTINEL};
903a5b8a 489
45ad74d3
SM
490 test_fail("--thumb=party --food=18 bateau --thumb waves", ARGPAR_ERROR_TYPE_UNKNOWN_OPT, 1,
491 "--food", 0, false, descrs);
492 }
903a5b8a 493
45ad74d3
SM
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 }
903a5b8a
SM
533}
534
535int main(void)
536{
45ad74d3
SM
537 plan_tests(309);
538 succeed_tests();
539 fail_tests();
540 return exit_status();
903a5b8a 541}
This page took 0.045619 seconds and 4 git commands to generate.