diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Makefile | 18 | ||||
| -rw-r--r-- | examples/basic_example.c | 17 | ||||
| -rw-r--r-- | ls_args.h | 269 | ||||
| -rw-r--r-- | tests/ls_test.h | 16 | ||||
| -rw-r--r-- | tests/tests.c | 286 |
6 files changed, 513 insertions, 94 deletions
@@ -3,3 +3,4 @@ tests/tests *.o compile_commands.json coverage/ +examples/basic_example @@ -1,18 +1,28 @@ # CAUTION: This Makefile builds ONLY the tests. # To use this library, see ls_test.h or README.md. +CFLAGS ?= -fsanitize=address,undefined +CFLAGS += -I. + +all: tests/tests examples/basic_example + tests/tests: ls_args.o tests/tests.c tests/ls_test.h - $(CC) -o $@ ls_args.o tests/tests.c -Itests -I. -ggdb + $(CC) -o $@ ls_args.o tests/tests.c -Itests -ggdb $(CFLAGS) # Usually you wouldn't do this, but for tests we want this compiled with the # most pedantic settings. # Dont use this. ls_args.o: ls_args.h - $(CC) -c -x c -o $@ $^ -Wall -Wextra -Wpedantic -Werror -std=c89 -ggdb \ + echo -e "#include <stddef.h>\nvoid* test_realloc(void*, size_t);" >.test.h + cat .test.h ls_args.h >.ls_args_test.c + rm .test.h + $(CC) -c -x c -o $@ .ls_args_test.c -Wall -Wextra -Wpedantic -Werror -std=c89 -ggdb \ -Wno-error=pragma-once-outside-header \ - -I. \ -DLS_ARGS_IMPLEMENTATION \ - -Wno-pragma-once-outside-header + -DLS_REALLOC=test_realloc \ + -Wno-pragma-once-outside-header \ + $(CFLAGS) + rm .ls_args_test.c .PHONY: clean diff --git a/examples/basic_example.c b/examples/basic_example.c new file mode 100644 index 0000000..293d5c5 --- /dev/null +++ b/examples/basic_example.c @@ -0,0 +1,17 @@ +#define LS_ARGS_IMPLEMENTATION +#include "ls_args.h" + +int main(int argc, char** argv) { + ls_args args; + int help = 0; + const char* outfile = "out.txt"; + + ls_args_init(&args); + ls_args_bool(&args, &help, "h", "help", "Prints help", 0); + ls_args_string(&args, &outfile, "o", "out", "Specify the outfile, default 'out.txt'", 0); + if (!ls_args_parse(&args, argc, argv)) { + printf("Error: %s\n%s\n", args.last_error, ls_args_help(&args)); + } + ls_args_free(&args); + return 0; +} @@ -11,8 +11,6 @@ #define LS_FREE free #endif -/* Yes the naming is a little inconsistent, but "arg optional" reads better than - * "args mode optional" */ typedef enum ls_args_mode { LS_ARG_OPTIONAL = 0, LS_ARG_REQUIRED = 1 @@ -33,28 +31,85 @@ typedef struct ls_args_arg { } ls_args_arg; typedef struct ls_args { + /* The last error, if any. Might be dynamically allocated; if so, it's + * free'd with `ls_args_free` automatically. Always a valid, printable + * string. */ + char* last_error; + + /* don't use the following fields outside the library */ ls_args_arg* args; - int args_len; - int args_cap; + size_t args_len; + size_t args_cap; + + /* some bookkeeping -- these are used to free dynamically allocated memory + * for help or errors cleanly on `ls_args_free`. */ + void* allocated_error; + void* allocated_help; } ls_args; +/* Zero-initializes the arguments, does not allocate */ void ls_args_init(ls_args*); -void ls_arg_bool(ls_args*, int* val, const char* short_opt, +/* The following functions register arguments. Upon a call to `ls_args_parse`, + * the given `val` parameter is filled. The `val` pointer must never be NULL. + * + * ONE of `short_opt` and `long_opt` may be NULL, if only a short- or long + * version should exist. The `help` string may be null, but is used to construct + * help with `ls_args_help`. + * + * In your short and long opts, you *can* have `-` or `--`, but you don't need + * them and, really, you should not use them. For example "h" or "help" are + * valid short- and long-opts respectively. + * + * You can call ls_args_* functions with the same `val` pointer, if multiple + * arguments should affect the same memory, however the order of evaluation (and + * thus the order of which they may overwrite each other) is the same order as + * the registration. + * + * BE AWARE that, if an argument is not present, the corresponding `val` is NOT + * touched. This means that, if you initialize a bool with `true` and then parse + * the args, and the corresponding flag is not present, the flag will not be set + * to `false` (it will instead stay untouched). This allows you to set defaults + * for the case in which the argument isn't present. + */ + +/* A "flag", aka a boolean argument. If the argument is present, `*val` is set + * to 1, otherwise it's left untouched. + * Can fail if the allocator fails. `args.last_error` is set on failure. */ +int ls_args_bool(ls_args*, int* val, const char* short_opt, const char* long_opt, const char* help, ls_args_mode mode); -void ls_arg_string(ls_args*, const char** val, const char* short_opt, +/* An argument which requires a string parameter, for example `--file + * hello.txt`. Can fail if the allocator fails. `args.last_error` is set on + * failure. */ +int ls_args_string(ls_args*, const char** val, const char* short_opt, const char* long_opt, const char* help, ls_args_mode mode); -int ls_args_parse(ls_args*, int argc, char** argv); +/* Does all the heavy lifting. Assumes that `argv` has `argc` elements. NULL + * termination of the `argv` array doesn't matter, but null-termination of each + * individual string is required of course. + * + * Returns 1 on success, 0 on failure (boolean behavior). + * On failure, the `args.last_error` is set to a human-readable string. */ +int ls_args_parse(ls_args* args, int argc, char** argv); +/* Same as args.last_error. */ +char* ls_args_get_error(ls_args*); +/* Constructs a help message from the arguments registered on the args struct + * via `ls_args_{bool, string, ...} functions. + * The string is statically allocated and only valid until `ls_args_help` is + * called again. */ char* ls_args_help(ls_args*); +/* Frees all memory allocated in the args. */ void ls_args_free(ls_args*); +/* Define this in exactly ONE source file, or in an object file compiled + * separately with -DLS_ARGS_IMPLEMENTATION. */ #ifdef LS_ARGS_IMPLEMENTATION #include <assert.h> -#include <limits.h> +#include <stdint.h> +#include <stdio.h> /* for sprintf */ #include <string.h> /* 0 on failure, 1 on success */ @@ -63,9 +118,11 @@ static int _lsa_add(ls_args* a, ls_args_arg** arg) { assert(arg != NULL); if (a->args_len + 1 > a->args_cap) { ls_args_arg* new_args; - int new_cap = a->args_cap + a->args_cap / 2 + 8; - if (new_cap > INT_MAX / (int)sizeof(*a->args)) { - /* int overflow */ + size_t new_cap = a->args_cap + a->args_cap / 2 + 8; + + size_t max_items = SIZE_MAX / sizeof(*a->args); + if (new_cap > max_items) { + /* would overflow size_t */ return 0; } new_args = LS_REALLOC(a->args, new_cap * sizeof(*new_args)); @@ -80,27 +137,37 @@ static int _lsa_add(ls_args* a, ls_args_arg** arg) { return 1; } -void ls_args_init(ls_args* a) { memset(a, 0, sizeof(*a)); } +void ls_args_init(ls_args* a) { + memset(a, 0, sizeof(*a)); + a->last_error = "Success"; +} -void _lsa_register(ls_args* a, void* val, ls_args_type type, +int _lsa_register(ls_args* a, void* val, ls_args_type type, const char* short_opt, const char* long_opt, const char* help, ls_args_mode mode) { ls_args_arg* arg; + int ret; assert(a != NULL); assert(val != NULL); /* only one can be NULL, not both, but neither have to be NULL */ assert(short_opt != NULL || long_opt != NULL); + /* remove preceding dashes for later matching */ + if (long_opt) + while (*long_opt == '-') + long_opt++; + /* remove preceding dashes for later matching */ + if (short_opt) + while (*short_opt == '-') + short_opt++; /* if short_opt isn't null, it must be 1 char */ assert(short_opt == NULL || strlen(short_opt) == 1); - assert(_lsa_add(a, &arg)); + ret = _lsa_add(a, &arg); + if (ret == 0) { + a->last_error = "Allocation failure"; + return 0; + } /* TODO: sanity check that there are no dashes in there, because that would * be a misuse of the API. */ - /* remove preceding dashes for later matching */ - while (*long_opt == '-') - long_opt++; - /* remove preceding dashes for later matching */ - while (*short_opt == '-') - short_opt++; /* the rest may be NULL */ arg->type = type; arg->short_opt = short_opt; @@ -108,16 +175,19 @@ void _lsa_register(ls_args* a, void* val, ls_args_type type, arg->help = help; arg->mode = mode; arg->val_ptr = val; + return 1; } -void ls_arg_bool(ls_args* a, int* val, const char* short_opt, +int ls_args_bool(ls_args* a, int* val, const char* short_opt, const char* long_opt, const char* help, ls_args_mode mode) { - _lsa_register(a, val, LS_ARGS_TYPE_BOOL, short_opt, long_opt, help, mode); + return _lsa_register( + a, val, LS_ARGS_TYPE_BOOL, short_opt, long_opt, help, mode); } -void ls_arg_string(ls_args* a, const char** val, const char* short_opt, +int ls_args_string(ls_args* a, const char** val, const char* short_opt, const char* long_opt, const char* help, ls_args_mode mode) { - _lsa_register(a, val, LS_ARGS_TYPE_STRING, short_opt, long_opt, help, mode); + return _lsa_register( + a, val, LS_ARGS_TYPE_STRING, short_opt, long_opt, help, mode); } typedef enum _lsa_parsed_type { @@ -142,7 +212,7 @@ typedef struct _lsa_parsed { } as; } _lsa_parsed; -_lsa_parsed _lsa_parse(const char* s) { +static _lsa_parsed _lsa_parse(const char* s) { size_t s_len = strlen(s); _lsa_parsed res; assert(s != NULL); @@ -166,13 +236,7 @@ _lsa_parsed _lsa_parse(const char* s) { res.as.long_arg = &s[2]; } else { /* short opt */ - size_t remaining = s_len - 1; - if (remaining == 0) { - /* shouldn't be possible due to earlier checks */ - res.type = LS_ARGS_PARSED_ERROR; - res.as.erroneous = s; - goto end; - } + /* guaranteed to be the right size due to earlier checks */ res.type = LS_ARGS_PARSED_SHORT; res.as.short_args = &s[1]; } @@ -197,11 +261,70 @@ void _lsa_apply(ls_args_arg* arg, ls_args_arg** prev_arg) { } } -#include <stdio.h> +static int _lsa_parse_long( + ls_args* a, _lsa_parsed* parsed, ls_args_arg** prev_arg) { + int found = 0; + size_t k; + for (k = 0; k < a->args_len; ++k) { + if (a->args[k].long_opt != NULL + && strcmp(parsed->as.long_arg, a->args[k].long_opt) == 0) { + _lsa_apply(&a->args[k], prev_arg); + found = 1; + break; + } + } + if (!found) { + const size_t len = 32 + strlen(parsed->as.erroneous); + a->allocated_error = LS_REALLOC(a->allocated_error, len); + memset(a->allocated_error, 0, len); + sprintf(a->allocated_error, "Invalid argument '--%s'", + parsed->as.erroneous); + a->last_error = a->allocated_error; + return 0; + } + return 1; +} + +static int _lsa_parse_short( + ls_args* a, _lsa_parsed* parsed, ls_args_arg** prev_arg) { + const char* args = parsed->as.short_args; + while (*args) { + char arg = *args++; + int found = 0; + size_t k; + if (*prev_arg) { + const size_t len = 128 + strlen((*prev_arg)->short_opt); + a->allocated_error = LS_REALLOC(a->allocated_error, len); + memset(a->allocated_error, 0, len); + sprintf(a->allocated_error, + "Expected argument following '-%s', instead got another " + "argument '-%c'", + (*prev_arg)->short_opt, arg); + a->last_error = a->allocated_error; + return 0; + } + for (k = 0; k < a->args_len; ++k) { + const char* opt = a->args[k].short_opt; + if (opt != NULL && opt[0] == arg) { + _lsa_apply(&a->args[k], prev_arg); + found = 1; + break; + } + } + if (!found) { + const size_t len = 32; + a->allocated_error = LS_REALLOC(a->allocated_error, len); + memset(a->allocated_error, 0, len); + sprintf(a->allocated_error, "Invalid argument '-%c'", arg); + a->last_error = a->allocated_error; + return 0; + } + } + return 1; +} int ls_args_parse(ls_args* a, int argc, char** argv) { int i; - int k; ls_args_arg* prev_arg = NULL; assert(a != NULL); assert(argv != NULL); @@ -210,64 +333,60 @@ int ls_args_parse(ls_args* a, int argc, char** argv) { if (prev_arg) { if (parsed.type != LS_ARGS_PARSED_POSITIONAL) { /* argument for the previous param expected, but none given */ - /* TODO: Error properly */ - fprintf(stderr, "Expected argument for '--%s'\n", - prev_arg->long_opt); + const size_t len = 64 + strlen(prev_arg->long_opt); + a->allocated_error = LS_REALLOC(a->allocated_error, len); + memset(a->allocated_error, 0, len); + sprintf(a->allocated_error, + "Expected argument following '--%s'", prev_arg->long_opt); + a->last_error = a->allocated_error; return 0; } - switch (prev_arg->type) { - case LS_ARGS_TYPE_BOOL: - assert(!"UNREACHABLE"); - abort(); - break; - case LS_ARGS_TYPE_STRING: + if (prev_arg->type == LS_ARGS_TYPE_STRING) { *(const char**)prev_arg->val_ptr = parsed.as.positional; - break; } prev_arg = NULL; continue; } switch (parsed.type) { - case LS_ARGS_PARSED_ERROR: - /* TODO: Return/print/save error */ - /* TODO: Error properly */ - fprintf(stderr, "Failed to parse '%s'\n", parsed.as.erroneous); + case LS_ARGS_PARSED_ERROR: { + const size_t len = 32 + strlen(parsed.as.erroneous); + a->allocated_error = LS_REALLOC(a->allocated_error, len); + memset(a->allocated_error, 0, len); + sprintf(a->allocated_error, "Invalid argument '%s'", + parsed.as.erroneous); + a->last_error = a->allocated_error; return 0; - case LS_ARGS_PARSED_LONG: - for (k = 0; k < a->args_len; ++k) { - if (a->args[k].long_opt != NULL - && strcmp(parsed.as.long_arg, a->args[k].long_opt) == 0) { - _lsa_apply(&a->args[k], &prev_arg); - break; - } + } + case LS_ARGS_PARSED_LONG: { + if (!_lsa_parse_long(a, &parsed, &prev_arg)) { + return 0; } break; + } case LS_ARGS_PARSED_SHORT: { - const char* args = parsed.as.short_args; - while (*args) { - char arg = *args++; - if (prev_arg) { - /* TODO: Error properly */ - fprintf(stderr, "Expected argument for '-%s'\n", - prev_arg->short_opt); - return 0; - } - for (k = 0; k < a->args_len; ++k) { - const char* opt = a->args[k].short_opt; - if (opt != NULL && opt[0] == arg) { - _lsa_apply(&a->args[k], &prev_arg); - break; - } - } + if (!_lsa_parse_short(a, &parsed, &prev_arg)) { + return 0; } break; } case LS_ARGS_PARSED_STOP: + /* TODO */ + break; case LS_ARGS_PARSED_POSITIONAL: assert(!"UNREACHABLE"); break; } } + if (prev_arg) { + /* argument for the previous param expected, but none given */ + const size_t len = 64 + strlen(prev_arg->long_opt); + a->allocated_error = LS_REALLOC(a->allocated_error, len); + memset(a->allocated_error, 0, len); + sprintf(a->allocated_error, "Expected argument following '--%s'", + prev_arg->long_opt); + a->last_error = a->allocated_error; + return 0; + } return 1; } @@ -276,12 +395,20 @@ char* ls_args_help(ls_args* a) { return "help!"; } +char* ls_args_get_error(ls_args* a) { return a->last_error; } + void ls_args_free(ls_args* a) { if (a) { LS_FREE(a->args); a->args = NULL; a->args_cap = 0; a->args_len = 0; + + LS_FREE(a->allocated_error); + a->allocated_error = NULL; + a->last_error = ""; + LS_FREE(a->allocated_help); + a->allocated_help = NULL; } } #endif diff --git a/tests/ls_test.h b/tests/ls_test.h index 5dda4aa..31d87f8 100644 --- a/tests/ls_test.h +++ b/tests/ls_test.h @@ -1,6 +1,6 @@ /* Lion's Standard (LS) test harness. * - * Version: 1.1 + * Version: 1.2 * Website: https://libls.org * Repo: https://github.com/libls/test * SPDX-License-Identifier: MIT @@ -96,6 +96,8 @@ _func += 6; \ fprintf(stderr, "%s: FAILED: %s (%s:%d)\n", _func, #cond, \ __FILE__, __LINE__); \ + ++lst_fail; \ + return 1; \ } \ } while (0) @@ -115,7 +117,6 @@ ++lst_fail; \ return 1; \ } \ - ++lst_ok; \ } while (0) #define ASSERT_NEQ(a, b, fmt) \ @@ -132,7 +133,6 @@ ++lst_fail; \ return 1; \ } \ - ++lst_ok; \ } while (0) #define ASSERT_LT(a, b, fmt) \ @@ -149,7 +149,6 @@ ++lst_fail; \ return 1; \ } \ - ++lst_ok; \ } while (0) #define ASSERT_LE(a, b, fmt) \ @@ -166,7 +165,6 @@ ++lst_fail; \ return 1; \ } \ - ++lst_ok; \ } while (0) #define ASSERT_GT(a, b, fmt) \ @@ -183,7 +181,6 @@ ++lst_fail; \ return 1; \ } \ - ++lst_ok; \ } while (0) #define ASSERT_GE(a, b, fmt) \ @@ -200,7 +197,6 @@ ++lst_fail; \ return 1; \ } \ - ++lst_ok; \ } while (0) #define LS_CAT2(a, b) a##b #define LS_CAT(a, b) LS_CAT2(a, b) @@ -217,7 +213,6 @@ extern lst_func* lst_funcs; extern int lst_n; extern int lst_cap; extern int lst_fail; -extern int lst_ok; void lst_reg(lst_func f); @@ -229,7 +224,6 @@ lst_func* lst_funcs; int lst_n; int lst_cap; int lst_fail = 0; -int lst_ok = 0; void lst_reg(lst_func f) { if (lst_n == lst_cap) { @@ -276,8 +270,8 @@ static int ls_test_main(int argc, char** argv) { } end: - fprintf(stderr, "%d succeeded, %d failed, %d total\n", lst_ok, lst_fail, - lst_ok + lst_fail); + fprintf(stderr, "%d succeeded, %d failed, %d total\n", lst_n - lst_fail, + lst_fail, lst_n); free(lst_funcs); if (lst_fail > 0) { diff --git a/tests/tests.c b/tests/tests.c index 450c6bd..8b22e06 100644 --- a/tests/tests.c +++ b/tests/tests.c @@ -1,6 +1,22 @@ +#include <stdint.h> +#include <limits.h> #define LS_TEST_IMPLEMENTATION #include "ls_test.h" +int fail_alloc = 0; + +void* test_realloc(void* p, size_t size) { + if (fail_alloc) { + return NULL; + } + return realloc(p, size); +} + +/* this is also set in the Makefile because of the way this is built for tests. + * It requires a bit of magic because of the way we do things. + */ +#define LS_REALLOC test_realloc + #include "ls_args.h" TEST_CASE(basic_args) { @@ -12,10 +28,201 @@ TEST_CASE(basic_args) { int argc = sizeof(argv) / sizeof(*argv) - 1; ls_args_init(&args); - ls_arg_bool(&args, &help, "h", "help", "Provides help", 0); - ls_arg_bool(&args, &test, "t", "test", "A test argument", 0); - ls_arg_bool(&args, &no, "n", "nope", "An argument that isn't present", 0); - ASSERT(ls_args_parse(&args, argc, argv)); + ls_args_bool(&args, &help, "h", "help", "Provides help", 0); + ls_args_bool(&args, &test, "t", "test", "A test argument", 0); + ls_args_bool(&args, &no, "n", "nope", "An argument that isn't present", 0); + if (!ls_args_parse(&args, argc, argv)) { + printf("Error: %s\n", args.last_error); + ASSERT(!"ls_args_parse failed"); + } + ASSERT_EQ(help, 1, "%d"); + ASSERT_EQ(test, 1, "%d"); + ASSERT_EQ(no, 0, "%d"); + ls_args_free(&args); + return 0; +} + +TEST_CASE(basic_args_only_short) { + int help = 0; + int test = 0; + int no = 0; + ls_args args; + char* argv[] = { "./hello", "-h", "-t", NULL }; + int argc = sizeof(argv) / sizeof(*argv) - 1; + + ls_args_init(&args); + ls_args_bool(&args, &help, "h", NULL, "Provides help", 0); + ls_args_bool(&args, &test, "t", NULL, "A test argument", 0); + ls_args_bool(&args, &no, "n", NULL, "An argument that isn't present", 0); + if (!ls_args_parse(&args, argc, argv)) { + printf("Error: %s\n", args.last_error); + ASSERT(!"ls_args_parse failed"); + } + ASSERT_EQ(help, 1, "%d"); + ASSERT_EQ(test, 1, "%d"); + ASSERT_EQ(no, 0, "%d"); + ls_args_free(&args); + return 0; +} + +TEST_CASE(basic_args_only_long) { + int help = 0; + int test = 0; + int no = 0; + ls_args args; + char* argv[] = { "./hello", "--help", "--test", NULL }; + int argc = sizeof(argv) / sizeof(*argv) - 1; + + ls_args_init(&args); + ls_args_bool(&args, &help, NULL, "help", "Provides help", 0); + ls_args_bool(&args, &test, NULL, "test", "A test argument", 0); + ls_args_bool(&args, &no, NULL, "nope", "An argument that isn't present", 0); + if (!ls_args_parse(&args, argc, argv)) { + printf("Error: %s\n", args.last_error); + ASSERT(!"ls_args_parse failed"); + } + ASSERT_EQ(help, 1, "%d"); + ASSERT_EQ(test, 1, "%d"); + ASSERT_EQ(no, 0, "%d"); + ls_args_free(&args); + return 0; +} + +TEST_CASE(basic_args_short_combined) { + int help = 0; + int test = 0; + int no = 0; + ls_args args; + char* argv[] = { "./hello", "-ht", NULL }; + int argc = sizeof(argv) / sizeof(*argv) - 1; + + ls_args_init(&args); + ls_args_bool(&args, &help, "h", "help", "Provides help", 0); + ls_args_bool(&args, &test, "t", "test", "A test argument", 0); + ls_args_bool(&args, &no, "n", "nope", "An argument that isn't present", 0); + if (!ls_args_parse(&args, argc, argv)) { + printf("Error: %s\n", args.last_error); + ASSERT(!"ls_args_parse failed"); + } + ASSERT_EQ(help, 1, "%d"); + ASSERT_EQ(test, 1, "%d"); + ASSERT_EQ(no, 0, "%d"); + ls_args_free(&args); + return 0; +} + +TEST_CASE(error_invalid_argument) { + int help = 0; + ls_args args; + char* argv[] = { "./hello", "-h", "--test", NULL }; + int argc = sizeof(argv) / sizeof(*argv) - 1; + + ls_args_init(&args); + ls_args_bool(&args, &help, "h", "help", "Provides help", 0); + ASSERT(!ls_args_parse(&args, argc, argv)); + ASSERT(strcmp(args.last_error, "Invalid argument '--test'") == 0); + ls_args_free(&args); + return 0; +} + +TEST_CASE(error_expected_argument) { + const char* file = 0; + ls_args args; + int help; + char* argv[] = { "./hello", "--file", "--help", NULL }; + int argc = sizeof(argv) / sizeof(*argv) - 1; + + ls_args_init(&args); + ls_args_bool(&args, &help, "h", "help", "Provides help", 0); + ls_args_string(&args, &file, "f", "file", "File to work on", 0); + ASSERT(!ls_args_parse(&args, argc, argv)); + ASSERT(strcmp(args.last_error, "Expected argument following '--file'") == 0); + ls_args_free(&args); + return 0; +} + +TEST_CASE(error_expected_argument_last_arg) { + const char* file = 0; + ls_args args; + char* argv[] = { "./hello", "--file", NULL }; + int argc = sizeof(argv) / sizeof(*argv) - 1; + + ls_args_init(&args); + ls_args_string(&args, &file, "f", "file", "File to work on", 0); + ASSERT(!ls_args_parse(&args, argc, argv)); + ASSERT(strcmp(args.last_error, "Expected argument following '--file'") == 0); + ls_args_free(&args); + return 0; +} + +TEST_CASE(error_expected_argument_short_combined) { + const char* file = 0; + ls_args args; + int help; + char* argv[] = { "./hello", "-fh", NULL }; + int argc = sizeof(argv) / sizeof(*argv) - 1; + + ls_args_init(&args); + ls_args_bool(&args, &help, "h", "help", "Provides help", 0); + ls_args_string(&args, &file, "f", "file", "File to work on", 0); + ASSERT(!ls_args_parse(&args, argc, argv)); + ASSERT(strcmp(args.last_error, "Expected argument following '-f', instead got another argument '-h'") == 0); + ls_args_free(&args); + return 0; +} + +TEST_CASE(error_parse_fail) { + int help = 0; + ls_args args; + char* argv[] = { "./hello", "-", NULL }; + int argc = sizeof(argv) / sizeof(*argv) - 1; + + ls_args_init(&args); + ASSERT(!ls_args_parse(&args, argc, argv)); + ASSERT(strcmp(args.last_error, "Invalid argument '-'") == 0); + ls_args_free(&args); + return 0; +} + +TEST_CASE(overflow_args_cap) { + int help = 0; + int dummy = 0; + ls_args args; + int ret; + + ls_args_init(&args); + /* First allocate one arg so the structure is properly initialized */ + ret = ls_args_bool(&args, &dummy, "d", "dummy", "Dummy argument", 0); + ASSERT(ret); + /* can't have more elements, not even one, at this count */ + args.args_len = SIZE_MAX / sizeof(ls_args_arg); + args.args_cap = SIZE_MAX / sizeof(ls_args_arg); + /* Try to add an argument, which should fail due to overflow */ + ret = ls_args_bool(&args, &help, "h", "help", "Provides help", 0); + ASSERT(!ret); + ls_args_free(&args); + return 0; +} + +TEST_CASE(strip_dashes) { + int help = 0; + int test = 0; + int no = 0; + ls_args args; + char* argv[] = { "./hello", "-h", "--test", NULL }; + int argc = sizeof(argv) / sizeof(*argv) - 1; + + ls_args_init(&args); + /* the dashes are optional */ + ls_args_bool(&args, &help, "-h", "--help", "Provides help", 0); + /* you can mix them */ + ls_args_bool(&args, &test, "t", "--test", "A test argument", 0); + /* have as many as you want */ + ls_args_bool(&args, &no, "-n", "----nope", "An argument that isn't present", 0); + if (!ls_args_parse(&args, argc, argv)) { + printf("Error: %s\n", args.last_error); + ASSERT(!"ls_args_parse failed"); + } ASSERT_EQ(help, 1, "%d"); ASSERT_EQ(test, 1, "%d"); ASSERT_EQ(no, 0, "%d"); @@ -23,18 +230,81 @@ TEST_CASE(basic_args) { return 0; } +TEST_CASE(alloc_fail) { + int help = 0; + ls_args args; + char* argv[] = { "./hello", "-", NULL }; + int argc = sizeof(argv) / sizeof(*argv) - 1; + int ret; + + ls_args_init(&args); + ASSERT_EQ(args.args_len, 0, "%uz"); + /* deliberately fail the allocation here */ + fail_alloc = 1; + /* if the allocation fails, this fails */ + ret = ls_args_bool(&args, &help, "h", "help", "Provides help", 0); + fail_alloc = 0; + ASSERT(!ret); + ASSERT(strcmp(args.last_error, "Allocation failure") == 0); + /* there is no documented error state for this; we simply fail to add the + * argument? */ + ASSERT_EQ(args.args_len, 0, "%uz"); + ls_args_free(&args); + return 0; +} + +TEST_CASE(error_parse_fail_empty) { + int help = 0; + ls_args args; + char* argv[] = { "./hello", "", NULL }; + int argc = sizeof(argv) / sizeof(*argv) - 1; + + ls_args_init(&args); + ASSERT(!ls_args_parse(&args, argc, argv)); + ASSERT(strcmp(args.last_error, "Invalid argument ''") == 0); + ls_args_free(&args); + return 0; +} + +TEST_CASE(error_parse_ignore_double_dash) { + int help = 0; + ls_args args; + char* argv[] = { "./hello", "--", NULL }; + int argc = sizeof(argv) / sizeof(*argv) - 1; + + ls_args_init(&args); + ASSERT(ls_args_parse(&args, argc, argv)); + ls_args_free(&args); + return 0; +} + +TEST_CASE(error_invalid_argument_short) { + int help = 0; + ls_args args; + char* argv[] = { "./hello", "-h", "-t", "-h", NULL }; + int argc = sizeof(argv) / sizeof(*argv) - 1; + + ls_args_init(&args); + ls_args_bool(&args, &help, "h", "help", "Provides help", 0); + ASSERT(!ls_args_parse(&args, argc, argv)); + ASSERT(strcmp(args.last_error, "Invalid argument '-t'") == 0); + ls_args_free(&args); + return 0; +} + TEST_CASE(string_args) { const char* input = NULL; const char* output = NULL; int verbose = 0; ls_args args; - char* argv[] = { "./program", "--input", "file.txt", "-o", "output.txt", "-v", NULL }; + char* argv[] = { "./program", "--input", "file.txt", "-o", "output.txt", + "-v", NULL }; int argc = sizeof(argv) / sizeof(*argv) - 1; ls_args_init(&args); - ls_arg_string(&args, &input, "i", "input", "Input file path", 0); - ls_arg_string(&args, &output, "o", "output", "Output file path", 0); - ls_arg_bool(&args, &verbose, "v", "verbose", "Verbose output", 0); + ls_args_string(&args, &input, "i", "input", "Input file path", 0); + ls_args_string(&args, &output, "o", "output", "Output file path", 0); + ls_args_bool(&args, &verbose, "v", "verbose", "Verbose output", 0); ASSERT(ls_args_parse(&args, argc, argv)); ASSERT(strcmp(input, "file.txt") == 0); ASSERT(strcmp(output, "output.txt") == 0); |
