You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucy.apache.org by nw...@apache.org on 2013/05/28 19:47:39 UTC

[lucy-commits] [06/19] Rework Clownfish test harness

http://git-wip-us.apache.org/repos/asf/lucy/blob/40220b9e/core/Clownfish/Test/TestVArray.c
----------------------------------------------------------------------
diff --git a/core/Clownfish/Test/TestVArray.c b/core/Clownfish/Test/TestVArray.c
index 110c01b..f342763 100644
--- a/core/Clownfish/Test/TestVArray.c
+++ b/core/Clownfish/Test/TestVArray.c
@@ -25,20 +25,14 @@
 #include "Clownfish/Err.h"
 #include "Clownfish/Num.h"
 #include "Clownfish/Test.h"
-#include "Clownfish/TestHarness/TestFormatter.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
 #include "Clownfish/TestHarness/TestUtils.h"
 #include "Clownfish/VArray.h"
 #include "Clownfish/VTable.h"
 
 TestVArray*
-TestVArray_new(TestFormatter *formatter) {
-    TestVArray *self = (TestVArray*)VTable_Make_Obj(TESTVARRAY);
-    return TestVArray_init(self, formatter);
-}
-
-TestVArray*
-TestVArray_init(TestVArray *self, TestFormatter *formatter) {
-    return (TestVArray*)TestBatch_init((TestBatch*)self, 45, formatter);
+TestVArray_new() {
+    return (TestVArray*)VTable_Make_Obj(TESTVARRAY);
 }
 
 static CharBuf*
@@ -47,38 +41,38 @@ S_new_cb(const char *text) {
 }
 
 static void
-test_Equals(TestBatch *batch) {
+test_Equals(TestBatchRunner *runner) {
     VArray *array = VA_new(0);
     VArray *other = VA_new(0);
     ZombieCharBuf *stuff = ZCB_WRAP_STR("stuff", 5);
 
-    TEST_TRUE(batch, VA_Equals(array, (Obj*)other),
+    TEST_TRUE(runner, VA_Equals(array, (Obj*)other),
               "Empty arrays are equal");
 
     VA_Push(array, (Obj*)CFISH_TRUE);
-    TEST_FALSE(batch, VA_Equals(array, (Obj*)other),
+    TEST_FALSE(runner, VA_Equals(array, (Obj*)other),
                "Add one elem and Equals returns false");
 
     VA_Push(other, (Obj*)CFISH_TRUE);
-    TEST_TRUE(batch, VA_Equals(array, (Obj*)other),
+    TEST_TRUE(runner, VA_Equals(array, (Obj*)other),
               "Add a matching elem and Equals returns true");
 
     VA_Store(array, 2, (Obj*)CFISH_TRUE);
-    TEST_FALSE(batch, VA_Equals(array, (Obj*)other),
+    TEST_FALSE(runner, VA_Equals(array, (Obj*)other),
                "Add elem after a NULL and Equals returns false");
 
     VA_Store(other, 2, (Obj*)CFISH_TRUE);
-    TEST_TRUE(batch, VA_Equals(array, (Obj*)other),
+    TEST_TRUE(runner, VA_Equals(array, (Obj*)other),
               "Empty elems don't spoil Equals");
 
     VA_Store(other, 2, INCREF(stuff));
-    TEST_FALSE(batch, VA_Equals(array, (Obj*)other),
+    TEST_FALSE(runner, VA_Equals(array, (Obj*)other),
                "Non-matching value spoils Equals");
 
     VA_Excise(array, 1, 2); // removes empty elems
     VA_Delete(other, 1);    // leaves NULL in place of deleted elem
     VA_Delete(other, 2);
-    TEST_FALSE(batch, VA_Equals(array, (Obj*)other),
+    TEST_FALSE(runner, VA_Equals(array, (Obj*)other),
                "Empty trailing elements spoil Equals");
 
     DECREF(array);
@@ -86,63 +80,63 @@ test_Equals(TestBatch *batch) {
 }
 
 static void
-test_Store_Fetch(TestBatch *batch) {
+test_Store_Fetch(TestBatchRunner *runner) {
     VArray *array = VA_new(0);
     CharBuf *elem;
 
-    TEST_TRUE(batch, VA_Fetch(array, 2) == NULL, "Fetch beyond end");
+    TEST_TRUE(runner, VA_Fetch(array, 2) == NULL, "Fetch beyond end");
 
     VA_Store(array, 2, (Obj*)CB_newf("foo"));
     elem = (CharBuf*)CERTIFY(VA_Fetch(array, 2), CHARBUF);
-    TEST_INT_EQ(batch, 3, VA_Get_Size(array), "Store updates size");
-    TEST_TRUE(batch, CB_Equals_Str(elem, "foo", 3), "Store");
+    TEST_INT_EQ(runner, 3, VA_Get_Size(array), "Store updates size");
+    TEST_TRUE(runner, CB_Equals_Str(elem, "foo", 3), "Store");
 
     INCREF(elem);
-    TEST_INT_EQ(batch, 2, CB_Get_RefCount(elem),
+    TEST_INT_EQ(runner, 2, CB_Get_RefCount(elem),
                 "start with refcount of 2");
     VA_Store(array, 2, (Obj*)CB_newf("bar"));
-    TEST_INT_EQ(batch, 1, CB_Get_RefCount(elem),
+    TEST_INT_EQ(runner, 1, CB_Get_RefCount(elem),
                 "Displacing elem via Store updates refcount");
     DECREF(elem);
     elem = (CharBuf*)CERTIFY(VA_Fetch(array, 2), CHARBUF);
-    TEST_TRUE(batch, CB_Equals_Str(elem, "bar", 3), "Store displacement");
+    TEST_TRUE(runner, CB_Equals_Str(elem, "bar", 3), "Store displacement");
 
     DECREF(array);
 }
 
 static void
-test_Push_Pop_Shift_Unshift(TestBatch *batch) {
+test_Push_Pop_Shift_Unshift(TestBatchRunner *runner) {
     VArray *array = VA_new(0);
     CharBuf *elem;
 
-    TEST_INT_EQ(batch, VA_Get_Size(array), 0, "size starts at 0");
+    TEST_INT_EQ(runner, VA_Get_Size(array), 0, "size starts at 0");
     VA_Push(array, (Obj*)CB_newf("a"));
     VA_Push(array, (Obj*)CB_newf("b"));
     VA_Push(array, (Obj*)CB_newf("c"));
 
-    TEST_INT_EQ(batch, VA_Get_Size(array), 3, "size after Push");
-    TEST_TRUE(batch, NULL != CERTIFY(VA_Fetch(array, 2), CHARBUF), "Push");
+    TEST_INT_EQ(runner, VA_Get_Size(array), 3, "size after Push");
+    TEST_TRUE(runner, NULL != CERTIFY(VA_Fetch(array, 2), CHARBUF), "Push");
 
     elem = (CharBuf*)CERTIFY(VA_Shift(array), CHARBUF);
-    TEST_TRUE(batch, CB_Equals_Str(elem, "a", 1), "Shift");
-    TEST_INT_EQ(batch, VA_Get_Size(array), 2, "size after Shift");
+    TEST_TRUE(runner, CB_Equals_Str(elem, "a", 1), "Shift");
+    TEST_INT_EQ(runner, VA_Get_Size(array), 2, "size after Shift");
     DECREF(elem);
 
     elem = (CharBuf*)CERTIFY(VA_Pop(array), CHARBUF);
-    TEST_TRUE(batch, CB_Equals_Str(elem, "c", 1), "Pop");
-    TEST_INT_EQ(batch, VA_Get_Size(array), 1, "size after Pop");
+    TEST_TRUE(runner, CB_Equals_Str(elem, "c", 1), "Pop");
+    TEST_INT_EQ(runner, VA_Get_Size(array), 1, "size after Pop");
     DECREF(elem);
 
     VA_Unshift(array, (Obj*)CB_newf("foo"));
     elem = (CharBuf*)CERTIFY(VA_Fetch(array, 0), CHARBUF);
-    TEST_TRUE(batch, CB_Equals_Str(elem, "foo", 3), "Unshift");
-    TEST_INT_EQ(batch, VA_Get_Size(array), 2, "size after Shift");
+    TEST_TRUE(runner, CB_Equals_Str(elem, "foo", 3), "Unshift");
+    TEST_INT_EQ(runner, VA_Get_Size(array), 2, "size after Shift");
 
     DECREF(array);
 }
 
 static void
-test_Delete(TestBatch *batch) {
+test_Delete(TestBatchRunner *runner) {
     VArray *wanted = VA_new(5);
     VArray *got    = VA_new(5);
     uint32_t i;
@@ -153,34 +147,34 @@ test_Delete(TestBatch *batch) {
     VA_Store(wanted, 4, (Obj*)CB_newf("4", i));
     DECREF(VA_Delete(got, 2));
     DECREF(VA_Delete(got, 3));
-    TEST_TRUE(batch, VA_Equals(wanted, (Obj*)got), "Delete");
+    TEST_TRUE(runner, VA_Equals(wanted, (Obj*)got), "Delete");
 
     DECREF(wanted);
     DECREF(got);
 }
 
 static void
-test_Resize(TestBatch *batch) {
+test_Resize(TestBatchRunner *runner) {
     VArray *array = VA_new(3);
     uint32_t i;
 
     for (i = 0; i < 2; i++) { VA_Push(array, (Obj*)CB_newf("%u32", i)); }
-    TEST_INT_EQ(batch, VA_Get_Capacity(array), 3, "Start with capacity 3");
+    TEST_INT_EQ(runner, VA_Get_Capacity(array), 3, "Start with capacity 3");
 
     VA_Resize(array, 4);
-    TEST_INT_EQ(batch, VA_Get_Size(array), 4, "Resize up");
-    TEST_INT_EQ(batch, VA_Get_Capacity(array), 4,
+    TEST_INT_EQ(runner, VA_Get_Size(array), 4, "Resize up");
+    TEST_INT_EQ(runner, VA_Get_Capacity(array), 4,
                 "Resize changes capacity");
 
     VA_Resize(array, 2);
-    TEST_INT_EQ(batch, VA_Get_Size(array), 2, "Resize down");
-    TEST_TRUE(batch, VA_Fetch(array, 2) == NULL, "Resize down zaps elem");
+    TEST_INT_EQ(runner, VA_Get_Size(array), 2, "Resize down");
+    TEST_TRUE(runner, VA_Fetch(array, 2) == NULL, "Resize down zaps elem");
 
     DECREF(array);
 }
 
 static void
-test_Excise(TestBatch *batch) {
+test_Excise(TestBatchRunner *runner) {
     VArray *wanted = VA_new(5);
     VArray *got    = VA_new(5);
 
@@ -190,7 +184,7 @@ test_Excise(TestBatch *batch) {
     }
 
     VA_Excise(got, 7, 1);
-    TEST_TRUE(batch, VA_Equals(wanted, (Obj*)got),
+    TEST_TRUE(runner, VA_Equals(wanted, (Obj*)got),
               "Excise outside of range is no-op");
 
     VA_Excise(got, 2, 2);
@@ -198,18 +192,18 @@ test_Excise(TestBatch *batch) {
     DECREF(VA_Delete(wanted, 3));
     VA_Store(wanted, 2, VA_Delete(wanted, 4));
     VA_Resize(wanted, 3);
-    TEST_TRUE(batch, VA_Equals(wanted, (Obj*)got),
+    TEST_TRUE(runner, VA_Equals(wanted, (Obj*)got),
               "Excise multiple elems");
 
     VA_Excise(got, 2, 2);
     VA_Resize(wanted, 2);
-    TEST_TRUE(batch, VA_Equals(wanted, (Obj*)got),
+    TEST_TRUE(runner, VA_Equals(wanted, (Obj*)got),
               "Splicing too many elems truncates");
 
     VA_Excise(got, 0, 1);
     VA_Store(wanted, 0, VA_Delete(wanted, 1));
     VA_Resize(wanted, 1);
-    TEST_TRUE(batch, VA_Equals(wanted, (Obj*)got),
+    TEST_TRUE(runner, VA_Equals(wanted, (Obj*)got),
               "Excise first elem");
 
     DECREF(got);
@@ -217,7 +211,7 @@ test_Excise(TestBatch *batch) {
 }
 
 static void
-test_Push_VArray(TestBatch *batch) {
+test_Push_VArray(TestBatchRunner *runner) {
     VArray *wanted  = VA_new(0);
     VArray *got     = VA_new(0);
     VArray *scratch = VA_new(0);
@@ -228,7 +222,7 @@ test_Push_VArray(TestBatch *batch) {
     for (i = 2; i < 4; i++) { VA_Push(scratch, (Obj*)CB_newf("%u32", i)); }
 
     VA_Push_VArray(got, scratch);
-    TEST_TRUE(batch, VA_Equals(wanted, (Obj*)got), "Push_VArray");
+    TEST_TRUE(runner, VA_Equals(wanted, (Obj*)got), "Push_VArray");
 
     DECREF(wanted);
     DECREF(got);
@@ -236,17 +230,17 @@ test_Push_VArray(TestBatch *batch) {
 }
 
 static void
-test_Slice(TestBatch *batch) {
+test_Slice(TestBatchRunner *runner) {
     VArray *array = VA_new(0);
     for (uint32_t i = 0; i < 10; i++) { VA_Push(array, (Obj*)CB_newf("%u32", i)); }
     {
         VArray *slice = VA_Slice(array, 0, 10);
-        TEST_TRUE(batch, VA_Equals(array, (Obj*)slice), "Slice entire array");
+        TEST_TRUE(runner, VA_Equals(array, (Obj*)slice), "Slice entire array");
         DECREF(slice);
     }
     {
         VArray *slice = VA_Slice(array, 0, 11);
-        TEST_TRUE(batch, VA_Equals(array, (Obj*)slice),
+        TEST_TRUE(runner, VA_Equals(array, (Obj*)slice),
             "Exceed length");
         DECREF(slice);
     }
@@ -254,26 +248,26 @@ test_Slice(TestBatch *batch) {
         VArray *wanted = VA_new(0);
         VA_Push(wanted, (Obj*)CB_newf("9"));
         VArray *slice = VA_Slice(array, 9, 11);
-        TEST_TRUE(batch, VA_Equals(slice, (Obj*)wanted),
+        TEST_TRUE(runner, VA_Equals(slice, (Obj*)wanted),
             "Exceed length, start near end");
         DECREF(slice);
         DECREF(wanted);
     }
     {
         VArray *slice = VA_Slice(array, 0, 0);
-        TEST_TRUE(batch, VA_Get_Size(slice) == 0, "empty slice");
+        TEST_TRUE(runner, VA_Get_Size(slice) == 0, "empty slice");
         DECREF(slice);
     }
     {
         VArray *slice = VA_Slice(array, 20, 1);
-        TEST_TRUE(batch, VA_Get_Size(slice) ==  0, "exceed offset");
+        TEST_TRUE(runner, VA_Get_Size(slice) ==  0, "exceed offset");
         DECREF(slice);
     }
     {
         VArray *wanted = VA_new(0);
         VA_Push(wanted, (Obj*)CB_newf("9"));
         VArray *slice = VA_Slice(array, 9, UINT32_MAX - 1);
-        TEST_TRUE(batch, VA_Get_Size(slice) == 1, "guard against overflow");
+        TEST_TRUE(runner, VA_Get_Size(slice) == 1, "guard against overflow");
         DECREF(slice);
         DECREF(wanted);
     }
@@ -281,7 +275,7 @@ test_Slice(TestBatch *batch) {
 }
 
 static void
-test_Clone_and_Shallow_Copy(TestBatch *batch) {
+test_Clone_and_Shallow_Copy(TestBatchRunner *runner) {
     VArray *array = VA_new(0);
     VArray *twin;
     uint32_t i;
@@ -290,14 +284,14 @@ test_Clone_and_Shallow_Copy(TestBatch *batch) {
         VA_Push(array, (Obj*)CB_newf("%u32", i));
     }
     twin = VA_Shallow_Copy(array);
-    TEST_TRUE(batch, VA_Equals(array, (Obj*)twin), "Shallow_Copy");
-    TEST_TRUE(batch, VA_Fetch(array, 1) == VA_Fetch(twin, 1),
+    TEST_TRUE(runner, VA_Equals(array, (Obj*)twin), "Shallow_Copy");
+    TEST_TRUE(runner, VA_Fetch(array, 1) == VA_Fetch(twin, 1),
               "Shallow_Copy doesn't clone elements");
     DECREF(twin);
 
     twin = VA_Clone(array);
-    TEST_TRUE(batch, VA_Equals(array, (Obj*)twin), "Clone");
-    TEST_TRUE(batch, VA_Fetch(array, 1) != VA_Fetch(twin, 1),
+    TEST_TRUE(runner, VA_Equals(array, (Obj*)twin), "Clone");
+    TEST_TRUE(runner, VA_Fetch(array, 1) != VA_Fetch(twin, 1),
               "Clone performs deep clone");
 
     DECREF(array);
@@ -305,7 +299,7 @@ test_Clone_and_Shallow_Copy(TestBatch *batch) {
 }
 
 static void
-test_Dump_and_Load(TestBatch *batch) {
+test_Dump_and_Load(TestBatchRunner *runner) {
     VArray *array = VA_new(0);
     Obj    *dump;
     VArray *loaded;
@@ -313,7 +307,7 @@ test_Dump_and_Load(TestBatch *batch) {
     VA_Push(array, (Obj*)S_new_cb("foo"));
     dump = (Obj*)VA_Dump(array);
     loaded = (VArray*)Obj_Load(dump, dump);
-    TEST_TRUE(batch, VA_Equals(array, (Obj*)loaded),
+    TEST_TRUE(runner, VA_Equals(array, (Obj*)loaded),
               "Dump => Load round trip");
 
     DECREF(array);
@@ -322,32 +316,32 @@ test_Dump_and_Load(TestBatch *batch) {
 }
 
 static void
-test_serialization(TestBatch *batch) {
+test_serialization(TestBatchRunner *runner) {
     VArray *array = VA_new(0);
     VArray *dupe;
     VA_Store(array, 1, (Obj*)CB_newf("foo"));
     VA_Store(array, 3, (Obj*)CB_newf("bar"));
     dupe = (VArray*)TestUtils_freeze_thaw((Obj*)array);
-    TEST_TRUE(batch, dupe && VA_Equals(array, (Obj*)dupe),
+    TEST_TRUE(runner, dupe && VA_Equals(array, (Obj*)dupe),
               "Round trip through FREEZE/THAW");
     DECREF(dupe);
     DECREF(array);
 }
 
 void
-TestVArray_run_tests(TestVArray *self) {
-    TestBatch *batch = (TestBatch*)self;
-    test_Equals(batch);
-    test_Store_Fetch(batch);
-    test_Push_Pop_Shift_Unshift(batch);
-    test_Delete(batch);
-    test_Resize(batch);
-    test_Excise(batch);
-    test_Push_VArray(batch);
-    test_Slice(batch);
-    test_Clone_and_Shallow_Copy(batch);
-    test_Dump_and_Load(batch);
-    test_serialization(batch);
+TestVArray_run(TestVArray *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 45);
+    test_Equals(runner);
+    test_Store_Fetch(runner);
+    test_Push_Pop_Shift_Unshift(runner);
+    test_Delete(runner);
+    test_Resize(runner);
+    test_Excise(runner);
+    test_Push_VArray(runner);
+    test_Slice(runner);
+    test_Clone_and_Shallow_Copy(runner);
+    test_Dump_and_Load(runner);
+    test_serialization(runner);
 }
 
 

http://git-wip-us.apache.org/repos/asf/lucy/blob/40220b9e/core/Clownfish/Test/TestVArray.cfh
----------------------------------------------------------------------
diff --git a/core/Clownfish/Test/TestVArray.cfh b/core/Clownfish/Test/TestVArray.cfh
index 478127a..735fccb 100644
--- a/core/Clownfish/Test/TestVArray.cfh
+++ b/core/Clownfish/Test/TestVArray.cfh
@@ -20,13 +20,10 @@ class Clownfish::Test::TestVArray
     inherits Clownfish::TestHarness::TestBatch {
 
     inert incremented TestVArray*
-    new(TestFormatter *formatter);
-
-    inert TestVArray*
-    init(TestVArray *self, TestFormatter *formatter);
+    new();
 
     void
-    Run_Tests(TestVArray *self);
+    Run(TestVArray *self, TestBatchRunner *runner);
 }
 
 

http://git-wip-us.apache.org/repos/asf/lucy/blob/40220b9e/core/Clownfish/Test/Util/TestAtomic.c
----------------------------------------------------------------------
diff --git a/core/Clownfish/Test/Util/TestAtomic.c b/core/Clownfish/Test/Util/TestAtomic.c
index 6187198..d2cb408 100644
--- a/core/Clownfish/Test/Util/TestAtomic.c
+++ b/core/Clownfish/Test/Util/TestAtomic.c
@@ -20,52 +20,46 @@
 #include "Clownfish/Test/Util/TestAtomic.h"
 
 #include "Clownfish/Test.h"
-#include "Clownfish/TestHarness/TestFormatter.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
 #include "Clownfish/Util/Atomic.h"
 #include "Clownfish/VTable.h"
 
 TestAtomic*
-TestAtomic_new(TestFormatter *formatter) {
-    TestAtomic *self = (TestAtomic*)VTable_Make_Obj(TESTATOMIC);
-    return TestAtomic_init(self, formatter);
-}
-
-TestAtomic*
-TestAtomic_init(TestAtomic *self, TestFormatter *formatter) {
-    return (TestAtomic*)TestBatch_init((TestBatch*)self, 6, formatter);
+TestAtomic_new() {
+    return (TestAtomic*)VTable_Make_Obj(TESTATOMIC);
 }
 
 static void
-test_cas_ptr(TestBatch *batch) {
+test_cas_ptr(TestBatchRunner *runner) {
     int    foo = 1;
     int    bar = 2;
     int   *foo_pointer = &foo;
     int   *bar_pointer = &bar;
     int   *target      = NULL;
 
-    TEST_TRUE(batch,
+    TEST_TRUE(runner,
               Atomic_cas_ptr((void**)&target, NULL, foo_pointer),
               "cas_ptr returns true on success");
-    TEST_TRUE(batch, target == foo_pointer, "cas_ptr sets target");
+    TEST_TRUE(runner, target == foo_pointer, "cas_ptr sets target");
 
     target = NULL;
-    TEST_FALSE(batch,
+    TEST_FALSE(runner,
                Atomic_cas_ptr((void**)&target, bar_pointer, foo_pointer),
                "cas_ptr returns false when it old_value doesn't match");
-    TEST_TRUE(batch, target == NULL,
+    TEST_TRUE(runner, target == NULL,
               "cas_ptr doesn't do anything to target when old_value doesn't match");
 
     target = foo_pointer;
-    TEST_TRUE(batch,
+    TEST_TRUE(runner,
               Atomic_cas_ptr((void**)&target, foo_pointer, bar_pointer),
               "cas_ptr from one value to another");
-    TEST_TRUE(batch, target == bar_pointer, "cas_ptr sets target");
+    TEST_TRUE(runner, target == bar_pointer, "cas_ptr sets target");
 }
 
 void
-TestAtomic_run_tests(TestAtomic *self) {
-    TestBatch *batch = (TestBatch*)self;
-    test_cas_ptr(batch);
+TestAtomic_run(TestAtomic *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 6);
+    test_cas_ptr(runner);
 }
 
 

http://git-wip-us.apache.org/repos/asf/lucy/blob/40220b9e/core/Clownfish/Test/Util/TestAtomic.cfh
----------------------------------------------------------------------
diff --git a/core/Clownfish/Test/Util/TestAtomic.cfh b/core/Clownfish/Test/Util/TestAtomic.cfh
index 576eec9..2788342 100644
--- a/core/Clownfish/Test/Util/TestAtomic.cfh
+++ b/core/Clownfish/Test/Util/TestAtomic.cfh
@@ -20,13 +20,10 @@ class Clownfish::Test::Util::TestAtomic
     inherits Clownfish::TestHarness::TestBatch {
 
     inert incremented TestAtomic*
-    new(TestFormatter *formatter);
-
-    inert TestAtomic*
-    init(TestAtomic *self, TestFormatter *formatter);
+    new();
 
     void
-    Run_Tests(TestAtomic *self);
+    Run(TestAtomic *self, TestBatchRunner *runner);
 }
 
 

http://git-wip-us.apache.org/repos/asf/lucy/blob/40220b9e/core/Clownfish/Test/Util/TestMemory.c
----------------------------------------------------------------------
diff --git a/core/Clownfish/Test/Util/TestMemory.c b/core/Clownfish/Test/Util/TestMemory.c
index 0c58b99..24c8ad3 100644
--- a/core/Clownfish/Test/Util/TestMemory.c
+++ b/core/Clownfish/Test/Util/TestMemory.c
@@ -21,23 +21,17 @@
 #include "Clownfish/Test/Util/TestMemory.h"
 
 #include "Clownfish/Test.h"
-#include "Clownfish/TestHarness/TestFormatter.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
 #include "Clownfish/Util/Memory.h"
 #include "Clownfish/VTable.h"
 
 TestMemory*
-TestMemory_new(TestFormatter *formatter) {
-    TestMemory *self = (TestMemory*)VTable_Make_Obj(TESTMEMORY);
-    return TestMemory_init(self, formatter);
-}
-
-TestMemory*
-TestMemory_init(TestMemory *self, TestFormatter *formatter) {
-    return (TestMemory*)TestBatch_init((TestBatch*)self, 30, formatter);
+TestMemory_new() {
+    return (TestMemory*)VTable_Make_Obj(TESTMEMORY);
 }
 
 static void
-test_oversize__growth_rate(TestBatch *batch) {
+test_oversize__growth_rate(TestBatchRunner *runner) {
     bool     success             = true;
     uint64_t size                = 0;
     double   growth_count        = 0;
@@ -47,7 +41,7 @@ test_oversize__growth_rate(TestBatch *batch) {
         uint64_t next_size = Memory_oversize((size_t)size + 1, sizeof(void*));
         if (next_size < size) {
             success = false;
-            FAIL(batch, "Asked for %" PRId64 ", got smaller amount %" PRId64,
+            FAIL(runner, "Asked for %" PRId64 ", got smaller amount %" PRId64,
                  size + 1, next_size);
             break;
         }
@@ -58,7 +52,7 @@ test_oversize__growth_rate(TestBatch *batch) {
             double sum = growth_rate + (growth_count - 1) * average_growth_rate;
             average_growth_rate = sum / growth_count;
             if (average_growth_rate < 1.1) {
-                FAIL(batch, "Average growth rate dropped below 1.1x: %f",
+                FAIL(runner, "Average growth rate dropped below 1.1x: %f",
                      average_growth_rate);
                 success = false;
                 break;
@@ -66,9 +60,9 @@ test_oversize__growth_rate(TestBatch *batch) {
         }
         size = next_size;
     }
-    TEST_TRUE(batch, growth_count > 0, "Grew %f times", growth_count);
+    TEST_TRUE(runner, growth_count > 0, "Grew %f times", growth_count);
     if (success) {
-        TEST_TRUE(batch, average_growth_rate > 1.1,
+        TEST_TRUE(runner, average_growth_rate > 1.1,
                   "Growth rate of oversize() averages above 1.1: %.3f",
                   average_growth_rate);
     }
@@ -76,26 +70,26 @@ test_oversize__growth_rate(TestBatch *batch) {
     for (int minimum = 1; minimum < 8; minimum++) {
         uint64_t next_size = Memory_oversize(minimum, sizeof(void*));
         double growth_rate = U64_TO_DOUBLE(next_size) / (double)minimum;
-        TEST_TRUE(batch, growth_rate > 1.2,
+        TEST_TRUE(runner, growth_rate > 1.2,
                   "Growth rate is higher for smaller arrays (%d, %.3f)", minimum,
                   growth_rate);
     }
 }
 
 static void
-test_oversize__ceiling(TestBatch *batch) {
+test_oversize__ceiling(TestBatchRunner *runner) {
     for (int width = 0; width < 10; width++) {
         size_t size = Memory_oversize(SIZE_MAX, width);
-        TEST_TRUE(batch, size == SIZE_MAX,
+        TEST_TRUE(runner, size == SIZE_MAX,
                   "Memory_oversize hits ceiling at SIZE_MAX (width %d)", width);
         size = Memory_oversize(SIZE_MAX - 1, width);
-        TEST_TRUE(batch, size == SIZE_MAX,
+        TEST_TRUE(runner, size == SIZE_MAX,
                   "Memory_oversize hits ceiling at SIZE_MAX (width %d)", width);
     }
 }
 
 static void
-test_oversize__rounding(TestBatch *batch) {
+test_oversize__rounding(TestBatchRunner *runner) {
     int widths[] = { 1, 2, 4, 0 };
 
     for (int width_tick = 0; widths[width_tick] != 0; width_tick++) {
@@ -104,21 +98,21 @@ test_oversize__rounding(TestBatch *batch) {
             size_t size = Memory_oversize(i, width);
             size_t bytes = size * width;
             if (bytes % sizeof(void*) != 0) {
-                FAIL(batch, "Rounding failure for %d, width %d",
+                FAIL(runner, "Rounding failure for %d, width %d",
                      i, width);
                 return;
             }
         }
     }
-    PASS(batch, "Round allocations up to the size of a pointer");
+    PASS(runner, "Round allocations up to the size of a pointer");
 }
 
 void
-TestMemory_run_tests(TestMemory *self) {
-    TestBatch *batch = (TestBatch*)self;
-    test_oversize__growth_rate(batch);
-    test_oversize__ceiling(batch);
-    test_oversize__rounding(batch);
+TestMemory_run(TestMemory *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 30);
+    test_oversize__growth_rate(runner);
+    test_oversize__ceiling(runner);
+    test_oversize__rounding(runner);
 }
 
 

http://git-wip-us.apache.org/repos/asf/lucy/blob/40220b9e/core/Clownfish/Test/Util/TestMemory.cfh
----------------------------------------------------------------------
diff --git a/core/Clownfish/Test/Util/TestMemory.cfh b/core/Clownfish/Test/Util/TestMemory.cfh
index 6426935..d0b5803 100644
--- a/core/Clownfish/Test/Util/TestMemory.cfh
+++ b/core/Clownfish/Test/Util/TestMemory.cfh
@@ -20,13 +20,10 @@ class Clownfish::Test::Util::TestMemory
     inherits Clownfish::TestHarness::TestBatch {
 
     inert incremented TestMemory*
-    new(TestFormatter *formatter);
-
-    inert TestMemory*
-    init(TestMemory *self, TestFormatter *formatter);
+    new();
 
     void
-    Run_Tests(TestMemory *self);
+    Run(TestMemory *self, TestBatchRunner *runner);
 }
 
 

http://git-wip-us.apache.org/repos/asf/lucy/blob/40220b9e/core/Clownfish/Test/Util/TestNumberUtils.c
----------------------------------------------------------------------
diff --git a/core/Clownfish/Test/Util/TestNumberUtils.c b/core/Clownfish/Test/Util/TestNumberUtils.c
index 04c79f0..0ae476d 100644
--- a/core/Clownfish/Test/Util/TestNumberUtils.c
+++ b/core/Clownfish/Test/Util/TestNumberUtils.c
@@ -24,25 +24,19 @@
 
 #include "Clownfish/Err.h"
 #include "Clownfish/Test.h"
-#include "Clownfish/TestHarness/TestFormatter.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
 #include "Clownfish/TestHarness/TestUtils.h"
 #include "Clownfish/Util/Memory.h"
 #include "Clownfish/Util/NumberUtils.h"
 #include "Clownfish/VTable.h"
 
 TestNumberUtils*
-TestNumUtil_new(TestFormatter *formatter) {
-    TestNumberUtils *self = (TestNumberUtils*)VTable_Make_Obj(TESTNUMBERUTILS);
-    return TestNumUtil_init(self, formatter);
-}
-
-TestNumberUtils*
-TestNumUtil_init(TestNumberUtils *self, TestFormatter *formatter) {
-    return (TestNumberUtils*)TestBatch_init((TestBatch*)self, 1196, formatter);
+TestNumUtil_new() {
+    return (TestNumberUtils*)VTable_Make_Obj(TESTNUMBERUTILS);
 }
 
 static void
-test_u1(TestBatch *batch) {
+test_u1(TestBatchRunner *runner) {
     size_t    count   = 64;
     uint64_t *ints    = TestUtils_random_u64s(NULL, count, 0, 2);
     size_t    amount  = count / 8;
@@ -52,7 +46,7 @@ test_u1(TestBatch *batch) {
         if (ints[i]) { NumUtil_u1set(bits, i); }
     }
     for (size_t i = 0; i < count; i++) {
-        TEST_INT_EQ(batch, NumUtil_u1get(bits, i), (long)ints[i],
+        TEST_INT_EQ(runner, NumUtil_u1get(bits, i), (long)ints[i],
                     "u1 set/get");
     }
 
@@ -60,7 +54,7 @@ test_u1(TestBatch *batch) {
         NumUtil_u1flip(bits, i);
     }
     for (size_t i = 0; i < count; i++) {
-        TEST_INT_EQ(batch, NumUtil_u1get(bits, i), !ints[i], "u1 flip");
+        TEST_INT_EQ(runner, NumUtil_u1get(bits, i), !ints[i], "u1 flip");
     }
 
     FREEMEM(bits);
@@ -68,7 +62,7 @@ test_u1(TestBatch *batch) {
 }
 
 static void
-test_u2(TestBatch *batch) {
+test_u2(TestBatchRunner *runner) {
     size_t    count = 32;
     uint64_t *ints = TestUtils_random_u64s(NULL, count, 0, 4);
     uint8_t  *bits = (uint8_t*)CALLOCATE((count / 4), sizeof(uint8_t));
@@ -77,7 +71,7 @@ test_u2(TestBatch *batch) {
         NumUtil_u2set(bits, i, (uint8_t)ints[i]);
     }
     for (size_t i = 0; i < count; i++) {
-        TEST_INT_EQ(batch, NumUtil_u2get(bits, i), (long)ints[i], "u2");
+        TEST_INT_EQ(runner, NumUtil_u2get(bits, i), (long)ints[i], "u2");
     }
 
     FREEMEM(bits);
@@ -85,7 +79,7 @@ test_u2(TestBatch *batch) {
 }
 
 static void
-test_u4(TestBatch *batch) {
+test_u4(TestBatchRunner *runner) {
     size_t    count = 128;
     uint64_t *ints  = TestUtils_random_u64s(NULL, count, 0, 16);
     uint8_t  *bits  = (uint8_t*)CALLOCATE((count / 2), sizeof(uint8_t));
@@ -94,7 +88,7 @@ test_u4(TestBatch *batch) {
         NumUtil_u4set(bits, i, (uint8_t)ints[i]);
     }
     for (size_t i = 0; i < count; i++) {
-        TEST_INT_EQ(batch, NumUtil_u4get(bits, i), (long)ints[i], "u4");
+        TEST_INT_EQ(runner, NumUtil_u4get(bits, i), (long)ints[i], "u4");
     }
 
     FREEMEM(bits);
@@ -102,7 +96,7 @@ test_u4(TestBatch *batch) {
 }
 
 static void
-test_c32(TestBatch *batch) {
+test_c32(TestBatchRunner *runner) {
     uint64_t  mins[]   = { 0,   0x4000 - 100, (uint32_t)INT32_MAX - 100, UINT32_MAX - 10 };
     uint64_t  limits[] = { 500, 0x4000 + 100, (uint32_t)INT32_MAX + 100, UINT32_MAX      };
     uint32_t  set_num;
@@ -125,44 +119,44 @@ test_c32(TestBatch *batch) {
         target = encoded;
         skip   = encoded;
         for (size_t i = 0; i < count; i++) {
-            TEST_INT_EQ(batch, NumUtil_decode_c32(&target), (long)ints[i],
+            TEST_INT_EQ(runner, NumUtil_decode_c32(&target), (long)ints[i],
                         "c32 %lu", (long)ints[i]);
             NumUtil_skip_cint(&skip);
             if (target > limit) { THROW(ERR, "overrun"); }
         }
-        TEST_TRUE(batch, skip == target, "skip %lu == %lu",
+        TEST_TRUE(runner, skip == target, "skip %lu == %lu",
                   (unsigned long)skip, (unsigned long)target);
 
         target = encoded;
         for (size_t i = 0; i < count; i++) {
             NumUtil_encode_padded_c32((uint32_t)ints[i], &target);
         }
-        TEST_TRUE(batch, target == limit,
+        TEST_TRUE(runner, target == limit,
                   "padded c32 uses 5 bytes (%lu == %lu)", (unsigned long)target,
                   (unsigned long)limit);
         target = encoded;
         skip   = encoded;
         for (size_t i = 0; i < count; i++) {
-            TEST_INT_EQ(batch, NumUtil_decode_c32(&target), (long)ints[i],
+            TEST_INT_EQ(runner, NumUtil_decode_c32(&target), (long)ints[i],
                         "padded c32 %lu", (long)ints[i]);
             NumUtil_skip_cint(&skip);
             if (target > limit) { THROW(ERR, "overrun"); }
         }
-        TEST_TRUE(batch, skip == target, "skip padded %lu == %lu",
+        TEST_TRUE(runner, skip == target, "skip padded %lu == %lu",
                   (unsigned long)skip, (unsigned long)target);
     }
 
     target = encoded;
     NumUtil_encode_c32(UINT32_MAX, &target);
     target = encoded;
-    TEST_INT_EQ(batch, NumUtil_decode_c32(&target), UINT32_MAX, "c32 UINT32_MAX");
+    TEST_INT_EQ(runner, NumUtil_decode_c32(&target), UINT32_MAX, "c32 UINT32_MAX");
 
     FREEMEM(encoded);
     FREEMEM(ints);
 }
 
 static void
-test_c64(TestBatch *batch) {
+test_c64(TestBatchRunner *runner) {
     uint64_t  mins[]    = { 0,   0x4000 - 100, (uint64_t)UINT32_MAX - 100,  UINT64_MAX - 10 };
     uint64_t  limits[]  = { 500, 0x4000 + 100, (uint64_t)UINT32_MAX + 1000, UINT64_MAX      };
     uint32_t  set_num;
@@ -186,12 +180,12 @@ test_c64(TestBatch *batch) {
         skip   = encoded;
         for (size_t i = 0; i < count; i++) {
             uint64_t got = NumUtil_decode_c64(&target);
-            TEST_TRUE(batch, got == ints[i],
+            TEST_TRUE(runner, got == ints[i],
                       "c64 %" PRIu64 " == %" PRIu64, got, ints[i]);
             if (target > limit) { THROW(ERR, "overrun"); }
             NumUtil_skip_cint(&skip);
         }
-        TEST_TRUE(batch, skip == target, "skip %lu == %lu",
+        TEST_TRUE(runner, skip == target, "skip %lu == %lu",
                   (unsigned long)skip, (unsigned long)target);
     }
 
@@ -200,14 +194,14 @@ test_c64(TestBatch *batch) {
     target = encoded;
 
     uint64_t got = NumUtil_decode_c64(&target);
-    TEST_TRUE(batch, got == UINT64_MAX, "c64 UINT64_MAX");
+    TEST_TRUE(runner, got == UINT64_MAX, "c64 UINT64_MAX");
 
     FREEMEM(encoded);
     FREEMEM(ints);
 }
 
 static void
-test_bigend_u16(TestBatch *batch) {
+test_bigend_u16(TestBatchRunner *runner) {
     size_t    count     = 32;
     uint64_t *ints      = TestUtils_random_u64s(NULL, count, 0, UINT16_MAX + 1);
     size_t    amount    = (count + 1) * sizeof(uint16_t);
@@ -222,21 +216,21 @@ test_bigend_u16(TestBatch *batch) {
     target = encoded;
     for (size_t i = 0; i < count; i++) {
         uint16_t got = NumUtil_decode_bigend_u16(target);
-        TEST_INT_EQ(batch, got, (long)ints[i], "bigend u16");
+        TEST_INT_EQ(runner, got, (long)ints[i], "bigend u16");
         target += sizeof(uint16_t);
     }
 
     target = encoded;
     NumUtil_encode_bigend_u16(1, &target);
-    TEST_INT_EQ(batch, encoded[0], 0, "Truly big-endian u16");
-    TEST_INT_EQ(batch, encoded[1], 1, "Truly big-endian u16");
+    TEST_INT_EQ(runner, encoded[0], 0, "Truly big-endian u16");
+    TEST_INT_EQ(runner, encoded[1], 1, "Truly big-endian u16");
 
     FREEMEM(allocated);
     FREEMEM(ints);
 }
 
 static void
-test_bigend_u32(TestBatch *batch) {
+test_bigend_u32(TestBatchRunner *runner) {
     size_t    count     = 32;
     uint64_t *ints      = TestUtils_random_u64s(NULL, count, 0, UINT64_C(1) + UINT32_MAX);
     size_t    amount    = (count + 1) * sizeof(uint32_t);
@@ -251,21 +245,21 @@ test_bigend_u32(TestBatch *batch) {
     target = encoded;
     for (size_t i = 0; i < count; i++) {
         uint32_t got = NumUtil_decode_bigend_u32(target);
-        TEST_INT_EQ(batch, got, (long)ints[i], "bigend u32");
+        TEST_INT_EQ(runner, got, (long)ints[i], "bigend u32");
         target += sizeof(uint32_t);
     }
 
     target = encoded;
     NumUtil_encode_bigend_u32(1, &target);
-    TEST_INT_EQ(batch, encoded[0], 0, "Truly big-endian u32");
-    TEST_INT_EQ(batch, encoded[3], 1, "Truly big-endian u32");
+    TEST_INT_EQ(runner, encoded[0], 0, "Truly big-endian u32");
+    TEST_INT_EQ(runner, encoded[3], 1, "Truly big-endian u32");
 
     FREEMEM(allocated);
     FREEMEM(ints);
 }
 
 static void
-test_bigend_u64(TestBatch *batch) {
+test_bigend_u64(TestBatchRunner *runner) {
     size_t    count     = 32;
     uint64_t *ints      = TestUtils_random_u64s(NULL, count, 0, UINT64_MAX);
     size_t    amount    = (count + 1) * sizeof(uint64_t);
@@ -280,21 +274,21 @@ test_bigend_u64(TestBatch *batch) {
     target = encoded;
     for (size_t i = 0; i < count; i++) {
         uint64_t got = NumUtil_decode_bigend_u64(target);
-        TEST_TRUE(batch, got == ints[i], "bigend u64");
+        TEST_TRUE(runner, got == ints[i], "bigend u64");
         target += sizeof(uint64_t);
     }
 
     target = encoded;
     NumUtil_encode_bigend_u64(1, &target);
-    TEST_INT_EQ(batch, encoded[0], 0, "Truly big-endian");
-    TEST_INT_EQ(batch, encoded[7], 1, "Truly big-endian");
+    TEST_INT_EQ(runner, encoded[0], 0, "Truly big-endian");
+    TEST_INT_EQ(runner, encoded[7], 1, "Truly big-endian");
 
     FREEMEM(allocated);
     FREEMEM(ints);
 }
 
 static void
-test_bigend_f32(TestBatch *batch) {
+test_bigend_f32(TestBatchRunner *runner) {
     float    source[]  = { -1.3f, 0.0f, 100.2f };
     size_t   count     = 3;
     size_t   amount    = (count + 1) * sizeof(float);
@@ -309,18 +303,18 @@ test_bigend_f32(TestBatch *batch) {
     target = encoded;
     for (size_t i = 0; i < count; i++) {
         float got = NumUtil_decode_bigend_f32(target);
-        TEST_TRUE(batch, got == source[i], "bigend f32");
+        TEST_TRUE(runner, got == source[i], "bigend f32");
         target += sizeof(float);
     }
 
     target = encoded;
     NumUtil_encode_bigend_f32(-2.0f, &target);
-    TEST_INT_EQ(batch, (encoded[0] & 0x80), 0x80,
+    TEST_INT_EQ(runner, (encoded[0] & 0x80), 0x80,
                 "Truly big-endian (IEEE 754 sign bit set for negative number)");
-    TEST_INT_EQ(batch, encoded[0], 0xC0,
+    TEST_INT_EQ(runner, encoded[0], 0xC0,
                 "IEEE 754 representation of -2.0f, byte 0");
     for (size_t i = 1; i < sizeof(float); i++) {
-        TEST_INT_EQ(batch, encoded[i], 0,
+        TEST_INT_EQ(runner, encoded[i], 0,
                     "IEEE 754 representation of -2.0f, byte %d", (int)i);
     }
 
@@ -328,7 +322,7 @@ test_bigend_f32(TestBatch *batch) {
 }
 
 static void
-test_bigend_f64(TestBatch *batch) {
+test_bigend_f64(TestBatchRunner *runner) {
     double   source[]  = { -1.3, 0.0, 100.2 };
     size_t   count     = 3;
     size_t   amount    = (count + 1) * sizeof(double);
@@ -343,18 +337,18 @@ test_bigend_f64(TestBatch *batch) {
     target = encoded;
     for (size_t i = 0; i < count; i++) {
         double got = NumUtil_decode_bigend_f64(target);
-        TEST_TRUE(batch, got == source[i], "bigend f64");
+        TEST_TRUE(runner, got == source[i], "bigend f64");
         target += sizeof(double);
     }
 
     target = encoded;
     NumUtil_encode_bigend_f64(-2.0, &target);
-    TEST_INT_EQ(batch, (encoded[0] & 0x80), 0x80,
+    TEST_INT_EQ(runner, (encoded[0] & 0x80), 0x80,
                 "Truly big-endian (IEEE 754 sign bit set for negative number)");
-    TEST_INT_EQ(batch, encoded[0], 0xC0,
+    TEST_INT_EQ(runner, encoded[0], 0xC0,
                 "IEEE 754 representation of -2.0, byte 0");
     for (size_t i = 1; i < sizeof(double); i++) {
-        TEST_INT_EQ(batch, encoded[i], 0,
+        TEST_INT_EQ(runner, encoded[i], 0,
                     "IEEE 754 representation of -2.0, byte %d", (int)i);
     }
 
@@ -362,19 +356,19 @@ test_bigend_f64(TestBatch *batch) {
 }
 
 void
-TestNumUtil_run_tests(TestNumberUtils *self) {
-    TestBatch *batch = (TestBatch*)self;
+TestNumUtil_run(TestNumberUtils *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 1196);
     srand((unsigned int)time((time_t*)NULL));
-    test_u1(batch);
-    test_u2(batch);
-    test_u4(batch);
-    test_c32(batch);
-    test_c64(batch);
-    test_bigend_u16(batch);
-    test_bigend_u32(batch);
-    test_bigend_u64(batch);
-    test_bigend_f32(batch);
-    test_bigend_f64(batch);
+    test_u1(runner);
+    test_u2(runner);
+    test_u4(runner);
+    test_c32(runner);
+    test_c64(runner);
+    test_bigend_u16(runner);
+    test_bigend_u32(runner);
+    test_bigend_u64(runner);
+    test_bigend_f32(runner);
+    test_bigend_f64(runner);
 }
 
 

http://git-wip-us.apache.org/repos/asf/lucy/blob/40220b9e/core/Clownfish/Test/Util/TestNumberUtils.cfh
----------------------------------------------------------------------
diff --git a/core/Clownfish/Test/Util/TestNumberUtils.cfh b/core/Clownfish/Test/Util/TestNumberUtils.cfh
index 1e4bacc..cd9e165 100644
--- a/core/Clownfish/Test/Util/TestNumberUtils.cfh
+++ b/core/Clownfish/Test/Util/TestNumberUtils.cfh
@@ -20,13 +20,10 @@ class Clownfish::Test::Util::TestNumberUtils cnick TestNumUtil
     inherits Clownfish::TestHarness::TestBatch {
 
     inert incremented TestNumberUtils*
-    new(TestFormatter *formatter);
-
-    inert TestNumberUtils*
-    init(TestNumberUtils *self, TestFormatter *formatter);
+    new();
 
     void
-    Run_Tests(TestNumberUtils *self);
+    Run(TestNumberUtils *self, TestBatchRunner *runner);
 }
 
 

http://git-wip-us.apache.org/repos/asf/lucy/blob/40220b9e/core/Clownfish/Test/Util/TestStringHelper.c
----------------------------------------------------------------------
diff --git a/core/Clownfish/Test/Util/TestStringHelper.c b/core/Clownfish/Test/Util/TestStringHelper.c
index 0ca46e9..7b24f1d 100644
--- a/core/Clownfish/Test/Util/TestStringHelper.c
+++ b/core/Clownfish/Test/Util/TestStringHelper.c
@@ -24,7 +24,7 @@
 #include "Clownfish/CharBuf.h"
 #include "Clownfish/Err.h"
 #include "Clownfish/Test.h"
-#include "Clownfish/TestHarness/TestFormatter.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
 #include "Clownfish/TestHarness/TestUtils.h"
 #include "Clownfish/Util/StringHelper.h"
 #include "Clownfish/VTable.h"
@@ -36,14 +36,8 @@
  */
 #define TRAIL_OK(n) (n >= 0x80 && n <= 0xBF)
 TestStringHelper*
-TestStrHelp_new(TestFormatter *formatter) {
-    TestStringHelper *self = (TestStringHelper*)VTable_Make_Obj(TESTSTRINGHELPER);
-    return TestStrHelp_init(self, formatter);
-}
-
-TestStringHelper*
-TestStrHelp_init(TestStringHelper *self, TestFormatter *formatter) {
-    return (TestStringHelper*)TestBatch_init((TestBatch*)self, 41, formatter);
+TestStrHelp_new() {
+    return (TestStringHelper*)VTable_Make_Obj(TESTSTRINGHELPER);
 }
 
 static bool
@@ -136,35 +130,35 @@ S_utf8_valid_alt(const char *maybe_utf8, size_t size) {
 }
 
 static void
-test_overlap(TestBatch *batch) {
+test_overlap(TestBatchRunner *runner) {
     int32_t result;
     result = StrHelp_overlap("", "", 0, 0);
-    TEST_INT_EQ(batch, result, 0, "two empty strings");
+    TEST_INT_EQ(runner, result, 0, "two empty strings");
     result = StrHelp_overlap("", "foo", 0, 3);
-    TEST_INT_EQ(batch, result, 0, "first string is empty");
+    TEST_INT_EQ(runner, result, 0, "first string is empty");
     result = StrHelp_overlap("foo", "", 3, 0);
-    TEST_INT_EQ(batch, result, 0, "second string is empty");
+    TEST_INT_EQ(runner, result, 0, "second string is empty");
     result = StrHelp_overlap("foo", "foo", 3, 3);
-    TEST_INT_EQ(batch, result, 3, "equal strings");
+    TEST_INT_EQ(runner, result, 3, "equal strings");
     result = StrHelp_overlap("foo bar", "foo", 7, 3);
-    TEST_INT_EQ(batch, result, 3, "first string is longer");
+    TEST_INT_EQ(runner, result, 3, "first string is longer");
     result = StrHelp_overlap("foo", "foo bar", 3, 7);
-    TEST_INT_EQ(batch, result, 3, "second string is longer");
+    TEST_INT_EQ(runner, result, 3, "second string is longer");
 }
 
 
 static void
-test_to_base36(TestBatch *batch) {
+test_to_base36(TestBatchRunner *runner) {
     char buffer[StrHelp_MAX_BASE36_BYTES];
     StrHelp_to_base36(UINT64_MAX, buffer);
-    TEST_STR_EQ(batch, "3w5e11264sgsf", buffer, "base36 UINT64_MAX");
+    TEST_STR_EQ(runner, "3w5e11264sgsf", buffer, "base36 UINT64_MAX");
     StrHelp_to_base36(1, buffer);
-    TEST_STR_EQ(batch, "1", buffer, "base36 1");
-    TEST_INT_EQ(batch, buffer[1], 0, "base36 NULL termination");
+    TEST_STR_EQ(runner, "1", buffer, "base36 1");
+    TEST_INT_EQ(runner, buffer[1], 0, "base36 NULL termination");
 }
 
 static void
-test_utf8_round_trip(TestBatch *batch) {
+test_utf8_round_trip(TestBatchRunner *runner) {
     uint32_t code_point;
     for (code_point = 0; code_point <= 0x10FFFF; code_point++) {
         char buffer[4];
@@ -194,109 +188,109 @@ test_utf8_round_trip(TestBatch *batch) {
         }
     }
     if (code_point == 0x110000) {
-        PASS(batch, "Successfully round tripped 0 - 0x10FFFF");
+        PASS(runner, "Successfully round tripped 0 - 0x10FFFF");
     }
     else {
-        FAIL(batch, "Failed round trip at 0x%.1X", (unsigned)code_point);
+        FAIL(runner, "Failed round trip at 0x%.1X", (unsigned)code_point);
     }
 }
 
 static void
-S_test_validity(TestBatch *batch, const char *content, size_t size,
+S_test_validity(TestBatchRunner *runner, const char *content, size_t size,
                 bool expected, const char *description) {
     bool sane = StrHelp_utf8_valid(content, size);
     bool double_check = S_utf8_valid_alt(content, size);
     if (sane != double_check) {
-        FAIL(batch, "Disagreement: %s", description);
+        FAIL(runner, "Disagreement: %s", description);
     }
     else {
-        TEST_TRUE(batch, sane == expected, "%s", description);
+        TEST_TRUE(runner, sane == expected, "%s", description);
     }
 }
 
 static void
-test_utf8_valid(TestBatch *batch) {
+test_utf8_valid(TestBatchRunner *runner) {
     // Musical symbol G clef:
     // Code point: U+1D11E
     // UTF-16:     0xD834 0xDD1E
     // UTF-8       0xF0 0x9D 0x84 0x9E
-    S_test_validity(batch, "\xF0\x9D\x84\x9E", 4, true,
+    S_test_validity(runner, "\xF0\x9D\x84\x9E", 4, true,
                     "Musical symbol G clef");
-    S_test_validity(batch, "\xED\xA0\xB4\xED\xB4\x9E", 6, false,
+    S_test_validity(runner, "\xED\xA0\xB4\xED\xB4\x9E", 6, false,
                     "G clef as UTF-8 encoded UTF-16 surrogates");
-    S_test_validity(batch, ".\xED\xA0\xB4.", 5, false,
+    S_test_validity(runner, ".\xED\xA0\xB4.", 5, false,
                     "Isolated high surrogate");
-    S_test_validity(batch, ".\xED\xB4\x9E.", 5, false,
+    S_test_validity(runner, ".\xED\xB4\x9E.", 5, false,
                     "Isolated low surrogate");
 
     // Shortest form.
-    S_test_validity(batch, ".\xC1\x9C.", 4, false,
+    S_test_validity(runner, ".\xC1\x9C.", 4, false,
                     "Non-shortest form ASCII backslash");
-    S_test_validity(batch, ".\xC0\xAF.", 4, false,
+    S_test_validity(runner, ".\xC0\xAF.", 4, false,
                     "Non-shortest form ASCII slash");
-    S_test_validity(batch, ".\xC0\x80.", 4, false,
+    S_test_validity(runner, ".\xC0\x80.", 4, false,
                     "Non-shortest form ASCII NUL character");
 
     // Range.
-    S_test_validity(batch, "\xF8\x88\x80\x80\x80", 5, false, "5-byte UTF-8");
+    S_test_validity(runner, "\xF8\x88\x80\x80\x80", 5, false, "5-byte UTF-8");
 
     // Bad continuations.
-    S_test_validity(batch, "\xE2\x98\xBA\xE2\x98\xBA", 6, true,
+    S_test_validity(runner, "\xE2\x98\xBA\xE2\x98\xBA", 6, true,
                     "SmileySmiley");
-    S_test_validity(batch, "\xE2\xBA\xE2\x98\xBA", 5, false,
+    S_test_validity(runner, "\xE2\xBA\xE2\x98\xBA", 5, false,
                     "missing first continuation byte");
-    S_test_validity(batch, "\xE2\x98\xE2\x98\xBA", 5, false,
+    S_test_validity(runner, "\xE2\x98\xE2\x98\xBA", 5, false,
                     "missing second continuation byte");
-    S_test_validity(batch, "\xE2\xE2\x98\xBA", 4, false,
+    S_test_validity(runner, "\xE2\xE2\x98\xBA", 4, false,
                     "missing both continuation bytes");
-    S_test_validity(batch, "\xBA\xE2\x98\xBA\xE2\xBA", 5, false,
+    S_test_validity(runner, "\xBA\xE2\x98\xBA\xE2\xBA", 5, false,
                     "missing first continuation byte (end)");
-    S_test_validity(batch, "\xE2\x98\xBA\xE2\x98", 5, false,
+    S_test_validity(runner, "\xE2\x98\xBA\xE2\x98", 5, false,
                     "missing second continuation byte (end)");
-    S_test_validity(batch, "\xE2\x98\xBA\xE2", 4, false,
+    S_test_validity(runner, "\xE2\x98\xBA\xE2", 4, false,
                     "missing both continuation bytes (end)");
-    S_test_validity(batch, "\xBA\xE2\x98\xBA", 4, false,
+    S_test_validity(runner, "\xBA\xE2\x98\xBA", 4, false,
                     "isolated continuation byte 0xBA");
-    S_test_validity(batch, "\x98\xE2\x98\xBA", 4, false,
+    S_test_validity(runner, "\x98\xE2\x98\xBA", 4, false,
                     "isolated continuation byte 0x98");
-    S_test_validity(batch, "\xE2\x98\xBA\xBA", 4, false,
+    S_test_validity(runner, "\xE2\x98\xBA\xBA", 4, false,
                     "isolated continuation byte 0xBA (end)");
-    S_test_validity(batch, "\xE2\x98\xBA\x98", 4, false,
+    S_test_validity(runner, "\xE2\x98\xBA\x98", 4, false,
                     "isolated continuation byte 0x98 (end)");
 }
 
 static void
-test_is_whitespace(TestBatch *batch) {
-    TEST_TRUE(batch, StrHelp_is_whitespace(' '), "space is whitespace");
-    TEST_TRUE(batch, StrHelp_is_whitespace('\n'), "newline is whitespace");
-    TEST_TRUE(batch, StrHelp_is_whitespace('\t'), "tab is whitespace");
-    TEST_TRUE(batch, StrHelp_is_whitespace('\v'),
+test_is_whitespace(TestBatchRunner *runner) {
+    TEST_TRUE(runner, StrHelp_is_whitespace(' '), "space is whitespace");
+    TEST_TRUE(runner, StrHelp_is_whitespace('\n'), "newline is whitespace");
+    TEST_TRUE(runner, StrHelp_is_whitespace('\t'), "tab is whitespace");
+    TEST_TRUE(runner, StrHelp_is_whitespace('\v'),
               "vertical tab is whitespace");
-    TEST_TRUE(batch, StrHelp_is_whitespace(0x180E),
+    TEST_TRUE(runner, StrHelp_is_whitespace(0x180E),
               "Mongolian vowel separator is whitespace");
-    TEST_FALSE(batch, StrHelp_is_whitespace('a'), "'a' isn't whitespace");
-    TEST_FALSE(batch, StrHelp_is_whitespace(0), "NULL isn't whitespace");
-    TEST_FALSE(batch, StrHelp_is_whitespace(0x263A),
+    TEST_FALSE(runner, StrHelp_is_whitespace('a'), "'a' isn't whitespace");
+    TEST_FALSE(runner, StrHelp_is_whitespace(0), "NULL isn't whitespace");
+    TEST_FALSE(runner, StrHelp_is_whitespace(0x263A),
                "Smiley isn't whitespace");
 }
 
 static void
-test_back_utf8_char(TestBatch *batch) {
+test_back_utf8_char(TestBatchRunner *runner) {
     char buffer[4];
     char *buf = buffer + 1;
     uint32_t len = StrHelp_encode_utf8_char(0x263A, buffer);
     char *end = buffer + len;
-    TEST_TRUE(batch, StrHelp_back_utf8_char(end, buffer) == buffer,
+    TEST_TRUE(runner, StrHelp_back_utf8_char(end, buffer) == buffer,
               "back_utf8_char");
-    TEST_TRUE(batch, StrHelp_back_utf8_char(end, buf) == NULL,
+    TEST_TRUE(runner, StrHelp_back_utf8_char(end, buf) == NULL,
               "back_utf8_char returns NULL rather than back up beyond start");
-    TEST_TRUE(batch, StrHelp_back_utf8_char(buffer, buffer) == NULL,
+    TEST_TRUE(runner, StrHelp_back_utf8_char(buffer, buffer) == NULL,
               "back_utf8_char returns NULL when end == start");
 }
 
 static void
-test_utf8proc_normalization(TestBatch *batch) {
-    SKIP(batch, "utf8proc can't handle control chars or Unicode non-chars");
+test_utf8proc_normalization(TestBatchRunner *runner) {
+    SKIP(runner, "utf8proc can't handle control chars or Unicode non-chars");
     return;
 
     for (int32_t i = 0; i < 100; i++) {
@@ -316,7 +310,7 @@ test_utf8proc_normalization(TestBatch *batch) {
             if (!json) {
                 json = CB_newf("[failed to encode]");
             }
-            FAIL(batch, "Failed to normalize: %s", CB_Get_Ptr8(json));
+            FAIL(runner, "Failed to normalize: %s", CB_Get_Ptr8(json));
             DECREF(json);
             DECREF(source);
             return;
@@ -338,23 +332,23 @@ test_utf8proc_normalization(TestBatch *batch) {
         free(normalized);
         DECREF(source);
         if (comparison != 0) {
-            FAIL(batch, "Not fully normalized");
+            FAIL(runner, "Not fully normalized");
             return;
         }
     }
-    PASS(batch, "Normalization successful.");
+    PASS(runner, "Normalization successful.");
 }
 
 void
-TestStrHelp_run_tests(TestStringHelper *self) {
-    TestBatch *batch = (TestBatch*)self;
-    test_overlap(batch);
-    test_to_base36(batch);
-    test_utf8_round_trip(batch);
-    test_utf8_valid(batch);
-    test_is_whitespace(batch);
-    test_back_utf8_char(batch);
-    test_utf8proc_normalization(batch);
+TestStrHelp_run(TestStringHelper *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 41);
+    test_overlap(runner);
+    test_to_base36(runner);
+    test_utf8_round_trip(runner);
+    test_utf8_valid(runner);
+    test_is_whitespace(runner);
+    test_back_utf8_char(runner);
+    test_utf8proc_normalization(runner);
 }
 
 

http://git-wip-us.apache.org/repos/asf/lucy/blob/40220b9e/core/Clownfish/Test/Util/TestStringHelper.cfh
----------------------------------------------------------------------
diff --git a/core/Clownfish/Test/Util/TestStringHelper.cfh b/core/Clownfish/Test/Util/TestStringHelper.cfh
index 219ec81..0f8351a 100644
--- a/core/Clownfish/Test/Util/TestStringHelper.cfh
+++ b/core/Clownfish/Test/Util/TestStringHelper.cfh
@@ -20,13 +20,10 @@ class Clownfish::Test::Util::TestStringHelper cnick TestStrHelp
     inherits Clownfish::TestHarness::TestBatch {
 
     inert incremented TestStringHelper*
-    new(TestFormatter *formatter);
-
-    inert TestStringHelper*
-    init(TestStringHelper *self, TestFormatter *formatter);
+    new();
 
     void
-    Run_Tests(TestStringHelper *self);
+    Run(TestStringHelper *self, TestBatchRunner *runner);
 }
 
 

http://git-wip-us.apache.org/repos/asf/lucy/blob/40220b9e/core/Clownfish/TestHarness/TestBatch.c
----------------------------------------------------------------------
diff --git a/core/Clownfish/TestHarness/TestBatch.c b/core/Clownfish/TestHarness/TestBatch.c
deleted file mode 100644
index 486e967..0000000
--- a/core/Clownfish/TestHarness/TestBatch.c
+++ /dev/null
@@ -1,293 +0,0 @@
-/* Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <math.h>
-#include <stdio.h>
-#include <string.h>
-
-#define C_CFISH_TESTBATCH
-#define CFISH_USE_SHORT_NAMES
-#define LUCY_USE_SHORT_NAMES
-#include "Clownfish/TestHarness/TestBatch.h"
-#include "Clownfish/CharBuf.h"
-#include "Clownfish/Err.h"
-#include "Clownfish/TestHarness/TestFormatter.h"
-#include "Clownfish/VArray.h"
-#include "Clownfish/VTable.h"
-
-struct try_run_tests_context {
-    TestBatch *batch;
-};
-
-static void
-S_try_run_tests(void *context);
-
-static bool
-S_vtest_true(TestBatch *self, bool condition, const char *pattern,
-             va_list args);
-
-TestBatch*
-TestBatch_new(uint32_t num_planned, TestFormatter *formatter) {
-    TestBatch *self = (TestBatch*)VTable_Make_Obj(TESTBATCH);
-    return TestBatch_init(self, num_planned, formatter);
-}
-
-TestBatch*
-TestBatch_init(TestBatch *self, uint32_t num_planned,
-               TestFormatter *formatter) {
-    // Assign.
-    self->num_planned = num_planned;
-    self->formatter   = (TestFormatter*)INCREF(formatter);
-
-    // Initialize.
-    self->test_num    = 0;
-    self->num_passed  = 0;
-    self->num_failed  = 0;
-    self->num_skipped = 0;
-
-    return self;
-}
-
-void
-TestBatch_destroy(TestBatch *self) {
-    DECREF(self->formatter);
-    SUPER_DESTROY(self, TESTBATCH);
-}
-
-bool
-TestBatch_run(TestBatch *self) {
-    TestFormatter_Batch_Prologue(self->formatter, self);
-
-    struct try_run_tests_context args;
-    args.batch = self;
-    Err *err = Err_trap(S_try_run_tests, &args);
-
-    bool failed = false;
-    if (err) {
-        failed = true;
-        CharBuf *mess = Err_Get_Mess(err);
-        INCREF(mess);
-        Err_warn_mess(mess);
-    }
-    if (self->num_failed > 0) {
-        failed = true;
-        TestFormatter_batch_comment(self->formatter, "%d/%d tests failed.\n",
-                                    self->num_failed, self->test_num);
-    }
-    if (self->test_num != self->num_planned) {
-        failed = true;
-        TestFormatter_batch_comment(self->formatter,
-                                    "Bad plan: You planned %d tests but ran"
-                                    " %d.\n",
-                                    self->num_planned, self->test_num);
-    }
-
-    return !failed;
-}
-
-static void
-S_try_run_tests(void *context) {
-    struct try_run_tests_context *args
-        = (struct try_run_tests_context*)context;
-    TestBatch_Run_Tests(args->batch);
-}
-
-uint32_t
-TestBatch_get_num_planned(TestBatch *self) {
-    return self->num_planned;
-}
-
-uint32_t
-TestBatch_get_num_tests(TestBatch *self) {
-    return self->test_num;
-}
-
-uint32_t
-TestBatch_get_num_failed(TestBatch *self) {
-    return self->num_failed;
-}
-
-bool
-TestBatch_test_true(void *vself, bool condition, const char *pattern, ...) {
-    va_list args;
-    va_start(args, pattern);
-    bool result = TestBatch_VTest_True((TestBatch*)vself, condition,
-                                         pattern, args);
-    va_end(args);
-    return result;
-}
-
-bool
-TestBatch_test_false(void *vself, bool condition, const char *pattern, ...) {
-    va_list args;
-    va_start(args, pattern);
-    bool result = TestBatch_VTest_False((TestBatch*)vself, condition,
-                                          pattern, args);
-    va_end(args);
-    return result;
-}
-
-bool
-TestBatch_test_int_equals(void *vself, long got, long expected,
-                          const char *pattern, ...) {
-    va_list args;
-    va_start(args, pattern);
-    bool result = TestBatch_VTest_Int_Equals((TestBatch*)vself, got,
-                                               expected, pattern, args);
-    va_end(args);
-    return result;
-}
-
-bool
-TestBatch_test_float_equals(void *vself, double got, double expected,
-                            const char *pattern, ...) {
-    va_list args;
-    va_start(args, pattern);
-    bool result = TestBatch_VTest_Float_Equals((TestBatch*)vself, got,
-                                                 expected, pattern, args);
-    va_end(args);
-    return result;
-}
-
-bool
-TestBatch_test_string_equals(void *vself, const char *got,
-                             const char *expected, const char *pattern, ...) {
-    va_list args;
-    va_start(args, pattern);
-    bool result = TestBatch_VTest_String_Equals((TestBatch*)vself, got,
-                                                  expected, pattern, args);
-    va_end(args);
-    return result;
-}
-
-bool
-TestBatch_pass(void *vself, const char *pattern, ...) {
-    va_list args;
-    va_start(args, pattern);
-    bool result = TestBatch_VPass((TestBatch*)vself, pattern, args);
-    va_end(args);
-    return result;
-}
-
-bool
-TestBatch_fail(void *vself, const char *pattern, ...) {
-    va_list args;
-    va_start(args, pattern);
-    bool result = TestBatch_VFail((TestBatch*)vself, pattern, args);
-    va_end(args);
-    return result;
-}
-
-void
-TestBatch_skip(void *vself, const char *pattern, ...) {
-    va_list args;
-    va_start(args, pattern);
-    TestBatch_VSkip((TestBatch*)vself, pattern, args);
-    va_end(args);
-}
-
-bool
-TestBatch_vtest_true(TestBatch *self, bool condition, const char *pattern,
-                     va_list args) {
-    return S_vtest_true(self, condition, pattern, args);
-}
-
-bool
-TestBatch_vtest_false(TestBatch *self, bool condition,
-                      const char *pattern, va_list args) {
-    return S_vtest_true(self, !condition, pattern, args);
-}
-
-bool
-TestBatch_vtest_int_equals(TestBatch *self, long got, long expected,
-                           const char *pattern, va_list args) {
-    bool pass = (got == expected);
-    S_vtest_true(self, pass, pattern, args);
-    if (!pass) {
-        TestFormatter_test_comment(self->formatter,
-                                   "Expected '%ld', got '%ld'.\n",
-                                   expected, got);
-    }
-    return pass;
-}
-
-bool
-TestBatch_vtest_float_equals(TestBatch *self, double got, double expected,
-                             const char *pattern, va_list args) {
-    double relative_error = got / expected - 1.0;
-    bool   pass           = (fabs(relative_error) < 1e-6);
-    S_vtest_true(self, pass, pattern, args);
-    if (!pass) {
-        TestFormatter_test_comment(self->formatter,
-                                   "Expected '%e', got '%e'.\n",
-                                   expected, got);
-    }
-    return pass;
-}
-
-bool
-TestBatch_vtest_string_equals(TestBatch *self, const char *got,
-                              const char *expected, const char *pattern,
-                              va_list args) {
-    bool pass = (strcmp(got, expected) == 0);
-    S_vtest_true(self, pass, pattern, args);
-    if (!pass) {
-        TestFormatter_test_comment(self->formatter,
-                                   "Expected '%s', got '%s'.\n",
-                                   expected, got);
-    }
-    return pass;
-}
-
-bool
-TestBatch_vpass(TestBatch *self, const char *pattern, va_list args) {
-    return S_vtest_true(self, true, pattern, args);
-}
-
-bool
-TestBatch_vfail(TestBatch *self, const char *pattern, va_list args) {
-    return S_vtest_true(self, false, pattern, args);
-}
-
-void
-TestBatch_vskip(TestBatch *self, const char *pattern, va_list args) {
-    self->test_num++;
-    // TODO: Add a VTest_Skip method to TestFormatter
-    TestFormatter_VTest_Result(self->formatter, true, self->test_num,
-                               pattern, args);
-    self->num_skipped++;
-}
-
-static bool
-S_vtest_true(TestBatch* self, bool condition, const char *pattern,
-             va_list args) {
-    // Increment test number.
-    self->test_num++;
-
-    if (condition) {
-        self->num_passed++;
-    }
-    else {
-        self->num_failed++;
-    }
-
-    TestFormatter_VTest_Result(self->formatter, condition, self->test_num,
-                               pattern, args);
-
-    return condition;
-}
-
-

http://git-wip-us.apache.org/repos/asf/lucy/blob/40220b9e/core/Clownfish/TestHarness/TestBatch.cfh
----------------------------------------------------------------------
diff --git a/core/Clownfish/TestHarness/TestBatch.cfh b/core/Clownfish/TestHarness/TestBatch.cfh
index cf82286..21b1117 100644
--- a/core/Clownfish/TestHarness/TestBatch.cfh
+++ b/core/Clownfish/TestHarness/TestBatch.cfh
@@ -16,105 +16,13 @@
 
 parcel Clownfish;
 
+/** Abstract base class for test modules.
+ */
 abstract class Clownfish::TestHarness::TestBatch inherits Clownfish::Obj {
-    TestFormatter *formatter;
-    uint32_t       test_num;
-    uint32_t       num_planned;
-    uint32_t       num_passed;
-    uint32_t       num_failed;
-    uint32_t       num_skipped;
-
-    inert incremented TestBatch*
-    new(uint32_t num_planned, TestFormatter *formatter);
-
-    inert TestBatch*
-    init(TestBatch *self, uint32_t num_planned, TestFormatter *formatter);
-
-    public void
-    Destroy(TestBatch *self);
-
-    /** Run the test batch and print test output and diagnosis.
-     *
-     * @return true if the test batch passed.
-     */
-    bool
-    Run(TestBatch *self);
-
     /** Run the tests of the test batch.
      */
     abstract void
-    Run_Tests(TestBatch *self);
-
-    /** Return the number of tests planned.
-     */
-    uint32_t
-    Get_Num_Planned(TestBatch *self);
-
-    /** Return the number of tests run.
-     */
-    uint32_t
-    Get_Num_Tests(TestBatch *self);
-
-    /** Return the number of failed tests.
-     */
-    uint32_t
-    Get_Num_Failed(TestBatch *self);
-
-    inert bool
-    test_true(void *vself, bool condition, const char *pattern, ...);
-
-    inert bool
-    test_false(void *vself, bool condition, const char *pattern, ...);
-
-    inert bool
-    test_int_equals(void *vself, long got, long expected,
-                    const char *pattern, ...);
-
-    inert bool
-    test_float_equals(void *vself, double got, double expected,
-                      const char *pattern, ...);
-
-    inert bool
-    test_string_equals(void *vself, const char *got, const char *expected,
-                       const char *pattern, ...);
-
-    inert bool
-    pass(void *vself, const char *pattern, ...);
-
-    inert bool
-    fail(void *vself, const char *pattern, ...);
-
-    inert void
-    skip(void *vself, const char *pattern, ...);
-
-    bool
-    VTest_True(TestBatch *self, bool condition, const char *pattern,
-               va_list args);
-
-    bool
-    VTest_False(TestBatch *self, bool condition, const char *pattern,
-                va_list args);
-
-    bool
-    VTest_Int_Equals(TestBatch *self, long got, long expected,
-                     const char *pattern, va_list args);
-
-    bool
-    VTest_Float_Equals(TestBatch *self, double got, double expected,
-                       const char *pattern, va_list args);
-
-    bool
-    VTest_String_Equals(TestBatch *self, const char *got, const char *expected,
-                       const char *pattern, va_list args);
-
-    bool
-    VPass(TestBatch *self, const char *pattern, va_list args);
-
-    bool
-    VFail(TestBatch *self, const char *pattern, va_list args);
-
-    void
-    VSkip(TestBatch *self, const char *pattern, va_list args);
+    Run(TestBatch *self, TestBatchRunner *runner);
 }
 
 

http://git-wip-us.apache.org/repos/asf/lucy/blob/40220b9e/core/Clownfish/TestHarness/TestBatchRunner.c
----------------------------------------------------------------------
diff --git a/core/Clownfish/TestHarness/TestBatchRunner.c b/core/Clownfish/TestHarness/TestBatchRunner.c
new file mode 100644
index 0000000..2263d0d
--- /dev/null
+++ b/core/Clownfish/TestHarness/TestBatchRunner.c
@@ -0,0 +1,308 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+#define C_CFISH_TESTBATCHRUNNER
+#define CFISH_USE_SHORT_NAMES
+#define LUCY_USE_SHORT_NAMES
+
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+
+#include "Clownfish/CharBuf.h"
+#include "Clownfish/Err.h"
+#include "Clownfish/TestHarness/TestBatch.h"
+#include "Clownfish/TestHarness/TestFormatter.h"
+#include "Clownfish/VArray.h"
+#include "Clownfish/VTable.h"
+
+struct try_run_tests_context {
+    TestBatchRunner *runner;
+    TestBatch       *batch;
+};
+
+static void
+S_try_run_tests(void *context);
+
+static bool
+S_vtest_true(TestBatchRunner *self, bool condition, const char *pattern,
+             va_list args);
+
+TestBatchRunner*
+TestBatchRunner_new(TestFormatter *formatter) {
+    TestBatchRunner *self = (TestBatchRunner*)VTable_Make_Obj(TESTBATCHRUNNER);
+    return TestBatchRunner_init(self, formatter);
+}
+
+TestBatchRunner*
+TestBatchRunner_init(TestBatchRunner *self, TestFormatter *formatter) {
+    // Assign.
+    self->formatter   = (TestFormatter*)INCREF(formatter);
+
+    // Initialize.
+    self->num_planned = 0;
+    self->test_num    = 0;
+    self->num_passed  = 0;
+    self->num_failed  = 0;
+    self->num_skipped = 0;
+
+    return self;
+}
+
+void
+TestBatchRunner_destroy(TestBatchRunner *self) {
+    DECREF(self->formatter);
+    SUPER_DESTROY(self, TESTBATCHRUNNER);
+}
+
+bool
+TestBatchRunner_run_batch(TestBatchRunner *self, TestBatch *batch) {
+    struct try_run_tests_context args;
+    args.runner = self;
+    args.batch  = batch;
+    Err *err = Err_trap(S_try_run_tests, &args);
+
+    bool failed = false;
+    if (err) {
+        failed = true;
+        CharBuf *mess = Err_Get_Mess(err);
+        INCREF(mess);
+        Err_warn_mess(mess);
+    }
+    if (self->num_failed > 0) {
+        failed = true;
+        TestFormatter_Batch_Comment(self->formatter, "%d/%d tests failed.\n",
+                                    self->num_failed, self->test_num);
+    }
+    if (self->test_num != self->num_planned) {
+        failed = true;
+        TestFormatter_Batch_Comment(self->formatter,
+                                    "Bad plan: You planned %d tests but ran"
+                                    " %d.\n",
+                                    self->num_planned, self->test_num);
+    }
+
+    return !failed;
+}
+
+static void
+S_try_run_tests(void *context) {
+    struct try_run_tests_context *args
+        = (struct try_run_tests_context*)context;
+    TestBatch_Run(args->batch, args->runner);
+}
+
+void
+TestBatchRunner_plan(TestBatchRunner *self, TestBatch *batch,
+                     uint32_t num_planned) {
+    self->num_planned = num_planned;
+    TestFormatter_Batch_Prologue(self->formatter, batch, num_planned);
+}
+
+uint32_t
+TestBatchRunner_get_num_planned(TestBatchRunner *self) {
+    return self->num_planned;
+}
+
+uint32_t
+TestBatchRunner_get_num_tests(TestBatchRunner *self) {
+    return self->test_num;
+}
+
+uint32_t
+TestBatchRunner_get_num_failed(TestBatchRunner *self) {
+    return self->num_failed;
+}
+
+bool
+TestBatchRunner_test_true(TestBatchRunner *self, bool condition,
+                          const char *pattern, ...) {
+    va_list args;
+    va_start(args, pattern);
+    bool result = TestBatchRunner_VTest_True(self, condition, pattern, args);
+    va_end(args);
+    return result;
+}
+
+bool
+TestBatchRunner_test_false(TestBatchRunner *self, bool condition,
+                           const char *pattern, ...) {
+    va_list args;
+    va_start(args, pattern);
+    bool result = TestBatchRunner_VTest_False(self, condition, pattern, args);
+    va_end(args);
+    return result;
+}
+
+bool
+TestBatchRunner_test_int_equals(TestBatchRunner *self, long got, long expected,
+                                const char *pattern, ...) {
+    va_list args;
+    va_start(args, pattern);
+    bool result = TestBatchRunner_VTest_Int_Equals(self, got, expected,
+                                                   pattern, args);
+    va_end(args);
+    return result;
+}
+
+bool
+TestBatchRunner_test_float_equals(TestBatchRunner *self, double got,
+                                  double expected, const char *pattern, ...) {
+    va_list args;
+    va_start(args, pattern);
+    bool result = TestBatchRunner_VTest_Float_Equals(self, got, expected,
+                                                     pattern, args);
+    va_end(args);
+    return result;
+}
+
+bool
+TestBatchRunner_test_string_equals(TestBatchRunner *self, const char *got,
+                                   const char *expected, const char *pattern,
+                                   ...) {
+    va_list args;
+    va_start(args, pattern);
+    bool result = TestBatchRunner_VTest_String_Equals(self, got, expected,
+                                                      pattern, args);
+    va_end(args);
+    return result;
+}
+
+bool
+TestBatchRunner_pass(TestBatchRunner *self, const char *pattern, ...) {
+    va_list args;
+    va_start(args, pattern);
+    bool result = TestBatchRunner_VPass(self, pattern, args);
+    va_end(args);
+    return result;
+}
+
+bool
+TestBatchRunner_fail(TestBatchRunner *self, const char *pattern, ...) {
+    va_list args;
+    va_start(args, pattern);
+    bool result = TestBatchRunner_VFail(self, pattern, args);
+    va_end(args);
+    return result;
+}
+
+void
+TestBatchRunner_skip(TestBatchRunner *self, const char *pattern, ...) {
+    va_list args;
+    va_start(args, pattern);
+    TestBatchRunner_VSkip(self, pattern, args);
+    va_end(args);
+}
+
+bool
+TestBatchRunner_vtest_true(TestBatchRunner *self, bool condition,
+                           const char *pattern, va_list args) {
+    return S_vtest_true(self, condition, pattern, args);
+}
+
+bool
+TestBatchRunner_vtest_false(TestBatchRunner *self, bool condition,
+                            const char *pattern, va_list args) {
+    return S_vtest_true(self, !condition, pattern, args);
+}
+
+bool
+TestBatchRunner_vtest_int_equals(TestBatchRunner *self, long got,
+                                 long expected, const char *pattern,
+                                 va_list args) {
+    bool pass = (got == expected);
+    S_vtest_true(self, pass, pattern, args);
+    if (!pass) {
+        TestFormatter_Test_Comment(self->formatter,
+                                   "Expected '%ld', got '%ld'.\n",
+                                   expected, got);
+    }
+    return pass;
+}
+
+bool
+TestBatchRunner_vtest_float_equals(TestBatchRunner *self, double got,
+                                   double expected, const char *pattern,
+                                   va_list args) {
+    double relative_error = got / expected - 1.0;
+    bool   pass           = (fabs(relative_error) < 1e-6);
+    S_vtest_true(self, pass, pattern, args);
+    if (!pass) {
+        TestFormatter_Test_Comment(self->formatter,
+                                   "Expected '%e', got '%e'.\n",
+                                   expected, got);
+    }
+    return pass;
+}
+
+bool
+TestBatchRunner_vtest_string_equals(TestBatchRunner *self, const char *got,
+                                    const char *expected, const char *pattern,
+                                    va_list args) {
+    bool pass = (strcmp(got, expected) == 0);
+    S_vtest_true(self, pass, pattern, args);
+    if (!pass) {
+        TestFormatter_Test_Comment(self->formatter,
+                                   "Expected '%s', got '%s'.\n",
+                                   expected, got);
+    }
+    return pass;
+}
+
+bool
+TestBatchRunner_vpass(TestBatchRunner *self, const char *pattern,
+                      va_list args) {
+    return S_vtest_true(self, true, pattern, args);
+}
+
+bool
+TestBatchRunner_vfail(TestBatchRunner *self, const char *pattern,
+                      va_list args) {
+    return S_vtest_true(self, false, pattern, args);
+}
+
+void
+TestBatchRunner_vskip(TestBatchRunner *self, const char *pattern,
+                      va_list args) {
+    self->test_num++;
+    // TODO: Add a VTest_Skip method to TestFormatter
+    TestFormatter_VTest_Result(self->formatter, true, self->test_num,
+                               pattern, args);
+    self->num_skipped++;
+}
+
+static bool
+S_vtest_true(TestBatchRunner* self, bool condition, const char *pattern,
+             va_list args) {
+    // Increment test number.
+    self->test_num++;
+
+    if (condition) {
+        self->num_passed++;
+    }
+    else {
+        self->num_failed++;
+    }
+
+    TestFormatter_VTest_Result(self->formatter, condition, self->test_num,
+                               pattern, args);
+
+    return condition;
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/40220b9e/core/Clownfish/TestHarness/TestBatchRunner.cfh
----------------------------------------------------------------------
diff --git a/core/Clownfish/TestHarness/TestBatchRunner.cfh b/core/Clownfish/TestHarness/TestBatchRunner.cfh
new file mode 100644
index 0000000..ea993bb
--- /dev/null
+++ b/core/Clownfish/TestHarness/TestBatchRunner.cfh
@@ -0,0 +1,135 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+parcel Clownfish;
+
+/** Run a single test batch and collect statistics.
+ */
+class Clownfish::TestHarness::TestBatchRunner inherits Clownfish::Obj {
+    TestFormatter *formatter;
+    uint32_t       test_num;
+    uint32_t       num_planned;
+    uint32_t       num_passed;
+    uint32_t       num_failed;
+    uint32_t       num_skipped;
+
+    inert incremented TestBatchRunner*
+    new(TestFormatter *formatter);
+
+    inert TestBatchRunner*
+    init(TestBatchRunner *self, TestFormatter *formatter);
+
+    public void
+    Destroy(TestBatchRunner *self);
+
+    /** Run the test batch and print test output and diagnosis.
+     *
+     * @return true if the test batch passed.
+     */
+    bool
+    Run_Batch(TestBatchRunner *self, TestBatch *batch);
+
+    void
+    Plan(TestBatchRunner *self, TestBatch *batch, uint32_t num_planned);
+
+    /** Return the number of tests planned.
+     */
+    uint32_t
+    Get_Num_Planned(TestBatchRunner *self);
+
+    /** Return the number of tests run.
+     */
+    uint32_t
+    Get_Num_Tests(TestBatchRunner *self);
+
+    /** Return the number of failed tests.
+     */
+    uint32_t
+    Get_Num_Failed(TestBatchRunner *self);
+
+    bool
+    Test_True(TestBatchRunner *self, bool condition, const char *pattern, ...);
+
+    bool
+    Test_False(TestBatchRunner *self, bool condition, const char *pattern,
+               ...);
+
+    bool
+    Test_Int_Equals(TestBatchRunner *self, long got, long expected,
+                    const char *pattern, ...);
+
+    bool
+    Test_Float_Equals(TestBatchRunner *self, double got, double expected,
+                      const char *pattern, ...);
+
+    bool
+    Test_String_Equals(TestBatchRunner *self, const char *got,
+                       const char *expected, const char *pattern, ...);
+
+    bool
+    Pass(TestBatchRunner *self, const char *pattern, ...);
+
+    bool
+    Fail(TestBatchRunner *self, const char *pattern, ...);
+
+    void
+    Skip(TestBatchRunner *self, const char *pattern, ...);
+
+    bool
+    VTest_True(TestBatchRunner *self, bool condition, const char *pattern,
+               va_list args);
+
+    bool
+    VTest_False(TestBatchRunner *self, bool condition, const char *pattern,
+                va_list args);
+
+    bool
+    VTest_Int_Equals(TestBatchRunner *self, long got, long expected,
+                     const char *pattern, va_list args);
+
+    bool
+    VTest_Float_Equals(TestBatchRunner *self, double got, double expected,
+                       const char *pattern, va_list args);
+
+    bool
+    VTest_String_Equals(TestBatchRunner *self, const char *got,
+                        const char *expected, const char *pattern,
+                        va_list args);
+
+    bool
+    VPass(TestBatchRunner *self, const char *pattern, va_list args);
+
+    bool
+    VFail(TestBatchRunner *self, const char *pattern, va_list args);
+
+    void
+    VSkip(TestBatchRunner *self, const char *pattern, va_list args);
+}
+
+__C__
+#ifdef CFISH_USE_SHORT_NAMES
+  #define TEST_TRUE            Cfish_TestBatchRunner_Test_True
+  #define TEST_FALSE           Cfish_TestBatchRunner_Test_False
+  #define TEST_INT_EQ          Cfish_TestBatchRunner_Test_Int_Equals
+  #define TEST_FLOAT_EQ        Cfish_TestBatchRunner_Test_Float_Equals
+  #define TEST_STR_EQ          Cfish_TestBatchRunner_Test_String_Equals
+  #define PASS                 Cfish_TestBatchRunner_Pass
+  #define FAIL                 Cfish_TestBatchRunner_Fail
+  #define SKIP                 Cfish_TestBatchRunner_Skip
+#endif
+__END_C__
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/40220b9e/core/Clownfish/TestHarness/TestFormatter.c
----------------------------------------------------------------------
diff --git a/core/Clownfish/TestHarness/TestFormatter.c b/core/Clownfish/TestHarness/TestFormatter.c
index b6f0ac0..8f0d08a 100644
--- a/core/Clownfish/TestHarness/TestFormatter.c
+++ b/core/Clownfish/TestHarness/TestFormatter.c
@@ -22,10 +22,11 @@
 #define CHY_USE_SHORT_NAMES
 
 #include "Clownfish/TestHarness/TestFormatter.h"
+
 #include "Clownfish/CharBuf.h"
 #include "Clownfish/Err.h"
 #include "Clownfish/TestHarness/TestBatch.h"
-#include "Clownfish/TestHarness/TestRunner.h"
+#include "Clownfish/TestHarness/TestSuiteRunner.h"
 #include "Clownfish/VTable.h"
 
 TestFormatter*
@@ -35,28 +36,27 @@ TestFormatter_init(TestFormatter *self) {
 }
 
 void
-TestFormatter_test_result(void *vself, bool pass, uint32_t test_num,
+TestFormatter_test_result(TestFormatter *self, bool pass, uint32_t test_num,
                           const char *fmt, ...) {
     va_list args;
     va_start(args, fmt);
-    TestFormatter_VTest_Result((TestFormatter*)vself, pass, test_num, fmt,
-                               args);
+    TestFormatter_VTest_Result(self, pass, test_num, fmt, args);
     va_end(args);
 }
 
 void
-TestFormatter_test_comment(void *vself, const char *fmt, ...) {
+TestFormatter_test_comment(TestFormatter *self, const char *fmt, ...) {
     va_list args;
     va_start(args, fmt);
-    TestFormatter_VTest_Comment((TestFormatter*)vself, fmt, args);
+    TestFormatter_VTest_Comment(self, fmt, args);
     va_end(args);
 }
 
 void
-TestFormatter_batch_comment(void *vself, const char *fmt, ...) {
+TestFormatter_batch_comment(TestFormatter *self, const char *fmt, ...) {
     va_list args;
     va_start(args, fmt);
-    TestFormatter_VBatch_Comment((TestFormatter*)vself, fmt, args);
+    TestFormatter_VBatch_Comment(self, fmt, args);
     va_end(args);
 }
 
@@ -73,7 +73,8 @@ TestFormatterCF_init(TestFormatterCF *self) {
 }
 
 void
-TestFormatterCF_batch_prologue(TestFormatterCF *self, TestBatch *batch) {
+TestFormatterCF_batch_prologue(TestFormatterCF *self, TestBatch *batch,
+                               uint32_t num_planned) {
     UNUSED_VAR(self);
     CharBuf *class_name = TestBatch_Get_Class_Name(batch);
     printf("Running %s...\n", CB_Get_Ptr8(class_name));
@@ -108,12 +109,13 @@ TestFormatterCF_vbatch_comment(TestFormatterCF *self, const char *fmt,
 }
 
 void
-TestFormatterCF_summary(TestFormatterCF *self, TestRunner *runner) {
+TestFormatterCF_summary(TestFormatterCF *self, TestSuiteRunner *runner) {
     UNUSED_VAR(self);
-    uint32_t num_batches        = TestRunner_Get_Num_Batches(runner);
-    uint32_t num_batches_failed = TestRunner_Get_Num_Batches_Failed(runner);
-    uint32_t num_tests          = TestRunner_Get_Num_Tests(runner);
-    uint32_t num_tests_failed   = TestRunner_Get_Num_Tests_Failed(runner);
+    uint32_t num_batches = TestSuiteRunner_Get_Num_Batches(runner);
+    uint32_t num_batches_failed
+        = TestSuiteRunner_Get_Num_Batches_Failed(runner);
+    uint32_t num_tests = TestSuiteRunner_Get_Num_Tests(runner);
+    uint32_t num_tests_failed = TestSuiteRunner_Get_Num_Tests_Failed(runner);
 
     if (num_batches == 0) {
         printf("No tests planned or run.\n");
@@ -143,9 +145,10 @@ TestFormatterTAP_init(TestFormatterTAP *self) {
 }
 
 void
-TestFormatterTAP_batch_prologue(TestFormatterTAP *self, TestBatch *batch) {
+TestFormatterTAP_batch_prologue(TestFormatterTAP *self, TestBatch *batch,
+                                uint32_t num_planned) {
     UNUSED_VAR(self);
-    printf("1..%u\n", TestBatch_Get_Num_Planned(batch));
+    printf("1..%u\n", num_planned);
 }
 
 void
@@ -176,7 +179,7 @@ TestFormatterTAP_vbatch_comment(TestFormatterTAP *self, const char *fmt,
 }
 
 void
-TestFormatterTAP_summary(TestFormatterTAP *self, TestRunner *runner) {
+TestFormatterTAP_summary(TestFormatterTAP *self, TestSuiteRunner *runner) {
     UNUSED_VAR(self);
     UNUSED_VAR(runner);
 }

http://git-wip-us.apache.org/repos/asf/lucy/blob/40220b9e/core/Clownfish/TestHarness/TestFormatter.cfh
----------------------------------------------------------------------
diff --git a/core/Clownfish/TestHarness/TestFormatter.cfh b/core/Clownfish/TestHarness/TestFormatter.cfh
index 06402d2..d42c6fa 100644
--- a/core/Clownfish/TestHarness/TestFormatter.cfh
+++ b/core/Clownfish/TestHarness/TestFormatter.cfh
@@ -16,29 +16,29 @@
 
 parcel Clownfish;
 
-/**
- * Abstract base class for Clownfish test formatters.
+/** Abstract base class for Clownfish test formatters.
  */
 abstract class Clownfish::TestHarness::TestFormatter inherits Clownfish::Obj {
     inert TestFormatter*
     init(TestFormatter *self);
 
-    inert void
-    test_result(void *vself, bool pass, uint32_t test_num, const char *fmt,
-                ...);
+    void
+    Test_Result(TestFormatter *self, bool pass, uint32_t test_num,
+                const char *fmt, ...);
 
-    inert void
-    test_comment(void *vself, const char *fmt, ...);
+    void
+    Test_Comment(TestFormatter *self, const char *fmt, ...);
 
-    inert void
-    batch_comment(void *vself, const char *fmt, ...);
+    void
+    Batch_Comment(TestFormatter *self, const char *fmt, ...);
 
     /** Print output at the beginning of a test batch.
      *
      * @param batch The test batch.
      */
     abstract void
-    Batch_Prologue(TestFormatter *self, TestBatch *batch);
+    Batch_Prologue(TestFormatter *self, TestBatch *batch,
+                   uint32_t num_planned);
 
     /** Print the result of a single test.
      *
@@ -72,10 +72,11 @@ abstract class Clownfish::TestHarness::TestFormatter inherits Clownfish::Obj {
      * @param runner The test runner.
      */
     abstract void
-    Summary(TestFormatter *self, TestRunner *runner);
+    Summary(TestFormatter *self, TestSuiteRunner *runner);
 }
 
-/**
+/** TestFormatter for "Clownfish" format.
+ *
  * A TestFormatter that produces human-readable output in a custom
  * "Clownfish" format.
  */
@@ -89,7 +90,8 @@ class Clownfish::Test::Formatter::TestFormatterCF
     init(TestFormatterCF *self);
 
     void
-    Batch_Prologue(TestFormatterCF *self, TestBatch *batch);
+    Batch_Prologue(TestFormatterCF *self, TestBatch *batch,
+                   uint32_t num_planned);
 
     void
     VTest_Result(TestFormatterCF *self, bool pass, uint32_t test_num,
@@ -102,10 +104,11 @@ class Clownfish::Test::Formatter::TestFormatterCF
     VBatch_Comment(TestFormatterCF *self, const char *fmt, va_list args);
 
     void
-    Summary(TestFormatterCF *self, TestRunner *runner);
+    Summary(TestFormatterCF *self, TestSuiteRunner *runner);
 }
 
-/**
+/** TestFormatter for TAP output.
+ *
  * A TestFormatter that produces TAP output (Test Anything Protocol).
  * See http://testanything.org/
  */
@@ -119,7 +122,8 @@ class Clownfish::Test::Formatter::TestFormatterTAP
     init(TestFormatterTAP *self);
 
     void
-    Batch_Prologue(TestFormatterTAP *self, TestBatch *batch);
+    Batch_Prologue(TestFormatterTAP *self, TestBatch *batch,
+                   uint32_t num_planned);
 
     void
     VTest_Result(TestFormatterTAP *self, bool pass, uint32_t test_num,
@@ -132,7 +136,7 @@ class Clownfish::Test::Formatter::TestFormatterTAP
     VBatch_Comment(TestFormatterTAP *self, const char *fmt, va_list args);
 
     void
-    Summary(TestFormatterTAP *self, TestRunner *runner);
+    Summary(TestFormatterTAP *self, TestSuiteRunner *runner);
 }
 
 

http://git-wip-us.apache.org/repos/asf/lucy/blob/40220b9e/core/Clownfish/TestHarness/TestRunner.c
----------------------------------------------------------------------
diff --git a/core/Clownfish/TestHarness/TestRunner.c b/core/Clownfish/TestHarness/TestRunner.c
deleted file mode 100644
index 3132d39..0000000
--- a/core/Clownfish/TestHarness/TestRunner.c
+++ /dev/null
@@ -1,93 +0,0 @@
-/* Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define C_CFISH_TESTRUNNER
-#define CFISH_USE_SHORT_NAMES
-#define LUCY_USE_SHORT_NAMES
-#define CHY_USE_SHORT_NAMES
-
-#include "Clownfish/TestHarness/TestRunner.h"
-#include "Clownfish/Err.h"
-#include "Clownfish/TestHarness/TestBatch.h"
-#include "Clownfish/TestHarness/TestFormatter.h"
-#include "Clownfish/VTable.h"
-
-TestRunner*
-TestRunner_new(TestFormatter *formatter) {
-    TestRunner *self = (TestRunner*)VTable_Make_Obj(TESTRUNNER);
-    return TestRunner_init(self, formatter);
-}
-
-TestRunner*
-TestRunner_init(TestRunner *self, TestFormatter *formatter) {
-    self->formatter          = (TestFormatter*)INCREF(formatter);
-    self->num_tests          = 0;
-    self->num_tests_failed   = 0;
-    self->num_batches        = 0;
-    self->num_batches_failed = 0;
-
-    return self;
-}
-
-void
-TestRunner_destroy(TestRunner *self) {
-    DECREF(self->formatter);
-    SUPER_DESTROY(self, TESTRUNNER);
-}
-
-bool
-TestRunner_run_batch(TestRunner *self, TestBatch *batch) {
-    bool success = TestBatch_Run(batch);
-
-    self->num_tests        += TestBatch_Get_Num_Tests(batch);
-    self->num_tests_failed += TestBatch_Get_Num_Failed(batch);
-    self->num_batches      += 1;
-
-    if (!success) {
-        self->num_batches_failed += 1;
-    }
-
-    return success;
-}
-
-bool
-TestRunner_finish(TestRunner *self) {
-    TestFormatter_Summary(self->formatter, self);
-
-    return self->num_batches != 0 && self->num_batches_failed == 0;
-}
-
-uint32_t
-TestRunner_get_num_tests(TestRunner *self) {
-    return self->num_tests;
-}
-
-uint32_t
-TestRunner_get_num_tests_failed(TestRunner *self) {
-    return self->num_tests_failed;
-}
-
-uint32_t
-TestRunner_get_num_batches(TestRunner *self) {
-    return self->num_batches;
-}
-
-uint32_t
-TestRunner_get_num_batches_failed(TestRunner *self) {
-    return self->num_batches_failed;
-}
-
-