Refactor unit tests using my spiffy new "tinytest" framework.
authorNick Mathewson <nickm@torproject.org>
Thu, 29 Jan 2009 23:19:57 +0000 (23:19 +0000)
committerNick Mathewson <nickm@torproject.org>
Thu, 29 Jan 2009 23:19:57 +0000 (23:19 +0000)
The big win here is that we can get process-level isolation.

This has been tested to work okay on at least Linux and Win32.  Only
the tests in regress.c have been converted wrapped in the new wrapper
functions; the others are still on the old system.

svn:r1073

test/Makefile.am
test/regress.c
test/regress.h
test/regress_et.c
test/regress_main.c [new file with mode: 0644]
test/regress_util.c
test/tinytest.c [new file with mode: 0644]
test/tinytest.h [new file with mode: 0644]
test/tinytest_demo.c [new file with mode: 0644]
test/tinytest_macros.h [new file with mode: 0644]

index 5548cbaef8d4ec8291ca31ec43f46fd6bcede34a..bedf7e4d1c09dc89c458bb6af57c600d8a05ca20 100644 (file)
@@ -1,11 +1,12 @@
 AUTOMAKE_OPTIONS = foreign no-dependencies
 
-AM_CFLAGS = -I$(top_srcdir) -I$(top_srcdir)/compat -I$(top_srcdir)/include 
+AM_CFLAGS = -I$(top_srcdir) -I$(top_srcdir)/compat -I$(top_srcdir)/include
 
 EXTRA_DIST = regress.rpc regress.gen.h regress.gen.c
 
 noinst_PROGRAMS = test-init test-eof test-weof test-time regress \
        bench bench_cascade bench_http
+noinst_HEADERS = tinytest.h tinytest_macros.h regress.h
 
 BUILT_SOURCES = regress.gen.c regress.gen.h
 test_init_SOURCES = test-init.c
@@ -16,9 +17,10 @@ test_weof_SOURCES = test-weof.c
 test_weof_LDADD = ../libevent_core.la
 test_time_SOURCES = test-time.c
 test_time_LDADD = ../libevent_core.la
-regress_SOURCES = regress.c regress.h regress_http.c regress_dns.c \
+
+regress_SOURCES = regress.c regress_http.c regress_dns.c \
        regress_rpc.c regress.gen.c regress.gen.h regress_et.c \
-       regress_util.c \
+       regress_util.c tinytest.c regress_main.c \
        $(regress_pthread_SOURCES) $(regress_zlib_SOURCES)
 if PTHREADS
 regress_pthread_SOURCES = regress_pthread.c
@@ -30,6 +32,7 @@ regress_LDADD = ../libevent.la $(PTHREAD_LIBS) $(ZLIB_LIBS)
 regress_CFLAGS = -I$(top_srcdir) -I$(top_srcdir)/compat \
        -I$(top_srcdir)/include  $(PTHREAD_CFLAGS) $(ZLIB_CFLAGS)
 regress_LDFLAGS = $(PTHREAD_CFLAGS)
+
 bench_SOURCES = bench.c
 bench_LDADD = ../libevent.la
 bench_cascade_SOURCES = bench_cascade.c
index feae6bbd396a2902ef198971c69a75b9ad7711b8..40a9b2cc29bbaa9b176dc8734e598469c591244d 100644 (file)
@@ -75,7 +75,9 @@
 
 int pair[2];
 int test_ok;
-static int called;
+int called;
+struct event_base *global_base;
+
 static char wbuf[4096];
 static char rbuf[4096];
 static int woff;
@@ -83,7 +85,8 @@ static int roff;
 static int usepersist;
 static struct timeval tset;
 static struct timeval tcalled;
-static struct event_base *global_base;
+
+
 
 #define TEST1  "this is a test"
 #define SECONDS        1
@@ -255,6 +258,8 @@ combined_write_cb(int fd, short event, void *arg)
 static int
 setup_test(const char *name)
 {
+        if (in_legacy_test_wrapper)
+                return 0;
 
        fprintf(stdout, "%s", name);
 
@@ -277,6 +282,9 @@ setup_test(const char *name)
 static int
 cleanup_test(void)
 {
+        if (in_legacy_test_wrapper)
+                return 0;
+
 #ifndef WIN32
        close(pair[0]);
        close(pair[1]);
@@ -491,8 +499,6 @@ test_persistent_timeout(void)
        struct event ev;
        int count = 0;
 
-       setup_test("Periodic timeout via EV_PERSIST: ");
-
        timerclear(&tv);
        tv.tv_usec = 10000;
 
@@ -504,7 +510,6 @@ test_persistent_timeout(void)
 
        event_del(&ev);
 
-       cleanup_test();
 }
 
 #ifndef WIN32
@@ -1077,7 +1082,7 @@ test_nonpersist_readd(void)
        cleanup_test();
 }
 
-/* validates that an evbuffer is good */
+/* Validates that an evbuffer is good. */
 static void
 evbuffer_validate(struct evbuffer *buf)
 {
@@ -1520,7 +1525,7 @@ test_evbuffer_iterative(void)
 }
 
 static void
-test_evbuffer_find(void)
+test_evbuffer_find(void *ptr)
 {
        u_char* p;
        const char* test1 = "1234567890\r\n";
@@ -1531,7 +1536,6 @@ test_evbuffer_find(void)
        struct evbuffer * buf = evbuffer_new();
 
        /* make sure evbuffer_find doesn't match past the end of the buffer */
-       fprintf(stdout, "Testing evbuffer_find 1: ");
        evbuffer_add(buf, (u_char*)test1, strlen(test1));
        evbuffer_validate(buf);
        evbuffer_drain(buf, strlen(test1));
@@ -1539,18 +1543,12 @@ test_evbuffer_find(void)
        evbuffer_add(buf, (u_char*)test2, strlen(test2));
        evbuffer_validate(buf);
        p = evbuffer_find(buf, (u_char*)"\r\n", 2);
-       if (p == NULL) {
-               fprintf(stdout, "OK\n");
-       } else {
-               fprintf(stdout, "FAILED\n");
-               exit(1);
-       }
+        tt_want(p == NULL);
 
        /*
         * drain the buffer and do another find; in r309 this would
         * read past the allocated buffer causing a valgrind error.
         */
-       fprintf(stdout, "Testing evbuffer_find 2: ");
        evbuffer_drain(buf, strlen(test2));
        evbuffer_validate(buf);
        for (i = 0; i < EVBUFFER_INITIAL_LENGTH; ++i)
@@ -1559,24 +1557,16 @@ test_evbuffer_find(void)
        evbuffer_add(buf, (u_char *)test3, EVBUFFER_INITIAL_LENGTH);
        evbuffer_validate(buf);
        p = evbuffer_find(buf, (u_char *)"xy", 2);
-       if (p == NULL) {
-               printf("OK\n");
-       } else {
-               fprintf(stdout, "FAILED\n");
-               exit(1);
-       }
+        tt_want(p == NULL);
 
        /* simple test for match at end of allocated buffer */
-       fprintf(stdout, "Testing evbuffer_find 3: ");
        p = evbuffer_find(buf, (u_char *)"ax", 2);
-       if (p != NULL && strncmp((char*)p, "ax", 2) == 0) {
-               printf("OK\n");
-       } else {
-               fprintf(stdout, "FAILED\n");
-               exit(1);
-       }
+        tt_assert(p != NULL);
+        tt_want(strncmp((char*)p, "ax", 2) == 0);
 
-       evbuffer_free(buf);
+end:
+        if (buf)
+                evbuffer_free(buf);
 }
 
 /*
@@ -1832,14 +1822,12 @@ test_priorities_cb(int fd, short what, void *arg)
 }
 
 static void
-test_priorities(int npriorities)
+test_priorities_impl(int npriorities)
 {
-       char buf[32];
        struct test_pri_event one, two;
        struct timeval tv;
 
-       evutil_snprintf(buf, sizeof(buf), "Testing Priorities %d: ", npriorities);
-       setup_test(buf);
+       TT_BLATHER(("Testing Priorities %d: ", npriorities));
 
        event_base_priority_init(global_base, npriorities);
 
@@ -1881,10 +1869,19 @@ test_priorities(int npriorities)
                if (one.count == 3 && two.count == 0)
                        test_ok = 1;
        }
+}
 
-       cleanup_test();
+static void
+test_priorities(void)
+{
+        test_priorities_impl(1);
+        if (test_ok)
+                test_priorities_impl(2);
+        if (test_ok)
+                test_priorities_impl(3);
 }
 
+
 static void
 test_multiple_cb(int fd, short event, void *arg)
 {
@@ -2282,7 +2279,6 @@ test_evutil_strtoll(void)
 {
         const char *s;
         char *endptr;
-        setup_test("evutil_stroll: ");
         test_ok = 0;
 
         if (evutil_strtoll("5000000000", NULL, 10) != ((ev_int64_t)5000000)*1000)
@@ -2298,142 +2294,139 @@ test_evutil_strtoll(void)
                 goto err;
 
         test_ok = 1;
- err:
-        cleanup_test();
+err:
+        ;
 }
 
 static void
-test_evutil_snprintf(void)
+test_evutil_snprintf(void *ptr)
 {
        char buf[16];
        int r;
-       setup_test("evutil_snprintf: ");
-       test_ok = 0;
        r = evutil_snprintf(buf, sizeof(buf), "%d %d", 50, 100);
-       if (strcmp(buf, "50 100")) {
-               fprintf(stderr, "buf='%s'\n", buf);
-               goto err;
-       }
-       if (r != 6) {
-               fprintf(stderr, "r=%d\n", r);
-               goto err;
-       }
+        tt_str_op(buf, ==, "50 100");
+        tt_int_op(r, ==, 6);
 
        r = evutil_snprintf(buf, sizeof(buf), "longish %d", 1234567890);
-       if (strcmp(buf, "longish 1234567")) {
-               fprintf(stderr, "buf='%s'\n", buf);
-               goto err;
-       }
-       if (r != 18) {
-               fprintf(stderr, "r=%d\n", r);
-               goto err;
-       }
+        tt_str_op(buf, ==, "longish 1234567");
+        tt_int_op(r, ==, 18);
 
-       test_ok = 1;
- err:
-       cleanup_test();
+      end:
+       ;
 }
 
 static void
-test_methods(void)
+test_methods(void *ptr)
 {
        const char **methods = event_get_supported_methods();
-       struct event_config *cfg;
-       struct event_base *base;
+       struct event_config *cfg = NULL;
+       struct event_base *base = NULL;
        const char *backend;
        int n_methods = 0;
 
-       fprintf(stdout, "Testing supported methods: ");
-
-       if (methods == NULL) {
-               fprintf(stdout, "FAILED\n");
-               exit(1);
-       }
+        tt_assert(methods);
 
        backend = methods[0];
        while (*methods != NULL) {
-               fprintf(stdout, "%s ", *methods);
+               TT_BLATHER(("Support method: %s", *methods));
                ++methods;
                ++n_methods;
        }
 
        if (n_methods == 1) {
                /* only one method supported; can't continue. */
-               goto done;
+                goto end;
        }
 
        cfg = event_config_new();
        assert(cfg != NULL);
 
-       assert(event_config_avoid_method(cfg, backend) == 0);
+       tt_int_op(event_config_avoid_method(cfg, backend), ==, 0);
 
        base = event_base_new_with_config(cfg);
-       if (base == NULL) {
-               fprintf(stdout, "FAILED\n");
-               exit(1);
-       }
-
-       if (strcmp(backend, event_base_get_method(base)) == 0) {
-               fprintf(stdout, "FAILED\n");
-               exit(1);
-       }
-
-       event_base_free(base);
-       event_config_free(cfg);
-done:
-       fprintf(stdout, "OK\n");
-}
-
-
-int
-main (int argc, char **argv)
-{
-#ifdef WIN32
-       WORD wVersionRequested;
-       WSADATA wsaData;
-       int     err;
-
-       wVersionRequested = MAKEWORD( 2, 2 );
+        tt_assert(base);
+
+       tt_str_op(backend, !=, event_base_get_method(base));
+
+end:
+        if (base)
+                event_base_free(base);
+        if (cfg)
+                event_config_free(cfg);
+}
+
+/* All the flags we set */
+#define TT_ISOLATED TT_FORK|TT_NEED_SOCKETPAIR|TT_NEED_BASE
+
+struct testcase_t legacy_testcases[] = {
+        /* Some converted-over tests */
+        { "methods", test_methods, TT_FORK, NULL, NULL },
+       { "evutil_snprintf", test_evutil_snprintf, 0, NULL, NULL },
+
+        /* These are still using the old API */
+        LEGACY(evutil_strtoll, 0),
+        LEGACY(persistent_timeout, TT_FORK|TT_NEED_BASE),
+        LEGACY(priorities, TT_FORK|TT_NEED_BASE),
+
+        /* These need to fork because of evbuffer_validate. Otherwise
+         * they'd be fine in the main process, since they don't mess
+         * with global state. */
+        LEGACY(evbuffer, TT_FORK),
+        LEGACY(evbuffer_reference, TT_FORK),
+        LEGACY(evbuffer_iterative, TT_FORK),
+        LEGACY(evbuffer_readln, TT_FORK),
+        { "evbuffer_find", test_evbuffer_find, TT_FORK, NULL, NULL },
+
+        LEGACY(bufferevent, TT_ISOLATED),
+        LEGACY(bufferevent_watermarks, TT_ISOLATED),
+        LEGACY(bufferevent_filters, TT_ISOLATED),
+
+        LEGACY(free_active_base, TT_FORK|TT_NEED_BASE),
+        LEGACY(event_base_new, TT_FORK|TT_NEED_SOCKETPAIR),
+
+        /* These legacy tests may not all need all of these flags. */
+        LEGACY(simpleread, TT_ISOLATED),
+        LEGACY(simpleread_multiple, TT_ISOLATED),
+        LEGACY(simplewrite, TT_ISOLATED),
+        LEGACY(multiple, TT_ISOLATED),
+        LEGACY(persistent, TT_ISOLATED),
+        LEGACY(combined, TT_ISOLATED),
+        LEGACY(simpletimeout, TT_ISOLATED),
+        LEGACY(loopbreak, TT_ISOLATED),
+        LEGACY(loopexit, TT_ISOLATED),
+       LEGACY(loopexit_multiple, TT_ISOLATED),
+       LEGACY(nonpersist_readd, TT_ISOLATED),
+       LEGACY(multiple_events_for_same_fd, TT_ISOLATED),
+       LEGACY(want_only_once, TT_ISOLATED),
 
-       err = WSAStartup( wVersionRequested, &wsaData );
+#ifndef WIN32
+        LEGACY(fork, TT_ISOLATED),
 #endif
 
+        END_OF_TESTCASES
+};
+
+struct testcase_t signal_testcases[] = {
 #ifndef WIN32
-       if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
-               return (1);
+       LEGACY(simplesignal, TT_ISOLATED),
+       LEGACY(multiplesignal, TT_ISOLATED),
+       LEGACY(immediatesignal, TT_ISOLATED),
+        LEGACY(signal_dealloc, TT_ISOLATED),
+       LEGACY(signal_pipeloss, TT_ISOLATED),
+       LEGACY(signal_switchbase, TT_ISOLATED),
+       LEGACY(signal_restore, TT_ISOLATED),
+       LEGACY(signal_assert, TT_ISOLATED),
+       LEGACY(signal_while_processing, TT_ISOLATED),
 #endif
-       setvbuf(stdout, NULL, _IONBF, 0);
-
-       test_methods();
+        END_OF_TESTCASES
+};
 
+int
+legacy_main(void)
+{
        /* Initalize the event library */
        global_base = event_init();
 
-       test_evutil_strtoll();
-       test_evutil_snprintf();
-       util_suite();
-
-       test_persistent_timeout();
-
-       /* use the global event base and need to be called first */
-       test_priorities(1);
-       test_priorities(2);
-       test_priorities(3);
-
-       test_evbuffer();
-       test_evbuffer_reference();
-       test_evbuffer_iterative();
-       test_evbuffer_readln();
-       test_evbuffer_find();
-
-       test_bufferevent();
-       test_bufferevent_watermarks();
-       test_bufferevent_filters();
-
-       test_free_active_base();
-
-       test_event_base_new();
-
 #if defined(_EVENT_HAVE_PTHREADS) && !defined(_EVENT_DISABLE_THREAD_SUPPORT)
        regress_pthread();
 #endif
@@ -2442,32 +2435,6 @@ main (int argc, char **argv)
        regress_zlib();
 #endif
 
-       test_simpleread();
-       test_simpleread_multiple();
-
-       test_simplewrite();
-
-       test_multiple();
-
-       test_persistent();
-
-       test_combined();
-
-       test_simpletimeout();
-
-#ifndef WIN32
-       test_fork();
-#endif
-
-#ifndef WIN32
-       test_edgetriggered();
-       test_simplesignal();
-       test_multiplesignal();
-       test_immediatesignal();
-#endif
-       test_loopexit();
-       test_loopbreak();
-
        http_suite();
 
 #ifndef WIN32
@@ -2476,27 +2443,10 @@ main (int argc, char **argv)
 
        dns_suite();
 
-       test_loopexit_multiple();
-
-        test_nonpersist_readd();
-
-       test_multiple_events_for_same_fd();
-
-       test_want_only_once();
-
        evtag_test();
 
        rpc_test();
 
-#ifndef WIN32
-       test_signal_dealloc();
-       test_signal_pipeloss();
-       test_signal_switchbase();
-       test_signal_restore();
-       test_signal_assert();
-       test_signal_while_processing();
-#endif
-
        return (0);
 }
 
index b5b6f3e5c9a9b25806d915e2b4b8a68e7d1f96c1..db3b04d9ee8562d0518c72819e6f9774d45e2f40 100644 (file)
 extern "C" {
 #endif
 
+#include "tinytest.h"
+#include "tinytest_macros.h"
+
+extern struct testcase_t legacy_testcases[];
+extern struct testcase_t util_testcases[];
+extern struct testcase_t signal_testcases[];
+
+int legacy_main(void);
+
 void http_suite(void);
 void http_basic_test(void);
 
@@ -38,13 +47,30 @@ void rpc_suite(void);
 
 void dns_suite(void);
 
-void util_suite(void);
-
 void regress_pthread(void);
 void regress_zlib(void);
 
 void test_edgetriggered(void);
 
+/* Helpers to wrap old testcases */
+extern int pair[2];
+extern int test_ok;
+extern int called;
+extern struct event_base *global_base;
+extern int in_legacy_test_wrapper;
+
+extern const struct testcase_setup_t legacy_setup;
+void run_legacy_test_fn(void *ptr);
+
+/* A couple of flags that legacy_setup can support. */
+#define TT_NEED_SOCKETPAIR   TT_FIRST_USER_FLAG
+#define TT_NEED_BASE         (TT_FIRST_USER_FLAG<<1)
+
+#define LEGACY(name,flags)                                             \
+       { #name, run_legacy_test_fn, flags, &legacy_setup,              \
+         test_## name }
+
+
 #ifdef __cplusplus
 }
 #endif
index cc637c3f07c21d09c9adad75fc964f07508573d0..ae4e197d0775a65a3b57a2b20e0a131776569dfa 100644 (file)
@@ -22,7 +22,6 @@
 
 #include "regress.h"
 
-static int called = 0;
 static int was_et = 0;
 
 static void
diff --git a/test/regress_main.c b/test/regress_main.c
new file mode 100644 (file)
index 0000000..4c6ed3f
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2003-2007 Niels Provos <provos@citi.umich.edu>
+ * Copyright (c) 2007-2009 Niels Provos and 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 WIN32
+#include <winsock2.h>
+#include <windows.h>
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "event-config.h"
+#endif
+
+#if 0
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef _EVENT_HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#include <sys/queue.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <errno.h>
+#endif
+
+
+#ifndef WIN32
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <unistd.h>
+#include <netdb.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <event2/util.h>
+#include <event2/event.h>
+#include <event2/event_compat.h>
+
+#include "regress.h"
+#include "tinytest.h"
+#include "tinytest_macros.h"
+
+/* ============================================================ */
+/* Code to wrap up old legacy test cases that used setup() and cleanup(). */
+
+/* This is set to true if we're inside a legacy test wrapper.  It lets the
+   setup() and cleanup() functions in regress.c know they're not needed.
+ */
+int in_legacy_test_wrapper = 0;
+
+/* The "data" for a legacy test is just a pointer to the void fn(void)
+   function implementing the test case.  We need to set up some globals,
+   though, since that's where legacy tests expect to find a socketpair
+   (sometimes) and a global event_base (sometimes).
+ */
+static void *
+legacy_test_setup(const struct testcase_t *testcase)
+{
+       if (testcase->flags & TT_NEED_SOCKETPAIR) {
+               if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) {
+                       fprintf(stderr, "%s: socketpair\n", __func__);
+                       exit(1);
+               }
+
+               if (evutil_make_socket_nonblocking(pair[0]) == -1) {
+                       fprintf(stderr, "fcntl(O_NONBLOCK)");
+                       exit(1);
+               }
+
+               if (evutil_make_socket_nonblocking(pair[1]) == -1) {
+                       fprintf(stderr, "fcntl(O_NONBLOCK)");
+                       exit(1);
+               }
+       }
+        if (testcase->flags & TT_NEED_BASE) {
+                global_base = event_init();
+        }
+
+       return testcase->setup_data;
+}
+
+/* This function is the implementation of every legacy test case.  It
+   sets test_ok to 0, invokes the test function, and tells tinytest that
+   the test failed if the test didn't set test_ok to 1.
+ */
+void
+run_legacy_test_fn(void *ptr)
+{
+       void (*fn)(void);
+       test_ok = called = 0;
+       fn = ptr;
+
+        in_legacy_test_wrapper = 1;
+       fn(); /* This part actually calls the test */
+        in_legacy_test_wrapper = 0;
+
+       if (!test_ok)
+               tt_abort_msg("Legacy unit test failed");
+
+end:
+       test_ok = 0;
+}
+
+/* This function doesn't have to clean up ptr (which is just a pointer
+   to the test function), but it may need to close the socketpair or
+   free the event_base.
+ */
+static int
+legacy_test_cleanup(const struct testcase_t *testcase, void *ptr)
+{
+       (void)ptr;
+       if (testcase->flags & TT_NEED_SOCKETPAIR) {
+                if (pair[0] != -1)
+                        EVUTIL_CLOSESOCKET(pair[0]);
+                if (pair[1] != -1)
+                        EVUTIL_CLOSESOCKET(pair[1]);
+                pair[0] = pair[1] = -1;
+        }
+
+        if (testcase->flags & TT_NEED_BASE) {
+                event_base_free(global_base);
+                global_base = NULL;
+        }
+
+
+       return 1;
+}
+
+const struct testcase_setup_t legacy_setup = {
+       legacy_test_setup, legacy_test_cleanup
+};
+
+/* ============================================================ */
+
+
+struct testgroup_t testgroups[] = {
+        { "main/", legacy_testcases },
+        { "signal/", signal_testcases },
+        { "util/", util_testcases },
+        END_OF_GROUPS
+};
+
+int
+main(int argc, const char **argv)
+{
+#ifdef WIN32
+       WORD wVersionRequested;
+       WSADATA wsaData;
+       int     err;
+
+       wVersionRequested = MAKEWORD(2, 2);
+
+       err = WSAStartup(wVersionRequested, &wsaData);
+#endif
+
+#ifndef WIN32
+       if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
+               return 1;
+#endif
+
+        if (tinytest_main(argc,argv,testgroups))
+                return 1;
+
+        in_legacy_test_wrapper = 0;
+        return legacy_main();
+}
+
index ec465a84c4733532c5d6296bf68a36dfbd28df98..2f604100a7ca75855e3dd2c672342a07596bee5d 100644 (file)
@@ -49,7 +49,7 @@
 #include "event2/util.h"
 #include "../ipv6-internal.h"
 
-void util_suite(void);
+#include "regress.h"
 
 enum entry_status { NORMAL, CANONICAL, BAD };
 
@@ -97,12 +97,9 @@ static struct ipv6_entry {
 };
 
 static void
-regress_ipv4_parse(void)
+regress_ipv4_parse(void *ptr)
 {
        int i;
-       int ok = 1;
-       printf("Testing IPv4 parsing...");
-
        for (i = 0; ipv4_entries[i].addr; ++i) {
                char written[128];
                struct ipv4_entry *ent = &ipv4_entries[i];
@@ -111,54 +108,44 @@ regress_ipv4_parse(void)
                r = evutil_inet_pton(AF_INET, ent->addr, &in);
                if (r == 0) {
                        if (ent->status != BAD) {
-                               printf("%s did not parse, but it's a good address!\n",
-                                          ent->addr);
-                               ok = 0;
+                               TT_FAIL(("%s did not parse, but it's a good address!",
+                                       ent->addr));
                        }
                        continue;
                }
                if (ent->status == BAD) {
-                       printf("%s parsed, but we expected an error\n", ent->addr);
-                       ok = 0;
+                       TT_FAIL(("%s parsed, but we expected an error", ent->addr));
                        continue;
                }
                if (ntohl(in.s_addr) != ent->res) {
-                       printf("%s parsed to %lx, but we expected %lx\n", ent->addr,
-                                  (unsigned long)ntohl(in.s_addr),
-                                  (unsigned long)ent->res);
-                       ok = 0;
+                       TT_FAIL(("%s parsed to %lx, but we expected %lx", ent->addr,
+                               (unsigned long)ntohl(in.s_addr),
+                               (unsigned long)ent->res));
                        continue;
                }
                if (ent->status == CANONICAL) {
                        const char *w = evutil_inet_ntop(AF_INET, &in, written,
                                                                                         sizeof(written));
                        if (!w) {
-                               printf("Tried to write out %s; got NULL.\n", ent->addr);
-                               ok = 0;
+                               TT_FAIL(("Tried to write out %s; got NULL.", ent->addr));
                                continue;
                        }
                        if (strcmp(written, ent->addr)) {
-                               printf("Tried to write out %s; got %s\n", ent->addr, written);
-                               ok = 0;
+                               TT_FAIL(("Tried to write out %s; got %s",
+                                       ent->addr, written));
                                continue;
                        }
                }
 
        }
-       if (!ok) {
-               printf("FAILED\n");
-               exit(1);
-       }
-       printf("OK\n");
+
 }
 
 static void
-regress_ipv6_parse(void)
+regress_ipv6_parse(void *ptr)
 {
 #ifdef AF_INET6
        int i, j;
-       int ok = 1;
-       printf("Testing IPv6 parsing...");
 
        for (i = 0; ipv6_entries[i].addr; ++i) {
                char written[128];
@@ -167,16 +154,13 @@ regress_ipv6_parse(void)
                int r;
                r = evutil_inet_pton(AF_INET6, ent->addr, &in6);
                if (r == 0) {
-                       if (ent->status != BAD) {
-                               printf("%s did not parse, but it's a good address!\n",
-                                          ent->addr);
-                               ok = 0;
-                       }
+                       if (ent->status != BAD)
+                               TT_FAIL(("%s did not parse, but it's a good address!",
+                                       ent->addr));
                        continue;
                }
                if (ent->status == BAD) {
-                       printf("%s parsed, but we expected an error\n", ent->addr);
-                       ok = 0;
+                       TT_FAIL(("%s parsed, but we expected an error", ent->addr));
                        continue;
                }
                for (j = 0; j < 4; ++j) {
@@ -187,8 +171,7 @@ regress_ipv6_parse(void)
                                (in6.s6_addr[j*4+2] << 8) |
                                (in6.s6_addr[j*4+3]);
                        if (u != ent->res[j]) {
-                               printf("%s did not parse as expected.\n", ent->addr);
-                               ok = 0;
+                               TT_FAIL(("%s did not parse as expected.", ent->addr));
                                continue;
                        }
                }
@@ -196,25 +179,18 @@ regress_ipv6_parse(void)
                        const char *w = evutil_inet_ntop(AF_INET6, &in6, written,
                                                                                         sizeof(written));
                        if (!w) {
-                               printf("Tried to write out %s; got NULL.\n", ent->addr);
-                               ok = 0;
+                               TT_FAIL(("Tried to write out %s; got NULL.", ent->addr));
                                continue;
                        }
                        if (strcmp(written, ent->addr)) {
-                               printf("Tried to write out %s; got %s\n", ent->addr, written);
-                               ok = 0;
+                               TT_FAIL(("Tried to write out %s; got %s", ent->addr, written));
                                continue;
                        }
                }
 
        }
-       if (!ok) {
-               printf("FAILED\n");
-               exit(1);
-       }
-       printf("OK\n");
 #else
-       print("Skipping IPv6 address parsing.\n");
+       TT_BLATHER(("Skipping IPv6 address parsing."));
 #endif
 }
 
@@ -235,10 +211,9 @@ static struct sa_port_ent {
 };
 
 static void
-regress_sockaddr_port_parse(void)
+regress_sockaddr_port_parse(void *ptr)
 {
        struct sockaddr_storage ss;
-       int ok = 1;
        int i, r;
 
        for (i = 0; sa_port_ents[i].parse; ++i) {
@@ -246,15 +221,11 @@ regress_sockaddr_port_parse(void)
                memset(&ss, 0, sizeof(ss));
                r = evutil_parse_sockaddr_port(ent->parse, (struct sockaddr*)&ss, sizeof(ss));
                if (r < 0) {
-                       if (ent->sa_family) {
-                               printf("Couldn't parse %s!\n", ent->parse);
-                               ok = 0;
-                       }
+                       if (ent->sa_family)
+                               TT_FAIL(("Couldn't parse %s!", ent->parse));
                        continue;
                } else if (! ent->sa_family) {
-                       printf("Shouldn't have been able to parse %s!\n",
-                                  ent->parse);
-                       ok = 0;
+                       TT_FAIL(("Shouldn't have been able to parse %s!", ent->parse));
                        continue;
                }
                if (ent->sa_family == AF_INET) {
@@ -267,11 +238,9 @@ regress_sockaddr_port_parse(void)
                        sin.sin_port = htons(ent->port);
                        r = evutil_inet_pton(AF_INET, ent->addr, &sin.sin_addr);
                        if (1 != r) {
-                               printf("Couldn't parse ipv4 target %s.\n", ent->addr);
-                               ok = 0;
+                               TT_FAIL(("Couldn't parse ipv4 target %s.", ent->addr));
                        } else if (memcmp(&sin, &ss, sizeof(sin))) {
-                               printf("Parse for %s was not as expected.\n", ent->parse);
-                               ok = 0;
+                               TT_FAIL(("Parse for %s was not as expected.", ent->parse));
                        }
                } else {
                        struct sockaddr_in6 sin6;
@@ -283,26 +252,19 @@ regress_sockaddr_port_parse(void)
                        sin6.sin6_port = htons(ent->port);
                        r = evutil_inet_pton(AF_INET6, ent->addr, &sin6.sin6_addr);
                        if (1 != r) {
-                               printf("Couldn't parse ipv6 target %s.\n", ent->addr);
-                               ok = 0;
+                               TT_FAIL(("Couldn't parse ipv6 target %s.", ent->addr));
                        } else if (memcmp(&sin6, &ss, sizeof(sin6))) {
-                               printf("Parse for %s was not as expected.\n", ent->parse);
-                               ok = 0;
+                               TT_FAIL(("Parse for %s was not as expected.", ent->parse));
                        }
                }
        }
-
-       if (!ok) {
-               printf("FAILED\n");
-               exit(1);
-       }
-       printf("OK\n");
 }
 
-void
-util_suite(void)
-{
-       regress_ipv4_parse();
-       regress_ipv6_parse();
-       regress_sockaddr_port_parse();
-}
+struct testcase_t util_testcases[] = {
+       { "ipv4_parse", regress_ipv4_parse, 0, NULL, NULL },
+       { "ipv6_parse", regress_ipv6_parse, 0, NULL, NULL },
+       { "sockaddr_port_parse", regress_sockaddr_port_parse, 0, NULL, NULL },
+       END_OF_TESTCASES,
+};
+
+
diff --git a/test/tinytest.c b/test/tinytest.c
new file mode 100644 (file)
index 0000000..cfe3419
--- /dev/null
@@ -0,0 +1,314 @@
+/* tinytest.c -- Copyright 2009 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#ifdef WIN32
+#include <windows.h>
+#else
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#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 opt_forked = 0; /**< True iff we're called from inside a win32 fork*/
+static int opt_verbosity = 1; /**< 0==quiet,1==normal,2==verbose */
+
+static int cur_test_outcome = 0; /**< True iff the current test has failed. */
+const char *cur_test_prefix = NULL; /**< prefix of the current test group */
+/** Name of the  current test, if we haven't logged is yet. Used for --quiet */
+const char *cur_test_name = NULL;
+
+#ifdef WIN32
+/** Pointer to argv[0] for win32. */
+static const char *commandname = NULL;
+#endif
+
+static int
+_testcase_run_bare(const struct testcase_t *testcase)
+{
+       void *env = NULL;
+       int outcome;
+       if (testcase->setup) {
+               env = testcase->setup->setup_fn(testcase);
+               assert(env);
+       }
+
+       cur_test_outcome = 1;
+       testcase->fn(env);
+       outcome = cur_test_outcome;
+
+       if (testcase->setup) {
+               if (testcase->setup->cleanup_fn(testcase, env) == 0)
+                       outcome = 0;
+       }
+
+       return outcome;
+}
+
+static int
+_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];
+       const char *verbosity;
+       STARTUPINFO 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)
+               printf("[forking] ");
+
+       verbosity = (opt_verbosity == 2) ? "--verbose" :
+               (opt_verbosity == 0) ? "--quiet" : "";
+       snprintf(buffer, sizeof(buffer), "%s --RUNNING-FORKED %s %s%s",
+                commandname, verbosity, group->prefix, testcase->name);
+
+       memset(&si, 0, sizeof(si));
+       memset(&info, 0, sizeof(info));
+       si.cb = sizeof(si);
+
+       ok = CreateProcess(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);
+       return exitcode == 0;
+#else
+       int outcome_pipe[2];
+       pid_t pid;
+        (void)group;
+
+       if (pipe(outcome_pipe))
+               perror("opening pipe");
+
+       if (opt_verbosity)
+               printf("[forking] ");
+       pid = fork();
+       if (!pid) {
+               /* child. */
+               int test_r, write_r;
+               close(outcome_pipe[0]);
+               test_r = _testcase_run_bare(testcase);
+               write_r = write(outcome_pipe[1], test_r ? "Y" : "N", 1);
+               if (write_r != 1) {
+                       perror("write outcome to pipe");
+                       exit(1);
+               }
+               exit(0);
+       } 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 = read(outcome_pipe[0], b, 1);
+               if (r == 0) {
+                       printf("[Lost connection!] ");
+                       return 0;
+               } else if (r != 1) {
+                       perror("read outcome from pipe");
+               }
+               waitpid(pid, &status, 0);
+               close(outcome_pipe[0]);
+               return b[0] == 'Y' ? 1 : 0;
+       }
+#endif
+}
+
+int
+testcase_run_one(const struct testgroup_t *group,
+                const struct testcase_t *testcase)
+{
+       int outcome;
+
+       if (testcase->flags & TT_SKIP) {
+               if (opt_verbosity)
+                       printf("%s%s... SKIPPED\n",
+                           group->prefix, testcase->name);
+               return 1;
+       }
+
+       if (opt_verbosity && !opt_forked)
+               printf("%s%s... ", group->prefix, testcase->name);
+       else {
+               cur_test_prefix = group->prefix;
+               cur_test_name = testcase->name;
+       }
+
+       if ((testcase->flags & TT_FORK) && !opt_forked) {
+               outcome = _testcase_run_forked(group, testcase);
+       } else {
+               outcome  = _testcase_run_bare(testcase);
+       }
+
+       if (outcome) {
+               ++n_ok;
+               if (opt_verbosity && !opt_forked)
+                       puts(opt_verbosity==1?"OK":"");
+       } else {
+               ++n_bad;
+               if (!opt_forked)
+                       printf("\n  [%s FAILED]\n", testcase->name);
+       }
+
+       if (opt_forked) {
+               exit(outcome ? 0 : 1);
+       } else {
+               return outcome;
+       }
+}
+
+int
+_tinytest_set_flag(struct testgroup_t *groups, const char *arg, unsigned long flag)
+{
+       int i, j;
+       int 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) {
+                       snprintf(fullname, sizeof(fullname), "%s%s",
+                                groups[i].prefix, groups[i].cases[j].name);
+                       if (!flag) /* Hack! */
+                               printf("    %s\n", fullname);
+                       if (!strncmp(fullname, arg, length)) {
+                               groups[i].cases[j].flags |= flag;
+                               ++found;
+                       }
+               }
+       }
+       return found;
+}
+
+static void
+usage(struct testgroup_t *groups)
+{
+       puts("Options are: --verbose --quiet");
+       puts("Known tests are:");
+       _tinytest_set_flag(groups, "..", 0);
+       exit(0);
+}
+
+int
+tinytest_main(int c, const char **v, struct testgroup_t *groups)
+{
+       int i, j, n=0;
+
+#ifdef WIN32
+       commandname = v[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], "--quiet"))
+                               opt_verbosity = 0;
+                       else if (!strcmp(v[i], "--verbose"))
+                               opt_verbosity = 2;
+                       else if (!strcmp(v[i], "--help"))
+                               usage(groups);
+                       else {
+                               printf("Unknown option %s.  Try --help\n",v[i]);
+                               return -1;
+                       }
+               } else {
+                       ++n;
+                       if (!_tinytest_set_flag(groups, v[i], _TT_ENABLED)) {
+                               printf("No such test as %s!\n", v[i]);
+                               return -1;
+                       }
+               }
+       }
+       if (!n)
+               _tinytest_set_flag(groups, "...", _TT_ENABLED);
+
+       setvbuf(stdout, NULL, _IONBF, 0);
+
+       ++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 (n_bad)
+               printf("%d TESTS FAILED.\n", n_bad);
+       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) {
+               printf("%s%s... ", cur_test_prefix, cur_test_name);
+               cur_test_name = NULL;
+       }
+       cur_test_outcome = 0;
+}
+
diff --git a/test/tinytest.h b/test/tinytest.h
new file mode 100644 (file)
index 0000000..f14acc8
--- /dev/null
@@ -0,0 +1,85 @@
+/* tinytest.h -- Copyright 2009 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.
+ */
+
+#ifndef _TINYTEST_H
+#define _TINYTEST_H
+
+/** Flag for a test that needs to run in a subprocess. */
+#define TT_FORK  (1<<0)
+/** Runtime flag for a test we've decided to skip. */
+#define TT_SKIP  (1<<1)
+/** Internal runtime flag for a test we've decided to run. */
+#define _TT_ENABLED  (1<<2)
+/** If you add your own flags, make them start at this point. */
+#define TT_FIRST_USER_FLAG (1<<3)
+
+typedef void (*testcase_fn)(void *);
+
+struct testcase_t;
+
+/** Functions to initialize/teardown a structure for a testcase. */
+struct testcase_setup_t {
+       /** Return a new structure for use by a given testcase. */
+       void *(*setup_fn)(const struct testcase_t *);
+       /** Clean/free a structure from setup_fn. Return 1 if ok, 0 on err. */
+       int (*cleanup_fn)(const struct testcase_t *, void *);
+};
+
+/** A single test-case that you can run. */
+struct testcase_t {
+       const char *name; /**< An identifier for this case. */
+       testcase_fn fn; /**< The function to run to implement this case. */
+       unsigned long flags; /**< Bitfield of TT_* flags. */
+       const struct testcase_setup_t *setup; /**< Optional setup/cleanup fns*/
+       void *setup_data; /**< Extra data usable by setup function */
+};
+#define END_OF_TESTCASES { NULL, NULL, 0, NULL, NULL }
+
+/** A group of tests that are selectable together. */
+struct testgroup_t {
+       const char *prefix; /**< Prefix to prepend to testnames. */
+       struct testcase_t *cases; /** Array, ending with END_OF_TESTCASES */
+};
+#define END_OF_GROUPS { NULL, NULL}
+
+/** Implementation: called from a test to indicate failure, before logging. */
+void _tinytest_set_test_failed(void);
+/** Implementation: return 0 for quiet, 1 for normal, 2 for loud. */
+int _tinytest_get_verbosity(void);
+/** Implementation: Set a flag on tests matching a name; returns number
+ * of tests that matched. */
+int _tinytest_set_flag(struct testgroup_t *, const char *, unsigned long);
+
+/** Set all tests in 'groups' matching the name 'named' to be skipped. */
+#define tinytest_skip(groups, named) \
+       _tinytest_set_flag(groups, named, TT_SKIP)
+
+/** Run a single testcase in a single group. */
+int testcase_run_one(const struct testgroup_t *,const struct testcase_t *);
+/** Run a set of testcases from an END_OF_GROUPS-terminated array of groups,
+    as selected from the command line. */
+int tinytest_main(int argc, const char **argv, struct testgroup_t *groups);
+
+#endif
diff --git a/test/tinytest_demo.c b/test/tinytest_demo.c
new file mode 100644 (file)
index 0000000..ef5803d
--- /dev/null
@@ -0,0 +1,215 @@
+/* tinytest_demo.c -- Copyright 2009 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.
+ */
+
+
+/* Welcome to the example file for tinytest!  I'll show you how to set up
+ * some simple and not-so-simple testcases. */
+
+/* Make sure you include these headers. */
+#include "tinytest.h"
+#include "tinytest_macros.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+/* ============================================================ */
+
+/* First, let's see if strcmp is working.  (All your test cases should be
+ * functions declared to take a single void * as) an argument. */
+void
+test_strcmp(void *data)
+{
+       (void)data; /* This testcase takes no data. */
+
+       /* Let's make sure the empty string is equal to itself */
+       if (strcmp("","")) {
+               /* This macro tells tinytest to stop the current test
+                * and go straight to the "end" label. */
+               tt_abort_msg("The empty string was not equal to itself");
+       }
+
+       /* Pretty often, calling tt_abort_msg to indicate failure is more
+          heavy-weight than you want.  Instead, just say: */
+       tt_assert(strcmp("testcase", "testcase") == 0);
+
+       /* Occasionally, you don't want to stop the current testcase just
+          because a single assertion has failed.  In that case, use
+          tt_want: */
+       tt_want(strcmp("tinytest", "testcase") > 0);
+
+       /* You can use the tt_*_op family of macros to compare values and to
+          fail unless they have the relationship you want.  They produce
+          more useful output than tt_assert, since they display the actual
+          values of the failing things.
+
+          Fail unless strcmp("abc, "abc") == 0 */
+       tt_int_op(strcmp("abc", "abc"), ==, 0);
+
+       /* Fail unless strcmp("abc, "abcd") is less than 0 */
+       tt_int_op(strcmp("abc", "abcd"), < , 0);
+
+       /* Incidentally, there's a test_str_op that uses strcmp internally. */
+       tt_str_op("abc", <, "abcd");
+
+
+       /* Every test-case function needs to finish with an "end:"
+          label and (optionally) code to clean up local variables. */
+ end:
+       ;
+}
+
+/* ============================================================ */
+
+/* Now let's mess with setup and teardown functions!  These are handy if
+   you have a bunch of tests that all need a similar environment, and you
+   wnat to reconstruct that environment freshly for each one. */
+
+/* First you declare a type to hold the environment info, and functions to
+   set it up and tear it down. */
+struct data_buffer {
+       /* We're just going to have couple of character buffer.  Using
+          setup/teardown functions is probably overkill for this case.
+
+          You could also do file descriptors, complicated handles, temporary
+          files, etc. */
+       char buffer1[512];
+       char buffer2[512];
+};
+/* The setup function needs to take a const struct testcase_t and return
+   void* */
+void *
+setup_data_buffer(const struct testcase_t *testcase)
+{
+       struct data_buffer *db = malloc(sizeof(struct data_buffer));
+
+       /* If you had a complicated set of setup rules, you might behave
+          differently here depending on testcase->flags or
+          testcase->setup_data or even or testcase->name. */
+
+       /* Returning a NULL here would mean that we couldn't set up for this
+          test, so we don't need to test db for null. */
+       return db;
+}
+/* The clean function deallocates storage carefully and returns true on
+   success. */
+int
+clean_data_buffer(const struct testcase_t *testcase, void *ptr)
+{
+       struct data_buffer *db = ptr;
+
+       if (db) {
+               free(db);
+               return 1;
+       }
+       return 0;
+}
+/* Finally, declare a testcase_setup_t with these functions. */
+struct testcase_setup_t data_buffer_setup = {
+       setup_data_buffer, clean_data_buffer
+};
+
+
+/* Now let's write our test. */
+void
+test_memcpy(void *ptr)
+{
+       /* This time, we use the argument. */
+       struct data_buffer *db = ptr;
+
+       /* We'll also introduce a local variable that might need cleaning up. */
+       char *mem = NULL;
+
+       /* Let's make sure that memcpy does what we'd like. */
+       strcpy(db->buffer1, "String 0");
+       memcpy(db->buffer2, db->buffer1, sizeof(db->buffer1));
+       tt_str_op(db->buffer1, ==, db->buffer2);
+
+       /* Now we've allocated memory that's referenced by a local variable.
+          The end block of the function will clean it up. */
+       mem = strdup("Hello world.");
+       tt_assert(mem);
+
+       /* Another rather trivial test. */
+       tt_str_op(db->buffer1, !=, mem);
+
+ end:
+       /* This time our end block has something to do. */
+       if (mem)
+               free(mem);
+}
+
+/* ============================================================ */
+
+/* Now we need to make sure that our tests get invoked.   First, you take
+   a bunch of related tests and put them into an array of struct testcase_t.
+*/
+
+struct testcase_t demo_tests[] = {
+       /* Here's a really simple test: it has a name you can refer to it
+          with, and a function to invoke it. */
+       { "strcmp", test_strcmp, },
+
+       /* The second test has a flag, "TT_FORK", to make it run in a
+          subprocess, and a pointer to the testcase_setup_t that configures
+          its environment. */
+       { "memcpy", test_memcpy, TT_FORK, &data_buffer_setup },
+
+       /* The array has to end with END_OF_TESTCASES. */
+       END_OF_TESTCASES
+};
+
+/* Next, we make an array of testgroups.  This is mandatory.  Unlike more
+   heavy-duty testing frameworks, groups can't next. */
+struct testgroup_t groups[] = {
+
+       /* Every group has a 'prefix', and an array of tests.  That's it. */
+       { "demo/", demo_tests },
+
+        END_OF_GROUPS
+};
+
+
+int
+main(int c, const char **v)
+{
+       /* Finally, just call tinytest_main().  It lets you specify verbose
+          or quiet output with --verbose and --quiet.  You can list
+          specific tests:
+
+              tinytest-demo demo/memcpy
+
+          or use a ..-wildcard to select multiple tests with a common
+          prefix:
+
+              tinytest-demo demo/..
+
+          If you list no tests, you get them all by default, so that
+          "tinytest-demo" and "tinytest-demo .." mean the same thing.
+
+       */
+       return tinytest_main(c, v, groups);
+}
diff --git a/test/tinytest_macros.h b/test/tinytest_macros.h
new file mode 100644 (file)
index 0000000..d56fa75
--- /dev/null
@@ -0,0 +1,137 @@
+/* tinytest_macros.h -- Copyright 2009 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.
+ */
+
+#ifndef _TINYTEST_MACROS_H
+#define _TINYTEST_MACROS_H
+
+/* Helpers for defining statement-like macros */
+#define TT_STMT_BEGIN do {
+#define TT_STMT_END } while(0)
+
+/* Redefine this if your test functions want to abort with something besides
+ * "goto end;" */
+#ifndef TT_EXIT_TEST_FUNCTION
+#define TT_EXIT_TEST_FUNCTION TT_STMT_BEGIN goto end; TT_STMT_END
+#endif
+
+/* Redefine this if you want to note success/failure in some different way. */
+#ifndef TT_DECLARE
+#define TT_DECLARE(prefix, args)                               \
+       TT_STMT_BEGIN                                           \
+       printf("\n  %s %s:%d: ",prefix,__FILE__,__LINE__);      \
+       printf args ;                                           \
+       TT_STMT_END
+#endif
+
+/* Announce a failure.  Args are parenthesized printf args. */
+#define TT_GRIPE(args) TT_DECLARE("FAIL", args)
+
+/* Announce a non-failure if we're verbose. */
+#define TT_BLATHER(args)                                               \
+       TT_STMT_BEGIN                                                   \
+       if (_tinytest_get_verbosity()>1) TT_DECLARE("  OK", args);      \
+       TT_STMT_END
+
+#define TT_DIE(args)                                           \
+       TT_STMT_BEGIN                                           \
+       _tinytest_set_test_failed();                            \
+       TT_GRIPE(args);                                         \
+       TT_EXIT_TEST_FUNCTION;                                  \
+       TT_STMT_END
+
+#define TT_FAIL(args)                          \
+       TT_STMT_BEGIN                                           \
+       _tinytest_set_test_failed();                            \
+       TT_GRIPE(args);                                         \
+       TT_STMT_END
+
+/* Fail and abort the current test for the reason in msg */
+#define tt_abort_msg(msg) TT_DIE((msg))
+#define tt_abort() tt_fail_msg("(Failed.)")
+
+/* Fail but do not abort the current test for the reason in msg. */
+#define tt_fail_msg(msg) TT_FAIL((msg))
+#define tt_fail() tt_fail_msg("(Failed.)")
+
+#define _tt_want(b, msg, fail)                         \
+       TT_STMT_BEGIN                                   \
+       if (!(b)) {                                     \
+               _tinytest_set_test_failed();            \
+               TT_GRIPE((msg));                        \
+               fail;                                   \
+       } else {                                        \
+               TT_BLATHER((msg));                      \
+       }                                               \
+       TT_STMT_END
+
+/* Assert b, but do not stop the test if b fails.  Log msg on failure. */
+#define tt_want_msg(b, msg)                    \
+       _tt_want(b, msg, );
+
+/* Assert b and stop the test if b fails.  Log msg on failure. */
+#define tt_assert_msg(b, msg)                  \
+       _tt_want(b, msg, TT_EXIT_TEST_FUNCTION);
+
+/* Assert b, but do not stop the test if b fails. */
+#define tt_want(b)   tt_want_msg( (b), "want("#b")")
+/* Assert b, and stop the test if b fails. */
+#define tt_assert(b) tt_assert_msg((b), "assert("#b")")
+
+#define tt_assert_test_type(a,b,str_test,type,test,fmt)                        \
+       TT_STMT_BEGIN                                                   \
+       type _val1 = (type)(a);                                         \
+       type _val2 = (type)(b);                                         \
+       if (!(test)) {                                                  \
+               TT_DIE(("assert(%s): "fmt" vs "fmt,                     \
+                       str_test, _val1, _val2));                       \
+       } else {                                                        \
+               TT_BLATHER(("assert(%s): "fmt" vs "fmt,                 \
+                           str_test, _val1, _val2));                   \
+       }                                                               \
+       TT_STMT_END
+
+/* Helper: assert that a op b, when cast to type.  Format the values with
+ * printf format fmt on failure. */
+#define tt_assert_op_type(a,op,b,type,fmt)                             \
+       tt_assert_test_type(a,b,#a" "#op" "#b,type,(_val1 op _val2),fmt)
+
+#define tt_int_op(a,op,b)                      \
+       tt_assert_test_type(a,b,#a" "#op" "#b,long,(_val1 op _val2),"%ld")
+
+#define tt_uint_op(a,op,b)                                             \
+       tt_assert_test_type(a,b,#a" "#op" "#b,unsigned long,            \
+                           (_val1 op _val2),"%lu")
+
+#define tt_str_op(a,op,b)                                              \
+       tt_assert_test_type(a,b,#a" "#op" "#b,const char *,             \
+                           (strcmp(_val1,_val2) op 0),"<%s>")
+
+/** Fail and log the errno as with perror. */
+#define tt_fail_perror(op)                                     \
+       TT_STMT_BEGIN                                           \
+       TT_DIE(("%s: %s [%d]",(op),strerror(errno),errno));     \
+       TT_STMT_END
+
+#endif