* Acutest -- Another C/C++ Unit Test facility
* <http://github.com/mity/acutest>
*
- * Copyright (c) 2013-2017 Martin Mitas
+ * Copyright (c) 2013-2019 Martin Mitas
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
*
* void test_func(void);
*/
-#define TEST_LIST const struct test__ test_list__[]
+#define TEST_LIST const struct test__ test_list__[]
/* Macros for testing whether an unit test succeeds or fails. These macros
* TEST_CHECK(ptr->member2 > 200);
* }
*/
-#define TEST_CHECK_(cond,...) test_check__((cond), __FILE__, __LINE__, __VA_ARGS__)
-#define TEST_CHECK(cond) test_check__((cond), __FILE__, __LINE__, "%s", #cond)
+#define TEST_CHECK_(cond,...) test_check__((cond), __FILE__, __LINE__, __VA_ARGS__)
+#define TEST_CHECK(cond) test_check__((cond), __FILE__, __LINE__, "%s", #cond)
+
+
+/* Sometimes it is useful to split execution of more complex unit tests to some
+ * smaller parts and associate those parts with some names.
+ *
+ * This is especially handy if the given unit test is implemented as a loop
+ * over some vector of multiple testing inputs. Using these macros allow to use
+ * sort of subtitle for each iteration of the loop (e.g. outputting the input
+ * itself or a name associated to it), so that if any TEST_CHECK condition
+ * fails in the loop, it can be easily seen which iteration triggers the
+ * failure, without the need to manually output the iteration-specific data in
+ * every single TEST_CHECK inside the loop body.
+ *
+ * TEST_CASE allows to specify only single string as the name of the case,
+ * TEST_CASE_ provides all the power of printf-like string formatting.
+ *
+ * Note that the test cases cannot be nested. Starting a new test case ends
+ * implicitly the previous one. To end the test case explicitly (e.g. to end
+ * the last test case after exiting the loop), you may use TEST_CASE(NULL).
+ */
+#define TEST_CASE_(...) test_case__(__VA_ARGS__)
+#define TEST_CASE(name) test_case__("%s", name);
/* printf-like macro for outputting an extra information about a failure.
* The macro can deal with multi-line output fairly well. It also automatically
* adds a final new-line if there is none present.
*/
-#define TEST_MSG(...) test_message__(__VA_ARGS__)
+#define TEST_MSG(...) test_message__(__VA_ARGS__)
+
/* Maximal output per TEST_MSG call. Longer messages are cut.
* You may define another limit prior including "acutest.h"
*/
#ifndef TEST_MSG_MAXSIZE
- #define TEST_MSG_MAXSIZE 1024
+ #define TEST_MSG_MAXSIZE 1024
#endif
extern const struct test__ test_list__[];
int test_check__(int cond, const char* file, int line, const char* fmt, ...);
+void test_case__(const char* fmt, ...);
void test_message__(const char* fmt, ...);
static size_t test_count__ = 0;
static int test_no_exec__ = -1;
static int test_no_summary__ = 0;
+static int test_tap__ = 0;
static int test_skip_mode__ = 0;
+static int test_worker__ = 0;
+static int test_worker_index__ = 0;
static int test_stat_failed_units__ = 0;
static int test_stat_run_units__ = 0;
static const struct test__* test_current_unit__ = NULL;
+static int test_current_index__ = 0;
+static char test_case_name__[64] = "";
static int test_current_already_logged__ = 0;
+static int test_case_current_already_logged__ = 0;
static int test_verbose_level__ = 2;
static int test_current_failures__ = 0;
static int test_colorize__ = 0;
case TEST_COLOR_GREEN_INTENSIVE__: col_str = "\033[1;32m"; break;
case TEST_COLOR_RED_INTENSIVE__: col_str = "\033[1;31m"; break;
case TEST_COLOR_DEFAULT_INTENSIVE__: col_str = "\033[1m"; break;
- default: col_str = "\033[0m"; break;
+ default: col_str = "\033[0m"; break;
}
printf("%s", col_str);
n = printf("%s", buffer);
case TEST_COLOR_GREEN_INTENSIVE__: attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY; break;
case TEST_COLOR_RED_INTENSIVE__: attr = FOREGROUND_RED | FOREGROUND_INTENSITY; break;
case TEST_COLOR_DEFAULT_INTENSIVE__: attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY; break;
- default: attr = 0; break;
+ default: attr = 0; break;
}
if(attr != 0)
SetConsoleTextAttribute(h, attr);
#endif
}
+static void
+test_begin_test_line__(const struct test__* test)
+{
+ if(!test_tap__) {
+ if(test_verbose_level__ >= 3) {
+ test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Test %s:\n", test->name);
+ test_current_already_logged__++;
+ } else if(test_verbose_level__ >= 1) {
+ int n;
+ char spaces[48];
+
+ n = test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Test %s... ", test->name);
+ memset(spaces, ' ', sizeof(spaces));
+ if(n < (int) sizeof(spaces))
+ printf("%.*s", (int) sizeof(spaces) - n, spaces);
+ } else {
+ test_current_already_logged__ = 1;
+ }
+ }
+}
+
+static void
+test_finish_test_line__(int result)
+{
+ if(test_tap__) {
+ const char* str = (result == 0) ? "ok" : "not ok";
+ printf("%s %u - %s\n", str, test_current_index__ + 1, test_current_unit__->name);
+ } else {
+ int color = (result == 0) ? TEST_COLOR_GREEN_INTENSIVE__ : TEST_COLOR_RED_INTENSIVE__;
+ const char* str = (result == 0) ? "OK" : "FAILED";
+ printf("[ ");
+ test_print_in_color__(color, str);
+ printf(" ]\n");
+ }
+}
+
+static void
+test_line_indent__(int level)
+{
+ static const char spaces[] = " ";
+ int n = level * 2;
+
+ if(test_tap__ && n > 0) {
+ n--;
+ printf("#");
+ }
+
+ while(n > 16) {
+ printf("%s", spaces);
+ n -= 16;
+ }
+ printf("%.*s", n, spaces);
+}
+
int
test_check__(int cond, const char* file, int line, const char* fmt, ...)
{
result_color = TEST_COLOR_GREEN__;
verbose_level = 3;
} else {
- if(!test_current_already_logged__ && test_current_unit__ != NULL) {
- printf("[ ");
- test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "FAILED");
- printf(" ]\n");
- }
+ if(!test_current_already_logged__ && test_current_unit__ != NULL)
+ test_finish_test_line__(-1);
+
result_str = "failed";
result_color = TEST_COLOR_RED__;
verbose_level = 2;
if(test_verbose_level__ >= verbose_level) {
va_list args;
- printf(" ");
+ if(!test_case_current_already_logged__ && test_case_name__[0]) {
+ test_line_indent__(1);
+ test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Case %s:\n", test_case_name__);
+ test_current_already_logged__++;
+ test_case_current_already_logged__++;
+ }
+ test_line_indent__(test_case_name__[0] ? 2 : 1);
if(file != NULL) {
if(test_verbose_level__ < 3) {
#ifdef ACUTEST_WIN__
test_current_already_logged__++;
}
- return cond != 0;
+ return (cond != 0);
+}
+
+void
+test_case__(const char* fmt, ...)
+{
+ va_list args;
+
+ if(test_verbose_level__ < 2)
+ return;
+
+ if(test_case_name__[0]) {
+ test_case_current_already_logged__ = 0;
+ test_case_name__[0] = '\0';
+ }
+
+ if(fmt == NULL)
+ return;
+
+ va_start(args, fmt);
+ vsnprintf(test_case_name__, sizeof(test_case_name__) - 1, fmt, args);
+ va_end(args);
+ test_case_name__[sizeof(test_case_name__) - 1] = '\0';
+
+ if(test_verbose_level__ >= 3) {
+ test_line_indent__(1);
+ test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Case %s:\n", test_case_name__);
+ test_current_already_logged__++;
+ test_case_current_already_logged__++;
+ }
}
void
line_end = strchr(line_beg, '\n');
if(line_end == NULL)
break;
- printf(" %.*s\n", (int)(line_end - line_beg), line_beg);
+ test_line_indent__(test_case_name__[0] ? 3 : 2);
+ printf("%.*s\n", (int)(line_end - line_beg), line_beg);
line_beg = line_end + 1;
}
- if(line_beg[0] != '\0')
- printf(" %s\n", line_beg);
+ if(line_beg[0] != '\0') {
+ test_line_indent__(test_case_name__[0] ? 3 : 2);
+ printf("%s\n", line_beg);
+ }
}
static void
return n;
}
+
+/* Called if anything goes bad in Acutest, or if the unit test ends in other
+ * way then by normal returning from its function (e.g. exception or some
+ * abnormal child process termination). */
+static void
+test_error__(const char* fmt, ...)
+{
+ va_list args;
+
+ if(test_verbose_level__ == 0)
+ return;
+
+ if(test_verbose_level__ <= 2 && !test_current_already_logged__ && test_current_unit__ != NULL) {
+ if(test_tap__) {
+ test_finish_test_line__(-1);
+ } else {
+ printf("[ ");
+ test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "FAILED");
+ printf(" ]\n");
+ }
+ }
+
+ if(test_verbose_level__ >= 2) {
+ test_line_indent__(1);
+ if(test_verbose_level__ >= 3)
+ test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "ERROR: ");
+ va_start(args, fmt);
+ vprintf(fmt, args);
+ va_end(args);
+ printf("\n");
+ }
+
+ if(test_verbose_level__ >= 3) {
+ printf("\n");
+ }
+}
+
/* Call directly the given test unit function. */
static int
-test_do_run__(const struct test__* test)
+test_do_run__(const struct test__* test, int index)
{
test_current_unit__ = test;
+ test_current_index__ = index;
test_current_failures__ = 0;
test_current_already_logged__ = 0;
- if(test_verbose_level__ >= 3) {
- test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Test %s:\n", test->name);
- test_current_already_logged__++;
- } else if(test_verbose_level__ >= 1) {
- int n;
- char spaces[48];
-
- n = test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Test %s... ", test->name);
- memset(spaces, ' ', sizeof(spaces));
- if(n < (int) sizeof(spaces))
- printf("%.*s", (int) sizeof(spaces) - n, spaces);
- } else {
- test_current_already_logged__ = 1;
- }
+ test_begin_test_line__(test);
#ifdef __cplusplus
try {
test->func();
+ if(test_verbose_level__ >= 3) {
+ test_line_indent__(1);
+ if(test_current_failures__ == 0) {
+ test_print_in_color__(TEST_COLOR_GREEN_INTENSIVE__, "SUCCESS: ");
+ printf("All conditions have passed.\n\n");
+ } else {
+ test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "FAILED: ");
+ printf("%d condition%s %s failed.\n\n",
+ test_current_failures__,
+ (test_current_failures__ == 1) ? "" : "s",
+ (test_current_failures__ == 1) ? "has" : "have");
+ }
+ } else if(test_verbose_level__ >= 1 && test_current_failures__ == 0) {
+ test_finish_test_line__(0);
+ }
+
+ test_case__(NULL);
+ test_current_unit__ = NULL;
+ return (test_current_failures__ == 0) ? 0 : -1;
+
#ifdef __cplusplus
} catch(std::exception& e) {
const char* what = e.what();
if(what != NULL)
- test_check__(0, NULL, 0, "Threw std::exception: %s", what);
+ test_error__("Threw std::exception: %s", what);
else
- test_check__(0, NULL, 0, "Threw std::exception");
+ test_error__("Threw std::exception");
+ return -1;
} catch(...) {
- test_check__(0, NULL, 0, "Threw an exception");
+ test_error__("Threw an exception");
+ return -1;
}
#endif
-
- if(test_verbose_level__ >= 3) {
- switch(test_current_failures__) {
- case 0: test_print_in_color__(TEST_COLOR_GREEN_INTENSIVE__, " All conditions have passed.\n\n"); break;
- case 1: test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, " One condition has FAILED.\n\n"); break;
- default: test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, " %d conditions have FAILED.\n\n", test_current_failures__); break;
- }
- } else if(test_verbose_level__ >= 1 && test_current_failures__ == 0) {
- printf("[ ");
- test_print_in_color__(TEST_COLOR_GREEN_INTENSIVE__, "OK");
- printf(" ]\n");
- }
-
- test_current_unit__ = NULL;
- return (test_current_failures__ == 0) ? 0 : -1;
}
-#if defined(ACUTEST_UNIX__) || defined(ACUTEST_WIN__)
-/* Called if anything goes bad in Acutest, or if the unit test ends in other
- * way then by normal returning from its function (e.g. exception or some
- * abnormal child process termination). */
-static void
-test_error__(const char* fmt, ...)
-{
- va_list args;
-
- if(test_verbose_level__ == 0)
- return;
-
- if(test_verbose_level__ <= 2 && !test_current_already_logged__ && test_current_unit__ != NULL) {
- printf("[ ");
- test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "FAILED");
- printf(" ]\n");
- }
-
- if(test_verbose_level__ >= 2) {
- test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, " Error: ");
- va_start(args, fmt);
- vprintf(fmt, args);
- va_end(args);
- printf("\n");
- }
-}
-#endif
-
/* Trigger the unit test. If possible (and not suppressed) it starts a child
* process who calls test_do_run__(), otherwise it calls test_do_run__()
* directly. */
static void
-test_run__(const struct test__* test)
+test_run__(const struct test__* test, int index)
{
int failed = 1;
pid_t pid;
int exit_code;
+ /* Make sure the child starts with empty I/O buffers. */
+ fflush(stdout);
+ fflush(stderr);
+
pid = fork();
if(pid == (pid_t)-1) {
test_error__("Cannot fork. %s [%d]", strerror(errno), errno);
failed = 1;
} else if(pid == 0) {
/* Child: Do the test. */
- failed = (test_do_run__(test) != 0);
+ failed = (test_do_run__(test, index) != 0);
exit(failed ? 1 : 0);
} else {
/* Parent: Wait until child terminates and analyze its exit code. */
/* Windows has no fork(). So we propagate all info into the child
* through a command line arguments. */
_snprintf(buffer, sizeof(buffer)-1,
- "%s --no-exec --no-summary --verbose=%d --color=%s -- \"%s\"",
- test_argv0__, test_verbose_level__,
- test_colorize__ ? "always" : "never", test->name);
+ "%s --worker=%d --no-exec --no-summary %s --verbose=%d --color=%s -- \"%s\"",
+ test_argv0__, index, test_tap__ ? "--tap" : "",
+ test_verbose_level__, test_colorize__ ? "always" : "never",
+ test->name);
memset(&startupInfo, 0, sizeof(startupInfo));
startupInfo.cb = sizeof(STARTUPINFO);
if(CreateProcessA(NULL, buffer, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo)) {
#else
/* A platform where we don't know how to run child process. */
- failed = (test_do_run__(test) != 0);
+ failed = (test_do_run__(test, index) != 0);
#endif
} else {
/* Child processes suppressed through --no-exec. */
- failed = (test_do_run__(test) != 0);
+ failed = (test_do_run__(test, index) != 0);
}
test_current_unit__ = NULL;
#endif
+#define TEST_CMDLINE_OPTFLAG_OPTIONALARG__ 0x0001
+#define TEST_CMDLINE_OPTFLAG_REQUIREDARG__ 0x0002
+
+#define TEST_CMDLINE_OPTID_NONE__ 0
+#define TEST_CMDLINE_OPTID_UNKNOWN__ (-0x7fffffff + 0)
+#define TEST_CMDLINE_OPTID_MISSINGARG__ (-0x7fffffff + 1)
+#define TEST_CMDLINE_OPTID_BOGUSARG__ (-0x7fffffff + 2)
+
+typedef struct TEST_CMDLINE_OPTION__ {
+ char shortname;
+ const char* longname;
+ int id;
+ unsigned flags;
+} TEST_CMDLINE_OPTION__;
+
+static int
+test_cmdline_handle_short_opt_group__(const TEST_CMDLINE_OPTION__* options,
+ const char* arggroup,
+ int (*callback)(int /*optval*/, const char* /*arg*/))
+{
+ const TEST_CMDLINE_OPTION__* opt;
+ int i;
+ int ret = 0;
+
+ for(i = 0; arggroup[i] != '\0'; i++) {
+ for(opt = options; opt->id != 0; opt++) {
+ if(arggroup[i] == opt->shortname)
+ break;
+ }
+
+ if(opt->id != 0 && !(opt->flags & TEST_CMDLINE_OPTFLAG_REQUIREDARG__)) {
+ ret = callback(opt->id, NULL);
+ } else {
+ /* Unknown option. */
+ char badoptname[3];
+ badoptname[0] = '-';
+ badoptname[1] = arggroup[i];
+ badoptname[2] = '\0';
+ ret = callback((opt->id != 0 ? TEST_CMDLINE_OPTID_MISSINGARG__ : TEST_CMDLINE_OPTID_UNKNOWN__),
+ badoptname);
+ }
+
+ if(ret != 0)
+ break;
+ }
+
+ return ret;
+}
+
+#define TEST_CMDLINE_AUXBUF_SIZE__ 32
+
+static int
+test_cmdline_read__(const TEST_CMDLINE_OPTION__* options, int argc, char** argv,
+ int (*callback)(int /*optval*/, const char* /*arg*/))
+{
+
+ const TEST_CMDLINE_OPTION__* opt;
+ char auxbuf[TEST_CMDLINE_AUXBUF_SIZE__+1];
+ int after_doubledash = 0;
+ int i = 1;
+ int ret = 0;
+
+ auxbuf[TEST_CMDLINE_AUXBUF_SIZE__] = '\0';
+
+ while(i < argc) {
+ if(after_doubledash || strcmp(argv[i], "-") == 0) {
+ /* Non-option argument. */
+ ret = callback(TEST_CMDLINE_OPTID_NONE__, argv[i]);
+ } else if(strcmp(argv[i], "--") == 0) {
+ /* End of options. All the remaining members are non-option arguments. */
+ after_doubledash = 1;
+ } else if(argv[i][0] != '-') {
+ /* Non-option argument. */
+ ret = callback(TEST_CMDLINE_OPTID_NONE__, argv[i]);
+ } else {
+ for(opt = options; opt->id != 0; opt++) {
+ if(opt->longname != NULL && strncmp(argv[i], "--", 2) == 0) {
+ size_t len = strlen(opt->longname);
+ if(strncmp(argv[i]+2, opt->longname, len) == 0) {
+ /* Regular long option. */
+ if(argv[i][2+len] == '\0') {
+ /* with no argument provided. */
+ if(!(opt->flags & TEST_CMDLINE_OPTFLAG_REQUIREDARG__))
+ ret = callback(opt->id, NULL);
+ else
+ ret = callback(TEST_CMDLINE_OPTID_MISSINGARG__, argv[i]);
+ break;
+ } else if(argv[i][2+len] == '=') {
+ /* with an argument provided. */
+ if(opt->flags & (TEST_CMDLINE_OPTFLAG_OPTIONALARG__ | TEST_CMDLINE_OPTFLAG_REQUIREDARG__)) {
+ ret = callback(opt->id, argv[i]+2+len+1);
+ } else {
+ sprintf(auxbuf, "--%s", opt->longname);
+ ret = callback(TEST_CMDLINE_OPTID_BOGUSARG__, auxbuf);
+ }
+ break;
+ } else {
+ continue;
+ }
+ }
+ } else if(opt->shortname != '\0' && argv[i][0] == '-') {
+ if(argv[i][1] == opt->shortname) {
+ /* Regular short option. */
+ if(opt->flags & TEST_CMDLINE_OPTFLAG_REQUIREDARG__) {
+ if(argv[i][2] != '\0')
+ ret = callback(opt->id, argv[i]+2);
+ else if(i+1 < argc)
+ ret = callback(opt->id, argv[++i]);
+ else
+ ret = callback(TEST_CMDLINE_OPTID_MISSINGARG__, argv[i]);
+ break;
+ } else {
+ ret = callback(opt->id, NULL);
+
+ /* There might be more (argument-less) short options
+ * grouped together. */
+ if(ret == 0 && argv[i][2] != '\0')
+ ret = test_cmdline_handle_short_opt_group__(options, argv[i]+2, callback);
+ break;
+ }
+ }
+ }
+ }
+
+ if(opt->id == 0) { /* still not handled? */
+ if(argv[i][0] != '-') {
+ /* Non-option argument. */
+ ret = callback(TEST_CMDLINE_OPTID_NONE__, argv[i]);
+ } else {
+ /* Unknown option. */
+ char* badoptname = argv[i];
+
+ if(strncmp(badoptname, "--", 2) == 0) {
+ /* Strip any argument from the long option. */
+ char* assignement = strchr(badoptname, '=');
+ if(assignement != NULL) {
+ size_t len = assignement - badoptname;
+ if(len > TEST_CMDLINE_AUXBUF_SIZE__)
+ len = TEST_CMDLINE_AUXBUF_SIZE__;
+ strncpy(auxbuf, badoptname, len);
+ auxbuf[len] = '\0';
+ badoptname = auxbuf;
+ }
+ }
+
+ ret = callback(TEST_CMDLINE_OPTID_UNKNOWN__, badoptname);
+ }
+ }
+ }
+
+ if(ret != 0)
+ return ret;
+ i++;
+ }
+
+ return ret;
+}
+
static void
test_help__(void)
{
printf("Usage: %s [options] [test...]\n", test_argv0__);
+ printf("\n");
printf("Run the specified unit tests; or if the option '--skip' is used, run all\n");
printf("tests in the suite but those listed. By default, if no tests are specified\n");
printf("on the command line, all unit tests in the suite are run.\n");
printf(" (WHEN is one of 'auto', 'always', 'never')\n");
printf(" -E, --no-exec Same as --exec=never\n");
printf(" --no-summary Suppress printing of test results summary\n");
+ printf(" --tap Produce TAP-compliant output\n");
+ printf(" (See https://testanything.org/)\n");
printf(" -l, --list List unit tests in the suite and exit\n");
- printf(" -v, --verbose Enable more verbose output\n");
+ printf(" -v, --verbose Make output more verbose\n");
printf(" --verbose=LEVEL Set verbose level to LEVEL:\n");
printf(" 0 ... Be silent\n");
printf(" 1 ... Output one line per test (and summary)\n");
printf(" 3 ... As 1 and all conditions (and extended summary)\n");
printf(" --color=WHEN Enable colorized output\n");
printf(" (WHEN is one of 'auto', 'always', 'never')\n");
+ printf(" --no-color Same as --color=never\n");
printf(" -h, --help Display this help and exit\n");
if(test_list_size__ < 16) {
}
}
+static const TEST_CMDLINE_OPTION__ test_cmdline_options__[] = {
+ { 's', "skip", 's', 0 },
+ { 0, "exec", 'e', TEST_CMDLINE_OPTFLAG_OPTIONALARG__ },
+ { 'E', "no-exec", 'E', 0 },
+ { 0, "no-summary", 'S', 0 },
+ { 0, "tap", 'T', 0 },
+ { 'l', "list", 'l', 0 },
+ { 'v', "verbose", 'v', TEST_CMDLINE_OPTFLAG_OPTIONALARG__ },
+ { 0, "color", 'c', TEST_CMDLINE_OPTFLAG_OPTIONALARG__ },
+ { 0, "no-color", 'C', 0 },
+ { 'h', "help", 'h', 0 },
+ { 0, "worker", 'w', TEST_CMDLINE_OPTFLAG_REQUIREDARG__ }, /* internal */
+ { 0, NULL, 0, 0 }
+};
+
+static int
+test_cmdline_callback__(int id, const char* arg)
+{
+ switch(id) {
+ case 's':
+ test_skip_mode__ = 1;
+ break;
+
+ case 'e':
+ if(arg == NULL || strcmp(arg, "always") == 0) {
+ test_no_exec__ = 0;
+ } else if(strcmp(arg, "never") == 0) {
+ test_no_exec__ = 1;
+ } else if(strcmp(arg, "auto") == 0) {
+ /*noop*/
+ } else {
+ fprintf(stderr, "%s: Unrecognized argument '%s' for option --exec.\n", test_argv0__, arg);
+ fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__);
+ exit(2);
+ }
+ break;
+
+ case 'E':
+ test_no_exec__ = 1;
+ break;
+
+ case 'S':
+ test_no_summary__ = 1;
+ break;
+
+ case 'T':
+ test_tap__ = 1;
+ break;
+
+ case 'l':
+ test_list_names__();
+ exit(0);
+
+ case 'v':
+ test_verbose_level__ = (arg != NULL ? atoi(arg) : test_verbose_level__+1);
+ break;
+
+ case 'c':
+ if(arg == NULL || strcmp(arg, "always") == 0) {
+ test_colorize__ = 1;
+ } else if(strcmp(arg, "never") == 0) {
+ test_colorize__ = 0;
+ } else if(strcmp(arg, "auto") == 0) {
+ /*noop*/
+ } else {
+ fprintf(stderr, "%s: Unrecognized argument '%s' for option --color.\n", test_argv0__, arg);
+ fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__);
+ exit(2);
+ }
+ break;
+
+ case 'C':
+ test_colorize__ = 0;
+ break;
+
+ case 'h':
+ test_help__();
+ exit(0);
+
+ case 'w':
+ test_worker__ = 1;
+ test_worker_index__ = atoi(arg);
+ break;
+
+ case 0:
+ if(test_lookup__(arg) == 0) {
+ fprintf(stderr, "%s: Unrecognized unit test '%s'\n", test_argv0__, arg);
+ fprintf(stderr, "Try '%s --list' for list of unit tests.\n", test_argv0__);
+ exit(2);
+ }
+ break;
+
+ case TEST_CMDLINE_OPTID_UNKNOWN__:
+ fprintf(stderr, "Unrecognized command line option '%s'.\n", arg);
+ fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__);
+ exit(2);
+
+ case TEST_CMDLINE_OPTID_MISSINGARG__:
+ fprintf(stderr, "The command line option '%s' requires an argument.\n", arg);
+ fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__);
+ exit(2);
+
+ case TEST_CMDLINE_OPTID_BOGUSARG__:
+ fprintf(stderr, "The command line option '%s' does not expect an argument.\n", arg);
+ fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__);
+ exit(2);
+ }
+
+ return 0;
+}
+
+
#ifdef ACUTEST_LINUX__
static int
test_is_tracer_present__(void)
main(int argc, char** argv)
{
int i;
- int seen_double_dash = 0;
-
test_argv0__ = argv[0];
#if defined ACUTEST_UNIX__
memset((void*) test_flags__, 0, sizeof(char) * test_list_size__);
/* Parse options */
- for(i = 1; i < argc; i++) {
- if(seen_double_dash || argv[i][0] != '-') {
- if(test_lookup__(argv[i]) == 0) {
- fprintf(stderr, "%s: Unrecognized unit test '%s'\n", argv[0], argv[i]);
- fprintf(stderr, "Try '%s --list' for list of unit tests.\n", argv[0]);
- exit(2);
- }
- } else if(strcmp(argv[i], "--") == 0) {
- seen_double_dash = 1;
- } else if(strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
- test_help__();
- exit(0);
- } else if(strcmp(argv[i], "--verbose") == 0 || strcmp(argv[i], "-v") == 0) {
- test_verbose_level__++;
- } else if(strncmp(argv[i], "--verbose=", 10) == 0) {
- test_verbose_level__ = atoi(argv[i] + 10);
- } else if(strcmp(argv[i], "--color=auto") == 0) {
- /* noop (set from above) */
- } else if(strcmp(argv[i], "--color=always") == 0 || strcmp(argv[i], "--color") == 0) {
- test_colorize__ = 1;
- } else if(strcmp(argv[i], "--color=never") == 0 || strcmp(argv[i], "--no-color") == 0) {
- test_colorize__ = 0;
- } else if(strcmp(argv[i], "--skip") == 0 || strcmp(argv[i], "-s") == 0) {
- test_skip_mode__ = 1;
- } else if(strcmp(argv[i], "--exec=auto") == 0) {
- /* noop (set from above) */
- } else if(strcmp(argv[i], "--exec=always") == 0 || strcmp(argv[i], "--exec") == 0) {
- test_no_exec__ = 0;
- } else if(strcmp(argv[i], "--exec=never") == 0 || strcmp(argv[i], "--no-exec") == 0 || strcmp(argv[i], "-E") == 0) {
- test_no_exec__ = 1;
- } else if(strcmp(argv[i], "--no-summary") == 0) {
- test_no_summary__ = 1;
- } else if(strcmp(argv[i], "--list") == 0 || strcmp(argv[i], "-l") == 0) {
- test_list_names__();
- exit(0);
- } else {
- fprintf(stderr, "%s: Unrecognized option '%s'\n", argv[0], argv[i]);
- fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]);
- exit(2);
- }
- }
+ test_cmdline_read__(test_cmdline_options__, argc, argv, test_cmdline_callback__);
#if defined(ACUTEST_WIN__)
SetUnhandledExceptionFilter(test_exception_filter__);
}
}
+ if(test_tap__) {
+ /* TAP requires we know test result ("ok", "not ok") before we output
+ * anything about the test, and this gets problematic for larger verbose
+ * levels. */
+ if(test_verbose_level__ > 2)
+ test_verbose_level__ = 2;
+
+ /* TAP harness should provide some summary. */
+ test_no_summary__ = 1;
+
+ if(!test_worker__)
+ printf("1..%d\n", (int) test_count__);
+ }
+
/* Run the tests */
if(!test_skip_mode__) {
/* Run the listed tests. */
for(i = 0; i < (int) test_count__; i++)
- test_run__(tests__[i]);
+ test_run__(tests__[i], test_worker_index__ + i);
} else {
/* Run all tests except those listed. */
+ int index = test_worker_index__;
for(i = 0; test_list__[i].func != NULL; i++) {
if(!test_flags__[i])
- test_run__(&test_list__[i]);
+ test_run__(&test_list__[i], index++);
}
}
printf(" All unit tests have passed.\n");
} else {
test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "FAILED:");
- printf(" %d of %d unit tests have failed.\n",
- test_stat_failed_units__, test_stat_run_units__);
+ printf(" %d of %d unit tests %s failed.\n",
+ test_stat_failed_units__, test_stat_run_units__,
+ (test_stat_failed_units__ == 1) ? "has" : "have");
}
if(test_verbose_level__ >= 3)