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 2016/07/11 11:25:40 UTC

[07/14] lucy-clownfish git commit: Move tests to separate directory

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/TestVector.c
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestVector.c b/runtime/core/Clownfish/Test/TestVector.c
deleted file mode 100644
index 17a7f98..0000000
--- a/runtime/core/Clownfish/Test/TestVector.c
+++ /dev/null
@@ -1,572 +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 <string.h>
-#include <stdlib.h>
-
-#define C_CFISH_VECTOR
-#define CFISH_USE_SHORT_NAMES
-#define TESTCFISH_USE_SHORT_NAMES
-
-#define MAX_VECTOR_SIZE (SIZE_MAX / sizeof(Obj*))
-
-#include "Clownfish/Test/TestVector.h"
-
-#include "Clownfish/String.h"
-#include "Clownfish/Boolean.h"
-#include "Clownfish/Err.h"
-#include "Clownfish/Num.h"
-#include "Clownfish/Test.h"
-#include "Clownfish/TestHarness/TestBatchRunner.h"
-#include "Clownfish/TestHarness/TestUtils.h"
-#include "Clownfish/Vector.h"
-#include "Clownfish/Class.h"
-
-TestVector*
-TestVector_new() {
-    return (TestVector*)Class_Make_Obj(TESTVECTOR);
-}
-
-// Return an array of size 10 with 30 garbage pointers behind.
-static Vector*
-S_array_with_garbage() {
-    Vector *array = Vec_new(100);
-
-    for (int i = 0; i < 40; i++) {
-        Vec_Push(array, (Obj*)CFISH_TRUE);
-    }
-
-    // Remove elements using different methods.
-    Vec_Excise(array, 10, 10);
-    for (int i = 0; i < 10; i++) { Vec_Pop(array); }
-    Vec_Resize(array, 10);
-
-    return array;
-}
-
-static void
-test_Equals(TestBatchRunner *runner) {
-    Vector *array = Vec_new(0);
-    Vector *other = Vec_new(0);
-    String *stuff = SSTR_WRAP_C("stuff");
-
-    TEST_TRUE(runner, Vec_Equals(array, (Obj*)array),
-              "Array equal to self");
-
-    TEST_FALSE(runner, Vec_Equals(array, (Obj*)CFISH_TRUE),
-               "Array not equal to non-array");
-
-    TEST_TRUE(runner, Vec_Equals(array, (Obj*)other),
-              "Empty arrays are equal");
-
-    Vec_Push(array, (Obj*)CFISH_TRUE);
-    TEST_FALSE(runner, Vec_Equals(array, (Obj*)other),
-               "Add one elem and Equals returns false");
-
-    Vec_Push(other, (Obj*)CFISH_TRUE);
-    TEST_TRUE(runner, Vec_Equals(array, (Obj*)other),
-              "Add a matching elem and Equals returns true");
-
-    Vec_Store(array, 2, (Obj*)CFISH_TRUE);
-    TEST_FALSE(runner, Vec_Equals(array, (Obj*)other),
-               "Add elem after a NULL and Equals returns false");
-
-    Vec_Store(other, 2, (Obj*)CFISH_TRUE);
-    TEST_TRUE(runner, Vec_Equals(array, (Obj*)other),
-              "Empty elems don't spoil Equals");
-
-    Vec_Store(other, 2, INCREF(stuff));
-    TEST_FALSE(runner, Vec_Equals(array, (Obj*)other),
-               "Non-matching value spoils Equals");
-
-    Vec_Store(other, 2, NULL);
-    TEST_FALSE(runner, Vec_Equals(array, (Obj*)other),
-               "NULL value spoils Equals");
-    TEST_FALSE(runner, Vec_Equals(other, (Obj*)array),
-               "NULL value spoils Equals (reversed)");
-
-    Vec_Excise(array, 1, 2);       // removes empty elems
-    DECREF(Vec_Delete(other, 1));  // leaves NULL in place of deleted elem
-    DECREF(Vec_Delete(other, 2));
-    TEST_FALSE(runner, Vec_Equals(array, (Obj*)other),
-               "Empty trailing elements spoil Equals");
-
-    DECREF(array);
-    DECREF(other);
-}
-
-static void
-test_Store_Fetch(TestBatchRunner *runner) {
-    Vector *array = Vec_new(0);
-    String *elem;
-
-    TEST_TRUE(runner, Vec_Fetch(array, 2) == NULL, "Fetch beyond end");
-
-    Vec_Store(array, 2, (Obj*)Str_newf("foo"));
-    elem = (String*)CERTIFY(Vec_Fetch(array, 2), STRING);
-    TEST_UINT_EQ(runner, 3, Vec_Get_Size(array), "Store updates size");
-    TEST_TRUE(runner, Str_Equals_Utf8(elem, "foo", 3), "Store");
-
-    elem = (String*)INCREF(elem);
-    TEST_INT_EQ(runner, 2, CFISH_REFCOUNT_NN(elem),
-                "start with refcount of 2");
-    Vec_Store(array, 2, (Obj*)Str_newf("bar"));
-    TEST_INT_EQ(runner, 1, CFISH_REFCOUNT_NN(elem),
-                "Displacing elem via Store updates refcount");
-    DECREF(elem);
-    elem = (String*)CERTIFY(Vec_Fetch(array, 2), STRING);
-    TEST_TRUE(runner, Str_Equals_Utf8(elem, "bar", 3), "Store displacement");
-
-    DECREF(array);
-
-    array = S_array_with_garbage();
-    Vec_Store(array, 40, (Obj*)CFISH_TRUE);
-    bool all_null = true;
-    for (size_t i = 10; i < 40; i++) {
-        if (Vec_Fetch(array, i) != NULL) { all_null = false; }
-    }
-    TEST_TRUE(runner, all_null, "Out-of-bounds Store clears excised elements");
-    DECREF(array);
-}
-
-static void
-test_Push_Pop_Insert(TestBatchRunner *runner) {
-    Vector *array = Vec_new(0);
-    String *elem;
-
-    TEST_UINT_EQ(runner, Vec_Get_Size(array), 0, "size starts at 0");
-    TEST_TRUE(runner, Vec_Pop(array) == NULL,
-              "Pop from empty array returns NULL");
-
-    Vec_Push(array, (Obj*)Str_newf("a"));
-    Vec_Push(array, (Obj*)Str_newf("b"));
-    Vec_Push(array, (Obj*)Str_newf("c"));
-
-    TEST_UINT_EQ(runner, Vec_Get_Size(array), 3, "size after Push");
-    TEST_TRUE(runner, NULL != CERTIFY(Vec_Fetch(array, 2), STRING), "Push");
-
-    elem = (String*)CERTIFY(Vec_Pop(array), STRING);
-    TEST_TRUE(runner, Str_Equals_Utf8(elem, "c", 1), "Pop");
-    TEST_UINT_EQ(runner, Vec_Get_Size(array), 2, "size after Pop");
-    DECREF(elem);
-
-    Vec_Insert(array, 0, (Obj*)Str_newf("foo"));
-    elem = (String*)CERTIFY(Vec_Fetch(array, 0), STRING);
-    TEST_TRUE(runner, Str_Equals_Utf8(elem, "foo", 3), "Insert");
-    TEST_UINT_EQ(runner, Vec_Get_Size(array), 3, "size after Insert");
-
-    for (int i = 0; i < 256; ++i) {
-        Vec_Push(array, (Obj*)Str_newf("flotsam"));
-    }
-    for (size_t i = 0; i < 512; ++i) {
-        Vec_Insert(array, i, (Obj*)Str_newf("jetsam"));
-    }
-    TEST_UINT_EQ(runner, Vec_Get_Size(array), 3 + 256 + 512,
-                 "size after exercising Push and Insert");
-
-    DECREF(array);
-}
-
-static void
-test_Insert_All(TestBatchRunner *runner) {
-    int64_t i;
-
-    {
-        Vector *dst    = Vec_new(20);
-        Vector *src    = Vec_new(10);
-        Vector *wanted = Vec_new(30);
-
-        for (i = 0; i < 10; i++) { Vec_Push(dst, (Obj*)Int_new(i)); }
-        for (i = 0; i < 10; i++) { Vec_Push(dst, (Obj*)Int_new(i + 20)); }
-        for (i = 0; i < 10; i++) { Vec_Push(src, (Obj*)Int_new(i + 10)); }
-        for (i = 0; i < 30; i++) { Vec_Push(wanted, (Obj*)Int_new(i)); }
-
-        Vec_Insert_All(dst, 10, src);
-        TEST_TRUE(runner, Vec_Equals(dst, (Obj*)wanted), "Insert_All between");
-
-        DECREF(wanted);
-        DECREF(src);
-        DECREF(dst);
-    }
-
-    {
-        Vector *dst    = Vec_new(10);
-        Vector *src    = Vec_new(10);
-        Vector *wanted = Vec_new(30);
-
-        for (i = 0; i < 10; i++) { Vec_Push(dst, (Obj*)Int_new(i)); }
-        for (i = 0; i < 10; i++) { Vec_Push(src, (Obj*)Int_new(i + 20)); }
-        for (i = 0; i < 10; i++) { Vec_Push(wanted, (Obj*)Int_new(i)); }
-        for (i = 0; i < 10; i++) {
-            Vec_Store(wanted, (size_t)i + 20, (Obj*)Int_new(i + 20));
-        }
-
-        Vec_Insert_All(dst, 20, src);
-        TEST_TRUE(runner, Vec_Equals(dst, (Obj*)wanted), "Insert_All after");
-
-        DECREF(wanted);
-        DECREF(src);
-        DECREF(dst);
-    }
-}
-
-static void
-test_Delete(TestBatchRunner *runner) {
-    Vector *wanted = Vec_new(5);
-    Vector *got    = Vec_new(5);
-    uint32_t i;
-
-    for (i = 0; i < 5; i++) { Vec_Push(got, (Obj*)Str_newf("%u32", i)); }
-    Vec_Store(wanted, 0, (Obj*)Str_newf("0", i));
-    Vec_Store(wanted, 1, (Obj*)Str_newf("1", i));
-    Vec_Store(wanted, 4, (Obj*)Str_newf("4", i));
-    DECREF(Vec_Delete(got, 2));
-    DECREF(Vec_Delete(got, 3));
-    TEST_TRUE(runner, Vec_Equals(wanted, (Obj*)got), "Delete");
-
-    TEST_TRUE(runner, Vec_Delete(got, 25000) == NULL,
-              "Delete beyond array size returns NULL");
-
-    DECREF(wanted);
-    DECREF(got);
-}
-
-static void
-test_Resize(TestBatchRunner *runner) {
-    Vector *array = Vec_new(3);
-    uint32_t i;
-
-    for (i = 0; i < 2; i++) { Vec_Push(array, (Obj*)Str_newf("%u32", i)); }
-    TEST_UINT_EQ(runner, Vec_Get_Capacity(array), 3, "Start with capacity 3");
-
-    Vec_Resize(array, 4);
-    TEST_UINT_EQ(runner, Vec_Get_Size(array), 4, "Resize up");
-    TEST_UINT_EQ(runner, Vec_Get_Capacity(array), 4,
-                "Resize changes capacity");
-
-    Vec_Resize(array, 2);
-    TEST_UINT_EQ(runner, Vec_Get_Size(array), 2, "Resize down");
-    TEST_TRUE(runner, Vec_Fetch(array, 2) == NULL, "Resize down zaps elem");
-
-    Vec_Resize(array, 2);
-    TEST_UINT_EQ(runner, Vec_Get_Size(array), 2, "Resize to same size");
-
-    DECREF(array);
-
-    array = S_array_with_garbage();
-    Vec_Resize(array, 40);
-    bool all_null = true;
-    for (size_t i = 10; i < 40; i++) {
-        if (Vec_Fetch(array, i) != NULL) { all_null = false; }
-    }
-    TEST_TRUE(runner, all_null, "Resize clears excised elements");
-    DECREF(array);
-}
-
-static void
-test_Excise(TestBatchRunner *runner) {
-    Vector *wanted = Vec_new(5);
-    Vector *got    = Vec_new(5);
-
-    for (uint32_t i = 0; i < 5; i++) {
-        Vec_Push(wanted, (Obj*)Str_newf("%u32", i));
-        Vec_Push(got, (Obj*)Str_newf("%u32", i));
-    }
-
-    Vec_Excise(got, 7, 1);
-    TEST_TRUE(runner, Vec_Equals(wanted, (Obj*)got),
-              "Excise outside of range is no-op");
-
-    Vec_Excise(got, 2, 2);
-    DECREF(Vec_Delete(wanted, 2));
-    DECREF(Vec_Delete(wanted, 3));
-    Vec_Store(wanted, 2, Vec_Delete(wanted, 4));
-    Vec_Resize(wanted, 3);
-    TEST_TRUE(runner, Vec_Equals(wanted, (Obj*)got),
-              "Excise multiple elems");
-
-    Vec_Excise(got, 2, 2);
-    Vec_Resize(wanted, 2);
-    TEST_TRUE(runner, Vec_Equals(wanted, (Obj*)got),
-              "Splicing too many elems truncates");
-
-    Vec_Excise(got, 0, 1);
-    Vec_Store(wanted, 0, Vec_Delete(wanted, 1));
-    Vec_Resize(wanted, 1);
-    TEST_TRUE(runner, Vec_Equals(wanted, (Obj*)got),
-              "Excise first elem");
-
-    DECREF(got);
-    DECREF(wanted);
-}
-
-static void
-test_Push_All(TestBatchRunner *runner) {
-    Vector *wanted  = Vec_new(0);
-    Vector *got     = Vec_new(0);
-    Vector *scratch = Vec_new(0);
-    Vector *empty   = Vec_new(0);
-    uint32_t i;
-
-    for (i =  0; i < 40; i++) { Vec_Push(wanted, (Obj*)Str_newf("%u32", i)); }
-    Vec_Push(wanted, NULL);
-    for (i =  0; i < 20; i++) { Vec_Push(got, (Obj*)Str_newf("%u32", i)); }
-    for (i = 20; i < 40; i++) { Vec_Push(scratch, (Obj*)Str_newf("%u32", i)); }
-    Vec_Push(scratch, NULL);
-
-    Vec_Push_All(got, scratch);
-    TEST_TRUE(runner, Vec_Equals(wanted, (Obj*)got), "Push_All");
-
-    Vec_Push_All(got, empty);
-    TEST_TRUE(runner, Vec_Equals(wanted, (Obj*)got),
-              "Push_All with empty array");
-
-    DECREF(wanted);
-    DECREF(got);
-    DECREF(scratch);
-    DECREF(empty);
-}
-
-static void
-test_Slice(TestBatchRunner *runner) {
-    Vector *array = Vec_new(0);
-    for (uint32_t i = 0; i < 10; i++) { Vec_Push(array, (Obj*)Str_newf("%u32", i)); }
-    {
-        Vector *slice = Vec_Slice(array, 0, 10);
-        TEST_TRUE(runner, Vec_Equals(array, (Obj*)slice), "Slice entire array");
-        DECREF(slice);
-    }
-    {
-        Vector *slice = Vec_Slice(array, 0, 11);
-        TEST_TRUE(runner, Vec_Equals(array, (Obj*)slice),
-            "Exceed length");
-        DECREF(slice);
-    }
-    {
-        Vector *wanted = Vec_new(0);
-        Vec_Push(wanted, (Obj*)Str_newf("9"));
-        Vector *slice = Vec_Slice(array, 9, 11);
-        TEST_TRUE(runner, Vec_Equals(slice, (Obj*)wanted),
-            "Exceed length, start near end");
-        DECREF(slice);
-        DECREF(wanted);
-    }
-    {
-        Vector *slice = Vec_Slice(array, 0, 0);
-        TEST_TRUE(runner, Vec_Get_Size(slice) == 0, "empty slice");
-        DECREF(slice);
-    }
-    {
-        Vector *slice = Vec_Slice(array, 20, 1);
-        TEST_TRUE(runner, Vec_Get_Size(slice) ==  0, "exceed offset");
-        DECREF(slice);
-    }
-    {
-        Vector *wanted = Vec_new(0);
-        Vec_Push(wanted, (Obj*)Str_newf("9"));
-        Vector *slice = Vec_Slice(array, 9, SIZE_MAX - 1);
-        TEST_TRUE(runner, Vec_Get_Size(slice) == 1, "guard against overflow");
-        DECREF(slice);
-        DECREF(wanted);
-    }
-    DECREF(array);
-}
-
-static void
-test_Clone(TestBatchRunner *runner) {
-    Vector *array = Vec_new(0);
-    Vector *twin;
-    uint32_t i;
-
-    for (i = 0; i < 10; i++) {
-        Vec_Push(array, (Obj*)Int_new(i));
-    }
-    Vec_Push(array, NULL);
-    twin = Vec_Clone(array);
-    TEST_TRUE(runner, Vec_Equals(array, (Obj*)twin), "Clone");
-    TEST_TRUE(runner, Vec_Fetch(array, 1) == Vec_Fetch(twin, 1),
-              "Clone doesn't clone elements");
-
-    DECREF(array);
-    DECREF(twin);
-}
-
-static void
-S_push(void *context) {
-    Vector *vec = (Vector*)context;
-    Vec_Push(vec, (Obj*)CFISH_TRUE);
-}
-
-static void
-S_insert_at_size_max(void *context) {
-    Vector *vec = (Vector*)context;
-    Vec_Insert(vec, SIZE_MAX, (Obj*)CFISH_TRUE);
-}
-
-static void
-S_store_at_size_max(void *context) {
-    Vector *vec = (Vector*)context;
-    Vec_Store(vec, SIZE_MAX, (Obj*)CFISH_TRUE);
-}
-
-typedef struct {
-    Vector *vec;
-    Vector *other;
-} VectorPair;
-
-static void
-S_push_all(void *vcontext) {
-    VectorPair *context = (VectorPair*)vcontext;
-    Vec_Push_All(context->vec, context->other);
-}
-
-static void
-S_insert_all_at_size_max(void *vcontext) {
-    VectorPair *context = (VectorPair*)vcontext;
-    Vec_Insert_All(context->vec, SIZE_MAX, context->other);
-}
-
-static void
-S_test_exception(TestBatchRunner *runner, Err_Attempt_t func, void *context,
-                 const char *test_name) {
-    Err *error = Err_trap(func, context);
-    TEST_TRUE(runner, error != NULL, test_name);
-    DECREF(error);
-}
-
-static void
-test_exceptions(TestBatchRunner *runner) {
-    {
-        Vector *vec = Vec_new(0);
-        vec->cap  = MAX_VECTOR_SIZE;
-        vec->size = vec->cap;
-        S_test_exception(runner, S_push, vec, "Push throws on overflow");
-        vec->size = 0;
-        DECREF(vec);
-    }
-
-    {
-        Vector *vec = Vec_new(0);
-        S_test_exception(runner, S_insert_at_size_max, vec,
-                         "Insert throws on overflow");
-        DECREF(vec);
-    }
-
-    {
-        Vector *vec = Vec_new(0);
-        S_test_exception(runner, S_store_at_size_max, vec,
-                         "Store throws on overflow");
-        DECREF(vec);
-    }
-
-    {
-        VectorPair context;
-        context.vec         = Vec_new(0);
-        context.vec->cap    = 1000000000;
-        context.vec->size   = context.vec->cap;
-        context.other       = Vec_new(0);
-        context.other->cap  = MAX_VECTOR_SIZE - context.vec->cap + 1;
-        context.other->size = context.other->cap;
-        S_test_exception(runner, S_push_all, &context,
-                         "Push_All throws on overflow");
-        context.vec->size   = 0;
-        context.other->size = 0;
-        DECREF(context.other);
-        DECREF(context.vec);
-    }
-
-    {
-        VectorPair context;
-        context.vec   = Vec_new(0);
-        context.other = Vec_new(0);
-        S_test_exception(runner, S_insert_all_at_size_max, &context,
-                         "Insert_All throws on overflow");
-        DECREF(context.other);
-        DECREF(context.vec);
-    }
-}
-
-static void
-test_Sort(TestBatchRunner *runner) {
-    Vector *array  = Vec_new(8);
-    Vector *wanted = Vec_new(8);
-
-    Vec_Push(array, NULL);
-    Vec_Push(array, (Obj*)Str_newf("aaab"));
-    Vec_Push(array, (Obj*)Str_newf("ab"));
-    Vec_Push(array, NULL);
-    Vec_Push(array, NULL);
-    Vec_Push(array, (Obj*)Str_newf("aab"));
-    Vec_Push(array, (Obj*)Str_newf("b"));
-
-    Vec_Push(wanted, (Obj*)Str_newf("aaab"));
-    Vec_Push(wanted, (Obj*)Str_newf("aab"));
-    Vec_Push(wanted, (Obj*)Str_newf("ab"));
-    Vec_Push(wanted, (Obj*)Str_newf("b"));
-    Vec_Push(wanted, NULL);
-    Vec_Push(wanted, NULL);
-    Vec_Push(wanted, NULL);
-
-    Vec_Sort(array);
-    TEST_TRUE(runner, Vec_Equals(array, (Obj*)wanted), "Sort with NULLs");
-
-    DECREF(array);
-    DECREF(wanted);
-}
-
-static void
-test_Grow(TestBatchRunner *runner) {
-    Vector *array = Vec_new(500);
-    size_t  cap;
-
-    cap = Vec_Get_Capacity(array);
-    TEST_TRUE(runner, cap >= 500, "Array is created with minimum capacity");
-
-    Vec_Grow(array, 2000);
-    cap = Vec_Get_Capacity(array);
-    TEST_TRUE(runner, cap >= 2000, "Grow to larger capacity");
-
-    size_t old_cap = cap;
-    Vec_Grow(array, old_cap);
-    cap = Vec_Get_Capacity(array);
-    TEST_TRUE(runner, cap >= old_cap, "Grow to same capacity");
-
-    Vec_Grow(array, 1000);
-    cap = Vec_Get_Capacity(array);
-    TEST_TRUE(runner, cap >= 1000, "Grow to smaller capacity");
-
-    DECREF(array);
-}
-
-void
-TestVector_Run_IMP(TestVector *self, TestBatchRunner *runner) {
-    TestBatchRunner_Plan(runner, (TestBatch*)self, 62);
-    test_Equals(runner);
-    test_Store_Fetch(runner);
-    test_Push_Pop_Insert(runner);
-    test_Insert_All(runner);
-    test_Delete(runner);
-    test_Resize(runner);
-    test_Excise(runner);
-    test_Push_All(runner);
-    test_Slice(runner);
-    test_Clone(runner);
-    test_exceptions(runner);
-    test_Sort(runner);
-    test_Grow(runner);
-}
-
-

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/TestVector.cfh
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestVector.cfh b/runtime/core/Clownfish/Test/TestVector.cfh
deleted file mode 100644
index 090b8b9..0000000
--- a/runtime/core/Clownfish/Test/TestVector.cfh
+++ /dev/null
@@ -1,29 +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.
- */
-
-parcel TestClownfish;
-
-class Clownfish::Test::TestVector
-    inherits Clownfish::TestHarness::TestBatch {
-
-    inert incremented TestVector*
-    new();
-
-    void
-    Run(TestVector *self, TestBatchRunner *runner);
-}
-
-

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/Util/TestAtomic.c
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/Util/TestAtomic.c b/runtime/core/Clownfish/Test/Util/TestAtomic.c
deleted file mode 100644
index f87279a..0000000
--- a/runtime/core/Clownfish/Test/Util/TestAtomic.c
+++ /dev/null
@@ -1,65 +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 CFISH_USE_SHORT_NAMES
-#define TESTCFISH_USE_SHORT_NAMES
-
-#include "Clownfish/Test/Util/TestAtomic.h"
-
-#include "Clownfish/Test.h"
-#include "Clownfish/TestHarness/TestBatchRunner.h"
-#include "Clownfish/Util/Atomic.h"
-#include "Clownfish/Class.h"
-
-TestAtomic*
-TestAtomic_new() {
-    return (TestAtomic*)Class_Make_Obj(TESTATOMIC);
-}
-
-static void
-test_cas_ptr(TestBatchRunner *runner) {
-    int    foo = 1;
-    int    bar = 2;
-    int   *foo_pointer = &foo;
-    int   *bar_pointer = &bar;
-    int   *target      = NULL;
-
-    TEST_TRUE(runner,
-              Atomic_cas_ptr((void**)&target, NULL, foo_pointer),
-              "cas_ptr returns true on success");
-    TEST_TRUE(runner, target == foo_pointer, "cas_ptr sets target");
-
-    target = NULL;
-    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(runner, target == NULL,
-              "cas_ptr doesn't do anything to target when old_value doesn't match");
-
-    target = foo_pointer;
-    TEST_TRUE(runner,
-              Atomic_cas_ptr((void**)&target, foo_pointer, bar_pointer),
-              "cas_ptr from one value to another");
-    TEST_TRUE(runner, target == bar_pointer, "cas_ptr sets target");
-}
-
-void
-TestAtomic_Run_IMP(TestAtomic *self, TestBatchRunner *runner) {
-    TestBatchRunner_Plan(runner, (TestBatch*)self, 6);
-    test_cas_ptr(runner);
-}
-
-

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/Util/TestAtomic.cfh
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/Util/TestAtomic.cfh b/runtime/core/Clownfish/Test/Util/TestAtomic.cfh
deleted file mode 100644
index 2788342..0000000
--- a/runtime/core/Clownfish/Test/Util/TestAtomic.cfh
+++ /dev/null
@@ -1,29 +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.
- */
-
-parcel TestClownfish;
-
-class Clownfish::Test::Util::TestAtomic
-    inherits Clownfish::TestHarness::TestBatch {
-
-    inert incremented TestAtomic*
-    new();
-
-    void
-    Run(TestAtomic *self, TestBatchRunner *runner);
-}
-
-

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/Util/TestMemory.c
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/Util/TestMemory.c b/runtime/core/Clownfish/Test/Util/TestMemory.c
deleted file mode 100644
index 8151c72..0000000
--- a/runtime/core/Clownfish/Test/Util/TestMemory.c
+++ /dev/null
@@ -1,119 +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 CFISH_USE_SHORT_NAMES
-#define TESTCFISH_USE_SHORT_NAMES
-
-#include "charmony.h"
-
-#include "Clownfish/Test/Util/TestMemory.h"
-
-#include "Clownfish/Test.h"
-#include "Clownfish/TestHarness/TestBatchRunner.h"
-#include "Clownfish/Util/Memory.h"
-#include "Clownfish/Class.h"
-
-TestMemory*
-TestMemory_new() {
-    return (TestMemory*)Class_Make_Obj(TESTMEMORY);
-}
-
-static void
-test_oversize__growth_rate(TestBatchRunner *runner) {
-    bool     success             = true;
-    uint64_t size                = 0;
-    double   growth_count        = 0;
-    double   average_growth_rate = 0.0;
-
-    while (size < SIZE_MAX) {
-        uint64_t next_size = Memory_oversize((size_t)size + 1, sizeof(void*));
-        if (next_size < size) {
-            success = false;
-            FAIL(runner, "Asked for %" PRId64 ", got smaller amount %" PRId64,
-                 size + 1, next_size);
-            break;
-        }
-        if (size > 0) {
-            growth_count += 1;
-            double growth_rate = CHY_U64_TO_DOUBLE(next_size) /
-                                 CHY_U64_TO_DOUBLE(size);
-            double sum = growth_rate + (growth_count - 1) * average_growth_rate;
-            average_growth_rate = sum / growth_count;
-            if (average_growth_rate < 1.1) {
-                FAIL(runner, "Average growth rate dropped below 1.1x: %f",
-                     average_growth_rate);
-                success = false;
-                break;
-            }
-        }
-        size = next_size;
-    }
-    TEST_TRUE(runner, growth_count > 0, "Grew %f times", growth_count);
-    if (success) {
-        TEST_TRUE(runner, average_growth_rate > 1.1,
-                  "Growth rate of oversize() averages above 1.1: %.3f",
-                  average_growth_rate);
-    }
-
-    for (size_t minimum = 1; minimum < 8; minimum++) {
-        uint64_t next_size = Memory_oversize(minimum, sizeof(void*));
-        double growth_rate = CHY_U64_TO_DOUBLE(next_size) / (double)minimum;
-        TEST_TRUE(runner, growth_rate > 1.2,
-                  "Growth rate is higher for smaller arrays (%u, %.3f)",
-                  (unsigned)minimum, growth_rate);
-    }
-}
-
-static void
-test_oversize__ceiling(TestBatchRunner *runner) {
-    for (unsigned width = 0; width < 10; width++) {
-        size_t size = Memory_oversize(SIZE_MAX, width);
-        TEST_TRUE(runner, size == SIZE_MAX,
-                  "Memory_oversize hits ceiling at SIZE_MAX (width %u)", width);
-        size = Memory_oversize(SIZE_MAX - 1, width);
-        TEST_TRUE(runner, size == SIZE_MAX,
-                  "Memory_oversize hits ceiling at SIZE_MAX (width %u)", width);
-    }
-}
-
-static void
-test_oversize__rounding(TestBatchRunner *runner) {
-    unsigned widths[] = { 1, 2, 4, 0 };
-
-    for (int width_tick = 0; widths[width_tick] != 0; width_tick++) {
-        unsigned width = widths[width_tick];
-        for (unsigned i = 0; i < 25; i++) {
-            size_t size = Memory_oversize(i, width);
-            size_t bytes = size * width;
-            if (bytes % sizeof(size_t) != 0) {
-                FAIL(runner, "Rounding failure for %u, width %u",
-                     i, width);
-                return;
-            }
-        }
-    }
-    PASS(runner, "Round allocations up to the size of a pointer");
-}
-
-void
-TestMemory_Run_IMP(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-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/Util/TestMemory.cfh
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/Util/TestMemory.cfh b/runtime/core/Clownfish/Test/Util/TestMemory.cfh
deleted file mode 100644
index d0b5803..0000000
--- a/runtime/core/Clownfish/Test/Util/TestMemory.cfh
+++ /dev/null
@@ -1,29 +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.
- */
-
-parcel TestClownfish;
-
-class Clownfish::Test::Util::TestMemory
-    inherits Clownfish::TestHarness::TestBatch {
-
-    inert incremented TestMemory*
-    new();
-
-    void
-    Run(TestMemory *self, TestBatchRunner *runner);
-}
-
-

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/Util/TestStringHelper.c
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/Util/TestStringHelper.c b/runtime/core/Clownfish/Test/Util/TestStringHelper.c
deleted file mode 100644
index 2a873fd..0000000
--- a/runtime/core/Clownfish/Test/Util/TestStringHelper.c
+++ /dev/null
@@ -1,373 +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 <string.h>
-
-#define CFISH_USE_SHORT_NAMES
-#define TESTCFISH_USE_SHORT_NAMES
-
-#include "Clownfish/Test/Util/TestStringHelper.h"
-
-#include "Clownfish/String.h"
-#include "Clownfish/Err.h"
-#include "Clownfish/Test.h"
-#include "Clownfish/TestHarness/TestBatchRunner.h"
-#include "Clownfish/Util/StringHelper.h"
-#include "Clownfish/Class.h"
-
-/* This alternative implementation of utf8_valid() is (presumably) slower, but
- * it implements the standard in a more linear, easy-to-grok way.
- */
-#define TRAIL_OK(n) (n >= 0x80 && n <= 0xBF)
-TestStringHelper*
-TestStrHelp_new() {
-    return (TestStringHelper*)Class_Make_Obj(TESTSTRINGHELPER);
-}
-
-static bool
-S_utf8_valid_alt(const char *maybe_utf8, size_t size) {
-    const uint8_t *string = (const uint8_t*)maybe_utf8;
-    const uint8_t *const end = string + size;
-    while (string < end) {
-        int count = StrHelp_UTF8_COUNT[*string];
-        bool valid = false;
-        if (count == 1) {
-            if (string[0] <= 0x7F) {
-                valid = true;
-            }
-        }
-        else if (count == 2) {
-            if (string[0] >= 0xC2 && string[0] <= 0xDF) {
-                if (TRAIL_OK(string[1])) {
-                    valid = true;
-                }
-            }
-        }
-        else if (count == 3) {
-            if (string[0] == 0xE0) {
-                if (string[1] >= 0xA0 && string[1] <= 0xBF
-                    && TRAIL_OK(string[2])
-                   ) {
-                    valid = true;
-                }
-            }
-            else if (string[0] >= 0xE1 && string[0] <= 0xEC) {
-                if (TRAIL_OK(string[1])
-                    && TRAIL_OK(string[2])
-                   ) {
-                    valid = true;
-                }
-            }
-            else if (string[0] == 0xED) {
-                if (string[1] >= 0x80 && string[1] <= 0x9F
-                    && TRAIL_OK(string[2])
-                   ) {
-                    valid = true;
-                }
-            }
-            else if (string[0] >= 0xEE && string[0] <= 0xEF) {
-                if (TRAIL_OK(string[1])
-                    && TRAIL_OK(string[2])
-                   ) {
-                    valid = true;
-                }
-            }
-        }
-        else if (count == 4) {
-            if (string[0] == 0xF0) {
-                if (string[1] >= 0x90 && string[1] <= 0xBF
-                    && TRAIL_OK(string[2])
-                    && TRAIL_OK(string[3])
-                   ) {
-                    valid = true;
-                }
-            }
-            else if (string[0] >= 0xF1 && string[0] <= 0xF3) {
-                if (TRAIL_OK(string[1])
-                    && TRAIL_OK(string[2])
-                    && TRAIL_OK(string[3])
-                   ) {
-                    valid = true;
-                }
-            }
-            else if (string[0] == 0xF4) {
-                if (string[1] >= 0x80 && string[1] <= 0x8F
-                    && TRAIL_OK(string[2])
-                    && TRAIL_OK(string[3])
-                   ) {
-                    valid = true;
-                }
-            }
-        }
-
-        if (!valid) {
-            return false;
-        }
-        string += count;
-    }
-
-    if (string != end) {
-        return false;
-    }
-
-    return true;
-}
-
-static void
-test_overlap(TestBatchRunner *runner) {
-    size_t result;
-    result = StrHelp_overlap("", "", 0, 0);
-    TEST_UINT_EQ(runner, result, 0, "two empty strings");
-    result = StrHelp_overlap("", "foo", 0, 3);
-    TEST_UINT_EQ(runner, result, 0, "first string is empty");
-    result = StrHelp_overlap("foo", "", 3, 0);
-    TEST_UINT_EQ(runner, result, 0, "second string is empty");
-    result = StrHelp_overlap("foo", "foo", 3, 3);
-    TEST_UINT_EQ(runner, result, 3, "equal strings");
-    result = StrHelp_overlap("foo bar", "foo", 7, 3);
-    TEST_UINT_EQ(runner, result, 3, "first string is longer");
-    result = StrHelp_overlap("foo", "foo bar", 3, 7);
-    TEST_UINT_EQ(runner, result, 3, "second string is longer");
-    result = StrHelp_overlap("bar", "baz", 3, 3);
-    TEST_UINT_EQ(runner, result, 2, "different byte");
-}
-
-
-static void
-test_to_base36(TestBatchRunner *runner) {
-    char buffer[StrHelp_MAX_BASE36_BYTES];
-    StrHelp_to_base36(UINT64_MAX, buffer);
-    TEST_STR_EQ(runner, "3w5e11264sgsf", buffer, "base36 UINT64_MAX");
-    StrHelp_to_base36(1, buffer);
-    TEST_STR_EQ(runner, "1", buffer, "base36 1");
-    TEST_INT_EQ(runner, buffer[1], 0, "base36 NULL termination");
-}
-
-static void
-test_utf8_round_trip(TestBatchRunner *runner) {
-    int32_t code_point;
-    for (code_point = 0; code_point <= 0x10FFFF; code_point++) {
-        char buffer[4];
-        uint32_t size = StrHelp_encode_utf8_char(code_point, buffer);
-        char *start = buffer;
-        char *end   = start + size;
-
-        // Verify length returned by encode_utf8_char().
-        if (size != StrHelp_UTF8_COUNT[(unsigned char)buffer[0]]) {
-            break;
-        }
-        // Verify that utf8_valid() agrees with alternate implementation.
-        if (!!StrHelp_utf8_valid(start, size)
-            != !!S_utf8_valid_alt(start, size)
-           ) {
-            break;
-        }
-
-        // Verify back_utf8_char().
-        if (StrHelp_back_utf8_char(end, start) != start) {
-            break;
-        }
-
-        // Verify round trip of encode/decode.
-        if (StrHelp_decode_utf8_char(buffer) != code_point) {
-            break;
-        }
-    }
-    if (code_point == 0x110000) {
-        PASS(runner, "Successfully round tripped 0 - 0x10FFFF");
-    }
-    else {
-        FAIL(runner, "Failed round trip at 0x%.1X", (unsigned)code_point);
-    }
-}
-
-static void
-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(runner, "Disagreement: %s", description);
-    }
-    else {
-        TEST_TRUE(runner, sane == expected, "%s", description);
-    }
-}
-
-static void
-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(runner, "\xF0\x9D\x84\x9E", 4, true,
-                    "Musical symbol G clef");
-    S_test_validity(runner, "\xED\xA0\xB4\xED\xB4\x9E", 6, false,
-                    "G clef as UTF-8 encoded UTF-16 surrogates");
-    S_test_validity(runner, ".\xED\xA0\xB4.", 5, false,
-                    "Isolated high surrogate");
-    S_test_validity(runner, ".\xED\xB4\x9E.", 5, false,
-                    "Isolated low surrogate");
-
-    // Shortest form.
-    S_test_validity(runner, ".\xC1\x9C.", 4, false,
-                    "Non-shortest form ASCII backslash");
-    S_test_validity(runner, ".\xC0\xAF.", 4, false,
-                    "Non-shortest form ASCII slash");
-    S_test_validity(runner, ".\xC0\x80.", 4, false,
-                    "Non-shortest form ASCII NUL character");
-    S_test_validity(runner, ".\xE0\x9F\xBF.", 5, false,
-                    "Non-shortest form three byte sequence");
-    S_test_validity(runner, ".\xF0\x8F\xBF\xBF.", 6, false,
-                    "Non-shortest form four byte sequence");
-
-    // Range.
-    S_test_validity(runner, "\xF8\x88\x80\x80\x80", 5, false, "5-byte UTF-8");
-    S_test_validity(runner, "\xF4\x8F\xBF\xBF", 4, true,
-                    "Code point 0x10FFFF");
-    S_test_validity(runner, "\xF4\x90\x80\x80", 4, false,
-                    "Code point 0x110000 too large");
-    S_test_validity(runner, "\xF5\x80\x80\x80", 4, false,
-                    "Sequence starting with 0xF5");
-
-    // Truncated sequences.
-    S_test_validity(runner, "\xC2", 1, false,
-                    "Truncated two byte sequence");
-    S_test_validity(runner, "\xE2\x98", 2, false,
-                    "Truncated three byte sequence");
-    S_test_validity(runner, "\xF0\x9D\x84", 3, false,
-                    "Truncated four byte sequence");
-
-    // Bad continuations.
-    S_test_validity(runner, "\xE2\x98\xBA\xE2\x98\xBA", 6, true,
-                    "SmileySmiley");
-    S_test_validity(runner, "\xE2\xBA\xE2\x98\xBA", 5, false,
-                    "missing first continuation byte");
-    S_test_validity(runner, "\xE2\x98\xE2\x98\xBA", 5, false,
-                    "missing second continuation byte");
-    S_test_validity(runner, "\xE2\xE2\x98\xBA", 4, false,
-                    "missing both continuation bytes");
-    S_test_validity(runner, "\xBA\xE2\x98\xBA\xE2\xBA", 5, false,
-                    "missing first continuation byte (end)");
-    S_test_validity(runner, "\xE2\x98\xBA\xE2\x98", 5, false,
-                    "missing second continuation byte (end)");
-    S_test_validity(runner, "\xE2\x98\xBA\xE2", 4, false,
-                    "missing both continuation bytes (end)");
-    S_test_validity(runner, "\xBA\xE2\x98\xBA", 4, false,
-                    "isolated continuation byte 0xBA");
-    S_test_validity(runner, "\x98\xE2\x98\xBA", 4, false,
-                    "isolated continuation byte 0x98");
-    S_test_validity(runner, "\xE2\x98\xBA\xBA", 4, false,
-                    "isolated continuation byte 0xBA (end)");
-    S_test_validity(runner, "\xE2\x98\xBA\x98", 4, false,
-                    "isolated continuation byte 0x98 (end)");
-    S_test_validity(runner, "\xF0xxxx", 5, false,
-                    "missing continuation byte 2/4");
-    S_test_validity(runner, "\xF0\x9Dxxxx", 5, false,
-                    "missing continuation byte 3/4");
-    S_test_validity(runner, "\xF0\x9D\x84xx", 5, false,
-                    "missing continuation byte 4/4");
-}
-
-static void
-S_validate_utf8(void *context) {
-    const char *text = (const char*)context;
-    StrHelp_validate_utf8(text, strlen(text), "src.c", 17, "fn");
-}
-
-static void
-test_validate_utf8(TestBatchRunner *runner) {
-    {
-        Err *error = Err_trap(S_validate_utf8, "Sigma\xC1\x9C.");
-        TEST_TRUE(runner, error != NULL, "validate_utf8 throws");
-        String *mess = Err_Get_Mess(error);
-        const char *expected = "Invalid UTF-8 after 'Sigma': C1 9C 2E\n";
-        bool ok = Str_Starts_With_Utf8(mess, expected, strlen(expected));
-        TEST_TRUE(runner, ok, "validate_utf8 throws correct error message");
-        DECREF(error);
-    }
-
-    {
-        Err *error = Err_trap(S_validate_utf8,
-                              "xxx123456789\xE2\x93\xAA"
-                              "1234567890\xC1\x9C.");
-        String *mess = Err_Get_Mess(error);
-        const char *expected =
-            "Invalid UTF-8 after '123456789\xE2\x93\xAA"
-            "1234567890': C1 9C 2E\n";
-        bool ok = Str_Starts_With_Utf8(mess, expected, strlen(expected));
-        TEST_TRUE(runner, ok, "validate_utf8 truncates long prefix");
-        DECREF(error);
-    }
-}
-
-static void
-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_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
-S_encode_utf8_char(void *context) {
-    int32_t *code_point_ptr = (int32_t*)context;
-    char buffer[4];
-    StrHelp_encode_utf8_char(*code_point_ptr, buffer);
-}
-
-static void
-test_encode_utf8_char(TestBatchRunner *runner) {
-    int32_t code_point = 0x110000;
-    Err *error = Err_trap(S_encode_utf8_char, &code_point);
-    TEST_TRUE(runner, error != NULL, "Encode code point 0x110000 throws");
-    DECREF(error);
-}
-
-static void
-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(runner, StrHelp_back_utf8_char(end, buffer) == buffer,
-              "back_utf8_char");
-    TEST_TRUE(runner, StrHelp_back_utf8_char(end, buf) == NULL,
-              "back_utf8_char returns NULL rather than back up beyond start");
-    TEST_TRUE(runner, StrHelp_back_utf8_char(buffer, buffer) == NULL,
-              "back_utf8_char returns NULL when end == start");
-}
-
-void
-TestStrHelp_Run_IMP(TestStringHelper *self, TestBatchRunner *runner) {
-    TestBatchRunner_Plan(runner, (TestBatch*)self, 55);
-    test_overlap(runner);
-    test_to_base36(runner);
-    test_utf8_round_trip(runner);
-    test_utf8_valid(runner);
-    test_validate_utf8(runner);
-    test_is_whitespace(runner);
-    test_encode_utf8_char(runner);
-    test_back_utf8_char(runner);
-}
-
-
-

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/Util/TestStringHelper.cfh
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/Util/TestStringHelper.cfh b/runtime/core/Clownfish/Test/Util/TestStringHelper.cfh
deleted file mode 100644
index 752c553..0000000
--- a/runtime/core/Clownfish/Test/Util/TestStringHelper.cfh
+++ /dev/null
@@ -1,29 +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.
- */
-
-parcel TestClownfish;
-
-class Clownfish::Test::Util::TestStringHelper nickname TestStrHelp
-    inherits Clownfish::TestHarness::TestBatch {
-
-    inert incremented TestStringHelper*
-    new();
-
-    void
-    Run(TestStringHelper *self, TestBatchRunner *runner);
-}
-
-

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/TestClownfish.c
----------------------------------------------------------------------
diff --git a/runtime/core/TestClownfish.c b/runtime/core/TestClownfish.c
deleted file mode 100644
index e9ec7a3..0000000
--- a/runtime/core/TestClownfish.c
+++ /dev/null
@@ -1,22 +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 "testcfish_parcel.h"
-
-void
-testcfish_init_parcel() {
-}
-

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/TestClownfish.cfp
----------------------------------------------------------------------
diff --git a/runtime/core/TestClownfish.cfp b/runtime/core/TestClownfish.cfp
deleted file mode 100644
index 6db4b4a..0000000
--- a/runtime/core/TestClownfish.cfp
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-    "name": "TestClownfish",
-    "nickname": "TestCfish",
-    "version": "v0.5.0",
-    "prerequisites": {
-        "Clownfish": "v0.5.0"
-    }
-}

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/go/build.go
----------------------------------------------------------------------
diff --git a/runtime/go/build.go b/runtime/go/build.go
index 6b0b0be..c6eb9e3 100644
--- a/runtime/go/build.go
+++ b/runtime/go/build.go
@@ -119,6 +119,7 @@ func configure() {
 func runCFC() {
 	hierarchy := cfc.NewHierarchy("autogen")
 	hierarchy.AddSourceDir("../core")
+	hierarchy.AddSourceDir("../test")
 	hierarchy.Build()
 	autogenHeader := "Auto-generated by build.go.\n"
 	coreBinding := cfc.NewBindCore(hierarchy, autogenHeader, "")

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/perl/buildlib/Clownfish/Build.pm
----------------------------------------------------------------------
diff --git a/runtime/perl/buildlib/Clownfish/Build.pm b/runtime/perl/buildlib/Clownfish/Build.pm
index 9506662..81b5fbf 100644
--- a/runtime/perl/buildlib/Clownfish/Build.pm
+++ b/runtime/perl/buildlib/Clownfish/Build.pm
@@ -54,13 +54,16 @@ my $XS_SOURCE_DIR = 'xs';
 my $CFC_BUILD     = catfile( $CFC_DIR, 'Build' );
 my $LIB_DIR       = 'lib';
 my $CORE_SOURCE_DIR;
+my $TEST_SOURCE_DIR;
 my $CHARMONIZER_C;
 if ($IS_CPAN_DIST) {
     $CORE_SOURCE_DIR = 'cfcore';
+    $TEST_SOURCE_DIR = 'cftest';
     $CHARMONIZER_C   = 'charmonizer.c';
 }
 else {
     $CORE_SOURCE_DIR = catdir( @BASE_PATH, 'core' );
+    $TEST_SOURCE_DIR = catdir( @BASE_PATH, 'test' );
     $CHARMONIZER_C   = catfile( $COMMON_SOURCE_DIR, 'charmonizer.c' );
 }
 
@@ -70,7 +73,7 @@ sub new {
     $args{clownfish_params} = {
         autogen_header => _autogen_header(),
         include        => [],                  # Don't use default includes.
-        source         => [ $CORE_SOURCE_DIR ],
+        source         => [ $CORE_SOURCE_DIR, $TEST_SOURCE_DIR ],
         modules => [
             {
                 name          => 'Clownfish',
@@ -317,6 +320,7 @@ sub ACTION_dist {
         '../../NOTICE'          => 'NOTICE',
         '../../README.md'       => 'README.md',
         $CORE_SOURCE_DIR        => 'cfcore',
+        $TEST_SOURCE_DIR        => 'cftest',
         $CHARMONIZER_C          => 'charmonizer.c',
     );
     print "Copying files...\n";

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/python/setup.py
----------------------------------------------------------------------
diff --git a/runtime/python/setup.py b/runtime/python/setup.py
index fa184ef..717ce50 100644
--- a/runtime/python/setup.py
+++ b/runtime/python/setup.py
@@ -51,6 +51,7 @@ make_command = "make" # TODO portability
 BASE_DIR        = os.path.abspath(os.path.join(os.pardir, os.pardir))
 PARENT_DIR      = os.path.abspath(os.pardir)
 CORE_SOURCE_DIR = os.path.join(PARENT_DIR, 'core')
+TEST_SOURCE_DIR = os.path.join(PARENT_DIR, 'test')
 CFEXT_DIR       = 'cfext'
 COMMON_SOURCE_DIR    = os.path.join(PARENT_DIR, 'common')
 CHARMONIZER_C        = os.path.join(COMMON_SOURCE_DIR, 'charmonizer.c')
@@ -133,6 +134,7 @@ class libclownfish(_Command):
         import cfc
         hierarchy = cfc.model.Hierarchy(dest="autogen")
         hierarchy.add_source_dir(CORE_SOURCE_DIR)
+        hierarchy.add_source_dir(TEST_SOURCE_DIR)
         hierarchy.build()
         header = "Autogenerated by setup.py"
         core_binding = cfc.binding.BindCore(hierarchy=hierarchy, header=header)

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test.c b/runtime/test/Clownfish/Test.c
new file mode 100644
index 0000000..4ae6163
--- /dev/null
+++ b/runtime/test/Clownfish/Test.c
@@ -0,0 +1,70 @@
+/* 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 CFISH_USE_SHORT_NAMES
+#define TESTCFISH_USE_SHORT_NAMES
+
+#include "Clownfish/Test.h"
+
+#include "Clownfish/TestHarness/TestBatch.h"
+#include "Clownfish/TestHarness/TestSuite.h"
+
+#include "Clownfish/Test/TestBlob.h"
+#include "Clownfish/Test/TestBoolean.h"
+#include "Clownfish/Test/TestByteBuf.h"
+#include "Clownfish/Test/TestString.h"
+#include "Clownfish/Test/TestCharBuf.h"
+#include "Clownfish/Test/TestClass.h"
+#include "Clownfish/Test/TestErr.h"
+#include "Clownfish/Test/TestHash.h"
+#include "Clownfish/Test/TestHashIterator.h"
+#include "Clownfish/Test/TestLockFreeRegistry.h"
+#include "Clownfish/Test/TestMethod.h"
+#include "Clownfish/Test/TestNum.h"
+#include "Clownfish/Test/TestObj.h"
+#include "Clownfish/Test/TestPtrHash.h"
+#include "Clownfish/Test/TestVector.h"
+#include "Clownfish/Test/Util/TestAtomic.h"
+#include "Clownfish/Test/Util/TestMemory.h"
+#include "Clownfish/Test/Util/TestStringHelper.h"
+
+TestSuite*
+Test_create_test_suite() {
+    TestSuite *suite = TestSuite_new();
+
+    TestSuite_Add_Batch(suite, (TestBatch*)TestClass_new());
+    TestSuite_Add_Batch(suite, (TestBatch*)TestMethod_new());
+    TestSuite_Add_Batch(suite, (TestBatch*)TestVector_new());
+    TestSuite_Add_Batch(suite, (TestBatch*)TestHash_new());
+    TestSuite_Add_Batch(suite, (TestBatch*)TestHashIterator_new());
+    TestSuite_Add_Batch(suite, (TestBatch*)TestObj_new());
+    TestSuite_Add_Batch(suite, (TestBatch*)TestErr_new());
+    TestSuite_Add_Batch(suite, (TestBatch*)TestBlob_new());
+    TestSuite_Add_Batch(suite, (TestBatch*)TestBB_new());
+    TestSuite_Add_Batch(suite, (TestBatch*)TestStr_new());
+    TestSuite_Add_Batch(suite, (TestBatch*)TestCB_new());
+    TestSuite_Add_Batch(suite, (TestBatch*)TestBoolean_new());
+    TestSuite_Add_Batch(suite, (TestBatch*)TestNum_new());
+    TestSuite_Add_Batch(suite, (TestBatch*)TestStrHelp_new());
+    TestSuite_Add_Batch(suite, (TestBatch*)TestAtomic_new());
+    TestSuite_Add_Batch(suite, (TestBatch*)TestLFReg_new());
+    TestSuite_Add_Batch(suite, (TestBatch*)TestMemory_new());
+    TestSuite_Add_Batch(suite, (TestBatch*)TestPtrHash_new());
+
+    return suite;
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test.cfh
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test.cfh b/runtime/test/Clownfish/Test.cfh
new file mode 100644
index 0000000..68dc8bc
--- /dev/null
+++ b/runtime/test/Clownfish/Test.cfh
@@ -0,0 +1,26 @@
+/* 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 TestClownfish;
+
+/** Clownfish test suite.
+ */
+inert class Clownfish::Test {
+    inert incremented TestSuite*
+    create_test_suite();
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestBlob.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestBlob.c b/runtime/test/Clownfish/Test/TestBlob.c
new file mode 100644
index 0000000..5776f47
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestBlob.c
@@ -0,0 +1,147 @@
+/* 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 CFISH_USE_SHORT_NAMES
+#define TESTCFISH_USE_SHORT_NAMES
+
+#include "Clownfish/Test/TestBlob.h"
+
+#include "Clownfish/Blob.h"
+#include "Clownfish/Test.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Clownfish/TestHarness/TestUtils.h"
+#include "Clownfish/Class.h"
+#include "Clownfish/Util/Memory.h"
+
+#include <string.h>
+
+TestBlob*
+TestBlob_new() {
+    return (TestBlob*)Class_Make_Obj(TESTBLOB);
+}
+
+static void
+test_new_steal(TestBatchRunner *runner) {
+    size_t size = 4;
+    char *buf = (char*)MALLOCATE(size);
+    memset(buf, 'x', size);
+    Blob *blob = Blob_new_steal(buf, size);
+    TEST_TRUE(runner, Blob_Get_Buf(blob) == buf, "new_steal steals buf");
+    TEST_TRUE(runner, Blob_Equals_Bytes(blob, "xxxx", 4),
+              "new_steal doesn't change buf");
+    DECREF(blob);
+}
+
+static void
+test_new_wrap(TestBatchRunner *runner) {
+    static const char buf[] = "xxxx";
+    Blob *blob = Blob_new_wrap(buf, 4);
+    TEST_TRUE(runner, Blob_Get_Buf(blob) == buf, "new_wrap wraps buf");
+    TEST_TRUE(runner, Blob_Equals_Bytes(blob, "xxxx", 4),
+              "new_wrap doesn't change buf");
+    DECREF(blob);
+}
+
+static void
+test_Equals(TestBatchRunner *runner) {
+    Blob *blob = Blob_new("foo", 4); // Include terminating NULL.
+
+    {
+        Blob *other = Blob_new("foo", 4);
+        TEST_TRUE(runner, Blob_Equals(blob, (Obj*)other), "Equals");
+        DECREF(other);
+    }
+
+    {
+        Blob *other = Blob_new("foo", 3);
+        TEST_FALSE(runner, Blob_Equals(blob, (Obj*)other),
+                   "Different size spoils Equals");
+        DECREF(other);
+    }
+
+    {
+        Blob *other = Blob_new("bar", 4);
+        TEST_UINT_EQ(runner, Blob_Get_Size(blob), Blob_Get_Size(other),
+                     "same length");
+        TEST_FALSE(runner, Blob_Equals(blob, (Obj*)other),
+                   "Different content spoils Equals");
+        DECREF(other);
+    }
+
+    TEST_FALSE(runner, Blob_Equals(blob, (Obj*)BLOB),
+               "Different type spoils Equals");
+
+    TEST_TRUE(runner, Blob_Equals_Bytes(blob, "foo", 4), "Equals_Bytes");
+    TEST_FALSE(runner, Blob_Equals_Bytes(blob, "foo", 3),
+               "Equals_Bytes spoiled by different size");
+    TEST_FALSE(runner, Blob_Equals_Bytes(blob, "bar", 4),
+               "Equals_Bytes spoiled by different content");
+
+    DECREF(blob);
+}
+
+static void
+test_Clone(TestBatchRunner *runner) {
+    Blob *blob = Blob_new("foo", 3);
+    Blob *twin = Blob_Clone(blob);
+    TEST_TRUE(runner, Blob_Equals(blob, (Obj*)twin), "Clone");
+    DECREF(blob);
+    DECREF(twin);
+}
+
+static void
+test_Compare_To(TestBatchRunner *runner) {
+    {
+        Blob *a = Blob_new("foo", 4);
+        Blob *b = Blob_new("foo", 4);
+        TEST_INT_EQ(runner, Blob_Compare_To(a, (Obj*)b), 0,
+                    "Compare_To returns 0 for equal Blobs");
+        DECREF(a);
+        DECREF(b);
+    }
+
+    {
+        Blob *a = Blob_new("foo", 3);
+        Blob *b = Blob_new("foo\0b", 5);
+        TEST_TRUE(runner, Blob_Compare_To(a, (Obj*)b) < 0,
+                  "shorter Blob sorts first");
+        TEST_TRUE(runner, Blob_Compare_To(b, (Obj*)a) > 0,
+                  "longer Blob sorts last");
+        DECREF(a);
+        DECREF(b);
+    }
+
+    {
+        Blob *a = Blob_new("foo\0a", 5);
+        Blob *b = Blob_new("foo\0b", 5);
+        TEST_TRUE(runner, Blob_Compare_To(a, (Obj*)b) < 0,
+                  "NULL doesn't interfere with Compare_To");
+        DECREF(a);
+        DECREF(b);
+    }
+}
+
+void
+TestBlob_Run_IMP(TestBlob *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 17);
+    test_new_steal(runner);
+    test_new_wrap(runner);
+    test_Equals(runner);
+    test_Clone(runner);
+    test_Compare_To(runner);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestBlob.cfh
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestBlob.cfh b/runtime/test/Clownfish/Test/TestBlob.cfh
new file mode 100644
index 0000000..4d3d4b6
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestBlob.cfh
@@ -0,0 +1,28 @@
+/* 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 TestClownfish;
+
+class Clownfish::Test::TestBlob inherits Clownfish::TestHarness::TestBatch {
+
+    inert incremented TestBlob*
+    new();
+
+    void
+    Run(TestBlob *self, TestBatchRunner *runner);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestBoolean.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestBoolean.c b/runtime/test/Clownfish/Test/TestBoolean.c
new file mode 100644
index 0000000..8835fe1
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestBoolean.c
@@ -0,0 +1,99 @@
+/* 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 CFISH_USE_SHORT_NAMES
+#define TESTCFISH_USE_SHORT_NAMES
+
+#include "Clownfish/Test/TestBoolean.h"
+
+#include "Clownfish/String.h"
+#include "Clownfish/Boolean.h"
+#include "Clownfish/Test.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Clownfish/TestHarness/TestUtils.h"
+#include "Clownfish/Class.h"
+
+TestBoolean*
+TestBoolean_new() {
+    return (TestBoolean*)Class_Make_Obj(TESTBOOLEAN);
+}
+
+static void
+test_singleton(TestBatchRunner *runner) {
+    TEST_TRUE(runner, Bool_singleton(true) == CFISH_TRUE,
+              "Bool_singleton true");
+    TEST_TRUE(runner, Bool_singleton(false) == CFISH_FALSE,
+              "Bool_singleton false");
+}
+
+static void
+test_To_String(TestBatchRunner *runner) {
+    String *true_string  = Bool_To_String(CFISH_TRUE);
+    String *false_string = Bool_To_String(CFISH_FALSE);
+
+    TEST_TRUE(runner, Str_Equals_Utf8(true_string, "true", 4),
+              "Bool_To_String [true]");
+    TEST_TRUE(runner, Str_Equals_Utf8(false_string, "false", 5),
+              "Bool_To_String [false]");
+
+    DECREF(false_string);
+    DECREF(true_string);
+}
+
+static void
+test_accessors(TestBatchRunner *runner) {
+    TEST_INT_EQ(runner, Bool_Get_Value(CFISH_TRUE), true,
+                "Bool_Get_Value [true]");
+    TEST_INT_EQ(runner, Bool_Get_Value(CFISH_FALSE), false,
+                "Bool_Get_Value [false]");
+}
+
+static void
+test_Equals_and_Compare_To(TestBatchRunner *runner) {
+    TEST_TRUE(runner, Bool_Equals(CFISH_TRUE, (Obj*)CFISH_TRUE),
+              "CFISH_TRUE Equals itself");
+    TEST_TRUE(runner, Bool_Equals(CFISH_FALSE, (Obj*)CFISH_FALSE),
+              "CFISH_FALSE Equals itself");
+    TEST_FALSE(runner, Bool_Equals(CFISH_FALSE, (Obj*)CFISH_TRUE),
+               "CFISH_FALSE not Equals CFISH_TRUE ");
+    TEST_FALSE(runner, Bool_Equals(CFISH_TRUE, (Obj*)CFISH_FALSE),
+               "CFISH_TRUE not Equals CFISH_FALSE ");
+    TEST_FALSE(runner, Bool_Equals(CFISH_TRUE, (Obj*)STRING),
+               "CFISH_TRUE not Equals random other object ");
+}
+
+static void
+test_Clone(TestBatchRunner *runner) {
+    TEST_TRUE(runner, Bool_Equals(CFISH_TRUE, (Obj*)Bool_Clone(CFISH_TRUE)),
+              "Boolean Clone");
+}
+
+void
+TestBoolean_Run_IMP(TestBoolean *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 12);
+
+    // Destroying the singletons should have no effect.
+    Bool_Destroy(CFISH_TRUE);
+    Bool_Destroy(CFISH_FALSE);
+
+    test_singleton(runner);
+    test_To_String(runner);
+    test_accessors(runner);
+    test_Equals_and_Compare_To(runner);
+    test_Clone(runner);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestBoolean.cfh
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestBoolean.cfh b/runtime/test/Clownfish/Test/TestBoolean.cfh
new file mode 100644
index 0000000..43702db
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestBoolean.cfh
@@ -0,0 +1,29 @@
+/* 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 TestClownfish;
+
+class Clownfish::Test::TestBoolean
+    inherits Clownfish::TestHarness::TestBatch {
+
+    inert incremented TestBoolean*
+    new();
+
+    void
+    Run(TestBoolean *self, TestBatchRunner *runner);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestByteBuf.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestByteBuf.c b/runtime/test/Clownfish/Test/TestByteBuf.c
new file mode 100644
index 0000000..7f8fc50
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestByteBuf.c
@@ -0,0 +1,218 @@
+/* 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 <string.h>
+
+#define CFISH_USE_SHORT_NAMES
+#define TESTCFISH_USE_SHORT_NAMES
+
+#include "Clownfish/Test/TestByteBuf.h"
+
+#include "Clownfish/ByteBuf.h"
+#include "Clownfish/Test.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Clownfish/TestHarness/TestUtils.h"
+#include "Clownfish/Blob.h"
+#include "Clownfish/Class.h"
+#include "Clownfish/Err.h"
+#include "Clownfish/String.h"
+#include "Clownfish/Util/Memory.h"
+
+#include <string.h>
+
+TestByteBuf*
+TestBB_new() {
+    return (TestByteBuf*)Class_Make_Obj(TESTBYTEBUF);
+}
+
+static void
+test_new_steal_bytes(TestBatchRunner *runner) {
+    char *buf = (char*)MALLOCATE(10);
+    memset(buf, 'x', 10);
+    ByteBuf *bb = BB_new_steal_bytes(buf, 5, 10);
+    TEST_TRUE(runner, BB_Get_Buf(bb) == buf, "new_steal_bytes steals buffer");
+    TEST_TRUE(runner, BB_Equals_Bytes(bb, "xxxxx", 5),
+              "new_steal_bytes sets correct size");
+    BB_Set_Size(bb, 10);
+    TEST_TRUE(runner, BB_Equals_Bytes(bb, "xxxxxxxxxx", 10),
+              "new_steal_bytes sets correct capacity");
+    DECREF(bb);
+}
+
+static void
+test_Equals(TestBatchRunner *runner) {
+    ByteBuf *bb = BB_new_bytes("foo", 4); // Include terminating NULL.
+
+    TEST_TRUE(runner, BB_Equals(bb, (Obj*)bb), "Equals self");
+    TEST_FALSE(runner, BB_Equals(bb, (Obj*)BYTEBUF),
+               "Equals spoiled by different type");
+
+    {
+        ByteBuf *other = BB_new_bytes("foo", 4);
+        TEST_TRUE(runner, BB_Equals(bb, (Obj*)other), "Equals");
+        DECREF(other);
+    }
+
+    TEST_TRUE(runner, BB_Equals_Bytes(bb, "foo", 4), "Equals_Bytes");
+    TEST_FALSE(runner, BB_Equals_Bytes(bb, "foo", 3),
+               "Equals_Bytes spoiled by different size");
+    TEST_FALSE(runner, BB_Equals_Bytes(bb, "bar", 4),
+               "Equals_Bytes spoiled by different content");
+
+    {
+        ByteBuf *other = BB_new_bytes("foo", 3);
+        TEST_FALSE(runner, BB_Equals(bb, (Obj*)other),
+                   "Different size spoils Equals");
+        DECREF(other);
+    }
+
+    {
+        ByteBuf *other = BB_new_bytes("bar", 4);
+        TEST_UINT_EQ(runner, BB_Get_Size(bb), BB_Get_Size(other),
+                     "same length");
+        TEST_FALSE(runner, BB_Equals(bb, (Obj*)other),
+                   "Different content spoils Equals");
+        DECREF(other);
+    }
+
+    DECREF(bb);
+}
+
+static void
+test_Grow(TestBatchRunner *runner) {
+    ByteBuf *bb = BB_new(1);
+    TEST_UINT_EQ(runner, BB_Get_Capacity(bb), 8,
+                "Allocate in 8-byte increments");
+    BB_Grow(bb, 9);
+    TEST_UINT_EQ(runner, BB_Get_Capacity(bb), 16,
+                "Grow in 8-byte increments");
+    BB_Grow(bb, 16);
+    TEST_UINT_EQ(runner, BB_Get_Capacity(bb), 16,
+                "Grow to same capacity has no effect");
+    DECREF(bb);
+}
+
+static void
+test_Clone(TestBatchRunner *runner) {
+    ByteBuf *bb = BB_new_bytes("foo", 3);
+    ByteBuf *twin = BB_Clone(bb);
+    TEST_TRUE(runner, BB_Equals(bb, (Obj*)twin), "Clone");
+    DECREF(bb);
+    DECREF(twin);
+}
+
+static void
+test_Compare_To(TestBatchRunner *runner) {
+    ByteBuf *a = BB_new_bytes("foo\0a", 5);
+    ByteBuf *b = BB_new_bytes("foo\0b", 5);
+
+    BB_Set_Size(a, 4);
+    BB_Set_Size(b, 4);
+    TEST_INT_EQ(runner, BB_Compare_To(a, (Obj*)b), 0,
+                "Compare_To returns 0 for equal ByteBufs");
+
+    BB_Set_Size(a, 3);
+    TEST_TRUE(runner, BB_Compare_To(a, (Obj*)b) < 0,
+              "shorter ByteBuf sorts first");
+    TEST_TRUE(runner, BB_Compare_To(b, (Obj*)a) > 0,
+              "longer ByteBuf sorts last");
+
+    BB_Set_Size(a, 5);
+    BB_Set_Size(b, 5);
+    TEST_TRUE(runner, BB_Compare_To(a, (Obj*)b) < 0,
+              "NULL doesn't interfere with Compare_To");
+
+    DECREF(a);
+    DECREF(b);
+}
+
+static void
+test_Cat(TestBatchRunner *runner) {
+    ByteBuf *bb = BB_new_bytes("foo", 3);
+
+    {
+        Blob *blob = Blob_new("bar", 3);
+        BB_Cat(bb, blob);
+        TEST_TRUE(runner, BB_Equals_Bytes(bb, "foobar", 6), "Cat");
+        DECREF(blob);
+    }
+
+    BB_Cat_Bytes(bb, "baz", 3);
+    TEST_TRUE(runner, BB_Equals_Bytes(bb, "foobarbaz", 9), "Cat_Bytes");
+
+    DECREF(bb);
+}
+
+static void
+test_Utf8_To_String(TestBatchRunner *runner) {
+    ByteBuf *bb = BB_new_bytes("foo", 3);
+
+    {
+        String *string = BB_Utf8_To_String(bb);
+        TEST_TRUE(runner, Str_Equals_Utf8(string, "foo", 3), "Utf8_To_String");
+        DECREF(string);
+    }
+
+    {
+        String *string = BB_Trusted_Utf8_To_String(bb);
+        TEST_TRUE(runner, Str_Equals_Utf8(string, "foo", 3),
+                  "Trusted_Utf8_To_String");
+        DECREF(string);
+    }
+
+    DECREF(bb);
+}
+
+static void
+S_set_wrong_size(void *context) {
+    ByteBuf *bb = (ByteBuf*)context;
+    BB_Set_Size(bb, BB_Get_Capacity(bb) + 1);
+}
+
+static void
+test_Set_Size(TestBatchRunner *runner) {
+    ByteBuf *bb = BB_new(10);
+    Err *error = Err_trap(S_set_wrong_size, bb);
+    TEST_TRUE(runner, error != NULL, "Setting size beyond capacity throws");
+    DECREF(error);
+    DECREF(bb);
+}
+
+static void
+test_Yield_Blob(TestBatchRunner *runner) {
+    ByteBuf *bb = BB_new_bytes("alpha", 5);
+    Blob *blob = BB_Yield_Blob(bb);
+    TEST_TRUE(runner, Blob_Equals_Bytes(blob, "alpha", 5), "Yield_Blob");
+    TEST_UINT_EQ(runner, BB_Get_Size(bb), 0, "Yield_Blob clears buf");
+    DECREF(blob);
+    DECREF(bb);
+}
+
+void
+TestBB_Run_IMP(TestByteBuf *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 27);
+    test_new_steal_bytes(runner);
+    test_Equals(runner);
+    test_Grow(runner);
+    test_Clone(runner);
+    test_Compare_To(runner);
+    test_Cat(runner);
+    test_Utf8_To_String(runner);
+    test_Set_Size(runner);
+    test_Yield_Blob(runner);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestByteBuf.cfh
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestByteBuf.cfh b/runtime/test/Clownfish/Test/TestByteBuf.cfh
new file mode 100644
index 0000000..d27e715
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestByteBuf.cfh
@@ -0,0 +1,29 @@
+/* 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 TestClownfish;
+
+class Clownfish::Test::TestByteBuf nickname TestBB
+    inherits Clownfish::TestHarness::TestBatch {
+
+    inert incremented TestByteBuf*
+    new();
+
+    void
+    Run(TestByteBuf *self, TestBatchRunner *runner);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestCharBuf.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestCharBuf.c b/runtime/test/Clownfish/Test/TestCharBuf.c
new file mode 100644
index 0000000..4329987
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestCharBuf.c
@@ -0,0 +1,394 @@
+/* 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 <string.h>
+#include <stdio.h>
+
+#define CFISH_USE_SHORT_NAMES
+#define TESTCFISH_USE_SHORT_NAMES
+#define C_CFISH_CHARBUF
+
+#include "charmony.h"
+
+#include "Clownfish/Test/TestCharBuf.h"
+
+#include "Clownfish/CharBuf.h"
+#include "Clownfish/Err.h"
+#include "Clownfish/Num.h"
+#include "Clownfish/String.h"
+#include "Clownfish/Test.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Clownfish/TestHarness/TestUtils.h"
+#include "Clownfish/Class.h"
+
+static char smiley[] = { (char)0xE2, (char)0x98, (char)0xBA, 0 };
+static uint32_t smiley_len = 3;
+
+TestCharBuf*
+TestCB_new() {
+    return (TestCharBuf*)Class_Make_Obj(TESTCHARBUF);
+}
+
+static CharBuf*
+S_get_cb(const char *string) {
+    CharBuf *cb = CB_new(0);
+    CB_Cat_Utf8(cb, string, strlen(string));
+    return cb;
+}
+
+static String*
+S_get_str(const char *string) {
+    return Str_new_from_utf8(string, strlen(string));
+}
+
+static bool
+S_cb_equals(CharBuf *cb, String *other) {
+    String *string = CB_To_String(cb);
+    bool retval = Str_Equals(string, (Obj*)other);
+    DECREF(string);
+    return retval;
+}
+
+static void
+S_cat_invalid_utf8(void *context) {
+    CharBuf *cb = (CharBuf*)context;
+    CB_Cat_Utf8(cb, "\xF0" "a", 2);
+}
+
+static void
+test_Cat(TestBatchRunner *runner) {
+    String  *wanted = Str_newf("a%s", smiley);
+    CharBuf *got    = S_get_cb("");
+
+    CB_Cat(got, wanted);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "Cat");
+    DECREF(got);
+
+    got = S_get_cb("a");
+    CB_Cat_Char(got, 0x263A);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "Cat_Char");
+    DECREF(got);
+
+    got = S_get_cb("a");
+    CB_Cat_Utf8(got, smiley, smiley_len);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "Cat_Utf8");
+    DECREF(got);
+
+    got = S_get_cb("a");
+    Err *error = Err_trap(S_cat_invalid_utf8, got);
+    TEST_TRUE(runner, error != NULL, "Cat_Utf8 throws with invalid UTF-8");
+    DECREF(error);
+    DECREF(got);
+
+    got = S_get_cb("a");
+    CB_Cat_Trusted_Utf8(got, smiley, smiley_len);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "Cat_Trusted_Utf8");
+    DECREF(got);
+
+    DECREF(wanted);
+}
+
+static void
+test_Clone(TestBatchRunner *runner) {
+    String  *wanted    = S_get_str("foo");
+    CharBuf *wanted_cb = S_get_cb("foo");
+    CharBuf *got       = CB_Clone(wanted_cb);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "Clone");
+    DECREF(got);
+    DECREF(wanted);
+    DECREF(wanted_cb);
+}
+
+static void
+test_vcatf_percent(TestBatchRunner *runner) {
+    String  *wanted = S_get_str("foo % bar");
+    CharBuf *got = S_get_cb("foo");
+    CB_catf(got, " %% bar");
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%%%");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_s(TestBatchRunner *runner) {
+    String  *wanted = S_get_str("foo bar bizzle baz");
+    CharBuf *got = S_get_cb("foo ");
+    CB_catf(got, "bar %s baz", "bizzle");
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%s");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+S_catf_s_invalid_utf8(void *context) {
+    CharBuf *buf = (CharBuf*)context;
+    CB_catf(buf, "bar %s baz", "\x82" "abcd");
+}
+
+static void
+test_vcatf_s_invalid_utf8(TestBatchRunner *runner) {
+    CharBuf *buf = S_get_cb("foo ");
+    Err *error = Err_trap(S_catf_s_invalid_utf8, buf);
+    TEST_TRUE(runner, error != NULL, "%%s with invalid UTF-8");
+    DECREF(error);
+    DECREF(buf);
+}
+
+static void
+test_vcatf_null_string(TestBatchRunner *runner) {
+    String  *wanted = S_get_str("foo bar [NULL] baz");
+    CharBuf *got = S_get_cb("foo ");
+    CB_catf(got, "bar %s baz", NULL);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%s NULL");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_str(TestBatchRunner *runner) {
+    String  *wanted = S_get_str("foo bar ZEKE baz");
+    String  *catworthy = S_get_str("ZEKE");
+    CharBuf *got = S_get_cb("foo ");
+    CB_catf(got, "bar %o baz", catworthy);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%o CharBuf");
+    DECREF(catworthy);
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_obj(TestBatchRunner *runner) {
+    String  *wanted = S_get_str("ooga 20 booga");
+    Integer *i64    = Int_new(20);
+    CharBuf *got    = S_get_cb("ooga");
+    CB_catf(got, " %o booga", i64);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%o Obj");
+    DECREF(i64);
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_null_obj(TestBatchRunner *runner) {
+    String  *wanted = S_get_str("foo bar [NULL] baz");
+    CharBuf *got = S_get_cb("foo ");
+    CB_catf(got, "bar %o baz", NULL);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%o NULL");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_i8(TestBatchRunner *runner) {
+    String *wanted = S_get_str("foo bar -3 baz");
+    int8_t num = -3;
+    CharBuf *got = S_get_cb("foo ");
+    CB_catf(got, "bar %i8 baz", num);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%i8");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_i32(TestBatchRunner *runner) {
+    String *wanted = S_get_str("foo bar -100000 baz");
+    int32_t num = -100000;
+    CharBuf *got = S_get_cb("foo ");
+    CB_catf(got, "bar %i32 baz", num);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%i32");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_i64(TestBatchRunner *runner) {
+    String *wanted = S_get_str("foo bar -5000000000 baz");
+    int64_t num = INT64_C(-5000000000);
+    CharBuf *got = S_get_cb("foo ");
+    CB_catf(got, "bar %i64 baz", num);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%i64");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_u8(TestBatchRunner *runner) {
+    String *wanted = S_get_str("foo bar 3 baz");
+    uint8_t num = 3;
+    CharBuf *got = S_get_cb("foo ");
+    CB_catf(got, "bar %u8 baz", num);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%u8");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_u32(TestBatchRunner *runner) {
+    String *wanted = S_get_str("foo bar 100000 baz");
+    uint32_t num = 100000;
+    CharBuf *got = S_get_cb("foo ");
+    CB_catf(got, "bar %u32 baz", num);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%u32");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_u64(TestBatchRunner *runner) {
+    String *wanted = S_get_str("foo bar 5000000000 baz");
+    uint64_t num = UINT64_C(5000000000);
+    CharBuf *got = S_get_cb("foo ");
+    CB_catf(got, "bar %u64 baz", num);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%u64");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_f64(TestBatchRunner *runner) {
+    String *wanted;
+    char buf[64];
+    float num = 1.3f;
+    CharBuf *got = S_get_cb("foo ");
+    sprintf(buf, "foo bar %g baz", num);
+    wanted = Str_new_from_trusted_utf8(buf, strlen(buf));
+    CB_catf(got, "bar %f64 baz", num);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%f64");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+static void
+test_vcatf_x32(TestBatchRunner *runner) {
+    String *wanted;
+    char buf[64];
+    unsigned long num = INT32_MAX;
+    CharBuf *got = S_get_cb("foo ");
+#if (CHY_SIZEOF_LONG == 4)
+    sprintf(buf, "foo bar %.8lx baz", num);
+#elif (CHY_SIZEOF_INT == 4)
+    sprintf(buf, "foo bar %.8x baz", (unsigned)num);
+#endif
+    wanted = Str_new_from_trusted_utf8(buf, strlen(buf));
+    CB_catf(got, "bar %x32 baz", (uint32_t)num);
+    TEST_TRUE(runner, S_cb_equals(got, wanted), "%%x32");
+    DECREF(wanted);
+    DECREF(got);
+}
+
+typedef struct {
+    CharBuf    *charbuf;
+    const char *pattern;
+} CatfContext;
+
+static void
+S_catf_invalid_pattern(void *vcontext) {
+    CatfContext *context = (CatfContext*)vcontext;
+    CB_catf(context->charbuf, context->pattern, 0);
+}
+
+static void
+test_vcatf_invalid(TestBatchRunner *runner) {
+    CatfContext context;
+    context.charbuf = S_get_cb("foo ");
+
+    static const char *const patterns[] = {
+        "bar %z baz",
+        "bar %i baz",
+        "bar %i1 baz",
+        "bar %i33 baz",
+        "bar %i65 baz",
+        "bar %u baz",
+        "bar %u9 baz",
+        "bar %u33 baz",
+        "bar %u65 baz",
+        "bar %x baz",
+        "bar %x9 baz",
+        "bar %x33 baz",
+        "bar %f baz",
+        "bar %f9 baz",
+        "bar %f65 baz",
+        "bar \xC2 baz"
+    };
+    static const size_t num_patterns = sizeof(patterns) / sizeof(patterns[0]);
+
+    for (size_t i = 0; i < num_patterns; i++) {
+        context.pattern = patterns[i];
+        Err *error = Err_trap(S_catf_invalid_pattern, &context);
+        TEST_TRUE(runner, error != NULL,
+                  "catf throws with invalid pattern '%s'", patterns[i]);
+        DECREF(error);
+    }
+
+    DECREF(context.charbuf);
+}
+
+static void
+test_Clear(TestBatchRunner *runner) {
+    CharBuf *cb = S_get_cb("foo");
+    CB_Clear(cb);
+    CB_Cat_Utf8(cb, "bar", 3);
+    String *string = CB_Yield_String(cb);
+    TEST_TRUE(runner, Str_Equals_Utf8(string, "bar", 3), "Clear");
+    DECREF(string);
+    DECREF(cb);
+}
+
+static void
+test_Grow(TestBatchRunner *runner) {
+    CharBuf *cb = S_get_cb("omega");
+    CB_Grow(cb, 100);
+    size_t cap = cb->cap;
+    TEST_TRUE(runner, cap >= 100, "Grow");
+    CB_Grow(cb, 100);
+    TEST_UINT_EQ(runner, cb->cap, cap, "Grow to same size has no effect");
+    DECREF(cb);
+}
+
+static void
+test_Get_Size(TestBatchRunner *runner) {
+    CharBuf *got = S_get_cb("a");
+    CB_Cat_Utf8(got, smiley, smiley_len);
+    TEST_UINT_EQ(runner, CB_Get_Size(got), smiley_len + 1, "Get_Size");
+    DECREF(got);
+}
+
+void
+TestCB_Run_IMP(TestCharBuf *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 41);
+    test_vcatf_percent(runner);
+    test_vcatf_s(runner);
+    test_vcatf_s_invalid_utf8(runner);
+    test_vcatf_null_string(runner);
+    test_vcatf_str(runner);
+    test_vcatf_obj(runner);
+    test_vcatf_null_obj(runner);
+    test_vcatf_i8(runner);
+    test_vcatf_i32(runner);
+    test_vcatf_i64(runner);
+    test_vcatf_u8(runner);
+    test_vcatf_u32(runner);
+    test_vcatf_u64(runner);
+    test_vcatf_f64(runner);
+    test_vcatf_x32(runner);
+    test_vcatf_invalid(runner);
+    test_Cat(runner);
+    test_Clone(runner);
+    test_Clear(runner);
+    test_Grow(runner);
+    test_Get_Size(runner);
+}
+

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestCharBuf.cfh
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestCharBuf.cfh b/runtime/test/Clownfish/Test/TestCharBuf.cfh
new file mode 100644
index 0000000..d568c14
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestCharBuf.cfh
@@ -0,0 +1,29 @@
+/* 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 TestClownfish;
+
+class Clownfish::Test::TestCharBuf nickname TestCB
+    inherits Clownfish::TestHarness::TestBatch {
+
+    inert incremented TestCharBuf*
+    new();
+
+    void
+    Run(TestCharBuf *self, TestBatchRunner *runner);
+}
+
+