[or-cvs] [tor/master] Refactor unit tests to use the tinytest framework.

Nick Mathewson nickm at seul.org
Wed Sep 23 04:30:24 UTC 2009


Author: Nick Mathewson <nickm at torproject.org>
Date: Mon, 21 Sep 2009 14:23:13 -0400
Subject: Refactor unit tests to use the tinytest framework.
Commit: d4b54549b83bbbfeb9c1d7d843ea244eed389c61

"Tinytest" is a minimalist C unit testing framework I wrote for
Libevent.  It supports some generally useful features, like being able
to run separate unit tests in their own processes.

I tried to do the refactoring to change test.c as little as possible.
Thus, we mostly don't call the tinytest macros directly.  Instead, the
test.h header is now a wrapper on tinytest.h to make our existing
test_foo() macros work.

The next step(s) here will be:
  - To break test.c into separate files, each with its own test group.
  - To look into which things we can test
  - To refactor the more fiddly tests to use the tinytest macros
    directly and/or run forked.
  - To see about writing unit tests for things we couldn't previously
    test without forking.
---
 Makefile.am              |    2 +-
 src/common/Makefile.am   |    2 +-
 src/common/test.h        |  184 -----------------------
 src/or/Makefile.am       |    7 +-
 src/or/test.c            |  202 +++++++++----------------
 src/or/test.h            |   66 ++++++++
 src/or/tinytest.c        |  369 ++++++++++++++++++++++++++++++++++++++++++++++
 src/or/tinytest.h        |   87 +++++++++++
 src/or/tinytest_demo.c   |  215 +++++++++++++++++++++++++++
 src/or/tinytest_macros.h |  167 +++++++++++++++++++++
 10 files changed, 983 insertions(+), 318 deletions(-)
 delete mode 100644 src/common/test.h
 create mode 100644 src/or/test.h
 create mode 100644 src/or/tinytest.c
 create mode 100644 src/or/tinytest.h
 create mode 100644 src/or/tinytest_demo.c
 create mode 100644 src/or/tinytest_macros.h

diff --git a/Makefile.am b/Makefile.am
index aa679e5..4e049a0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -66,7 +66,7 @@ check-spaces:
 	./contrib/checkSpace.pl -C                    \
 	        src/common/*.h                        \
 		src/common/[^asO]*.c src/common/address.c \
-		src/or/[^et]*.[ch] src/or/t*.c src/or/eventdns_tor.h
+		src/or/[^et]*.[ch] src/or/t[^i]*.c src/or/eventdns_tor.h
 
 check-docs:
 	./contrib/checkOptionDocs.pl
diff --git a/src/common/Makefile.am b/src/common/Makefile.am
index 9601384..d57593d 100644
--- a/src/common/Makefile.am
+++ b/src/common/Makefile.am
@@ -16,7 +16,7 @@ libor_a_SOURCES = address.c log.c util.c compat.c container.c mempool.c \
 libor_crypto_a_SOURCES = crypto.c aes.c tortls.c torgzip.c
 libor_event_a_SOURCES = compat_libevent.c
 
-noinst_HEADERS = address.h log.h crypto.h test.h util.h compat.h aes.h torint.h tortls.h strlcpy.c strlcat.c torgzip.h container.h ht.h mempool.h memarea.h ciphers.inc compat_libevent.h
+noinst_HEADERS = address.h log.h crypto.h util.h compat.h aes.h torint.h tortls.h strlcpy.c strlcat.c torgzip.h container.h ht.h mempool.h memarea.h ciphers.inc compat_libevent.h
 
 common_sha1.i: $(libor_SOURCES) $(libor_crypto_a_SOURCES) $(noinst_HEADERS)
 	if test "@SHA1SUM@" != none; then \
diff --git a/src/common/test.h b/src/common/test.h
deleted file mode 100644
index 52a249c..0000000
--- a/src/common/test.h
+++ /dev/null
@@ -1,184 +0,0 @@
-/* Copyright (c) 2001-2003, Roger Dingledine.
- * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2009, The Tor Project, Inc. */
-/* See LICENSE for licensing information */
-
-#ifndef _TOR_TEST_H
-#define _TOR_TEST_H
-
-/**
- * \file test.h
- * \brief Macros used by unit tests.
- */
-
-#include "compat.h"
-
-#ifdef __GNUC__
-#define PRETTY_FUNCTION __PRETTY_FUNCTION__
-#else
-#define PRETTY_FUNCTION ""
-#endif
-
-#define test_fail_msg(msg)                                      \
-  STMT_BEGIN                                                    \
-    have_failed = 1;                                            \
-    printf("\nFile %s: line %d (%s): %s",                       \
-      _SHORT_FILE_,                                             \
-      __LINE__,                                                 \
-      PRETTY_FUNCTION,                                          \
-      msg);                                                     \
-    goto done;                                                  \
-  STMT_END
-
-#define test_fail() test_fail_msg("Assertion failed.")
-
-#define test_assert(expr)                                       \
-  STMT_BEGIN                                                    \
-  if (expr) { printf("."); fflush(stdout); } else {             \
-    have_failed = 1;                                            \
-    printf("\nFile %s: line %d (%s): assertion failed: (%s)\n", \
-      _SHORT_FILE_,                                             \
-      __LINE__,                                                 \
-      PRETTY_FUNCTION,                                          \
-      #expr);                                                   \
-    goto done;                                                  \
-  } STMT_END
-
-#define test_eq_type(tp, fmt, expr1, expr2) \
-  STMT_BEGIN                                                            \
-  tp _test_v1=(tp)(expr1);                                              \
-  tp _test_v2=(tp)(expr2);                                              \
-  if (_test_v1==_test_v2) { printf("."); fflush(stdout); } else {       \
-    have_failed = 1;                                                    \
-    printf("\nFile %s: line %d (%s): Assertion failed: (%s==%s)\n"      \
-           "      "fmt "!="fmt"\n",                                     \
-             _SHORT_FILE_,                                              \
-             __LINE__,                                                  \
-             PRETTY_FUNCTION,                                           \
-             #expr1, #expr2,                                            \
-           _test_v1, _test_v2);                                         \
-    goto done;                                                          \
-  } STMT_END
-
-#define test_eq(expr1, expr2)                   \
-  test_eq_type(long, "%ld", expr1, expr2)
-
-#define test_eq_ptr(expr1, expr2)               \
-  test_eq_type(void*, "%p", expr1, expr2)
-
-#define test_neq_type(tp, fmt, expr1, expr2)                            \
-  STMT_BEGIN                                                            \
-  tp _test_v1=(tp)(expr1);                                              \
-  tp _test_v2=(tp)(expr2);                                              \
-  if (_test_v1!=_test_v2) { printf("."); fflush(stdout); } else {       \
-    have_failed = 1;                                                    \
-    printf("\nFile %s: line %d (%s): Assertion failed: (%s!=%s)\n"      \
-           "      ("fmt" == "fmt")\n",                                  \
-           _SHORT_FILE_,                                                \
-           __LINE__,                                                    \
-           PRETTY_FUNCTION,                                             \
-           #expr1, #expr2,                                              \
-           _test_v1, _test_v2);                                         \
-    goto done;                                                          \
-  } STMT_END
-
-#define test_neq(expr1, expr2)                  \
-  test_neq_type(long, "%ld", expr1, expr2)
-
-#define test_neq_ptr(expr1, expr2)              \
-  test_neq_type(void *, "%p", expr1, expr2)
-
-#define test_streq(expr1, expr2)                                \
-  STMT_BEGIN                                                    \
-    const char *_test_v1=(expr1), *_test_v2=(expr2);                        \
-    if (!strcmp(_test_v1,_test_v2)) { printf("."); fflush(stdout); } else { \
-    have_failed = 1;                                            \
-    printf("\nFile %s: line %d (%s): Assertion failed: (%s==%s)\n"\
-           "      (\"%s\" != \"%s\")\n",                        \
-      _SHORT_FILE_,                                             \
-      __LINE__,                                                 \
-      PRETTY_FUNCTION,                                          \
-      #expr1, #expr2,                                           \
-      _test_v1, _test_v2);                                      \
-    goto done;                                                  \
-  } STMT_END
-
-#define test_strneq(expr1, expr2)                               \
-  STMT_BEGIN                                                    \
-    const char *_test_v1=(expr1), *_test_v2=(expr2);                        \
-    if (strcmp(_test_v1,_test_v2)) { printf("."); fflush(stdout); } else {  \
-    have_failed = 1;                                            \
-    printf("\nFile %s: line %d (%s): Assertion failed: (%s!=%s)\n"\
-           "      (\"%s\" == \"%s\")\n",                        \
-      _SHORT_FILE_,                                             \
-      __LINE__,                                                 \
-      PRETTY_FUNCTION,                                          \
-      #expr1, #expr2,                                           \
-      _test_v1, _test_v2);                                      \
-    goto done;                                                  \
-  } STMT_END
-
-#define test_memeq(expr1, expr2, len)                           \
-  STMT_BEGIN                                                    \
-    const void *_test_v1=(expr1), *_test_v2=(expr2);            \
-    char *mem1, *mem2;                                          \
-    if (!memcmp(_test_v1,_test_v2,(len))) {                     \
-      printf("."); fflush(stdout); } else {                     \
-    have_failed = 1;                                            \
-    mem1 = tor_malloc(len*2+1);                                    \
-    mem2 = tor_malloc(len*2+1);                                    \
-    base16_encode(mem1, len*2+1, _test_v1, len);                   \
-    base16_encode(mem2, len*2+1, _test_v2, len);                   \
-    printf("\nFile %s: line %d (%s): Assertion failed: (%s==%s)\n" \
-           "      %s != %s\n",                                     \
-      _SHORT_FILE_,                                             \
-      __LINE__,                                                 \
-      PRETTY_FUNCTION,                                          \
-      #expr1, #expr2, mem1, mem2);                              \
-    tor_free(mem1);                                             \
-    tor_free(mem2);                                             \
-    goto done;                                                  \
-  } STMT_END
-
-#define test_memeq_hex(expr1, hex)                                      \
-  STMT_BEGIN                                                            \
-    const char *_test_v1 = (char*)(expr1);                              \
-    const char *_test_v2 = (hex);                                       \
-    size_t _len_v2 = strlen(_test_v2);                                  \
-    char *_mem2 = tor_malloc(_len_v2/2);                                \
-    tor_assert((_len_v2 & 1) == 0);                                     \
-    base16_decode(_mem2, _len_v2/2, _test_v2, _len_v2);                 \
-    if (!memcmp(_mem2, _test_v1, _len_v2/2)) {                          \
-      printf("."); fflush(stdout); } else {                             \
-      char *_mem1 = tor_malloc(_len_v2+1);                              \
-      base16_encode(_mem1, _len_v2+1, _test_v1, _len_v2/2);             \
-      printf("\nFile %s: line %d (%s): Assertion failed: (%s==%s)\n"    \
-             "      %s != %s\n",                                        \
-             _SHORT_FILE_,                                              \
-             __LINE__,                                                  \
-             PRETTY_FUNCTION,                                           \
-             #expr1, _test_v2, _mem1, _test_v2);                        \
-      tor_free(_mem1);                                                  \
-      tor_free(_mem2);                                                  \
-      goto done;                                                        \
-    }                                                                   \
-    tor_free(_mem2);                                                    \
-  STMT_END
-
-#define test_memneq(expr1, expr2, len)                          \
-  STMT_BEGIN                                                    \
-   void *_test_v1=(expr1), *_test_v2=(expr2);                   \
-   if (memcmp(_test_v1,_test_v2,(len))) {                       \
-     printf("."); fflush(stdout);                               \
-   } else {                                                     \
-    have_failed = 1;                                            \
-    printf("\nFile %s: line %d (%s): Assertion failed: (%s!=%s)\n", \
-      _SHORT_FILE_,                                             \
-      __LINE__,                                                 \
-      PRETTY_FUNCTION,                                          \
-      #expr1, #expr2);                                          \
-    goto done;                                                  \
-  } STMT_END
-
-#endif
-
diff --git a/src/or/Makefile.am b/src/or/Makefile.am
index e9916d5..04a66d0 100644
--- a/src/or/Makefile.am
+++ b/src/or/Makefile.am
@@ -42,7 +42,7 @@ tor_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ @TOR_LDFLAGS_libevent@
 tor_LDADD = ../common/libor.a ../common/libor-crypto.a \
 	../common/libor-event.a \
 	-lz -lm -levent -lssl -lcrypto @TOR_LIB_WS32@ @TOR_LIB_GDI@
-test_SOURCES = $(COMMON_SRC) test_data.c test.c
+test_SOURCES = $(COMMON_SRC) test_data.c test.c tinytest.c
 
 test_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ \
         @TOR_LDFLAGS_libevent@
@@ -50,7 +50,10 @@ test_LDADD = ../common/libor.a ../common/libor-crypto.a \
 	../common/libor-event.a \
 	-lz -lm -levent -lssl -lcrypto @TOR_LIB_WS32@ @TOR_LIB_GDI@
 
-noinst_HEADERS = or.h eventdns.h eventdns_tor.h micro-revision.i
+noinst_HEADERS = or.h eventdns.h eventdns_tor.h micro-revision.i tinytest.h \
+	tinytest_macros.h
+
+EXTRA_DIST = tinytest_demo.c
 
 config_codedigest.o: or_sha1.i
 
diff --git a/src/or/test.c b/src/or/test.c
index 9bb54fa..1af037e 100644
--- a/src/or/test.c
+++ b/src/or/test.c
@@ -1376,50 +1376,28 @@ test_util(void)
   ;
 }
 
-/** Helper: assert that IPv6 addresses <b>a</b> and <b>b</b> are the same.  On
- * failure, reports an error, describing the addresses as <b>e1</b> and
- * <b>e2</b>, and reporting the line number as <b>line</b>. */
-static void
-_test_eq_ip6(struct in6_addr *a, struct in6_addr *b, const char *e1,
-             const char *e2, int line)
-{
-  int i;
-  int ok = 1;
-  for (i = 0; i < 16; ++i) {
-    if (a->s6_addr[i] != b->s6_addr[i]) {
-      ok = 0;
-      break;
-    }
-  }
-  if (ok) {
-    printf("."); fflush(stdout);
-  } else {
-    char buf1[128], *cp1;
-    char buf2[128], *cp2;
-    have_failed = 1;
-    cp1 = buf1; cp2 = buf2;
-    for (i=0; i<16; ++i) {
-      tor_snprintf(cp1, sizeof(buf1)-(cp1-buf1), "%02x", a->s6_addr[i]);
-      tor_snprintf(cp2, sizeof(buf2)-(cp2-buf2), "%02x", b->s6_addr[i]);
-      cp1 += 2; cp2 += 2;
-      if ((i%2)==1 && i != 15) {
-        *cp1++ = ':';
-        *cp2++ = ':';
-      }
-    }
-    *cp1 = *cp2 = '\0';
-    printf("Line %d: assertion failed: (%s == %s)\n"
-           "      %s != %s\n", line, e1, e2, buf1, buf2);
-    fflush(stdout);
-  }
-}
+#define _test_op_ip6(a,op,b,e1,e2)                               \
+  STMT_BEGIN                                                     \
+  tt_assert_test_fmt_type(a,b,e1" "#op" "e2,struct in6_addr*,    \
+    (memcmp(_val1->s6_addr, _val2->s6_addr, 16) op 0),           \
+    char *, "%s",                                                \
+    { int i; char *cp;                                           \
+      cp = _print = tor_malloc(64);                              \
+      for (i=0;i<16;++i) {                                       \
+        tor_snprintf(cp, 3,"%02x", (unsigned)_value->s6_addr[i]);\
+        cp += 2;                                                 \
+        if (i != 15) *cp++ = ':';                                \
+      }                                                          \
+    }, { tor_free(_print); }                                     \
+  );                                                             \
+  STMT_END
 
 /** Helper: Assert that two strings both decode as IPv6 addresses with
  * tor_inet_pton(), and both decode to the same address. */
 #define test_pton6_same(a,b) STMT_BEGIN                \
      test_eq(tor_inet_pton(AF_INET6, a, &a1), 1);      \
      test_eq(tor_inet_pton(AF_INET6, b, &a2), 1);      \
-    _test_eq_ip6(&a1,&a2,#a,#b,__LINE__);              \
+     _test_op_ip6(&a1,==,&a2,#a,#b);                   \
   STMT_END
 
 /** Helper: Assert that <b>a</b> is recognized as a bad IPv6 address by
@@ -1434,7 +1412,7 @@ _test_eq_ip6(struct in6_addr *a, struct in6_addr *b, const char *e1,
     test_eq(tor_inet_pton(AF_INET6, a, &a1), 1);                        \
     test_streq(tor_inet_ntop(AF_INET6, &a1, buf, sizeof(buf)), b);      \
     test_eq(tor_inet_pton(AF_INET6, b, &a2), 1);                        \
-    _test_eq_ip6(&a1, &a2, a, b, __LINE__);                             \
+    _test_op_ip6(&a1, ==, &a2, a, b);                                   \
   STMT_END
 
 /** Helper: assert that <b>a</b> parses by tor_inet_pton() into a address that
@@ -3186,9 +3164,11 @@ test_dir_format(void)
   test_eq(VER_RELEASE, ver1.status);
   test_streq("", ver1.status_tag);
 
-#define test_eq_vs(vs1, vs2) test_eq_type(version_status_t, "%d", (vs1), (vs2))
-#define test_v_i_o(val, ver, lst) \
-  test_eq_vs(val, tor_version_is_obsolete(ver, lst))
+#define tt_versionstatus_op(vs1, op, vs2)                               \
+  tt_assert_test_type(vs1,vs2,#vs1" "#op" "#vs2,version_status_t,       \
+                      (_val1 op _val2),"%d")
+#define test_v_i_o(val, ver, lst)                                       \
+  tt_versionstatus_op(val, ==, tor_version_is_obsolete(ver, lst))
 
   /* make sure tor_version_is_obsolete() works */
   test_v_i_o(VS_OLD, "0.0.1", "Tor 0.0.2");
@@ -5045,28 +5025,40 @@ test_geoip(void)
   tor_free(s);
 }
 
-/** For test_array. Declare an CLI-invocable off-by-default function in the
- * unit tests, with function name and user-visible name <b>x</b>*/
-#define DISABLED(x) { #x, x, 0, 0, 0 }
-/** For test_array. Declare an CLI-invocable unit test function, with function
- * name test_<b>x</b>(), and user-visible name <b>x</b> */
-#define ENT(x) { #x, test_ ## x, 0, 0, 1 }
-/** For test_array. Declare an CLI-invocable unit test function, with function
- * name test_<b>x</b>_<b>y</b>(), and user-visible name
- * <b>x</b>/<b>y</b>. This function will be treated as a subentry of <b>x</b>,
- * so that invoking <b>x</b> from the CLI invokes this test too. */
-#define SUBENT(x,y) { #x "/" #y, test_ ## x ## _ ## y, 1, 0, 1 }
-
-/** An array of functions and information for all the unit tests we can run. */
-static struct {
-  const char *test_name; /**< How does the user refer to this test from the
-                          * command line? */
-  void (*test_fn)(void); /**< What function is called to run this test? */
-  int is_subent; /**< Is this a subentry of a bigger set of related tests? */
-  int selected; /**< Are we planning to run this one? */
-  int is_default; /**< If the user doesn't say what tests they want, do they
-                   * get this function by default? */
-} test_array[] = {
+static void *
+legacy_test_setup(const struct testcase_t *testcase)
+{
+  return testcase->setup_data;
+}
+
+static void
+legacy_test_helper(void *data)
+{
+  void (*fn)(void) = data;
+  fn();
+}
+
+static int
+legacy_test_cleanup(const struct testcase_t *testcase, void *ptr)
+{
+  (void)ptr;
+  (void)testcase;
+  return 1;
+}
+
+static const struct testcase_setup_t legacy_setup = {
+  legacy_test_setup, legacy_test_cleanup
+};
+
+#define ENT(name)                                                       \
+  { #name, legacy_test_helper, 0, &legacy_setup, test_ ## name }
+#define SUBENT(group, name)                                             \
+  { #group "_" #name, legacy_test_helper, 0, &legacy_setup,             \
+      test_ ## group ## _ ## name }
+#define DISABLED(name)                                                  \
+  { #name, legacy_test_helper, TT_SKIP, &legacy_setup, name }
+
+static struct testcase_t test_array[] = {
   ENT(buffers),
   ENT(crypto),
   SUBENT(crypto, rng),
@@ -5111,36 +5103,22 @@ static struct {
 
   DISABLED(bench_aes),
   DISABLED(bench_dmap),
-  { NULL, NULL, 0, 0, 0 },
+  END_OF_TESTCASES
 };
 
-static void syntax(void) ATTR_NORETURN;
-
-/** Print a syntax usage message, and exit.*/
-static void
-syntax(void)
-{
-  int i;
-  printf("Syntax:\n"
-         "  test [-v|--verbose] [--warn|--notice|--info|--debug]\n"
-         "       [testname...]\n"
-         "Recognized tests are:\n");
-  for (i = 0; test_array[i].test_name; ++i) {
-    printf("   %s\n", test_array[i].test_name);
-  }
-
-  exit(0);
-}
+static struct testgroup_t testgroups[] = {
+  { "", test_array },
+  END_OF_GROUPS
+};
 
 /** Main entry point for unit test code: parse the command line, and run
  * some unit tests. */
 int
-main(int c, char**v)
+main(int c, const char **v)
 {
   or_options_t *options;
   char *errmsg = NULL;
-  int i;
-  int verbose = 0, any_selected = 0;
+  int i, i_out;
   int loglevel = LOG_ERR;
 
 #ifdef USE_DMALLOC
@@ -5155,44 +5133,20 @@ main(int c, char**v)
   tor_threads_init();
   init_logging();
 
-  for (i = 1; i < c; ++i) {
-    if (!strcmp(v[i], "-v") || !strcmp(v[i], "--verbose"))
-      verbose++;
-    else if (!strcmp(v[i], "--warn"))
+  for (i_out = i = 1; i < c; ++i) {
+    if (!strcmp(v[i], "--warn")) {
       loglevel = LOG_WARN;
-    else if (!strcmp(v[i], "--notice"))
+    } else if (!strcmp(v[i], "--notice")) {
       loglevel = LOG_NOTICE;
-    else if (!strcmp(v[i], "--info"))
+    } else if (!strcmp(v[i], "--info")) {
       loglevel = LOG_INFO;
-    else if (!strcmp(v[i], "--debug"))
+    } else if (!strcmp(v[i], "--debug")) {
       loglevel = LOG_DEBUG;
-    else if (!strcmp(v[i], "--help") || !strcmp(v[i], "-h") || v[i][0] == '-')
-      syntax();
-    else {
-      int j, found=0;
-      for (j = 0; test_array[j].test_name; ++j) {
-        if (!strcmp(v[i], test_array[j].test_name) ||
-            (test_array[j].is_subent &&
-             !strcmpstart(test_array[j].test_name, v[i]) &&
-             test_array[j].test_name[strlen(v[i])] == '/') ||
-            (v[i][0] == '=' && !strcmp(v[i]+1, test_array[j].test_name))) {
-          test_array[j].selected = 1;
-          any_selected = 1;
-          found = 1;
-        }
-      }
-      if (!found) {
-        printf("Unknown test: %s\n", v[i]);
-        syntax();
-      }
-    }
-  }
-
-  if (!any_selected) {
-    for (i = 0; test_array[i].test_name; ++i) {
-      test_array[i].selected = test_array[i].is_default;
+    } else {
+      v[i_out++] = v[i];
     }
   }
+  c = i_out;
 
   {
     log_severity_list_t s;
@@ -5219,19 +5173,7 @@ main(int c, char**v)
 
   atexit(remove_directory);
 
-  printf("Running Tor unit tests on %s\n", get_uname());
-
-  for (i = 0; test_array[i].test_name; ++i) {
-    if (!test_array[i].selected)
-      continue;
-    if (!test_array[i].is_subent) {
-      printf("\n============================== %s\n",test_array[i].test_name);
-    } else if (test_array[i].is_subent && verbose) {
-      printf("\n%s", test_array[i].test_name);
-    }
-    test_array[i].test_fn();
-  }
-  puts("");
+  have_failed = (tinytest_main(c, v, testgroups) < 0);
 
   free_pregenerated_keys();
 #ifdef USE_DMALLOC
diff --git a/src/or/test.h b/src/or/test.h
new file mode 100644
index 0000000..e4bae6d
--- /dev/null
+++ b/src/or/test.h
@@ -0,0 +1,66 @@
+/* Copyright (c) 2001-2003, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2009, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef _TOR_TEST_H
+#define _TOR_TEST_H
+
+/**
+ * \file test.h
+ * \brief Macros used by unit tests.
+ */
+
+#include "compat.h"
+#include "tinytest.h"
+#define TT_EXIT_TEST_FUNCTION STMT_BEGIN goto done; STMT_END
+#include "tinytest_macros.h"
+
+#ifdef __GNUC__
+#define PRETTY_FUNCTION __PRETTY_FUNCTION__
+#else
+#define PRETTY_FUNCTION ""
+#endif
+
+#define test_fail_msg(msg) TT_DIE((msg))
+
+#define test_fail() test_fail_msg("Assertion failed.")
+
+#define test_assert(expr) tt_assert(expr)
+
+#define test_eq(expr1, expr2) tt_int_op((expr1), ==, (expr2))
+#define test_eq_ptr(expr1, expr2) tt_ptr_op((expr1), ==, (expr2))
+#define test_neq(expr1, expr2) tt_int_op((expr1), !=, (expr2))
+#define test_neq_ptr(expr1, expr2) tt_ptr_op((expr1), !=, (expr2))
+#define test_streq(expr1, expr2) tt_str_op((expr1), ==, (expr2))
+#define test_strneq(expr1, expr2) tt_str_op((expr1), !=, (expr2))
+#define test_streq(expr1, expr2) tt_str_op((expr1), ==, (expr2))
+
+#define test_mem_op(expr1, op, expr2, len)                              \
+  tt_assert_test_fmt_type(expr1,expr2,#expr1" "#op" "#expr2,            \
+                          const char *,                                 \
+                          (memcmp(_val1, _val2, len) op 0),             \
+                          char *, "%s",                                 \
+                          { size_t printlen = (len)*2+1;                \
+                            _print = tor_malloc(printlen);              \
+                            base16_encode(_print, printlen, _value,     \
+                                          (len)); },                    \
+                          { tor_free(_print); }                         \
+                          );
+
+#define test_memeq(expr1, expr2, len) test_mem_op((expr1), ==, (expr2), len)
+#define test_memneq(expr1, expr2, len) test_mem_op((expr1), !=, (expr2), len)
+
+#define test_mem_op_hex(expr1, op, hex)                                 \
+  STMT_BEGIN                                                            \
+  size_t length = strlen(hex);                                          \
+  char *value2 = tor_malloc(length/2);                                  \
+  tor_assert((length&1)==0);                                            \
+  base16_decode(value2, length/2, hex, length);                         \
+  test_mem_op(expr1, op, value2, length/2);                             \
+  STMT_END
+
+#define test_memeq_hex(expr1, hex) test_mem_op_hex(expr1, ==, hex)
+
+#endif
+
diff --git a/src/or/tinytest.c b/src/or/tinytest.c
new file mode 100644
index 0000000..b358bb3
--- /dev/null
+++ b/src/or/tinytest.c
@@ -0,0 +1,369 @@
+/* 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
+
+#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 */
+const char *verbosity_flag = "";
+
+enum outcome { SKIP=2, OK=1, FAIL=0 };
+static enum outcome cur_test_outcome = 0;
+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 void usage(struct testgroup_t *groups, int list_groups)
+  __attribute__((noreturn));
+
+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
+
+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];
+	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>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 = 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);
+	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] ");
+	pid = fork();
+	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);
+	} 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 0;
+		} else if (r != 1) {
+			perror("read outcome from pipe");
+		}
+		waitpid(pid, &status, 0);
+		close(outcome_pipe[0]);
+		return b[0]=='Y' ? OK : (b[0]=='S' ? SKIP : FAIL);
+	}
+#endif
+}
+
+int
+testcase_run_one(const struct testgroup_t *group,
+		 const struct testcase_t *testcase)
+{
+	enum outcome outcome;
+
+	if (testcase->flags & TT_SKIP) {
+		if (opt_verbosity>0)
+			printf("%s%s: SKIPPED\n",
+			    group->prefix, testcase->name);
+		++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;
+	}
+
+	if ((testcase->flags & TT_FORK) && !(opt_forked||opt_nofork)) {
+		outcome = _testcase_run_forked(group, testcase);
+	} else {
+		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));
+	} else {
+		return (int)outcome;
+	}
+}
+
+int
+_tinytest_set_flag(struct testgroup_t *groups, const char *arg, 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) {
+			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, int list_groups)
+{
+	puts("Options are: [--verbose|--quiet|--terse] [--no-fork]");
+	puts("  Specify tests by name, or using a prefix ending with '..'");
+	puts("  Use --list-tests for a list of tests.");
+	if (list_groups) {
+		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], "--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 {
+			++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 (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;
+}
+
diff --git a/src/or/tinytest.h b/src/or/tinytest.h
new file mode 100644
index 0000000..a0cb913
--- /dev/null
+++ b/src/or/tinytest.h
@@ -0,0 +1,87 @@
+/* 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: called from a test to indicate that we're skipping. */
+void _tinytest_set_test_skipped(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/src/or/tinytest_demo.c b/src/or/tinytest_demo.c
new file mode 100644
index 0000000..0117176
--- /dev/null
+++ b/src/or/tinytest_demo.c
@@ -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 nest. */
+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/src/or/tinytest_macros.h b/src/or/tinytest_macros.h
new file mode 100644
index 0000000..48c1fbd
--- /dev/null
+++ b/src/or/tinytest_macros.h
@@ -0,0 +1,167 @@
+/* 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_printf(msg) TT_DIE(msg)
+#define tt_abort_perror(op) TT_DIE(("%s: %s [%d]",(op),strerror(errno), errno))
+#define tt_abort_msg(msg) TT_DIE(("%s", msg))
+#define tt_abort() TT_DIE(("%s", "(Failed.)"))
+
+/* Fail but do not abort the current test for the reason in msg. */
+#define tt_fail_printf(msg) TT_FAIL(msg)
+#define tt_fail_perror(op) TT_FAIL(("%s: %s [%d]",(op),strerror(errno), errno))
+#define tt_fail_msg(msg) TT_FAIL(("%s", msg))
+#define tt_fail() TT_FAIL(("%s", "(Failed.)"))
+
+/* End the current test, and indicate we are skipping it. */
+#define tt_skip()                               \
+	TT_STMT_BEGIN						\
+	_tinytest_set_test_skipped();				\
+	TT_EXIT_TEST_FUNCTION;					\
+	TT_STMT_END
+
+#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_fmt_type(a,b,str_test,type,test,printf_type,printf_fmt, \
+                                setup_block,cleanup_block)              \
+	TT_STMT_BEGIN							\
+	type _val1 = (type)(a);						\
+	type _val2 = (type)(b);						\
+	int _tt_status = (test);					\
+	if (!_tt_status || _tinytest_get_verbosity()>1)	{		\
+		printf_type _print;					\
+		printf_type _print1;					\
+		printf_type _print2;					\
+		type _value = _val1;					\
+		setup_block;						\
+		_print1 = _print;					\
+		_value = _val2;						\
+		setup_block;						\
+		_print2 = _print;					\
+		TT_DECLARE(_tt_status?"  OK":"FAIL",			\
+			   ("assert(%s): "printf_fmt" vs "printf_fmt,	\
+			    str_test, _print1, _print2));		\
+		_print = _print1;					\
+		cleanup_block;						\
+		_print = _print2;					\
+		cleanup_block;						\
+		if (!_tt_status) {					\
+			_tinytest_set_test_failed();			\
+			TT_EXIT_TEST_FUNCTION;				\
+		}							\
+	}								\
+	TT_STMT_END
+
+#define tt_assert_test_type(a,b,str_test,type,test,fmt)			\
+	tt_assert_test_fmt_type(a,b,str_test,type,test,type,fmt,	\
+				{_print=_value;},{})
+
+/* 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_ptr_op(a,op,b)						\
+	tt_assert_test_type(a,b,#a" "#op" "#b,void*,			\
+			    (_val1 op _val2),"%p")
+
+#define tt_str_op(a,op,b)						\
+	tt_assert_test_type(a,b,#a" "#op" "#b,const char *,		\
+			    (strcmp(_val1,_val2) op 0),"<%s>")
+
+#endif
-- 
1.5.6.5




More information about the tor-commits mailing list