123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520 |
- /* tinytest.c -- Copyright 2009-2012 Nick Mathewson
- *
- * 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.
- * 3. The name of the author may not be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
- */
- #ifdef TINYTEST_LOCAL
- #include "tinytest_local.h"
- #endif
- #define TINYTEST_POSTFORK
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <assert.h>
- #ifndef NO_FORKING
- #ifdef _WIN32
- #include <windows.h>
- #else
- #include <sys/types.h>
- #include <sys/wait.h>
- #include <unistd.h>
- #endif
- #if defined(__APPLE__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__)
- #if (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1060 && \
- __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 1070)
- /* Workaround for a stupid bug in OSX 10.6 */
- #define FORK_BREAKS_GCOV
- #include <vproc.h>
- #endif
- #endif
- #endif /* !NO_FORKING */
- #ifndef __GNUC__
- #define __attribute__(x)
- #endif
- #include "tinytest.h"
- #include "tinytest_macros.h"
- #define LONGEST_TEST_NAME 16384
- static int in_tinytest_main = 0; /**< true if we're in tinytest_main().*/
- static int n_ok = 0; /**< Number of tests that have passed */
- static int n_bad = 0; /**< Number of tests that have failed. */
- static int n_skipped = 0; /**< Number of tests that have been skipped. */
- static int opt_forked = 0; /**< True iff we're called from inside a win32 fork*/
- static int opt_nofork = 0; /**< Suppress calls to fork() for debugging. */
- static int opt_verbosity = 1; /**< -==quiet,0==terse,1==normal,2==verbose */
- static const char *verbosity_flag = "";
- static const struct testlist_alias_t *cfg_aliases=NULL;
- enum outcome { SKIP=2, OK=1, FAIL=0 };
- static enum outcome cur_test_outcome = 0;
- /** prefix of the current test group */
- static const char *cur_test_prefix = NULL;
- /** Name of the current test, if we haven't logged is yet. Used for --quiet */
- static const char *cur_test_name = NULL;
- #ifdef _WIN32
- /* Copy of argv[0] for win32. */
- static char commandname[MAX_PATH+1];
- #endif
- static void usage(struct testgroup_t *groups, int list_groups)
- __attribute__((noreturn));
- static int process_test_option(struct testgroup_t *groups, const char *test);
- static enum outcome
- testcase_run_bare_(const struct testcase_t *testcase)
- {
- void *env = NULL;
- int outcome;
- if (testcase->setup) {
- env = testcase->setup->setup_fn(testcase);
- if (!env)
- return FAIL;
- else if (env == (void*)TT_SKIP)
- return SKIP;
- }
- cur_test_outcome = OK;
- testcase->fn(env);
- outcome = cur_test_outcome;
- if (testcase->setup) {
- if (testcase->setup->cleanup_fn(testcase, env) == 0)
- outcome = FAIL;
- }
- return outcome;
- }
- #define MAGIC_EXITCODE 42
- #ifndef NO_FORKING
- #ifdef TINYTEST_POSTFORK
- void tinytest_prefork(void);
- void tinytest_postfork(void);
- #else
- static void tinytest_prefork(void) { }
- static void tinytest_postfork(void) { }
- #endif
- static enum outcome
- testcase_run_forked_(const struct testgroup_t *group,
- const struct testcase_t *testcase)
- {
- #ifdef _WIN32
- /* Fork? On Win32? How primitive! We'll do what the smart kids do:
- we'll invoke our own exe (whose name we recall from the command
- line) with a command line that tells it to run just the test we
- want, and this time without forking.
- (No, threads aren't an option. The whole point of forking is to
- share no state between tests.)
- */
- int ok;
- char buffer[LONGEST_TEST_NAME+256];
- STARTUPINFOA si;
- PROCESS_INFORMATION info;
- DWORD exitcode;
- if (!in_tinytest_main) {
- printf("\nERROR. On Windows, testcase_run_forked_ must be"
- " called from within tinytest_main.\n");
- abort();
- }
- if (opt_verbosity>0)
- printf("[forking] ");
- snprintf(buffer, sizeof(buffer), "\"%s\" --RUNNING-FORKED %s %s%s",
- commandname, verbosity_flag, group->prefix, testcase->name);
- memset(&si, 0, sizeof(si));
- memset(&info, 0, sizeof(info));
- si.cb = sizeof(si);
- ok = CreateProcessA(commandname, buffer, NULL, NULL, 0,
- 0, NULL, NULL, &si, &info);
- if (!ok) {
- printf("CreateProcess failed!\n");
- return 0;
- }
- WaitForSingleObject(info.hProcess, INFINITE);
- GetExitCodeProcess(info.hProcess, &exitcode);
- CloseHandle(info.hProcess);
- CloseHandle(info.hThread);
- if (exitcode == 0)
- return OK;
- else if (exitcode == MAGIC_EXITCODE)
- return SKIP;
- else
- return FAIL;
- #else
- int outcome_pipe[2];
- pid_t pid;
- (void)group;
- if (pipe(outcome_pipe))
- perror("opening pipe");
- if (opt_verbosity>0)
- printf("[forking] ");
- tinytest_prefork();
- pid = fork();
- #ifdef FORK_BREAKS_GCOV
- vproc_transaction_begin(0);
- #endif
- tinytest_postfork();
- if (!pid) {
- /* child. */
- int test_r, write_r;
- char b[1];
- close(outcome_pipe[0]);
- test_r = testcase_run_bare_(testcase);
- assert(0<=(int)test_r && (int)test_r<=2);
- b[0] = "NYS"[test_r];
- write_r = (int)write(outcome_pipe[1], b, 1);
- if (write_r != 1) {
- perror("write outcome to pipe");
- exit(1);
- }
- exit(0);
- return FAIL; /* unreachable */
- } else {
- /* parent */
- int status, r;
- char b[1];
- /* Close this now, so that if the other side closes it,
- * our read fails. */
- close(outcome_pipe[1]);
- r = (int)read(outcome_pipe[0], b, 1);
- if (r == 0) {
- printf("[Lost connection!] ");
- return FAIL;
- } else if (r != 1) {
- perror("read outcome from pipe");
- }
- r = waitpid(pid, &status, 0);
- close(outcome_pipe[0]);
- if (r == -1) {
- perror("waitpid");
- return FAIL;
- }
- if (! WIFEXITED(status) || WEXITSTATUS(status) != 0) {
- printf("[did not exit cleanly.]");
- return FAIL;
- }
- return b[0]=='Y' ? OK : (b[0]=='S' ? SKIP : FAIL);
- }
- #endif
- }
- #endif /* !NO_FORKING */
- int
- testcase_run_one(const struct testgroup_t *group,
- const struct testcase_t *testcase)
- {
- enum outcome outcome;
- if (testcase->flags & (TT_SKIP|TT_OFF_BY_DEFAULT)) {
- if (opt_verbosity>0)
- printf("%s%s: %s\n",
- group->prefix, testcase->name,
- (testcase->flags & TT_SKIP) ? "SKIPPED" : "DISABLED");
- ++n_skipped;
- return SKIP;
- }
- if (opt_verbosity>0 && !opt_forked) {
- printf("%s%s: ", group->prefix, testcase->name);
- } else {
- if (opt_verbosity==0) printf(".");
- cur_test_prefix = group->prefix;
- cur_test_name = testcase->name;
- }
- #ifndef NO_FORKING
- if ((testcase->flags & TT_FORK) && !(opt_forked||opt_nofork)) {
- outcome = testcase_run_forked_(group, testcase);
- } else {
- #else
- {
- #endif
- outcome = testcase_run_bare_(testcase);
- }
- if (outcome == OK) {
- ++n_ok;
- if (opt_verbosity>0 && !opt_forked)
- puts(opt_verbosity==1?"OK":"");
- } else if (outcome == SKIP) {
- ++n_skipped;
- if (opt_verbosity>0 && !opt_forked)
- puts("SKIPPED");
- } else {
- ++n_bad;
- if (!opt_forked)
- printf("\n [%s FAILED]\n", testcase->name);
- }
- if (opt_forked) {
- exit(outcome==OK ? 0 : (outcome==SKIP?MAGIC_EXITCODE : 1));
- return 1; /* unreachable */
- } else {
- return (int)outcome;
- }
- }
- int
- tinytest_set_flag_(struct testgroup_t *groups, const char *arg, int set, unsigned long flag)
- {
- int i, j;
- size_t length = LONGEST_TEST_NAME;
- char fullname[LONGEST_TEST_NAME];
- int found=0;
- if (strstr(arg, ".."))
- length = strstr(arg,"..")-arg;
- for (i=0; groups[i].prefix; ++i) {
- for (j=0; groups[i].cases[j].name; ++j) {
- struct testcase_t *testcase = &groups[i].cases[j];
- snprintf(fullname, sizeof(fullname), "%s%s",
- groups[i].prefix, testcase->name);
- if (!flag) { /* Hack! */
- printf(" %s", fullname);
- if (testcase->flags & TT_OFF_BY_DEFAULT)
- puts(" (Off by default)");
- else if (testcase->flags & TT_SKIP)
- puts(" (DISABLED)");
- else
- puts("");
- }
- if (!strncmp(fullname, arg, length)) {
- if (set)
- testcase->flags |= flag;
- else
- testcase->flags &= ~flag;
- ++found;
- }
- }
- }
- return found;
- }
- static void
- usage(struct testgroup_t *groups, int list_groups)
- {
- puts("Options are: [--verbose|--quiet|--terse] [--no-fork]");
- puts(" Specify tests by name, or using a prefix ending with '..'");
- puts(" To skip a test, prefix its name with a colon.");
- puts(" To enable a disabled test, prefix its name with a plus.");
- puts(" Use --list-tests for a list of tests.");
- if (list_groups) {
- puts("Known tests are:");
- tinytest_set_flag_(groups, "..", 1, 0);
- }
- exit(0);
- }
- static int
- process_test_alias(struct testgroup_t *groups, const char *test)
- {
- int i, j, n, r;
- for (i=0; cfg_aliases && cfg_aliases[i].name; ++i) {
- if (!strcmp(cfg_aliases[i].name, test)) {
- n = 0;
- for (j = 0; cfg_aliases[i].tests[j]; ++j) {
- r = process_test_option(groups, cfg_aliases[i].tests[j]);
- if (r<0)
- return -1;
- n += r;
- }
- return n;
- }
- }
- printf("No such test alias as @%s!",test);
- return -1;
- }
- static int
- process_test_option(struct testgroup_t *groups, const char *test)
- {
- int flag = TT_ENABLED_;
- int n = 0;
- if (test[0] == '@') {
- return process_test_alias(groups, test + 1);
- } else if (test[0] == ':') {
- ++test;
- flag = TT_SKIP;
- } else if (test[0] == '+') {
- ++test;
- ++n;
- if (!tinytest_set_flag_(groups, test, 0, TT_OFF_BY_DEFAULT)) {
- printf("No such test as %s!\n", test);
- return -1;
- }
- } else {
- ++n;
- }
- if (!tinytest_set_flag_(groups, test, 1, flag)) {
- printf("No such test as %s!\n", test);
- return -1;
- }
- return n;
- }
- void
- tinytest_set_aliases(const struct testlist_alias_t *aliases)
- {
- cfg_aliases = aliases;
- }
- int
- tinytest_main(int c, const char **v, struct testgroup_t *groups)
- {
- int i, j, n=0;
- #ifdef _WIN32
- const char *sp = strrchr(v[0], '.');
- const char *extension = "";
- if (!sp || stricmp(sp, ".exe"))
- extension = ".exe"; /* Add an exe so CreateProcess will work */
- snprintf(commandname, sizeof(commandname), "%s%s", v[0], extension);
- commandname[MAX_PATH]='\0';
- #endif
- for (i=1; i<c; ++i) {
- if (v[i][0] == '-') {
- if (!strcmp(v[i], "--RUNNING-FORKED")) {
- opt_forked = 1;
- } else if (!strcmp(v[i], "--no-fork")) {
- opt_nofork = 1;
- } else if (!strcmp(v[i], "--quiet")) {
- opt_verbosity = -1;
- verbosity_flag = "--quiet";
- } else if (!strcmp(v[i], "--verbose")) {
- opt_verbosity = 2;
- verbosity_flag = "--verbose";
- } else if (!strcmp(v[i], "--terse")) {
- opt_verbosity = 0;
- verbosity_flag = "--terse";
- } else if (!strcmp(v[i], "--help")) {
- usage(groups, 0);
- } else if (!strcmp(v[i], "--list-tests")) {
- usage(groups, 1);
- } else {
- printf("Unknown option %s. Try --help\n",v[i]);
- return -1;
- }
- } else {
- int r = process_test_option(groups, v[i]);
- if (r<0)
- return -1;
- n += r;
- }
- }
- if (!n)
- tinytest_set_flag_(groups, "..", 1, TT_ENABLED_);
- #ifdef _IONBF
- setvbuf(stdout, NULL, _IONBF, 0);
- #endif
- ++in_tinytest_main;
- for (i=0; groups[i].prefix; ++i)
- for (j=0; groups[i].cases[j].name; ++j)
- if (groups[i].cases[j].flags & TT_ENABLED_)
- testcase_run_one(&groups[i],
- &groups[i].cases[j]);
- --in_tinytest_main;
- if (opt_verbosity==0)
- puts("");
- if (n_bad)
- printf("%d/%d TESTS FAILED. (%d skipped)\n", n_bad,
- n_bad+n_ok,n_skipped);
- else if (opt_verbosity >= 1)
- printf("%d tests ok. (%d skipped)\n", n_ok, n_skipped);
- return (n_bad == 0) ? 0 : 1;
- }
- int
- tinytest_get_verbosity_(void)
- {
- return opt_verbosity;
- }
- void
- tinytest_set_test_failed_(void)
- {
- if (opt_verbosity <= 0 && cur_test_name) {
- if (opt_verbosity==0) puts("");
- printf("%s%s: ", cur_test_prefix, cur_test_name);
- cur_test_name = NULL;
- }
- cur_test_outcome = 0;
- }
- void
- tinytest_set_test_skipped_(void)
- {
- if (cur_test_outcome==OK)
- cur_test_outcome = SKIP;
- }
- char *
- tinytest_format_hex_(const void *val_, unsigned long len)
- {
- const unsigned char *val = val_;
- char *result, *cp;
- size_t i;
- int ellipses = 0;
- if (!val)
- return strdup("null");
- if (len > 1024) {
- ellipses = 3;
- len = 1024;
- }
- if (!(result = malloc(len*2+4)))
- return strdup("<allocation failure>");
- cp = result;
- for (i=0;i<len;++i) {
- *cp++ = "0123456789ABCDEF"[(val[i] >> 4)&0x0f];
- *cp++ = "0123456789ABCDEF"[val[i] & 0x0f];
- }
- while (ellipses--)
- *cp++ = '.';
- *cp = 0;
- return result;
- }
|