]> granicus.if.org Git - neomutt/commitdiff
acutest: sync to upstream
authorPietro Cerutti <gahr@gahr.ch>
Mon, 7 Jan 2019 14:54:47 +0000 (14:54 +0000)
committerPietro Cerutti <gahr@gahr.ch>
Mon, 7 Jan 2019 14:54:47 +0000 (14:54 +0000)
test/acutest.h

index c56a0dd89d7379cafa068a5ee6f616be615823cb..b852186ce75d38383b8de64b243bd116d1d12287 100644 (file)
@@ -2,7 +2,7 @@
  * 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"),
@@ -56,7 +56,7 @@
  *
  *   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
 
 
@@ -160,6 +183,7 @@ struct test__ {
 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, ...);
 
 
@@ -172,13 +196,19 @@ static char* test_flags__ = NULL;
 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;
@@ -215,7 +245,7 @@ test_print_in_color__(int color, const char* fmt, ...)
             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);
@@ -237,7 +267,7 @@ test_print_in_color__(int color, const char* fmt, ...)
             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);
@@ -251,6 +281,60 @@ test_print_in_color__(int color, const char* fmt, ...)
 #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, ...)
 {
@@ -263,11 +347,9 @@ 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;
@@ -278,8 +360,14 @@ test_check__(int cond, const char* file, int line, const char* fmt, ...)
     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__
@@ -309,7 +397,36 @@ test_check__(int cond, const char* file, int line, const char* fmt, ...)
         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
@@ -338,11 +455,14 @@ test_message__(const char* fmt, ...)
         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
@@ -430,28 +550,53 @@ test_lookup__(const char* pattern)
     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 {
@@ -463,67 +608,46 @@ test_do_run__(const struct test__* test)
 
         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;
 
@@ -537,13 +661,17 @@ test_run__(const struct test__* test)
         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. */
@@ -584,9 +712,10 @@ test_run__(const struct test__* test)
         /* 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)) {
@@ -603,13 +732,13 @@ test_run__(const struct test__* test)
 #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;
@@ -634,10 +763,169 @@ test_exception_filter__(EXCEPTION_POINTERS *ptrs)
 #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");
@@ -648,8 +936,10 @@ test_help__(void)
     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");
@@ -657,6 +947,7 @@ test_help__(void)
     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) {
@@ -665,6 +956,118 @@ test_help__(void)
     }
 }
 
+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)
@@ -708,8 +1111,6 @@ int
 main(int argc, char** argv)
 {
     int i;
-    int seen_double_dash = 0;
-
     test_argv0__ = argv[0];
 
 #if defined ACUTEST_UNIX__
@@ -738,47 +1139,7 @@ main(int argc, char** argv)
     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__);
@@ -809,16 +1170,31 @@ main(int argc, char** argv)
         }
     }
 
+    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++);
         }
     }
 
@@ -839,8 +1215,9 @@ main(int argc, char** argv)
             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)