From: Rusty Russell Date: Mon, 25 Oct 2010 03:28:50 +0000 (+1030) Subject: opt: wean off getopt_long, beef up tests. X-Git-Url: https://git.ozlabs.org/?p=ccan;a=commitdiff_plain;h=9056c31b46452c92c7dd9c276664f514720a84c6 opt: wean off getopt_long, beef up tests. Doing our own parsing lost a few lines of code, too. Our coverage is over 99% now. --- diff --git a/ccan/opt/helpers.c b/ccan/opt/helpers.c index 0f3671e5..e96f5db8 100644 --- a/ccan/opt/helpers.c +++ b/ccan/opt/helpers.c @@ -91,10 +91,8 @@ char *opt_set_longval(const char *arg, long *l) *l = strtol(arg, &endp, 0); if (*endp || !arg[0]) return arg_bad("'%s' is not a number", arg); - if (errno == ERANGE) - return arg_bad("'%s' is out of range", arg); if (errno) - return opt_invalid_argument(arg); + return arg_bad("'%s' is out of range", arg); return NULL; } diff --git a/ccan/opt/opt.c b/ccan/opt/opt.c index 0929daff..8f677400 100644 --- a/ccan/opt/opt.c +++ b/ccan/opt/opt.c @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include @@ -53,7 +52,7 @@ static const char *next_opt(const char *p, unsigned *i, unsigned *len) return NULL; } -static const char *first_lopt(unsigned *i, unsigned *len) +const char *first_lopt(unsigned *i, unsigned *len) { const char *p; for (p = first_opt(i, len); p; p = next_opt(p, i, len)) { @@ -67,7 +66,7 @@ static const char *first_lopt(unsigned *i, unsigned *len) return p; } -static const char *next_lopt(const char *p, unsigned *i, unsigned *len) +const char *next_lopt(const char *p, unsigned *i, unsigned *len) { for (p = next_opt(p, i, len); p; p = next_opt(p, i, len)) { if (p[0] == '-') { @@ -128,22 +127,15 @@ static void check_opt(const struct opt_table *entry) if (len != 1) errx(1, "Option %s: invalid short option" " '%.*s'", entry->names, len+1, p-1); - if (*p == ':') - errx(1, "Option %s: invalid short option '-:'", - entry->names); opt_num_short++; - if (entry->type == OPT_HASARG) { + if (entry->type == OPT_HASARG) opt_num_short_arg++; - if (*p == '?') - errx(1, "Option %s: '-?' cannot take" - " an argument", entry->names); - } } /* Don't document args unless there are some. */ if (entry->type == OPT_NOARG) { if (p[len] == ' ' || p[len] == '=') errx(1, "Option %s: does not take arguments" - "'%s'", entry->names, p+len+1); + " '%s'", entry->names, p+len+1); } } } @@ -194,151 +186,19 @@ void opt_register_table(const struct opt_table entry[], const char *desc) opt_table[start].arg = (void *)(intptr_t)(opt_count - start); } -static char *make_optstring(void) -{ - char *str = malloc(1 + opt_num_short + opt_num_short_arg + 1); - const char *p; - unsigned int i, num = 0; - - /* This tells getopt_long we want a ':' returned for missing arg. */ - str[num++] = ':'; - for (p = first_sopt(&i); p; p = next_sopt(p, &i)) { - str[num++] = *p; - if (opt_table[i].type == OPT_HASARG) - str[num++] = ':'; - } - str[num++] = '\0'; - assert(num == 1 + opt_num_short + opt_num_short_arg + 1); - return str; -} - -static struct option *make_options(void) -{ - struct option *options = malloc(sizeof(*options) * (opt_num_long + 1)); - unsigned int i, num = 0, len = 0 /* GCC bogus warning */; - const char *p; - - for (p = first_lopt(&i, &len); p; p = next_lopt(p, &i, &len)) { - char *buf = malloc(len + 1); - memcpy(buf, p, len); - buf[len] = 0; - options[num].name = buf; - options[num].has_arg = (opt_table[i].type == OPT_HASARG); - options[num].flag = NULL; - options[num].val = 0; - num++; - } - memset(&options[num], 0, sizeof(options[num])); - assert(num == opt_num_long); - return options; -} - -static struct opt_table *find_short(char shortopt) -{ - unsigned int i; - const char *p; - - for (p = first_sopt(&i); p; p = next_sopt(p, &i)) { - if (*p == shortopt) - return &opt_table[i]; - } - abort(); -} - -/* We want the index'th long entry. */ -static struct opt_table *find_long(int index, const char **name) -{ - unsigned int i, len; - const char *p; - - for (p = first_lopt(&i, &len); p; p = next_lopt(p, &i, &len)) { - if (index == 0) { - *name = p; - return &opt_table[i]; - } - index--; - } - abort(); -} - -/* glibc does this as: -/tmp/opt-example: invalid option -- 'x' -/tmp/opt-example: unrecognized option '--long' -/tmp/opt-example: option '--someflag' doesn't allow an argument -/tmp/opt-example: option '--s' is ambiguous -/tmp/opt-example: option requires an argument -- 's' -*/ -static void parse_fail(void (*errlog)(const char *fmt, ...), - char shortopt, const char *longopt, const char *problem) -{ - if (shortopt) - errlog("%s: -%c: %s", opt_argv0, shortopt, problem); - else - errlog("%s: --%.*s: %s", opt_argv0, - strcspn(longopt, "|"), longopt, problem); -} - /* Parse your arguments. */ bool opt_parse(int *argc, char *argv[], void (*errlog)(const char *fmt, ...)) { - char *optstring = make_optstring(); - struct option *options = make_options(); - int ret, longidx = 0; - struct opt_table *e; + int ret; + unsigned offset = 0; - /* We will do our own error reporting. */ - opterr = 0; + /* This helps opt_usage. */ opt_argv0 = argv[0]; - /* Reset in case we're called more than once. */ - optopt = 0; - optind = 0; - while ((ret = getopt_long(*argc, argv, optstring, options, &longidx)) - != -1) { - char *problem; - const char *name = NULL; /* GCC bogus warning */ - - /* optopt is 0 if it's an unknown long option, *or* if - * -? is a valid short option. */ - if (ret == '?') { - if (optopt || strncmp(argv[optind-1], "--", 2) == 0) { - parse_fail(errlog, optopt, argv[optind-1]+2, - "unrecognized option"); - break; - } - } else if (ret == ':') { - /* Missing argument: longidx not updated :( */ - parse_fail(errlog, optopt, argv[optind-1]+2, - "option requires an argument"); - break; - } - - if (ret != 0) - e = find_short(ret); - else - e = find_long(longidx, &name); - - if (e->type == OPT_HASARG) - problem = e->cb_arg(optarg, e->arg); - else - problem = e->cb(e->arg); - - if (problem) { - parse_fail(errlog, ret, name, problem); - free(problem); - break; - } - } - free(optstring); - free(options); - if (ret != -1) - return false; - - /* We hide everything but remaining arguments. */ - memmove(&argv[1], &argv[optind], sizeof(argv[1]) * (*argc-optind+1)); - *argc -= optind - 1; + while ((ret = parse_one(argc, argv, &offset, errlog)) == 1); - return ret == -1 ? true : false; + /* parse_one returns 0 on finish, -1 on error */ + return (ret == 0); } void opt_log_stderr(const char *fmt, ...) diff --git a/ccan/opt/parse.c b/ccan/opt/parse.c new file mode 100644 index 00000000..228808dd --- /dev/null +++ b/ccan/opt/parse.c @@ -0,0 +1,130 @@ +/* Actual code to parse commandline. */ +#include +#include +#include +#include +#include "private.h" + +/* glibc does this as: +/tmp/opt-example: invalid option -- 'x' +/tmp/opt-example: unrecognized option '--long' +/tmp/opt-example: option '--someflag' doesn't allow an argument +/tmp/opt-example: option '--s' is ambiguous +/tmp/opt-example: option requires an argument -- 's' +*/ +static int parse_err(void (*errlog)(const char *fmt, ...), + const char *argv0, const char *arg, unsigned len, + const char *problem) +{ + errlog("%s: %.*s: %s", argv0, len, arg, problem); + return -1; +} + +static void consume_option(int *argc, char *argv[], unsigned optnum) +{ + memmove(&argv[optnum], &argv[optnum+1], + sizeof(argv[optnum]) * (*argc-optnum)); + (*argc)--; +} + +/* Returns 1 if argument consumed, 0 if all done, -1 on error. */ +int parse_one(int *argc, char *argv[], unsigned *offset, + void (*errlog)(const char *fmt, ...)) +{ + unsigned i, arg, len; + const char *o, *optarg = NULL; + char *problem; + + if (getenv("POSIXLY_CORRECT")) { + /* Don't find options after non-options. */ + arg = 1; + } else { + for (arg = 1; argv[arg]; arg++) { + if (argv[arg][0] == '-') + break; + } + } + + if (!argv[arg] || argv[arg][0] != '-') + return 0; + + /* Special arg terminator option. */ + if (strcmp(argv[arg], "--") == 0) { + consume_option(argc, argv, arg); + return 0; + } + + /* Long options start with -- */ + if (argv[arg][1] == '-') { + assert(*offset == 0); + for (o = first_lopt(&i, &len); o; o = next_lopt(o, &i, &len)) { + if (strncmp(argv[arg] + 2, o, len) != 0) + continue; + if (argv[arg][2 + len] == '=') + optarg = argv[arg] + 2 + len + 1; + else if (argv[arg][2 + len] != '\0') + continue; + break; + } + if (!o) + return parse_err(errlog, argv[0], + argv[arg], strlen(argv[arg]), + "unrecognized option"); + /* For error messages, we include the leading '--' */ + o -= 2; + len += 2; + } else { + /* offset allows us to handle -abc */ + for (o = first_sopt(&i); o; o = next_sopt(o, &i)) { + if (argv[arg][*offset + 1] != *o) + continue; + (*offset)++; + break; + } + if (!o) + return parse_err(errlog, argv[0], + argv[arg], strlen(argv[arg]), + "unrecognized option"); + /* For error messages, we include the leading '-' */ + o--; + len = 2; + } + + if (opt_table[i].type == OPT_NOARG) { + if (optarg) + return parse_err(errlog, argv[0], o, len, + "doesn't allow an argument"); + problem = opt_table[i].cb(opt_table[i].arg); + } else { + if (!optarg) { + /* Swallow any short options as optarg, eg -afile */ + if (*offset && argv[arg][*offset + 1]) { + optarg = argv[arg] + *offset + 1; + *offset = 0; + } else + optarg = argv[arg+1]; + } + if (!optarg) + return parse_err(errlog, argv[0], o, len, + "requires an argument"); + problem = opt_table[i].cb_arg(optarg, opt_table[i].arg); + } + + if (problem) { + parse_err(errlog, argv[0], o, len, problem); + free(problem); + return -1; + } + + /* If no more letters in that short opt, reset offset. */ + if (*offset && !argv[arg][*offset + 1]) + *offset = 0; + + /* All finished with that option? */ + if (*offset == 0) { + consume_option(argc, argv, arg); + if (optarg && optarg == argv[arg]) + consume_option(argc, argv, arg); + } + return 1; +} diff --git a/ccan/opt/private.h b/ccan/opt/private.h index 5d9eca23..048951e9 100644 --- a/ccan/opt/private.h +++ b/ccan/opt/private.h @@ -10,5 +10,10 @@ extern const char *opt_argv0; const char *first_sopt(unsigned *i); const char *next_sopt(const char *names, unsigned *i); +const char *first_lopt(unsigned *i, unsigned *len); +const char *next_lopt(const char *p, unsigned *i, unsigned *len); + +int parse_one(int *argc, char *argv[], unsigned *offset, + void (*errlog)(const char *fmt, ...)); #endif /* CCAN_OPT_PRIVATE_H */ diff --git a/ccan/opt/test/run-checkopt.c b/ccan/opt/test/run-checkopt.c new file mode 100644 index 00000000..9e2f88a7 --- /dev/null +++ b/ccan/opt/test/run-checkopt.c @@ -0,0 +1,144 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include "utils.h" + +/* We don't actually want it to exit... */ +static jmp_buf exited; +#define errx save_and_jump + +static void save_and_jump(int ecode, const char *fmt, ...); + +#include +#include +#include +#include + +static char *output = NULL; + +static int saved_vprintf(const char *fmt, va_list ap) +{ + char *p; + int ret = vasprintf(&p, fmt, ap); + + if (output) { + output = realloc(output, strlen(output) + strlen(p) + 1); + strcat(output, p); + free(p); + } else + output = p; + return ret; +} + +static void save_and_jump(int ecode, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + saved_vprintf(fmt, ap); + va_end(ap); + longjmp(exited, ecode + 1); +} + +static void reset(void) +{ + free(output); + output = NULL; + free(opt_table); + opt_table = NULL; + opt_count = opt_num_short = opt_num_short_arg = opt_num_long = 0; +} + +int main(int argc, char *argv[]) +{ + int exitval; + + plan_tests(14); + + exitval = setjmp(exited); + if (exitval == 0) { + /* Bad type. */ + _opt_register("-a", OPT_SUBTABLE, (void *)opt_version_and_exit, + NULL, NULL, "1.2.3", ""); + fail("_opt_register returned?"); + } else { + ok1(exitval - 1 == 1); + ok1(strstr(output, "Option -a: unknown entry type")); + } + reset(); + + exitval = setjmp(exited); + if (exitval == 0) { + /* NULL description. */ + opt_register_noarg("-a", test_noarg, "", NULL); + fail("_opt_register returned?"); + } else { + ok1(exitval - 1 == 1); + ok1(strstr(output, "Option -a: description cannot be NULL")); + } + reset(); + + exitval = setjmp(exited); + if (exitval == 0) { + /* Bad option name. */ + opt_register_noarg("a", test_noarg, "", ""); + fail("_opt_register returned?"); + } else { + ok1(exitval - 1 == 1); + ok1(strstr(output, "Option a: does not begin with '-'")); + } + + reset(); + + exitval = setjmp(exited); + if (exitval == 0) { + /* Bad option name. */ + opt_register_noarg("--", test_noarg, "", ""); + fail("_opt_register returned?"); + } else { + ok1(exitval - 1 == 1); + ok1(strstr(output, "Option --: invalid long option '--'")); + } + + reset(); + + exitval = setjmp(exited); + if (exitval == 0) { + /* Bad option name. */ + opt_register_noarg("--a|-aaa", test_noarg, "", ""); + fail("_opt_register returned?"); + } else { + ok1(exitval - 1 == 1); + ok1(strstr(output, + "Option --a|-aaa: invalid short option '-aaa'")); + } + reset(); + + exitval = setjmp(exited); + if (exitval == 0) { + /* Documentation for non-optios. */ + opt_register_noarg("--a foo", test_noarg, "", ""); + fail("_opt_register returned?"); + } else { + ok1(exitval - 1 == 1); + ok1(strstr(output, + "Option --a foo: does not take arguments 'foo'")); + } + reset(); + + exitval = setjmp(exited); + if (exitval == 0) { + /* Documentation for non-optios. */ + opt_register_noarg("--a=foo", test_noarg, "", ""); + fail("_opt_register returned?"); + } else { + ok1(exitval - 1 == 1); + ok1(strstr(output, + "Option --a=foo: does not take arguments 'foo'")); + } + return exit_status(); +} diff --git a/ccan/opt/test/run-correct-reporting.c b/ccan/opt/test/run-correct-reporting.c index b51d53eb..4f775a29 100644 --- a/ccan/opt/test/run-correct-reporting.c +++ b/ccan/opt/test/run-correct-reporting.c @@ -5,6 +5,7 @@ #include #include #include +#include #include "utils.h" int main(int argc, char *argv[]) @@ -14,30 +15,30 @@ int main(int argc, char *argv[]) /* --aaa without args. */ opt_register_arg("-a|--aaa", test_arg, NULL, "aaa", ""); ok1(!parse_args(&argc, &argv, "--aaa", NULL)); - ok1(strstr(err_output, ": --aaa: option requires an argument")); + ok1(strstr(err_output, ": --aaa: requires an argument")); free(err_output); err_output = NULL; ok1(!parse_args(&argc, &argv, "-a", NULL)); - ok1(strstr(err_output, ": -a: option requires an argument")); + ok1(strstr(err_output, ": -a: requires an argument")); free(err_output); err_output = NULL; /* Multiple */ opt_register_arg("--bbb|-b|-c|--ccc", test_arg, NULL, "aaa", ""); ok1(!parse_args(&argc, &argv, "--bbb", NULL)); - ok1(strstr(err_output, ": --bbb: option requires an argument")); + ok1(strstr(err_output, ": --bbb: requires an argument")); free(err_output); err_output = NULL; ok1(!parse_args(&argc, &argv, "-b", NULL)); - ok1(strstr(err_output, ": -b: option requires an argument")); + ok1(strstr(err_output, ": -b: requires an argument")); free(err_output); err_output = NULL; ok1(!parse_args(&argc, &argv, "-c", NULL)); - ok1(strstr(err_output, ": -c: option requires an argument")); + ok1(strstr(err_output, ": -c: requires an argument")); free(err_output); err_output = NULL; ok1(!parse_args(&argc, &argv, "--ccc", NULL)); - ok1(strstr(err_output, ": --ccc: option requires an argument")); + ok1(strstr(err_output, ": --ccc: requires an argument")); free(err_output); err_output = NULL; diff --git a/ccan/opt/test/run-helpers.c b/ccan/opt/test/run-helpers.c index 34ffbab8..3bb70eb4 100644 --- a/ccan/opt/test/run-helpers.c +++ b/ccan/opt/test/run-helpers.c @@ -22,6 +22,7 @@ static int saved_vprintf(const char *fmt, va_list ap); #include #include #include +#include static void reset_options(void) { diff --git a/ccan/opt/test/run-iter.c b/ccan/opt/test/run-iter.c index 652cd316..36e2c33c 100644 --- a/ccan/opt/test/run-iter.c +++ b/ccan/opt/test/run-iter.c @@ -8,70 +8,82 @@ #include #include #include +#include + +static void reset_options(void) +{ + free(opt_table); + opt_table = NULL; + opt_count = opt_num_short = opt_num_short_arg = opt_num_long = 0; +} /* Test iterators. */ int main(int argc, char *argv[]) { - unsigned i, len; + unsigned j, i, len; const char *p; - plan_tests(37); - opt_register_table(subtables, NULL); + plan_tests(37 * 2); + for (j = 0; j < 2; j ++) { + reset_options(); + /* Giving subtable a title makes an extra entry! */ + opt_register_table(subtables, j == 0 ? NULL : "subtable"); - p = first_lopt(&i, &len); - ok1(i == 0); - ok1(len == 3); - ok1(strncmp(p, "jjj", len) == 0); - p = next_lopt(p, &i, &len); - ok1(i == 0); - ok1(len == 3); - ok1(strncmp(p, "lll", len) == 0); - p = next_lopt(p, &i, &len); - ok1(i == 1); - ok1(len == 3); - ok1(strncmp(p, "mmm", len) == 0); - p = next_lopt(p, &i, &len); - ok1(i == 5); - ok1(len == 3); - ok1(strncmp(p, "ddd", len) == 0); - p = next_lopt(p, &i, &len); - ok1(i == 6); - ok1(len == 3); - ok1(strncmp(p, "eee", len) == 0); - p = next_lopt(p, &i, &len); - ok1(i == 7); - ok1(len == 3); - ok1(strncmp(p, "ggg", len) == 0); - p = next_lopt(p, &i, &len); - ok1(i == 8); - ok1(len == 3); - ok1(strncmp(p, "hhh", len) == 0); - p = next_lopt(p, &i, &len); - ok1(!p); + p = first_lopt(&i, &len); + ok1(i == j + 0); + ok1(len == 3); + ok1(strncmp(p, "jjj", len) == 0); + p = next_lopt(p, &i, &len); + ok1(i == j + 0); + ok1(len == 3); + ok1(strncmp(p, "lll", len) == 0); + p = next_lopt(p, &i, &len); + ok1(i == j + 1); + ok1(len == 3); + ok1(strncmp(p, "mmm", len) == 0); + p = next_lopt(p, &i, &len); + ok1(i == j + 5); + ok1(len == 3); + ok1(strncmp(p, "ddd", len) == 0); + p = next_lopt(p, &i, &len); + ok1(i == j + 6); + ok1(len == 3); + ok1(strncmp(p, "eee", len) == 0); + p = next_lopt(p, &i, &len); + ok1(i == j + 7); + ok1(len == 3); + ok1(strncmp(p, "ggg", len) == 0); + p = next_lopt(p, &i, &len); + ok1(i == j + 8); + ok1(len == 3); + ok1(strncmp(p, "hhh", len) == 0); + p = next_lopt(p, &i, &len); + ok1(!p); - p = first_sopt(&i); - ok1(i == 0); - ok1(*p == 'j'); - p = next_sopt(p, &i); - ok1(i == 0); - ok1(*p == 'l'); - p = next_sopt(p, &i); - ok1(i == 1); - ok1(*p == 'm'); - p = next_sopt(p, &i); - ok1(i == 2); - ok1(*p == 'a'); - p = next_sopt(p, &i); - ok1(i == 3); - ok1(*p == 'b'); - p = next_sopt(p, &i); - ok1(i == 7); - ok1(*p == 'g'); - p = next_sopt(p, &i); - ok1(i == 8); - ok1(*p == 'h'); - p = next_sopt(p, &i); - ok1(!p); + p = first_sopt(&i); + ok1(i == j + 0); + ok1(*p == 'j'); + p = next_sopt(p, &i); + ok1(i == j + 0); + ok1(*p == 'l'); + p = next_sopt(p, &i); + ok1(i == j + 1); + ok1(*p == 'm'); + p = next_sopt(p, &i); + ok1(i == j + 2); + ok1(*p == 'a'); + p = next_sopt(p, &i); + ok1(i == j + 3); + ok1(*p == 'b'); + p = next_sopt(p, &i); + ok1(i == j + 7); + ok1(*p == 'g'); + p = next_sopt(p, &i); + ok1(i == j + 8); + ok1(*p == 'h'); + p = next_sopt(p, &i); + ok1(!p); + } return exit_status(); } diff --git a/ccan/opt/test/run-no-options.c b/ccan/opt/test/run-no-options.c index 7b05cbae..5ae2dd35 100644 --- a/ccan/opt/test/run-no-options.c +++ b/ccan/opt/test/run-no-options.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "utils.h" int main(int argc, char *argv[]) diff --git a/ccan/opt/test/run-usage.c b/ccan/opt/test/run-usage.c index 9d448dc4..dd5e42e7 100644 --- a/ccan/opt/test/run-usage.c +++ b/ccan/opt/test/run-usage.c @@ -8,18 +8,26 @@ #include #include #include +#include static char *my_cb(void *p) { return NULL; } +static void reset_options(void) +{ + free(opt_table); + opt_table = NULL; + opt_count = opt_num_short = opt_num_short_arg = opt_num_long = 0; +} + /* Test helpers. */ int main(int argc, char *argv[]) { char *output; - plan_tests(38); + plan_tests(42); opt_register_table(subtables, NULL); opt_register_noarg("--kkk|-k", my_cb, NULL, "magic kkk option"); opt_register_noarg("-?", opt_usage_and_exit, "...", @@ -73,5 +81,19 @@ int main(int argc, char *argv[]) ok1(!strstr(output, "--mmm|-m")); free(output); + reset_options(); + /* Empty table test. */ + output = opt_usage("nothing", NULL); + ok1(strstr(output, "Usage: nothing \n")); + free(output); + + /* No short args. */ + opt_register_noarg("--aaa", test_noarg, NULL, "AAAAll"); + output = opt_usage("onearg", NULL); + ok1(strstr(output, "Usage: onearg \n")); + ok1(strstr(output, "--aaa")); + ok1(strstr(output, "AAAAll")); + free(output); + return exit_status(); } diff --git a/ccan/opt/test/run.c b/ccan/opt/test/run.c index 1e04ec16..8982baa0 100644 --- a/ccan/opt/test/run.c +++ b/ccan/opt/test/run.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "utils.h" static void reset_options(void) @@ -18,7 +19,7 @@ int main(int argc, char *argv[]) { const char *myname = argv[0]; - plan_tests(148); + plan_tests(215); /* Simple short arg.*/ opt_register_noarg("-a", test_noarg, NULL, "All"); @@ -52,6 +53,14 @@ int main(int argc, char *argv[]) ok1(strcmp(argv[2], "args") == 0); ok1(test_cb_called == 6); + /* Malformed versions. */ + ok1(!parse_args(&argc, &argv, "--aaa=arg", NULL)); + ok1(strstr(err_output, ": --aaa: doesn't allow an argument")); + ok1(!parse_args(&argc, &argv, "--aa", NULL)); + ok1(strstr(err_output, ": --aa: unrecognized option")); + ok1(!parse_args(&argc, &argv, "--aaargh", NULL)); + ok1(strstr(err_output, ": --aaargh: unrecognized option")); + /* Argument variants. */ reset_options(); test_cb_called = 0; @@ -71,6 +80,16 @@ int main(int argc, char *argv[]) ok1(argv[0] == myname); ok1(test_cb_called == 3); + /* Malformed versions. */ + ok1(!parse_args(&argc, &argv, "-a", NULL)); + ok1(strstr(err_output, ": -a: requires an argument")); + ok1(!parse_args(&argc, &argv, "--aaa", NULL)); + ok1(strstr(err_output, ": --aaa: requires an argument")); + ok1(!parse_args(&argc, &argv, "--aa", NULL)); + ok1(strstr(err_output, ": --aa: unrecognized option")); + ok1(!parse_args(&argc, &argv, "--aaargh", NULL)); + ok1(strstr(err_output, ": --aaargh: unrecognized option")); + /* Now, tables. */ /* Short table: */ reset_options(); @@ -215,5 +234,62 @@ int main(int argc, char *argv[]) test_cb_called = 0; reset_options(); + /* Corner cases involving short arg parsing weirdness. */ + opt_register_noarg("-a|--aaa", test_noarg, NULL, "a"); + opt_register_arg("-b|--bbb", test_arg, NULL, "bbb", "b"); + opt_register_arg("-c|--ccc", test_arg, NULL, "aaa", "c"); + /* -aa == -a -a */ + ok1(parse_args(&argc, &argv, "-aa", NULL)); + ok1(test_cb_called == 2); + ok1(parse_args(&argc, &argv, "-aab", NULL) == false); + ok1(test_cb_called == 4); + ok1(strstr(err_output, ": -b: requires an argument")); + ok1(parse_args(&argc, &argv, "-bbbb", NULL)); + ok1(test_cb_called == 5); + ok1(parse_args(&argc, &argv, "-aabbbb", NULL)); + ok1(test_cb_called == 8); + ok1(parse_args(&argc, &argv, "-aabbbb", "-b", "bbb", NULL)); + ok1(test_cb_called == 12); + ok1(parse_args(&argc, &argv, "-aabbbb", "--bbb", "bbb", NULL)); + ok1(test_cb_called == 16); + ok1(parse_args(&argc, &argv, "-aabbbb", "--bbb=bbb", NULL)); + ok1(test_cb_called == 20); + ok1(parse_args(&argc, &argv, "-aacaaa", NULL)); + ok1(test_cb_called == 23); + ok1(parse_args(&argc, &argv, "-aacaaa", "-a", NULL)); + ok1(test_cb_called == 27); + ok1(parse_args(&argc, &argv, "-aacaaa", "--bbb", "bbb", "-aacaaa", + NULL)); + ok1(test_cb_called == 34); + + test_cb_called = 0; + reset_options(); + + /* -- and POSIXLY_CORRECT */ + opt_register_noarg("-a|--aaa", test_noarg, NULL, "a"); + ok1(parse_args(&argc, &argv, "-a", "--", "-a", NULL)); + ok1(test_cb_called == 1); + ok1(argc == 2); + ok1(strcmp(argv[1], "-a") == 0); + ok1(!argv[2]); + + unsetenv("POSIXLY_CORRECT"); + ok1(parse_args(&argc, &argv, "-a", "somearg", "-a", "--", "-a", NULL)); + ok1(test_cb_called == 3); + ok1(argc == 3); + ok1(strcmp(argv[1], "somearg") == 0); + ok1(strcmp(argv[2], "-a") == 0); + ok1(!argv[3]); + + setenv("POSIXLY_CORRECT", "1", 1); + ok1(parse_args(&argc, &argv, "-a", "somearg", "-a", "--", "-a", NULL)); + ok1(test_cb_called == 4); + ok1(argc == 5); + ok1(strcmp(argv[1], "somearg") == 0); + ok1(strcmp(argv[2], "-a") == 0); + ok1(strcmp(argv[3], "--") == 0); + ok1(strcmp(argv[4], "-a") == 0); + ok1(!argv[5]); + return exit_status(); } diff --git a/tools/ccanlint/Makefile b/tools/ccanlint/Makefile index bbd37054..b84b8353 100644 --- a/tools/ccanlint/Makefile +++ b/tools/ccanlint/Makefile @@ -12,7 +12,7 @@ CORE_OBJS := tools/ccanlint/ccanlint.o \ ccan/btree/btree.o \ ccan/talloc/talloc.o ccan/noerr/noerr.o \ ccan/read_write_all/read_write_all.o \ - ccan/opt/opt.o ccan/opt/usage.o ccan/opt/helpers.o + ccan/opt/opt.o ccan/opt/usage.o ccan/opt/helpers.o ccan/opt/parse.o OBJS := $(CORE_OBJS) $(TEST_OBJS)