From 7ac57709d4964e2c59c9b0592632c374056caa00 Mon Sep 17 00:00:00 2001 From: Simon Marchi Date: Wed, 4 Dec 2019 17:33:56 -0500 Subject: [PATCH] Add build system, remove dependency on glib, add TAP library This commit... * adds an automake-based system. * removes glib dependencies from the library code (i.e. not from the tests), replacing them with home-grown code. * sets up the tests to run using the TAP library [1]. [1] https://github.com/shlomif/libtap-prev Signed-off-by: Simon Marchi --- Makefile.am | 5 + argpar/Makefile.am | 3 + argpar/argpar.c | 279 ++++++++++++++++++++------ argpar/argpar.h | 35 +++- configure.ac | 17 ++ tests/Makefile.am | 15 ++ tests/tap/Makefile.am | 3 + tests/tap/tap.c | 447 ++++++++++++++++++++++++++++++++++++++++++ tests/tap/tap.h | 249 +++++++++++++++++++++++ tests/test_argpar.c | 17 +- 10 files changed, 994 insertions(+), 76 deletions(-) create mode 100644 Makefile.am create mode 100644 argpar/Makefile.am create mode 100644 configure.ac create mode 100644 tests/Makefile.am create mode 100644 tests/tap/Makefile.am create mode 100644 tests/tap/tap.c create mode 100644 tests/tap/tap.h diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..6b36a14 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,5 @@ +SUBDIRS = \ + argpar \ + tests + +ACLOCAL_AMFLAGS = -I m4 diff --git a/argpar/Makefile.am b/argpar/Makefile.am new file mode 100644 index 0000000..175526a --- /dev/null +++ b/argpar/Makefile.am @@ -0,0 +1,3 @@ +noinst_LTLIBRARIES = libargpar.la + +libargpar_la_SOURCES = argpar.c argpar.h diff --git a/argpar/argpar.c b/argpar/argpar.c index 7f4e46b..6a9e256 100644 --- a/argpar/argpar.c +++ b/argpar/argpar.c @@ -20,16 +20,99 @@ * SOFTWARE. */ +#include +#include #include +#include #include #include -#include - -#include "common/assert.h" -#include "common/common.h" #include "argpar.h" +#define argpar_realloc(_ptr, _type, _nmemb) ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type))) +#define argpar_calloc(_type, _nmemb) ((_type *) calloc((_nmemb), sizeof(_type))) +#define argpar_zalloc(_type) argpar_calloc(_type, 1) + +#define ARGPAR_ASSERT(_cond) assert(_cond) + +static +char *argpar_vasprintf(const char *fmt, va_list args) +{ + int len1, len2; + char *str; + va_list args2; + + va_copy(args2, args); + + len1 = vsnprintf(NULL, 0, fmt, args); + if (len1 < 0) { + str = NULL; + goto end; + } + + str = malloc(len1 + 1); + if (!str) { + goto end; + } + + len2 = vsnprintf(str, len1 + 1, fmt, args2); + + ARGPAR_ASSERT(len1 == len2); + +end: + return str; +} + + +static +char *argpar_asprintf(const char *fmt, ...) +{ + va_list args; + char *str; + + va_start(args, fmt); + str = argpar_vasprintf(fmt, args); + va_end(args); + + return str; +} + +static +bool argpar_string_append_printf(char **str, const char *fmt, ...) +{ + char *new_str = NULL; + char *addendum; + bool success; + va_list args; + + ARGPAR_ASSERT(str); + + va_start(args, fmt); + addendum = argpar_vasprintf(fmt, args); + va_end(args); + + if (!addendum) { + success = false; + goto end; + } + + new_str = argpar_asprintf("%s%s", *str ? *str : "", addendum); + if (!new_str) { + success = false; + goto end; + } + + free(*str); + *str = new_str; + + success = true; + +end: + free(addendum); + + return success; +} + static void destroy_item(struct bt_argpar_item * const item) { @@ -40,22 +123,98 @@ void destroy_item(struct bt_argpar_item * const item) if (item->type == BT_ARGPAR_ITEM_TYPE_OPT) { struct bt_argpar_item_opt * const opt_item = (void *) item; - g_free((void *) opt_item->arg); + free((void *) opt_item->arg); } - g_free(item); + free(item); end: return; } +static +bool push_item(struct bt_argpar_item_array * const array, + struct bt_argpar_item * const item) +{ + bool success; + + ARGPAR_ASSERT(array); + ARGPAR_ASSERT(item); + + if (array->n_items == array->n_alloc) { + unsigned int new_n_alloc = array->n_alloc * 2; + struct bt_argpar_item **new_items; + + new_items = argpar_realloc(array->items, + struct bt_argpar_item *, new_n_alloc); + if (!new_items) { + success = false; + goto end; + } + + array->n_alloc = new_n_alloc; + array->items = new_items; + } + + array->items[array->n_items] = item; + array->n_items++; + + success = true; + +end: + return success; +} + +static +void destroy_item_array(struct bt_argpar_item_array * const array) +{ + if (array) { + unsigned int i; + + for (i = 0; i < array->n_items; i++) { + destroy_item(array->items[i]); + } + + free(array->items); + free(array); + } +} + +static +struct bt_argpar_item_array *new_item_array(void) +{ + struct bt_argpar_item_array *ret; + const int initial_size = 10; + + ret = argpar_zalloc(struct bt_argpar_item_array); + if (!ret) { + goto end; + } + + ret->items = argpar_calloc(struct bt_argpar_item *, initial_size); + if (!ret->items) { + goto error; + } + + ret->n_alloc = initial_size; + + goto end; + +error: + destroy_item_array(ret); + ret = NULL; + +end: + return ret; +} + static struct bt_argpar_item_opt *create_opt_item( const struct bt_argpar_opt_descr * const descr, const char * const arg) { struct bt_argpar_item_opt *opt_item = - g_new0(struct bt_argpar_item_opt, 1); + argpar_zalloc(struct bt_argpar_item_opt); if (!opt_item) { goto end; @@ -65,7 +224,7 @@ struct bt_argpar_item_opt *create_opt_item( opt_item->descr = descr; if (arg) { - opt_item->arg = g_strdup(arg); + opt_item->arg = strdup(arg); if (!opt_item->arg) { goto error; } @@ -87,7 +246,7 @@ struct bt_argpar_item_non_opt *create_non_opt_item(const char * const arg, const unsigned int non_opt_index) { struct bt_argpar_item_non_opt * const non_opt_item = - g_new0(struct bt_argpar_item_non_opt, 1); + argpar_zalloc(struct bt_argpar_item_non_opt); if (!non_opt_item) { goto end; @@ -142,7 +301,7 @@ enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts, const char *short_opt_ch = short_opts; if (strlen(short_opts) == 0) { - g_string_append(parse_ret->error, "Invalid argument"); + argpar_string_append_printf(&parse_ret->error, "Invalid argument"); goto error; } @@ -155,7 +314,7 @@ enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts, descr = find_descr(descrs, *short_opt_ch, NULL); if (!descr) { ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT; - g_string_append_printf(parse_ret->error, + argpar_string_append_printf(&parse_ret->error, "Unknown option `-%c`", *short_opt_ch); goto error; } @@ -176,7 +335,7 @@ enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts, * expected. */ if (!opt_arg || (short_opt_ch[1] && strlen(opt_arg) == 0)) { - g_string_append_printf(parse_ret->error, + argpar_string_append_printf(&parse_ret->error, "Missing required argument for option `-%c`", *short_opt_ch); *used_next_orig_arg = false; @@ -190,7 +349,9 @@ enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts, goto error; } - g_ptr_array_add(parse_ret->items, opt_item); + if (!push_item(parse_ret->items, &opt_item->base)) { + goto error; + } if (descr->with_arg) { /* Option has an argument: no more options */ @@ -237,7 +398,8 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, const char *long_opt_name = long_opt_arg; if (strlen(long_opt_arg) == 0) { - g_string_append(parse_ret->error, "Invalid argument"); + argpar_string_append_printf(&parse_ret->error, + "Invalid argument"); goto error; } @@ -248,7 +410,7 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, /* Isolate the option name */ if (long_opt_name_size > max_len) { - g_string_append_printf(parse_ret->error, + argpar_string_append_printf(&parse_ret->error, "Invalid argument `--%s`", long_opt_arg); goto error; } @@ -261,7 +423,7 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, /* Find corresponding option descriptor */ descr = find_descr(descrs, '\0', long_opt_name); if (!descr) { - g_string_append_printf(parse_ret->error, + argpar_string_append_printf(&parse_ret->error, "Unknown option `--%s`", long_opt_name); ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT; goto error; @@ -275,7 +437,7 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, } else { /* `--long-opt arg` style */ if (!next_orig_arg) { - g_string_append_printf(parse_ret->error, + argpar_string_append_printf(&parse_ret->error, "Missing required argument for option `--%s`", long_opt_name); goto error; @@ -292,7 +454,10 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, goto error; } - g_ptr_array_add(parse_ret->items, opt_item); + if (!push_item(parse_ret->items, &opt_item->base)) { + goto error; + } + goto end; error: @@ -313,7 +478,7 @@ enum parse_orig_arg_opt_ret parse_orig_arg_opt(const char * const orig_arg, { enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; - BT_ASSERT(orig_arg[0] == '-'); + ARGPAR_ASSERT(orig_arg[0] == '-'); if (orig_arg[1] == '-') { /* Long option */ @@ -331,29 +496,31 @@ enum parse_orig_arg_opt_ret parse_orig_arg_opt(const char * const orig_arg, } static -void prepend_while_parsing_arg_to_error(GString * const error, +bool prepend_while_parsing_arg_to_error(char **error, const unsigned int i, const char * const arg) { - /* 🙁 There's no g_string_prepend_printf()! */ - GString * const tmp_str = g_string_new(NULL); + char *new_error; + bool success; - BT_ASSERT(error); - BT_ASSERT(arg); + ARGPAR_ASSERT(error); + ARGPAR_ASSERT(*error); - if (!tmp_str) { + new_error = argpar_asprintf("While parsing argument #%u (`%s`): %s", + i + 1, arg, *error); + if (!new_error) { + success = false; goto end; } - g_string_append_printf(tmp_str, "While parsing argument #%u (`%s`): %s", - i + 1, arg, error->str); - g_string_assign(error, tmp_str->str); - g_string_free(tmp_str, TRUE); + free(*error); + *error = new_error; + success = true; end: - return; + return success; } -BT_HIDDEN +ARGPAR_HIDDEN struct bt_argpar_parse_ret bt_argpar_parse(unsigned int argc, const char * const *argv, const struct bt_argpar_opt_descr * const descrs, @@ -363,13 +530,7 @@ struct bt_argpar_parse_ret bt_argpar_parse(unsigned int argc, unsigned int i; unsigned int non_opt_index = 0; - parse_ret.error = g_string_new(NULL); - if (!parse_ret.error) { - goto error; - } - - parse_ret.items = g_ptr_array_new_with_free_func( - (GDestroyNotify) destroy_item); + parse_ret.items = new_item_array(); if (!parse_ret.items) { goto error; } @@ -391,7 +552,11 @@ struct bt_argpar_parse_ret bt_argpar_parse(unsigned int argc, } non_opt_index++; - g_ptr_array_add(parse_ret.items, non_opt_item); + + if (!push_item(parse_ret.items, &non_opt_item->base)) { + goto error; + } + continue; } @@ -402,11 +567,11 @@ struct bt_argpar_parse_ret bt_argpar_parse(unsigned int argc, case PARSE_ORIG_ARG_OPT_RET_OK: break; case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT: - BT_ASSERT(!used_next_orig_arg); + ARGPAR_ASSERT(!used_next_orig_arg); if (fail_on_unknown_opt) { prepend_while_parsing_arg_to_error( - parse_ret.error, i, orig_arg); + &parse_ret.error, i, orig_arg); goto error; } @@ -416,15 +581,15 @@ struct bt_argpar_parse_ret bt_argpar_parse(unsigned int argc, * unknown option. */ parse_ret.ingested_orig_args = i; - g_string_free(parse_ret.error, TRUE); + free(parse_ret.error); parse_ret.error = NULL; goto end; case PARSE_ORIG_ARG_OPT_RET_ERROR: prepend_while_parsing_arg_to_error( - parse_ret.error, i, orig_arg); + &parse_ret.error, i, orig_arg); goto error; default: - bt_common_abort(); + abort(); } if (used_next_orig_arg) { @@ -433,33 +598,27 @@ struct bt_argpar_parse_ret bt_argpar_parse(unsigned int argc, } parse_ret.ingested_orig_args = argc; - g_string_free(parse_ret.error, TRUE); + free(parse_ret.error); parse_ret.error = NULL; goto end; error: - if (parse_ret.items) { - /* That's how we indicate that an error occured */ - g_ptr_array_free(parse_ret.items, TRUE); - parse_ret.items = NULL; - } + /* That's how we indicate that an error occured */ + destroy_item_array(parse_ret.items); + parse_ret.items = NULL; end: return parse_ret; } -BT_HIDDEN +ARGPAR_HIDDEN void bt_argpar_parse_ret_fini(struct bt_argpar_parse_ret *ret) { - BT_ASSERT(ret); + ARGPAR_ASSERT(ret); - if (ret->items) { - g_ptr_array_free(ret->items, TRUE); - ret->items = NULL; - } + destroy_item_array(ret->items); + ret->items = NULL; - if (ret->error) { - g_string_free(ret->error, TRUE); - ret->error = NULL; - } + free(ret->error); + ret->error = NULL; } diff --git a/argpar/argpar.h b/argpar/argpar.h index 85a663d..5732a88 100644 --- a/argpar/argpar.h +++ b/argpar/argpar.h @@ -23,14 +23,24 @@ * SOFTWARE. */ -#include #include -#include "common/macros.h" - /* Sentinel for an option descriptor array */ #define BT_ARGPAR_OPT_DESCR_SENTINEL { -1, '\0', NULL, false } +/* + * ARGPAR_HIDDEN: if argpar is used in some shared library, we don't want them + * to be exported by that library, so mark them as "hidden". + * + * On Windows, symbols are local unless explicitly exported, + * see https://gcc.gnu.org/wiki/Visibility + */ +#if defined(_WIN32) || defined(__CYGWIN__) +#define ARGPAR_HIDDEN +#else +#define ARGPAR_HIDDEN __attribute__((visibility("hidden"))) +#endif + /* Option descriptor */ struct bt_argpar_opt_descr { /* Numeric ID for this option */ @@ -88,13 +98,24 @@ struct bt_argpar_item_non_opt { unsigned int non_opt_index; }; +struct bt_argpar_item_array { + /* Array of `struct bt_argpar_item *`, or `NULL` on error */ + struct bt_argpar_item **items; + + /* Number of used slots in `data`. */ + unsigned int n_items; + + /* Number of allocated slots in `data`. */ + unsigned int n_alloc; +}; + /* What is returned by bt_argpar_parse() */ struct bt_argpar_parse_ret { /* Array of `struct bt_argpar_item *`, or `NULL` on error */ - GPtrArray *items; + struct bt_argpar_item_array *items; /* Error string, or `NULL` if none */ - GString *error; + char *error; /* Number of original arguments (`argv`) ingested */ unsigned int ingested_orig_args; @@ -194,7 +215,7 @@ struct bt_argpar_parse_ret { * You can finalize the returned structure with * bt_argpar_parse_ret_fini(). */ -BT_HIDDEN +ARGPAR_HIDDEN struct bt_argpar_parse_ret bt_argpar_parse(unsigned int argc, const char * const *argv, const struct bt_argpar_opt_descr *descrs, @@ -206,7 +227,7 @@ struct bt_argpar_parse_ret bt_argpar_parse(unsigned int argc, * It is safe to call bt_argpar_parse() multiple times with the same * structure. */ -BT_HIDDEN +ARGPAR_HIDDEN void bt_argpar_parse_ret_fini(struct bt_argpar_parse_ret *ret); #endif /* BABELTRACE_ARGPAR_H */ diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..01f70d7 --- /dev/null +++ b/configure.ac @@ -0,0 +1,17 @@ +AC_INIT([argpar], [1]) +AM_INIT_AUTOMAKE([foreign]) +LT_INIT + +AC_CONFIG_MACRO_DIRS([m4]) + +# Depend on glib just for the tests. +PKG_CHECK_MODULES([GLIB], [glib-2.0]) + +AC_CONFIG_FILES([ + Makefile + argpar/Makefile + tests/Makefile + tests/tap/Makefile +]) + +AC_OUTPUT diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..fa84fc1 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,15 @@ +SUBDIRS = tap + +AM_CPPFLAGS = \ + -I$(top_srcdir) \ + -I$(top_srcdir)/tests/tap \ + $(GLIB_CFLAGS) + +noinst_PROGRAMS = test_argpar +test_argpar_SOURCES = test_argpar.c +test_argpar_LDADD = \ + $(top_builddir)/tests/tap/libtap.la \ + $(top_builddir)/argpar/libargpar.la \ + $(GLIB_LIBS) + +TESTS = test_argpar diff --git a/tests/tap/Makefile.am b/tests/tap/Makefile.am new file mode 100644 index 0000000..7b1bc21 --- /dev/null +++ b/tests/tap/Makefile.am @@ -0,0 +1,3 @@ +noinst_LTLIBRARIES = libtap.la + +libtap_la_SOURCES = tap.c tap.h diff --git a/tests/tap/tap.c b/tests/tap/tap.c new file mode 100644 index 0000000..09ac2af --- /dev/null +++ b/tests/tap/tap.c @@ -0,0 +1,447 @@ +/*- + * Copyright (c) 2004 Nik Clayton + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include "tap.h" + +static int no_plan = 0; +static int skip_all = 0; +static int have_plan = 0; +static unsigned int test_count = 0; /* Number of tests that have been run */ +static unsigned int e_tests = 0; /* Expected number of tests to run */ +static unsigned int failures = 0; /* Number of tests that failed */ +static char *todo_msg = NULL; +static char *todo_msg_fixed = "libtap malloc issue"; +static int todo = 0; +static int test_died = 0; +static const int exit_die = 255; /* exit-code on die() */ + + +/* Encapsulate the pthread code in a conditional. In the absence of + libpthread the code does nothing */ +#if defined(LIBTAP_ENABLE_BROKEN_THREAD_SAFE) && defined(HAVE_LIBPTHREAD) +#include +static pthread_mutex_t M = PTHREAD_MUTEX_INITIALIZER; +# define LOCK pthread_mutex_lock(&M); +# define UNLOCK pthread_mutex_unlock(&M); +#else +# define LOCK +# define UNLOCK +#endif + +static void _expected_tests(unsigned int); +static void _tap_init(void); +static void _cleanup(void); + +/* + * Generate a test result. + * + * ok -- boolean, indicates whether or not the test passed. + * test_name -- the name of the test, may be NULL + * test_comment -- a comment to print afterwards, may be NULL + */ +unsigned int +_gen_result(int ok, const char *func, const char *file, unsigned int line, + const char *test_name, ...) +{ + va_list ap; + char *local_test_name = NULL; + char *c; + int name_is_digits; + + LOCK; + + test_count++; + + /* Start by taking the test name and performing any printf() + expansions on it */ + if(test_name != NULL) { + va_start(ap, test_name); + if (vasprintf(&local_test_name, test_name, ap) < 0) + { + local_test_name = NULL; + } + va_end(ap); + + /* Make sure the test name contains more than digits + and spaces. Emit an error message and exit if it + does */ + if(local_test_name) { + name_is_digits = 1; + for(c = local_test_name; *c != '\0'; c++) { + if(!isdigit(*c) && !isspace(*c)) { + name_is_digits = 0; + break; + } + } + + if(name_is_digits) { + diag(" You named your test '%s'. You shouldn't use numbers for your test names.", local_test_name); + diag(" Very confusing."); + } + } + } + + if(!ok) { + printf("not "); + failures++; + } + + printf("ok %d", test_count); + + if(test_name != NULL) { + printf(" - "); + + /* Print the test name, escaping any '#' characters it + might contain */ + if(local_test_name != NULL) { + flockfile(stdout); + for(c = local_test_name; *c != '\0'; c++) { + if(*c == '#') + fputc('\\', stdout); + fputc((int)*c, stdout); + } + funlockfile(stdout); + } else { /* vasprintf() failed, use a fixed message */ + printf("%s", todo_msg_fixed); + } + } + + /* If we're in a todo_start() block then flag the test as being + TODO. todo_msg should contain the message to print at this + point. If it's NULL then asprintf() failed, and we should + use the fixed message. + + This is not counted as a failure, so decrement the counter if + the test failed. */ + if(todo) { + printf(" # TODO %s", todo_msg ? todo_msg : todo_msg_fixed); + if(!ok) + failures--; + } + + printf("\n"); + + if(!ok) + diag(" Failed %stest (%s:%s() at line %d)", + todo ? "(TODO) " : "", file, func, line); + + free(local_test_name); + + UNLOCK; + + /* We only care (when testing) that ok is positive, but here we + specifically only want to return 1 or 0 */ + return ok ? 1 : 0; +} + +/* + * Initialise the TAP library. Will only do so once, however many times it's + * called. + */ +void +_tap_init(void) +{ + static int run_once = 0; + + LOCK; + + if(!run_once) { + atexit(_cleanup); + + /* stdout needs to be unbuffered so that the output appears + in the same place relative to stderr output as it does + with Test::Harness */ + setbuf(stdout, 0); + run_once = 1; + } + + UNLOCK; +} + +/* + * Note that there's no plan. + */ +int +plan_no_plan(void) +{ + + LOCK; + + _tap_init(); + + if(have_plan != 0) { + fprintf(stderr, "You tried to plan twice!\n"); + test_died = 1; + UNLOCK; + exit(exit_die); + } + + have_plan = 1; + no_plan = 1; + + UNLOCK; + + return 1; +} + +/* + * Note that the plan is to skip all tests + */ +int +plan_skip_all(const char *reason) +{ + + LOCK; + + _tap_init(); + + skip_all = 1; + + printf("1..0"); + + if(reason != NULL) + printf(" # SKIP %s", reason); + + printf("\n"); + + UNLOCK; + + exit(0); +} + +/* + * Note the number of tests that will be run. + */ +int +plan_tests(unsigned int tests) +{ + + LOCK; + + _tap_init(); + + if(have_plan != 0) { + fprintf(stderr, "You tried to plan twice!\n"); + test_died = 1; + UNLOCK; + exit(exit_die); + } + + if(tests == 0) { + fprintf(stderr, "You said to run 0 tests! You've got to run something.\n"); + test_died = 1; + UNLOCK; + exit(exit_die); + } + + have_plan = 1; + + _expected_tests(tests); + + UNLOCK; + + return 1; +} + +unsigned int +diag(const char *fmt, ...) +{ + va_list ap; + + LOCK; + + fputs("# ", stderr); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + fputs("\n", stderr); + + UNLOCK; + + return 0; +} + +void +_expected_tests(unsigned int tests) +{ + + LOCK; + + printf("1..%d\n", tests); + e_tests = tests; + + UNLOCK; +} + +int +skip(unsigned int n, const char *fmt, ...) +{ + va_list ap; + char *skip_msg; + + LOCK; + + va_start(ap, fmt); + if (vasprintf(&skip_msg, fmt, ap) < 0) + { + skip_msg = NULL; + } + va_end(ap); + + while(n-- > 0) { + test_count++; + printf("ok %d # skip %s\n", test_count, + skip_msg != NULL ? + skip_msg : "libtap():malloc() failed"); + } + + free(skip_msg); + + UNLOCK; + + return 1; +} + +void +todo_start(const char *fmt, ...) +{ + va_list ap; + + LOCK; + + va_start(ap, fmt); + if (vasprintf(&todo_msg, fmt, ap) < 0) + { + todo_msg = NULL; + } + va_end(ap); + + todo = 1; + + UNLOCK; +} + +void +todo_end(void) +{ + + LOCK; + + todo = 0; + free(todo_msg); + + UNLOCK; +} + +int +exit_status(void) +{ + int r; + + LOCK; + + /* If there's no plan, just return the number of failures */ + if(no_plan || !have_plan) { + UNLOCK; + return failures; + } + + /* Ran too many tests? Return the number of tests that were run + that shouldn't have been */ + if(e_tests < test_count) { + r = test_count - e_tests; + UNLOCK; + return r; + } + + /* Return the number of tests that failed + the number of tests + that weren't run */ + r = failures + e_tests - test_count; + UNLOCK; + + return r; +} + +/* + * Cleanup at the end of the run, produce any final output that might be + * required. + */ +void +_cleanup(void) +{ + + LOCK; + + /* If plan_no_plan() wasn't called, and we don't have a plan, + and we're not skipping everything, then something happened + before we could produce any output */ + if(!no_plan && !have_plan && !skip_all) { + diag("Looks like your test died before it could output anything."); + UNLOCK; + return; + } + + if(test_died) { + diag("Looks like your test exited with %d just after %d.", exit_die, test_count); + UNLOCK; + return; + } + + + /* No plan provided, but now we know how many tests were run, and can + print the header at the end */ + if(!skip_all && (no_plan || !have_plan)) { + printf("1..%d\n", test_count); + } + + if((have_plan && !no_plan) && e_tests < test_count) { + diag("Looks like you planned %d test%s but ran %d extra.", + e_tests, e_tests == 1 ? "":"s", test_count - e_tests); + UNLOCK; + return; + } + + if((have_plan || !no_plan) && e_tests > test_count) { + diag("Looks like you planned %d test%s but ran %d.", + e_tests, e_tests == 1 ? "":"s", test_count); + UNLOCK; + return; + } + + if(failures) + diag("Looks like you failed %d test%s of %d.", + failures, failures == 1 ? "":"s", test_count); + + UNLOCK; +} diff --git a/tests/tap/tap.h b/tests/tap/tap.h new file mode 100644 index 0000000..c2dfb4b --- /dev/null +++ b/tests/tap/tap.h @@ -0,0 +1,249 @@ +/*- + * Copyright (c) 2004 Nik Clayton + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/** + * plan_tests - announce the number of tests you plan to run + * @tests: the number of tests + * + * This should be the first call in your test program: it allows tracing + * of failures which mean that not all tests are run. + * + * If you don't know how many tests will actually be run, assume all of them + * and use skip() if you don't actually run some tests. + * + * Example: + * plan_tests(13); + */ +int plan_tests(unsigned int tests); +static inline int plan(unsigned int tests) +{ + return plan_tests(tests); +} +#if (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L) && !defined(__GNUC__) +# error "Needs gcc or C99 compiler for variadic macros." +#else + +/** + * ok1 - Simple conditional test + * @e: the expression which we expect to be true. + * + * This is the simplest kind of test: if the expression is true, the + * test passes. The name of the test which is printed will simply be + * file name, line number, and the expression itself. + * + * Example: + * ok1(init_subsystem() == 1); + */ +# define ok1(e) ((e) ? \ + _gen_result(1, __func__, __FILE__, __LINE__, "%s", #e) : \ + _gen_result(0, __func__, __FILE__, __LINE__, "%s", #e)) + +/** + * ok - Conditional test with a name + * @e: the expression which we expect to be true. + * @...: the printf-style name of the test. + * + * If the expression is true, the test passes. The name of the test will be + * the filename, line number, and the printf-style string. This can be clearer + * than simply the expression itself. + * + * Example: + * ok1(init_subsystem() == 1); + * ok(init_subsystem() == 0, "Second initialization should fail"); + */ +# define ok(e, ...) ((e) ? \ + _gen_result(1, __func__, __FILE__, __LINE__, \ + __VA_ARGS__) : \ + _gen_result(0, __func__, __FILE__, __LINE__, \ + __VA_ARGS__)) + +/** + * pass - Note that a test passed + * @...: the printf-style name of the test. + * + * For complicated code paths, it can be easiest to simply call pass() in one + * branch and fail() in another. + * + * Example: + * x = do_something(); + * if (!checkable(x) || check_value(x)) + * pass("do_something() returned a valid value"); + * else + * fail("do_something() returned an invalid value"); + */ +# define pass(...) ok(1, __VA_ARGS__) + +/** + * fail - Note that a test failed + * @...: the printf-style name of the test. + * + * For complicated code paths, it can be easiest to simply call pass() in one + * branch and fail() in another. + */ +# define fail(...) ok(0, __VA_ARGS__) + +/* I don't find these to be useful. */ +# define skip_if(cond, n, ...) \ + if (cond) skip((n), __VA_ARGS__); \ + else + +# define skip_start(test, n, ...) \ + do { \ + if((test)) { \ + skip(n, __VA_ARGS__); \ + continue; \ + } + +# define skip_end } while(0) + +#ifndef PRINTF_ATTRIBUTE +#ifdef __GNUC__ +#define PRINTF_ATTRIBUTE(a1, a2) __attribute__ ((format (__printf__, a1, a2))) +#else +#define PRINTF_ATTRIBUTE(a1, a2) +#endif +#endif + +unsigned int _gen_result(int, const char *, const char *, unsigned int, + const char *, ...) PRINTF_ATTRIBUTE(5, 6); + +/** + * diag - print a diagnostic message (use instead of printf/fprintf) + * @fmt: the format of the printf-style message + * + * diag ensures that the output will not be considered to be a test + * result by the TAP test harness. It will append '\n' for you. + * + * Example: + * diag("Now running complex tests"); + */ +unsigned int diag(const char *fmt, ...) PRINTF_ATTRIBUTE(1, 2); + +/** + * skip - print a diagnostic message (use instead of printf/fprintf) + * @n: number of tests you're skipping. + * @fmt: the format of the reason you're skipping the tests. + * + * Sometimes tests cannot be run because the test system lacks some feature: + * you should explicitly document that you're skipping tests using skip(). + * + * From the Test::More documentation: + * If it's something the user might not be able to do, use SKIP. This + * includes optional modules that aren't installed, running under an OS that + * doesn't have some feature (like fork() or symlinks), or maybe you need an + * Internet connection and one isn't available. + * + * Example: + * #ifdef HAVE_SOME_FEATURE + * ok1(test_some_feature()); + * #else + * skip(1, "Don't have SOME_FEATURE"); + * #endif + */ +int skip(unsigned int n, const char *fmt, ...) PRINTF_ATTRIBUTE(2, 3); + +/** + * todo_start - mark tests that you expect to fail. + * @fmt: the reason they currently fail. + * + * It's extremely useful to write tests before you implement the matching fix + * or features: surround these tests by todo_start()/todo_end(). These tests + * will still be run, but with additional output that indicates that they are + * expected to fail. + * + * This way, should a test start to succeed unexpectedly, tools like prove(1) + * will indicate this and you can move the test out of the todo block. This + * is much more useful than simply commenting out (or '#if 0') the tests. + * + * From the Test::More documentation: + * If it's something the programmer hasn't done yet, use TODO. This is for + * any code you haven't written yet, or bugs you have yet to fix, but want to + * put tests in your testing script (always a good idea). + * + * Example: + * todo_start("dwim() not returning true yet"); + * ok(dwim(), "Did what the user wanted"); + * todo_end(); + */ +void todo_start(const char *fmt, ...) PRINTF_ATTRIBUTE(1, 2); + +/** + * todo_end - end of tests you expect to fail. + * + * See todo_start(). + */ +void todo_end(void); + +/** + * exit_status - the value that main should return. + * + * For maximum compatability your test program should return a particular exit + * code (ie. 0 if all tests were run, and every test which was expected to + * succeed succeeded). + * + * Example: + * exit(exit_status()); + */ +int exit_status(void); + +/** + * plan_no_plan - I have no idea how many tests I'm going to run. + * + * In some situations you may not know how many tests you will be running, or + * you are developing your test program, and do not want to update the + * plan_tests() call every time you make a change. For those situations use + * plan_no_plan() instead of plan_tests(). It indicates to the test harness + * that an indeterminate number of tests will be run. + * + * Remember, if you fail to plan, you plan to fail. + * + * Example: + * plan_no_plan(); + * while (random() % 2) + * ok1(some_test()); + * exit(exit_status()); + */ +int plan_no_plan(void); + +/** + * plan_skip_all - Indicate that you will skip all tests. + * @reason: the string indicating why you can't run any tests. + * + * If your test program detects at run time that some required functionality + * is missing (for example, it relies on a database connection which is not + * present, or a particular configuration option that has not been included + * in the running kernel) use plan_skip_all() instead of plan_tests(). + * + * Example: + * if (!have_some_feature) { + * plan_skip_all("Need some_feature support"); + * exit(exit_status()); + * } + * plan_tests(13); + */ +int plan_skip_all(const char *reason); + +#endif /* C99 or gcc */ diff --git a/tests/test_argpar.c b/tests/test_argpar.c index fa915d8..4674d89 100644 --- a/tests/test_argpar.c +++ b/tests/test_argpar.c @@ -15,13 +15,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include #include #include #include #include "tap/tap.h" -#include "common/assert.h" - #include "argpar/argpar.h" /* @@ -48,8 +47,8 @@ void test_succeed(const char *cmdline, gchar **argv = g_strsplit(cmdline, " ", 0); unsigned int i; - BT_ASSERT(argv); - BT_ASSERT(res_str); + assert(argv); + assert(res_str); parse_ret = bt_argpar_parse(g_strv_length(argv), (const char * const *) argv, descrs, false); ok(parse_ret.items, @@ -70,8 +69,8 @@ void test_succeed(const char *cmdline, goto end; } - for (i = 0; i < parse_ret.items->len; i++) { - const struct bt_argpar_item *arg = parse_ret.items->pdata[i]; + for (i = 0; i < parse_ret.items->n_items; i++) { + const struct bt_argpar_item *arg = parse_ret.items->items[i]; switch (arg->type) { case BT_ARGPAR_ITEM_TYPE_OPT: @@ -518,12 +517,12 @@ void test_fail(const char *cmdline, const char *expected_error, goto end; } - ok(strcmp(expected_error, parse_ret.error->str) == 0, + ok(strcmp(expected_error, parse_ret.error) == 0, "bt_argpar_parse() writes the expected error string " "for command line `%s`", cmdline); - if (strcmp(expected_error, parse_ret.error->str) != 0) { + if (strcmp(expected_error, parse_ret.error) != 0) { diag("Expected: `%s`", expected_error); - diag("Got: `%s`", parse_ret.error->str); + diag("Got: `%s`", parse_ret.error); } end: -- 2.34.1