test: new regtest based on tinytest
authorMarko Kreen <markokr@gmail.com>
Fri, 22 Jan 2010 08:35:01 +0000 (10:35 +0200)
committerMarko Kreen <markokr@gmail.com>
Tue, 4 May 2010 11:27:39 +0000 (14:27 +0300)
18 files changed:
Makefile
test/Makefile [new file with mode: 0644]
test/force_compat.sed [new file with mode: 0644]
test/test_aatree.c [new file with mode: 0644]
test/test_cbtree.c [new file with mode: 0644]
test/test_common.c [new file with mode: 0644]
test/test_common.h [new file with mode: 0644]
test/test_crypto.c [new file with mode: 0644]
test/test_heap.c [new file with mode: 0644]
test/test_list.c [new file with mode: 0644]
test/test_string.c [new file with mode: 0644]
test/test_strpool.c [new file with mode: 0644]
test/test_utf8.c [new file with mode: 0644]
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]
usual/base.h

index 905c8e6667a5f6625965c2c2f2cffd39a515a2b7..69d4a3de4a8e13fa27511bce2d0b5a114951e0a6 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -9,7 +9,7 @@ CPPFLAGS = $(USUAL_CPPFLAGS)
 # sources
 USUAL_DIR = .
 USUAL_OBJDIR = obj
-USUAL_MODULES = $(filter-out pgsocket, $(subst .h,, $(notdir $(wildcard usual/*.h))))
+USUAL_MODULES = $(filter-out pgsocket, $(subst .h,, $(notdir $(wildcard $(USUAL_DIR)/usual/*.h))))
 include $(USUAL_DIR)/Setup.mk
 
 # full path for files
@@ -35,13 +35,13 @@ libusual.a: $(objs)
        $(E) "  AR" $@
        $(Q) $(MKAR) $@ $(objs)
 
-obj/%.o: usual/%.c config.mak $(hdrs)
-       @mkdir -p obj
+$(USUAL_OBJDIR)/%.o: $(USUAL_DIR)/usual/%.c config.mak $(hdrs)
+       @mkdir -p $(USUAL_OBJDIR)
        $(E) "  CC" $<
        $(Q) $(CC) -c -o $@ $(DEFS) $(CPPFLAGS) $(CFLAGS) $<
 
-obj/%.s: usual/%.c config.mak $(hdrs)
-       @mkdir -p obj
+$(USUAL_OBJDIR)/%.s: $(USUAL_DIR)/usual/%.c config.mak $(hdrs)
+       @mkdir -p $(USUAL_OBJDIR)
        $(E) "  CC -S" $<
        $(Q) $(CC) -S -fverbose-asm -o - $(DEFS) $(CPPFLAGS) $(CFLAGS) $< \
        | cleanasm > $@
@@ -50,6 +50,18 @@ obj/testcompile: test/compile.c libusual.a config.mak $(hdrs)
        $(E) "  CHECK" $<
        $(Q) $(CC) -o $@ $(DEFS) $(CPPFLAGS) $(CFLAGS) $< $(USUAL_LDFLAGS) $(USUAL_LIBS) $(LIBS)
 
+test/test_string: test/test_string.c libusual.a config.mak $(hdrs)
+       $(E) "  TEST" $@
+       $(Q) $(CC) -o $@ $(DEFS) $(CPPFLAGS) $(CFLAGS) $< $(USUAL_LDFLAGS) $(USUAL_LIBS) $(LIBS)
+       $(Q) ./$@
+
+check: config.mak
+       mkdir -p test/usual
+       sed -f test/compat.sed usual/config.h > test/usual/config.h
+       make clean
+       make CPPFLAGS="-I./test $(CPPFLAGS)" all
+       make CPPFLAGS="-I./test $(CPPFLAGS)" test/test_string
+
 clean:
        rm -f libusual.a obj/*.[os] obj/test* aclocal* config.log
        rm -rf autom4te*
@@ -64,13 +76,13 @@ boot:
        autoconf
        rm -rf aclocal* autom4te.*
 
-config.mak: usual/config.h
-       @echo "Config out-of-date, please run ./configure again"
-       @exit 1
+#config.mak:
+#      @echo "Config out-of-date, please run ./configure again"
+#      @exit 1
 
-usual/config.h:
-       @echo "Please run ./configure first"
-       @exit 1
+#usual/config.h:
+#      @echo "Please run ./configure first"
+#      @exit 1
 
 # run sparse over code
 check: config.mak
@@ -80,3 +92,9 @@ check: config.mak
 asms = $(objs:.o=.s)
 asm: $(asms)
 
+dbg:
+       @echo srcs=$(srcs)
+       @echo objs=$(objs)
+       @echo hdrs=$(hdrs)
+       @echo CPPFLAGS=$(CPPFLAGS)
+
diff --git a/test/Makefile b/test/Makefile
new file mode 100644 (file)
index 0000000..971705d
--- /dev/null
@@ -0,0 +1,34 @@
+
+
+#vpath %.mak ..
+#vpath %.mk ..
+#vpath %.c ..
+#vpath %.h ..
+
+override CPPFLAGS = -I. -I..
+override USUAL_DIR = ..
+
+override DEFS = -DUSUAL_TEST_CONFIG
+OBJS = test_string.o test_crypto.o test_aatree.o test_heap.o \
+       test_common.o test_list.o tinytest.o test_cbtree.o \
+       test_utf8.o test_strpool.o
+
+test-all: regtest
+
+
+include ../Makefile
+
+test_config.h: ../usual/config.h force_compat.sed
+       sed -f force_compat.sed $< > $@
+
+#libusual.a: test_config.h
+
+regtest: test_config.h libusual.a $(OBJS)
+       $(CC) -o $@ $(OBJS) -L. -lusual
+
+clean: clean-test
+
+clean-test:
+       rm -f libusual.a obj/* *.o regtest
+       rm -f config.test usual/config.h test_config.h
+
diff --git a/test/force_compat.sed b/test/force_compat.sed
new file mode 100644 (file)
index 0000000..839b983
--- /dev/null
@@ -0,0 +1,5 @@
+/STRLCPY/s,^,//,
+/STRLCAT/s,^,//,
+/BASENAME/s,^,//,
+/DIRNAME/s,^,//,
+/POLL/s,^,//,
diff --git a/test/test_aatree.c b/test/test_aatree.c
new file mode 100644 (file)
index 0000000..956be8c
--- /dev/null
@@ -0,0 +1,224 @@
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "test_common.h"
+
+#include <usual/aatree.h>
+
+static char *OK = "OK";
+
+typedef struct MyNode MyNode;
+struct MyNode {
+       struct AANode node;
+       int value;
+};
+
+static int my_node_pair_cmp(const struct AANode *n1, const struct AANode *n2)
+{
+       const struct MyNode *m1 = container_of(n1, struct MyNode, node);
+       const struct MyNode *m2 = container_of(n2, struct MyNode, node);
+       return m1->value - m2->value;
+}
+
+static int my_node_cmp(long value, struct AANode *node)
+{
+       MyNode *my = container_of(node, MyNode, node);
+       return value - my->value;
+}
+
+static MyNode *make_node(int value)
+{
+       MyNode *node = malloc(sizeof(*node));
+       memset(node, 0, sizeof(*node));
+       node->value = value;
+       return node;
+}
+
+static void my_node_free(struct AANode *node, void *arg)
+{
+       MyNode *my = container_of(node, MyNode, node);
+       free(my);
+}
+
+/*
+ * Test tree sanity
+ */
+
+static const char *mkerr(const char *msg, int v, const struct AANode *node)
+{
+       static char buf[128];
+       snprintf(buf, sizeof(buf), "%s: %d", msg, v);
+       return buf;
+}
+
+static const char *check_sub(const struct AATree *tree, const struct AANode *node, int i)
+{
+       int cmp_left = 0, cmp_right = 0;
+       const char *res;
+       if (aatree_is_nil_node(node))
+               return OK;
+       if (node->level != node->left->level + 1)
+               return mkerr("bad left level", i, node);
+       if (!((node->level == node->right->level + 1)
+             || (node->level == node->right->level
+                 && node->right->level != node->right->level + 1)))
+               return mkerr("bad right level", i, node);
+       if (!aatree_is_nil_node(node->left))
+               cmp_left = my_node_pair_cmp(node, node->left);
+       if (!aatree_is_nil_node(node->right))
+               cmp_right = my_node_pair_cmp(node, node->right);
+       if (cmp_left < 0)
+               return mkerr("wrong left order", i, node);
+       if (cmp_right > 0)
+               return mkerr("wrong right order", i, node);
+       res = check_sub(tree, node->left, i);
+       if (!res)
+               res = check_sub(tree, node->right, i);
+       return res;
+}
+
+static const char *check(struct AATree *tree, int i)
+{
+       return check_sub(tree, tree->root, i);
+}
+
+/*
+ * checking operations
+ */
+
+static const char * my_search(struct AATree *tree, int value)
+{
+       struct AANode *res;
+       res = aatree_search(tree, value);
+       return res ? OK : "not found";
+}
+
+static const char *my_insert(struct AATree *tree, int value)
+{
+       MyNode *my = make_node(value);
+       aatree_insert(tree, value, &my->node);
+       return check(tree, value);
+}
+
+static const char *my_remove(struct AATree *tree, int value)
+{
+       const char *res;
+       res = my_search(tree, value);
+       if (res != OK)
+               return res;
+       aatree_remove(tree, value);
+       res = check(tree, value);
+       if (res != OK)
+               return res;
+       if (aatree_search(tree, value) != NULL)
+               return "still found";
+       return OK;
+}
+
+/*
+ * Simple opeartions.
+ */
+
+static void test_aatree_basic(void *p)
+{
+       struct AATree tree[1];
+       int i;
+
+       aatree_init(tree, my_node_cmp, my_node_free);
+
+       str_check(my_search(tree, 1), "not found");
+
+       for (i = 0; i < 15; i++) {
+               str_check(my_insert(tree, i), "OK");
+       }
+       for (i = -1; i > -15; i--) {
+               str_check(my_insert(tree, i), "OK");
+       }
+       for (i = 30; i < 45; i++) {
+               str_check(my_insert(tree, i), "OK");
+       }
+       for (i = 15; i < 30; i++) {
+               str_check(my_insert(tree, i), "OK");
+       }
+
+       for (i = -14; i < 45; i++) {
+               str_check(my_remove(tree, i), "OK");
+       }
+end:
+       aatree_destroy(tree);
+}
+
+/*
+ * randomized test
+ */
+
+#define RSIZE 3000
+
+static int get_next(bool with_stat, bool added[])
+{
+       int r = random() % RSIZE;
+       int i = r;
+       while (1) {
+               if (added[i] == with_stat)
+                       return i;
+               if (++i >= RSIZE)
+                       i = 0;
+               if (i == r)
+                       return -1;
+       }
+}
+
+static void test_aatree_random(void *p)
+{
+       bool is_added[RSIZE];
+       int prefer_remove = 0; // 0 - insert, 1 - delete
+       int n;
+       int op; // 0 - insert, 1 - delete
+       struct AATree tree[1];
+       unsigned long long total = 0;
+
+       //printf("\n\n*** rand test ***\n\n");
+       srandom(123123);
+       memset(is_added, 0, sizeof(is_added));
+
+       aatree_init(tree, my_node_cmp, my_node_free);
+       while (total < 100000) {
+               int r = random() & 15;
+               if (prefer_remove)
+                       op = r > 5;
+               else
+                       op = r > 10;
+               //op = 0;
+
+               n = get_next(op, is_added);
+               if (n < 0) {
+                       //break;
+                       if (prefer_remove == op) {
+                               prefer_remove = !prefer_remove;
+                               //printf("** toggling remove to %d\n", prefer_remove);
+                       }
+                       continue;
+               }
+
+               if (op == 0) {
+                       str_check(my_insert(tree, n), "OK");
+                       is_added[n] = 1;
+               } else {
+                       str_check(my_remove(tree, n), "OK");
+                       is_added[n] = 0;
+               }
+               total++;
+       }
+end:
+       aatree_destroy(tree);
+}
+
+struct testcase_t aatree_tests[] = {
+       { "basic", test_aatree_basic },
+       { "random", test_aatree_random },
+       END_OF_TESTCASES
+};
+
diff --git a/test/test_cbtree.c b/test/test_cbtree.c
new file mode 100644 (file)
index 0000000..9904e9e
--- /dev/null
@@ -0,0 +1,179 @@
+
+#include "test_common.h"
+
+#include <usual/cbtree.c>
+
+static char *OK = "OK";
+
+struct MyNode {
+       char str[64];
+       int len;
+};
+
+static unsigned int my_getkey(void *obj, const void **dst_p)
+{
+       struct MyNode *node = obj;
+       *dst_p = node->str;
+       return node->len;
+}
+
+static struct MyNode *make_node(int value)
+{
+       struct MyNode *node = malloc(sizeof(*node));
+       memset(node, 0, sizeof(*node));
+       snprintf(node->str, sizeof(node->str), "%d", value);
+       node->len = strlen(node->str);
+       return node;
+}
+
+static void my_node_free(struct MyNode *node)
+{
+       free(node);
+}
+
+/*
+ * Test tree sanity
+ */
+
+/*
+ * checking operations
+ */
+
+static const char *my_search(struct CBTree *tree, int value)
+{
+       struct AANode *res;
+       char buf[64];
+       snprintf(buf, sizeof(buf), "%d", value);
+       res = cbtree_lookup(tree, buf, strlen(buf));
+       return res ? OK : "not found";
+}
+
+static const char *my_insert(struct CBTree *tree, int value)
+{
+       struct MyNode *my = make_node(value);
+       if (!cbtree_insert(tree, my))
+               return "insert failed";
+       return my_search(tree, value);
+}
+
+static const char *my_remove(struct CBTree *tree, int value)
+{
+       struct MyNode *my;
+       char buf[64];
+       snprintf(buf, sizeof(buf), "%d", value);
+
+       my = cbtree_lookup(tree, buf, strlen(buf));
+       if (!my)
+               return "nonexsist element";
+       cbtree_delete(tree, buf, strlen(buf));
+       if (cbtree_lookup(tree, buf, strlen(buf)) != NULL)
+               return "still found";
+       my_node_free(my);
+       return OK;
+}
+
+/*
+ * Simple opeartions.
+ */
+
+static void test_cbtree_basic(void *p)
+{
+       struct CBTree *tree;
+       int i;
+
+       tree = cbtree_create(my_getkey);
+
+       str_check(my_search(tree, 1), "not found");
+
+       for (i = 0; i < 15; i++) {
+               str_check(my_insert(tree, i), "OK");
+       }
+       for (i = -1; i > -15; i--) {
+               str_check(my_insert(tree, i), "OK");
+       }
+       for (i = 30; i < 45; i++) {
+               str_check(my_insert(tree, i), "OK");
+       }
+       for (i = 15; i < 30; i++) {
+               str_check(my_insert(tree, i), "OK");
+       }
+
+       for (i = -14; i < 45; i++) {
+               str_check(my_remove(tree, i), "OK");
+       }
+end:
+       cbtree_destroy(tree);
+}
+
+/*
+ * randomized test
+ */
+
+#define RSIZE 3000
+
+static int get_next(bool with_stat, bool added[])
+{
+       int r = random() % RSIZE;
+       int i = r;
+       while (1) {
+               if (added[i] == with_stat)
+                       return i;
+               if (++i >= RSIZE)
+                       i = 0;
+               if (i == r)
+                       return -1;
+       }
+}
+
+static void test_cbtree_random(void *p)
+{
+       bool is_added[RSIZE];
+       int prefer_remove = 0; // 0 - insert, 1 - delete
+       int n;
+       int op; // 0 - insert, 1 - delete
+       struct CBTree *tree;
+       unsigned long long total = 0;
+
+       //printf("\n\n*** rand test ***\n\n");
+       srandom(123123);
+       memset(is_added, 0, sizeof(is_added));
+
+       tree = cbtree_create(my_getkey);
+
+       while (total < 100000) {
+               int r = random() & 15;
+               if (prefer_remove)
+                       op = r > 5;
+               else
+                       op = r > 10;
+               //op = 0;
+
+               n = get_next(op, is_added);
+               if (n < 0) {
+                       //break;
+                       if (prefer_remove == op) {
+                               prefer_remove = !prefer_remove;
+                               //printf("** toggling remove to %d\n", prefer_remove);
+                       }
+                       continue;
+               }
+
+               if (op == 0) {
+                       str_check(my_insert(tree, n), "OK");
+                       is_added[n] = 1;
+               } else {
+                       str_check(my_remove(tree, n), "OK");
+                       is_added[n] = 0;
+               }
+               total++;
+       }
+end:
+       cbtree_destroy(tree);
+}
+
+struct testcase_t cbtree_tests[] = {
+       { "basic", test_cbtree_basic },
+       { "random", test_cbtree_random },
+       END_OF_TESTCASES
+};
+
diff --git a/test/test_common.c b/test/test_common.c
new file mode 100644 (file)
index 0000000..fa821dc
--- /dev/null
@@ -0,0 +1,21 @@
+
+
+#include "test_common.h"
+
+struct testgroup_t groups[] = {
+       { "aatree/", aatree_tests },
+       { "cbtree/", cbtree_tests },
+       { "crypto/", crypto_tests },
+       { "string/", string_tests },
+       { "heap/", heap_tests },
+       { "list/", list_tests },
+       { "utf8/", utf8_tests },
+       { "strpool/", strpool_tests },
+       END_OF_GROUPS
+};
+
+int main(int argc, const char *argv[])
+{
+       return tinytest_main(argc, argv, groups);
+}
+
diff --git a/test/test_common.h b/test/test_common.h
new file mode 100644 (file)
index 0000000..54f04fa
--- /dev/null
@@ -0,0 +1,18 @@
+
+#include <usual/base.h>
+
+#include "tinytest.h"
+#include "tinytest_macros.h"
+
+#define str_check(a, b) tt_str_op(a, ==, b)
+#define int_check(a, b) tt_int_op(a, ==, b)
+
+extern struct testcase_t aatree_tests[];
+extern struct testcase_t cbtree_tests[];
+extern struct testcase_t string_tests[];
+extern struct testcase_t crypto_tests[];
+extern struct testcase_t heap_tests[];
+extern struct testcase_t list_tests[];
+extern struct testcase_t utf8_tests[];
+extern struct testcase_t strpool_tests[];
+
diff --git a/test/test_crypto.c b/test/test_crypto.c
new file mode 100644 (file)
index 0000000..379fb6c
--- /dev/null
@@ -0,0 +1,116 @@
+
+#include <usual/string.h>
+
+#include "tinytest.h"
+#include "tinytest_macros.h"
+
+#define str_check(a, b) tt_str_op(a, ==, b)
+
+#include <usual/md5.h>
+#include <usual/sha1.h>
+
+static const char *mkhex(const uint8_t *src, int len)
+{
+       static char buf[128];
+       static const char hextbl[] = "0123456789abcdef";
+       int i;
+       for (i = 0; i < len; i++) {
+               buf[i*2] = hextbl[src[i] >> 4];
+               buf[i*2+1] = hextbl[src[i] & 15];
+       }
+       buf[i*2] = 0;
+       return buf;
+}
+
+/*
+ * MD5
+ */
+
+static const char *run_md5(const char *str)
+{
+       struct md5_ctx ctx[1];
+       uint8_t res[MD5_DIGEST_LENGTH];
+       uint8_t res2[MD5_DIGEST_LENGTH];
+       int i, len = strlen(str), step;
+
+       md5_reset(ctx);
+       md5_update(ctx, str, len);
+       md5_final(res, ctx);
+
+       md5_reset(ctx);
+       step = 3;
+       for (i = 0; i < len; i += step)
+               md5_update(ctx, str+i,
+                          (i + step <= len)
+                          ? (step) : (len - i));
+       md5_final(res2, ctx);
+
+       if (memcmp(res, res2, MD5_DIGEST_LENGTH) != 0)
+               return "FAIL";
+       
+       return mkhex(res, MD5_DIGEST_LENGTH);
+}
+
+static void test_md5(void *ptr)
+{
+       str_check(run_md5(""), "d41d8cd98f00b204e9800998ecf8427e");
+       str_check(run_md5("a"), "0cc175b9c0f1b6a831c399e269772661");
+       str_check(run_md5("abc"), "900150983cd24fb0d6963f7d28e17f72");
+       str_check(run_md5("message digest"), "f96b697d7cb7938d525a2f31aaf161d0");
+       str_check(run_md5("abcdefghijklmnopqrstuvwxyz"), "c3fcd3d76192e4007dfb496cca67e13b");
+       str_check(run_md5("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), "d174ab98d277d9f5a5611c2c9f419d9f");
+       str_check(run_md5("12345678901234567890123456789012345678901234567890123456789012345678901234567890"), "57edf4a22be3c955ac49da2e2107b67a");
+end:;
+}
+
+/*
+ * SHA1
+ */
+
+static const char *run_sha1(const char *str)
+{
+       struct sha1_ctx ctx[1];
+       uint8_t res[SHA1_DIGEST_LENGTH];
+       uint8_t res2[SHA1_DIGEST_LENGTH];
+       int i, len = strlen(str), step;
+
+       sha1_reset(ctx);
+       sha1_update(ctx, str, len);
+       sha1_final(res, ctx);
+
+       sha1_reset(ctx);
+       step = 3;
+       for (i = 0; i < len; i += step)
+               sha1_update(ctx, str+i,
+                           (i + step <= len)
+                           ? (step) : (len - i));
+       sha1_final(res2, ctx);
+
+       if (memcmp(res, res2, SHA1_DIGEST_LENGTH) != 0)
+               return "FAIL";
+       
+       return mkhex(res, SHA1_DIGEST_LENGTH);
+}
+
+static void test_sha1(void *ptr)
+{
+       str_check(run_sha1(""), "da39a3ee5e6b4b0d3255bfef95601890afd80709");
+       str_check(run_sha1("a"), "86f7e437faa5a7fce15d1ddcb9eaeaea377667b8");
+       str_check(run_sha1("abc"), "a9993e364706816aba3e25717850c26c9cd0d89d");
+       str_check(run_sha1("message digest"), "c12252ceda8be8994d5fa0290a47231c1d16aae3");
+       str_check(run_sha1("abcdefghijklmnopqrstuvwxyz"), "32d10c7b8cf96570ca04ce37f2a19d84240d3a89");
+       str_check(run_sha1("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), "761c457bf73b14d27e9e9265c46f4b4dda11f940");
+       str_check(run_sha1("12345678901234567890123456789012345678901234567890123456789012345678901234567890"), "50abf5706a150990a08b2c5ea40fa0e585554732");
+end:;
+}
+
+/*
+ * Laucher.
+ */
+
+struct testcase_t crypto_tests[] = {
+       { "md5", test_md5 },
+       { "sha1", test_sha1 },
+       END_OF_TESTCASES
+};
+
diff --git a/test/test_heap.c b/test/test_heap.c
new file mode 100644 (file)
index 0000000..750d178
--- /dev/null
@@ -0,0 +1,225 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "test_common.h"
+
+static void my_save_pos(void *p, unsigned i);
+
+#define SAVE_POS(p, i) my_save_pos(p, i)
+
+#include <usual/heap-impl.h>
+
+struct MyNode {
+       int value;
+       unsigned heap_idx;
+};
+
+/* min-heap */
+static inline bool heap_is_better(const void *a, const void *b)
+{
+       const struct MyNode *aa = a, *bb = b;
+       return (aa->value < bb->value);
+}
+
+static void my_save_pos(void *p, unsigned i)
+{
+       struct MyNode *node = p;
+       node->heap_idx = i;
+}
+
+static char *OK = "OK";
+
+static struct MyNode *make_node(int v)
+{
+       struct MyNode *n = malloc(sizeof(*n));
+       n->value = v;
+       n->heap_idx = -1;
+       return n;
+}
+
+/*
+ * Test tree sanity
+ */
+
+static const char *mkerr(const char *msg, unsigned idx, int val)
+{
+       static char buf[128];
+       snprintf(buf, sizeof(buf), "%s: idx=%d curval=%d", msg, idx, val);
+       return buf;
+}
+
+static const char *check_sub(struct Heap *heap, unsigned idx, int i)
+{
+       unsigned c0 = _heap_get_child(idx, 0);
+       unsigned c1 = _heap_get_child(idx, 1);
+       struct MyNode *n;
+       const char *res;
+
+       if (idx >= heap->used)
+               return OK;
+
+       n = heap->data[idx];
+       if (n->heap_idx != idx)
+               return mkerr("wrong saved idx", idx, i);
+
+       if (c0 < heap->used && _heap_is_better(heap, c0, idx))
+               return mkerr("c0 wrong order", idx, i);
+       if (c1 < heap->used && _heap_is_better(heap, c1, idx))
+               return mkerr("c1 wrong order", idx, i);
+
+       res = check_sub(heap, c0, i);
+       if (res == OK)
+               res = check_sub(heap, c1, i);
+       return res;
+}
+
+static const char *check(struct Heap *heap, int i)
+{
+       return check_sub(heap, 0, i);
+}
+
+/*
+ * checking operations
+ */
+
+static const char *my_insert(struct Heap *heap, int value)
+{
+       struct MyNode *my = make_node(value);
+       if (!heap_insert(heap, my))
+               return "FAIL";
+       return check(heap, value);
+}
+
+static const char *my_remove(struct Heap *heap, unsigned idx)
+{
+       struct MyNode *n;
+       if (idx >= heap->used)
+               return "NEXIST";
+       n = heap->data[idx];
+       heap_delete_pos(heap, idx);
+       free(n);
+       return check(heap, 0);
+}
+
+static const char *my_clean(struct Heap *heap)
+{
+       const char *res;
+       while (heap->used > 0) {
+               res = my_remove(heap, 0);
+               if (res != OK)
+                       return res;
+       }
+       return OK;
+}
+
+/*
+ * Simple operations.
+ */
+
+static void test_heap_basic(void *p)
+{
+       struct Heap heap[1];
+       int i;
+
+       heap_init(heap);
+
+       str_check(my_remove(heap, 0), "NEXIST");
+       str_check(my_insert(heap, 0), "OK");
+       str_check(my_remove(heap, 0), "OK");
+
+       for (i = 0; i < 15; i++) {
+               str_check(my_insert(heap, i), "OK");
+       }
+       str_check(my_clean(heap), "OK");
+
+       for (i = -1; i > -15; i--) {
+               str_check(my_insert(heap, i), "OK");
+       }
+       str_check(my_clean(heap), "OK");
+       for (i = 30; i < 45; i++) {
+               str_check(my_insert(heap, i), "OK");
+       }
+       str_check(my_clean(heap), "OK");
+       for (i = 15; i < 30; i++) {
+               str_check(my_insert(heap, i), "OK");
+       }
+       str_check(my_clean(heap), "OK");
+end:
+       heap_destroy(heap);
+}
+
+#if 0
+/*
+ * randomized test
+ */
+
+#define RSIZE 3000
+
+static int get_next(bool with_stat, bool added[])
+{
+       int r = random() % RSIZE;
+       int i = r;
+       while (1) {
+               if (added[i] == with_stat)
+                       return i;
+               if (++i >= RSIZE)
+                       i = 0;
+               if (i == r)
+                       return -1;
+       }
+}
+
+static void test_aatree_random(void *p)
+{
+       bool is_added[RSIZE];
+       int prefer_remove = 0; // 0 - insert, 1 - delete
+       int n;
+       int op; // 0 - insert, 1 - delete
+       struct AATree tree[1];
+       unsigned long long total = 0;
+
+       //printf("\n\n*** rand test ***\n\n");
+       srandom(123123);
+       memset(is_added, 0, sizeof(is_added));
+
+       aatree_init(tree, my_node_cmp, my_node_free);
+       while (total < 100000) {
+               int r = random() & 15;
+               if (prefer_remove)
+                       op = r > 5;
+               else
+                       op = r > 10;
+               //op = 0;
+
+               n = get_next(op, is_added);
+               if (n < 0) {
+                       //break;
+                       if (prefer_remove == op) {
+                               prefer_remove = !prefer_remove;
+                               //printf("** toggling remove to %d\n", prefer_remove);
+                       }
+                       continue;
+               }
+
+               if (op == 0) {
+                       str_check(my_insert(tree, n), "OK");
+                       is_added[n] = 1;
+               } else {
+                       str_check(my_remove(tree, n), "OK");
+                       is_added[n] = 0;
+               }
+               total++;
+       }
+end:
+       aatree_destroy(tree);
+}
+#endif
+
+struct testcase_t heap_tests[] = {
+       { "basic", test_heap_basic },
+       //{ "random", test_aatree_random },
+       END_OF_TESTCASES
+};
+
diff --git a/test/test_list.c b/test/test_list.c
new file mode 100644 (file)
index 0000000..7a1f40c
--- /dev/null
@@ -0,0 +1,154 @@
+
+#include "test_common.h"
+
+#include <usual/list.h>
+#include <usual/mempool.h>
+
+struct MyNode {
+       struct List node;
+       int val;
+       int seq;
+};
+
+static int my_cmp(const struct List *a, const struct List *b)
+{
+       struct MyNode *aa, *bb;
+       aa = container_of(a, struct MyNode, node);
+       bb = container_of(b, struct MyNode, node);
+       if (aa->val < bb->val) return -1;
+       if (aa->val > bb->val) return 1;
+       return 0;
+}
+
+static bool check_list(struct List *list, int n)
+{
+       struct List *old, *cur;
+       int i;
+
+       old = NULL;
+       i = 0;
+       for (cur = list->next; cur != list; cur = cur->next) {
+               i++;
+               if (old) {
+                       struct MyNode *mcur, *mold;
+                       mcur = container_of(cur, struct MyNode, node);
+                       mold = container_of(old, struct MyNode, node);
+                       if (mold->val > mcur->val) {
+                               printf("bad order(%d): old.val=%d new.val=%d", n, mold->val, mcur->val);
+                               return false;
+                       }
+                       if (mold->val == mcur->val && mold->seq > mcur->seq) {
+                               printf("unstable(%d): old.seq=%d new.seq=%d", n, mold->seq, mcur->seq);
+                               return false;
+                       }
+                       if (cur->prev != old) {
+                               printf("llist err 2 (n=%d)", n);
+                               return false;
+                       }
+               } else {
+                       if (cur->prev != list) {
+                               printf("llist err (n=%d)", n);
+                               return false;
+                       }
+               }
+               old = cur;
+       }
+       if (list->prev != ((old) ? old : list)) {
+               printf("llist err 3 (n=%d)", n);
+               return false;
+       }
+       if (i != n) {
+               printf("llist err 3 (n=%d)", n);
+               return false;
+       }
+       return true;
+}
+
+static bool test_sort(void (*sort)(struct List *list, list_cmp_f cmp), int n)
+{
+       struct MemPool *pool = NULL;
+       struct List list[1];
+       bool ok;
+       int i;
+       
+       /* random */
+       list_init(list);
+       for (i = 0; i < n; i++) {
+               struct MyNode *e = mempool_alloc(&pool, sizeof(*e));
+               list_init(&e->node);
+               e->val = random() % 100;
+               e->seq = i;
+               list_append(list, &e->node);
+       }
+       sort(list, my_cmp);
+       ok = check_list(list, n);
+       mempool_destroy(&pool);
+       if (!ok)
+               return false;
+
+       /* seq */
+       list_init(list);
+       for (i = 0; i < n; i++) {
+               struct MyNode *e = mempool_alloc(&pool, sizeof(*e));
+               list_init(&e->node);
+               e->val = i;
+               e->seq = i;
+               list_append(list, &e->node);
+       }
+       sort(list, my_cmp);
+       ok = check_list(list, n);
+       mempool_destroy(&pool);
+       if (!ok)
+               return false;
+
+       /* reverse */
+       list_init(list);
+       for (i = 0; i < n; i++) {
+               struct MyNode *e = mempool_alloc(&pool, sizeof(*e));
+               list_init(&e->node);
+               e->val = -i;
+               e->seq = i;
+               list_append(list, &e->node);
+       }
+       sort(list, my_cmp);
+       ok = check_list(list, n);
+       mempool_destroy(&pool);
+       if (!ok)
+               return false;
+
+       return true;
+}
+
+static void test_list_sort(void *p)
+{
+       int i;
+       for (i = 0; i < 259; i++)
+               tt_assert(test_sort(list_sort, i));
+end:;
+}
+
+#if 0
+static void test_list_sort2(void *p)
+{
+       int i;
+       for (i = 0; i < 259; i++)
+               tt_assert(test_sort(list_sort2, i));
+end:;
+}
+
+static void test_list_sort3(void *p)
+{
+       int i;
+       for (i = 0; i < 259; i++)
+               tt_assert(test_sort(list_sort3, i));
+end:;
+}
+#endif
+
+struct testcase_t list_tests[] = {
+       { "sort", test_list_sort },
+       //{ "sort2", test_list_sort2 },
+       //{ "sort3", test_list_sort3 },
+       END_OF_TESTCASES
+};
+
diff --git a/test/test_string.c b/test/test_string.c
new file mode 100644 (file)
index 0000000..5967350
--- /dev/null
@@ -0,0 +1,207 @@
+
+#include <usual/string.h>
+
+#include "test_common.h"
+
+/*
+ * strlcpy
+ */
+
+static char *run_strlcpy(char *dst, const char *src, int size, int expres)
+{
+       int res;
+       strcpy(dst, "XXX");
+       res = strlcpy(dst, src, size);
+       if (res != expres)
+               return "FAIL";
+       return dst;
+}
+
+static void test_strlcpy(void *ptr)
+{
+       char buf[128];
+       str_check(run_strlcpy(buf, "", 16, 0), "");
+       str_check(run_strlcpy(buf, "", 0, 0), "XXX");
+       str_check(run_strlcpy(buf, "", 16, 0), "");
+       str_check(run_strlcpy(buf, "abc", 16, 3), "abc");
+       str_check(run_strlcpy(buf, "abc", 4, 3), "abc");
+       str_check(run_strlcpy(buf, "abc", 3, 3), "ab");
+       str_check(run_strlcpy(buf, "abc", 2, 3), "a");
+       str_check(run_strlcpy(buf, "abc", 1, 3), "");
+       str_check(run_strlcpy(buf, "abc", 0, 3), "XXX");
+end:;
+}
+
+/*
+ * strlcat
+ */
+
+static char *run_strlcat(char *dst, const char *src, int size, int expres)
+{
+       int res;
+       strcpy(dst, "PFX");
+       res = strlcat(dst, src, size);
+       if (res != expres)
+               return "FAIL";
+       return dst;
+}
+
+static void test_strlcat(void *ptr)
+{
+       char buf[128];
+       str_check(run_strlcat(buf, "", 16, 3), "PFX");
+       str_check(run_strlcat(buf, "abc", 16, 6), "PFXabc");
+       str_check(run_strlcat(buf, "abc", 7, 6), "PFXabc");
+       str_check(run_strlcat(buf, "abc", 6, 6), "PFXab");
+       str_check(run_strlcat(buf, "abc", 5, 6), "PFXa");
+       str_check(run_strlcat(buf, "abc", 4, 6), "PFX");
+       str_check(run_strlcat(buf, "abc", 3, 6), "PFX");
+       str_check(run_strlcat(buf, "abc", 2, 5), "PFX");
+       str_check(run_strlcat(buf, "abc", 1, 4), "PFX");
+       str_check(run_strlcat(buf, "abc", 0, 3), "PFX");
+end:;
+}
+
+/*
+ * strerror_r()
+ */
+
+static void test_strerror_r(void *p)
+{
+       char buf[128];
+       /* "int" vs. "const char *" */
+       tt_assert(strerror_r(EINTR, buf, sizeof(buf)) != 0);
+       tt_assert(strlen(strerror_r(EINTR, buf, sizeof(buf))) != 0);
+end:;
+}
+
+/*
+ * fls
+ */
+
+static void test_fls(void *p)
+{
+       int_check(fls(0), 0);
+       int_check(fls(1), 1);
+       int_check(fls(3), 2);
+       int_check(fls((int)-1), 32);
+end:;
+}
+
+static void test_flsl(void *p)
+{
+       int_check(flsl(0), 0);
+       int_check(flsl(1), 1);
+       int_check(flsl(3), 2);
+       if (sizeof(long) == 4)
+               int_check(flsl((long)-1), 32);
+       else
+               int_check(flsl((long)-1), 64);
+end:;
+}
+
+static void test_flsll(void *p)
+{
+       int_check(flsll(0), 0);
+       int_check(flsll(1), 1);
+       int_check(flsll(3), 2);
+       int_check(flsll((long long)-1), 64);
+end:;
+}
+
+/*
+ * memrchr
+ */
+
+static void test_memrchr(void *p)
+{
+       static const char data[] = "abcdabc";
+       tt_assert(memrchr(data, 'a', 8) == data + 4);
+       tt_assert(memrchr(data, 'a', 4) == data + 0);
+       tt_assert(memrchr(data, 'd', 8) == data + 3);
+       tt_assert(memrchr(data, 'x', 8) == NULL);
+end:;
+}
+
+/*
+ * basename
+ */
+
+static const char *run_basename(const char *path)
+{
+       char buf[128];
+       const char *res;
+       if (!path)
+               return basename(path);
+       strlcpy(buf, path, sizeof(buf));
+       res = basename(buf);
+       if (strcmp(buf, path) != 0)
+               return "MODIFIES";
+       return res;
+}
+
+static void test_basename(void *p)
+{
+       str_check(run_basename("/usr/lib"), "lib");
+       str_check(run_basename("/usr/"), "usr");
+       str_check(run_basename("/"), "/");
+       str_check(run_basename("///"), "/");
+       str_check(run_basename("//usr//lib//"), "lib");
+       str_check(run_basename(""), ".");
+       str_check(run_basename("a/"), "a");
+       str_check(run_basename(NULL), ".");
+end:;
+}
+
+/*
+ * dirname
+ */
+
+static const char *run_dirname(const char *path)
+{
+       char buf[128];
+       const char *res;
+       if (!path)
+               return dirname(path);
+       strlcpy(buf, path, sizeof(buf));
+       res = dirname(buf);
+       if (strcmp(buf, path) != 0)
+               return "MODIFIES";
+       return res;
+}
+
+static void test_dirname(void *p)
+{
+       str_check(run_dirname("/usr/lib"), "/usr");
+       str_check(run_dirname("/usr/"), "/");
+       str_check(run_dirname("usr"), ".");
+       str_check(run_dirname("/usr/"), "/");
+       str_check(run_dirname("/"), "/");
+       str_check(run_dirname("/"), "/");
+       str_check(run_dirname(".."), ".");
+       str_check(run_dirname("."), ".");
+       str_check(run_dirname(""), ".");
+       str_check(run_dirname("a/"), ".");
+       str_check(run_dirname("a//"), ".");
+       str_check(run_dirname(NULL), ".");
+end:;
+}
+
+
+/*
+ * Describe
+ */
+
+struct testcase_t string_tests[] = {
+       { "strlcpy", test_strlcpy },
+       { "strlcat", test_strlcat },
+       { "strerror_r", test_strerror_r },
+       { "fls", test_fls },
+       { "flsl", test_flsl },
+       { "flsll", test_flsll },
+       { "memrchr", test_memrchr },
+       { "basename", test_basename },
+       { "dirname", test_dirname },
+       END_OF_TESTCASES
+};
+
diff --git a/test/test_strpool.c b/test/test_strpool.c
new file mode 100644 (file)
index 0000000..ca05546
--- /dev/null
@@ -0,0 +1,64 @@
+
+#include <usual/strpool.h>
+
+#include "test_common.h"
+
+#include <string.h>
+
+static void test_strpool(void *p)
+{
+       struct StrPool *pool;
+       struct PStr *s;
+
+       pool = strpool_create();
+       tt_assert(pool);
+       strpool_free(pool);
+
+       pool = strpool_create();
+       tt_assert(pool);
+       int_check(strpool_total(pool), 0);
+
+       s = strpool_get(pool, "foo", -1);
+       str_check(s->str, "foo");
+       int_check(s->refcnt, 1);
+       int_check(s->len, 3);
+       int_check(strpool_total(pool), 1);
+
+       tt_assert(s == strpool_get(pool, "fooTAIL", 3));
+       int_check(s->refcnt, 2);
+       int_check(strpool_total(pool), 1);
+
+       strpool_incref(s);
+       int_check(s->refcnt, 3);
+
+       strpool_decref(s);
+       int_check(s->refcnt, 2);
+       strpool_decref(s);
+       int_check(s->refcnt, 1);
+       int_check(strpool_total(pool), 1);
+       strpool_decref(s);
+       int_check(strpool_total(pool), 0);
+
+       strpool_free(pool);
+
+       /* free strc with strings */
+       pool = strpool_create();
+       tt_assert(pool);
+       s = strpool_get(pool, "foo", -1);
+       s = strpool_get(pool, "bar", 3);
+       int_check(strpool_total(pool), 2);
+       strpool_free(pool);
+
+end:;
+}
+
+
+/*
+ * Describe
+ */
+
+struct testcase_t strpool_tests[] = {
+       { "strpool", test_strpool },
+       END_OF_TESTCASES
+};
+
diff --git a/test/test_utf8.c b/test/test_utf8.c
new file mode 100644 (file)
index 0000000..72f2ba8
--- /dev/null
@@ -0,0 +1,109 @@
+
+#include <usual/utf8.h>
+
+#include "test_common.h"
+
+#include <stdarg.h>
+
+static int uget1(int a)
+{
+       char buf[2] = { a, 0 };
+       const char *p = buf;
+       return utf8_get_char(&p, buf + 1);
+}
+
+static int uget2(int a, int b)
+{
+       char buf[3] = { a, b, 0 };
+       const char *p = buf;
+       return utf8_get_char(&p, buf + 2);
+}
+
+static int uget3(int a, int b, int c)
+{
+       char buf[4] = { a, b, c, 0 };
+       const char *p = buf;
+       return utf8_get_char(&p, buf + 3);
+}
+
+static int uget4(int a, int b, int c, int d)
+{
+       char buf[5] = { a, b, c, d, 0 };
+       const char *p = buf;
+       return utf8_get_char(&p, buf + 4);
+}
+
+
+static void test_utf8_char_size(void *p)
+{
+       int_check(utf8_char_size(0), 1);
+       int_check(utf8_char_size('a'), 1);
+       int_check(utf8_char_size(0x7F), 1);
+       int_check(utf8_char_size(0x80), 2);
+       int_check(utf8_char_size(0x7FF), 2);
+       int_check(utf8_char_size(0x800), 3);
+       int_check(utf8_char_size(0xFFFF), 3);
+       int_check(utf8_char_size(0x10000), 4);
+       int_check(utf8_char_size(0x100000), 4);
+       int_check(utf8_char_size(0x10FFFF), 4);
+end:;
+}
+
+static void test_utf8_seq_size(void *p)
+{
+       int_check(utf8_seq_size(0), 1);
+       int_check(utf8_seq_size(0x7F), 1);
+       int_check(utf8_seq_size(0x80), 0);
+       int_check(utf8_seq_size(0xBF), 0);
+       int_check(utf8_seq_size(0xC0), 0);
+       int_check(utf8_seq_size(0xC1), 0);
+       int_check(utf8_seq_size(0xC2), 2);
+       int_check(utf8_seq_size(0xDF), 2);
+       int_check(utf8_seq_size(0xE0), 3);
+       int_check(utf8_seq_size(0xEF), 3);
+       int_check(utf8_seq_size(0xF0), 4);
+       int_check(utf8_seq_size(0xF4), 4);
+       int_check(utf8_seq_size(0xF5), 0);
+       int_check(utf8_seq_size(0xFF), 0);
+end:;
+}
+
+static void test_utf8_get_char(void *p)
+{
+       int_check(uget1(0), 0);
+       int_check(uget1(0x7F), 0x7F);
+       int_check(uget2(0xC2, 0xA2), 0xA2);
+       int_check(uget2(0xC2, 0xA2), 0xA2);
+       int_check(uget3(0xE2, 0x82, 0xAC), 0x20AC);
+       int_check(uget4(0xF0, 0xA4, 0xAD, 0xA2), 0x024B62);
+
+       /* invalid reads */
+       int_check(uget1(0x80), -0x80);
+       int_check(uget1(0xC1), -0xC1);
+       int_check(uget3(0xE0, 0x82, 0xAC), -0xE0);
+
+       /* short reads */
+       int_check(uget1(0xC2), -0xC2);
+       int_check(uget2(0xE2, 0x82), -0xE2);
+       int_check(uget3(0xF0, 0xA4, 0xAD), -0xF0);
+end:;
+}
+
+static void test_utf8_put_char(void *p)
+{
+       int_check(uget1(0), 0);
+end:;
+}
+
+/*
+ * Describe
+ */
+
+struct testcase_t utf8_tests[] = {
+       { "utf8_char_size", test_utf8_char_size },
+       { "utf8_seq_size", test_utf8_seq_size },
+       { "utf8_get_char", test_utf8_get_char },
+       { "utf8_put_char", test_utf8_put_char },
+       END_OF_TESTCASES
+};
+
diff --git a/test/tinytest.c b/test/tinytest.c
new file mode 100644 (file)
index 0000000..48ee0f0
--- /dev/null
@@ -0,0 +1,366 @@
+/* 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 <winsock2.h>
+#include <windows.h>
+#else
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#endif
+
+#include <usual/socket.h>
+#define evutil_snprintf snprintf
+
+#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 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] ");
+
+       evutil_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 = 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 = 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;
+       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) {
+                       evutil_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/test/tinytest.h b/test/tinytest.h
new file mode 100644 (file)
index 0000000..a0cb913
--- /dev/null
@@ -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/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..d989de9
--- /dev/null
@@ -0,0 +1,145 @@
+/* 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_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_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
index f615e775c6ace7c32bb53da6836bb3d9008e5a32..5a905c6b1092ad5f06c6917948fc47c754050bed 100644 (file)
 #ifndef _USUAL_BASE_H_
 #define _USUAL_BASE_H_
 
+#ifdef USUAL_TEST_CONFIG
+#include "test_config.h"
+#else
 #include <usual/config.h>
+#endif
 
 #include <sys/types.h>
 #include <sys/param.h>