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:34 UTC

[01/14] lucy-clownfish git commit: Add "xs_prereqs" module parameter

Repository: lucy-clownfish
Updated Branches:
  refs/heads/master 56f1982e7 -> 0be314b9d


Add "xs_prereqs" module parameter

This parameter lists the names of other Clownfish XS modules whose
symbols are referenced from a module. The corresponding libraries
are linked against on Windows.

This obviates the need to call cf_linker_flags manually.

If we start to install parcel metadata, it should be possible to
determine the XS prerequisites automatically (see CLOWNFISH-83).


Project: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/commit/a80e405e
Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/a80e405e
Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/a80e405e

Branch: refs/heads/master
Commit: a80e405ea7878f2eafc3562ab35c3e9ba6eff0c4
Parents: 24fb4d9
Author: Nick Wellnhofer <we...@aevum.de>
Authored: Fri Jul 8 13:57:27 2016 +0200
Committer: Nick Wellnhofer <we...@aevum.de>
Committed: Fri Jul 8 14:25:36 2016 +0200

----------------------------------------------------------------------
 compiler/perl/lib/Clownfish/CFC/Perl/Build.pm | 26 +++++++++-------------
 1 file changed, 11 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/a80e405e/compiler/perl/lib/Clownfish/CFC/Perl/Build.pm
----------------------------------------------------------------------
diff --git a/compiler/perl/lib/Clownfish/CFC/Perl/Build.pm b/compiler/perl/lib/Clownfish/CFC/Perl/Build.pm
index 6d0f584..9c1ff84 100644
--- a/compiler/perl/lib/Clownfish/CFC/Perl/Build.pm
+++ b/compiler/perl/lib/Clownfish/CFC/Perl/Build.pm
@@ -134,7 +134,7 @@ sub cf_base_path {
 }
 
 sub cf_linker_flags {
-    my $self_or_class = shift;
+    my $self = shift;
 
     my $dlext = $Config{dlext};
     # Only needed on Windows
@@ -152,7 +152,7 @@ sub cf_linker_flags {
         my $lib_file;
         my $found;
 
-        for my $dir (@INC) {
+        for my $dir ( catdir( $self->blib, 'arch' ), @INC ) {
             $lib_file = catfile(
                 $dir, 'auto', @module_parts, "$class_name.$ext",
             );
@@ -503,13 +503,19 @@ sub _compile_custom_xs {
     # .o => .(a|bundle)
     my $lib_file = catfile( $archdir, "$class_name.$Config{dlext}" );
     if ( !$self->up_to_date( [ @objects, $AUTOGEN_DIR ], $lib_file ) ) {
+        my $linker_flags = $self->extra_linker_flags;
+        if ( $module->{xs_prereqs} ) {
+            push @$linker_flags,
+                 $self->cf_linker_flags( @{ $module->{xs_prereqs} } );
+        }
         $cbuilder->link(
             module_name        => $module_name,
             objects            => \@objects,
             lib_file           => $lib_file,
-            extra_linker_flags => $self->extra_linker_flags,
+            extra_linker_flags => $linker_flags,
         );
         # Install .lib file on Windows
+        # TODO: Install .dll.a when building with GCC on Windows?
         my $implib_file = catfile( $libdir, "$class_name.lib" );
         if ( -e $implib_file ) {
             $self->copy_if_modified(
@@ -615,16 +621,12 @@ the Perl bindings for Clownfish modules.
     use File::Spec::Functions qw( catdir );
 
     my @cf_base_path    = Clownfish::CFC::Perl::Build->cf_base_path;
-    my @cf_linker_flags = Clownfish::CFC::Perl::Build->cf_linker_flags(
-        'Other::Module',
-    );
 
     my $builder = Clownfish::CFC::Perl::Build->new(
         module_name        => 'My::Module',
         dist_abstract      => 'Do something with this and that',
         dist_author        => 'The Author <au...@example.com>',
         dist_version       => '0.1.0',
-        extra_linker_flags => [ @cf_linker_flags ],
         clownfish_params => {
             source  => [ catdir( @cf_base_path, 'core' ) ],
             modules => [
@@ -632,10 +634,12 @@ the Perl bindings for Clownfish modules.
                     name          => 'My::Module',
                     c_source_dirs => 'xs',
                     parcels       => [ 'MyModule' ],
+                    xs_prereqs    => [ 'Clownfish' ],
                 },
                 {
                     name          => 'My::Module::Test',
                     parcels       => [ 'TestMyModule' ],
+                    xs_prereqs    => [ 'Clownfish', 'My::Module' ],
                 },
             ],
         },
@@ -729,14 +733,6 @@ Clownfish .c files.
 Returns the base path components of the source tree where C<core> was found.
 Currently either C<()> or C<('..')>.
 
-=head2 cf_linker_flags( I<[module_names]> )
-
-    my @flags = Clownfish::CFC::Perl::Build->cf_linker_flags(@module_names);
-
-Returns the linker flags needed to link against all Clownfish modules in
-C<@module_names>. Should be added to C<extra_linker_flags> for all module
-dependencies. Only needed on Windows.
-
 =head1 METHODS
 
 =head2 cf_copy_include_file( I<[path components]> )


[12/14] lucy-clownfish git commit: Bump ExtUtils::ParseXS dependency to 3.00

Posted by nw...@apache.org.
Bump ExtUtils::ParseXS dependency to 3.00

Older versions wouldn't clear the @BootCode global and mess up the
BOOT section when building multiple XS files.


Project: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/commit/bad95e07
Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/bad95e07
Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/bad95e07

Branch: refs/heads/master
Commit: bad95e074b7cf094cf8c06205f7b1c41ddcaa45b
Parents: ef738c2
Author: Nick Wellnhofer <we...@aevum.de>
Authored: Fri Jul 8 13:57:36 2016 +0200
Committer: Nick Wellnhofer <we...@aevum.de>
Committed: Fri Jul 8 14:35:42 2016 +0200

----------------------------------------------------------------------
 compiler/perl/Build.PL | 2 +-
 runtime/perl/Build.PL  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/bad95e07/compiler/perl/Build.PL
----------------------------------------------------------------------
diff --git a/compiler/perl/Build.PL b/compiler/perl/Build.PL
index 783971f..395478e 100644
--- a/compiler/perl/Build.PL
+++ b/compiler/perl/Build.PL
@@ -31,7 +31,7 @@ my $builder = Clownfish::CFC::Build->new(
     build_requires    => {
         'Module::Build'      => 0.280801,
         'ExtUtils::CBuilder' => 0.18,
-        'ExtUtils::ParseXS'  => 2.16,
+        'ExtUtils::ParseXS'  => 3.00,
         'Devel::PPPort'      => 3.14,
     },
     meta_merge => { keywords => [qw( clownfish )], },

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/bad95e07/runtime/perl/Build.PL
----------------------------------------------------------------------
diff --git a/runtime/perl/Build.PL b/runtime/perl/Build.PL
index 5190a1e..2178e79 100644
--- a/runtime/perl/Build.PL
+++ b/runtime/perl/Build.PL
@@ -34,7 +34,7 @@ my $builder = Clownfish::Build->new(
     build_requires => {
         'Module::Build'      => 0.280801,
         'ExtUtils::CBuilder' => 0.21,
-        'ExtUtils::ParseXS'  => 2.18,
+        'ExtUtils::ParseXS'  => 3.00,
         'Devel::PPPort'      => 3.14,
         'Clownfish::CFC'     => 0.005000,
     },


[02/14] lucy-clownfish git commit: Disable a test in TestClass

Posted by nw...@apache.org.
Disable a test in TestClass

Ivars offsets aren't visible from a separate test binary.


Project: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/commit/24fb4d90
Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/24fb4d90
Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/24fb4d90

Branch: refs/heads/master
Commit: 24fb4d901a56a59913c9ff5a5e67840540215a48
Parents: 83c9045
Author: Nick Wellnhofer <we...@aevum.de>
Authored: Fri Jul 8 13:57:23 2016 +0200
Committer: Nick Wellnhofer <we...@aevum.de>
Committed: Fri Jul 8 14:25:36 2016 +0200

----------------------------------------------------------------------
 runtime/test/Clownfish/Test/TestClass.c | 6 ++++++
 1 file changed, 6 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/24fb4d90/runtime/test/Clownfish/Test/TestClass.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestClass.c b/runtime/test/Clownfish/Test/TestClass.c
index a6646f8..b467cd5 100644
--- a/runtime/test/Clownfish/Test/TestClass.c
+++ b/runtime/test/Clownfish/Test/TestClass.c
@@ -59,7 +59,9 @@ static void
 test_bootstrap_idempotence(TestBatchRunner *runner) {
     Class    *bool_class        = BOOLEAN;
     uint32_t  bool_class_size   = BOOLEAN->class_alloc_size;
+#if 0
     uint32_t  bool_ivars_offset = cfish_Bool_IVARS_OFFSET;
+#endif
     Boolean  *true_singleton    = Bool_true_singleton;
 
     char *bool_class_contents = (char*)MALLOCATE(bool_class_size);
@@ -80,8 +82,12 @@ test_bootstrap_idempotence(TestBatchRunner *runner) {
     TEST_TRUE(runner,
               memcmp(bool_class_contents, BOOLEAN, bool_class_size) == 0,
               "Boolean class unchanged");
+#if 0
     TEST_TRUE(runner, bool_ivars_offset == cfish_Bool_IVARS_OFFSET,
               "Boolean ivars offset unchanged");
+#else
+    SKIP(runner, 1, "TODO: Make ivars offset accessible somehow?");
+#endif
     TEST_TRUE(runner, true_singleton == Bool_true_singleton,
               "Boolean singleton unchanged");
 


[11/14] lucy-clownfish git commit: Fix shared library compilation for Python

Posted by nw...@apache.org.
Fix shared library compilation for Python

Untested. AFAIU, the static library built for Python is linked into
a shared library later. This requires to use -fPIC and define
CFP_CFISH.

I think it would be better if the Python bindings followed the
approach I took for Perl: Pass a list of object files instead of
using a static library.


Project: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/commit/3764180f
Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/3764180f
Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/3764180f

Branch: refs/heads/master
Commit: 3764180f0e7b5893f79d7ecb806b7106dd530b29
Parents: bad95e0
Author: Nick Wellnhofer <we...@aevum.de>
Authored: Fri Jul 8 13:57:39 2016 +0200
Committer: Nick Wellnhofer <we...@aevum.de>
Committed: Fri Jul 8 14:35:42 2016 +0200

----------------------------------------------------------------------
 runtime/common/charmonizer.c    | 18 ++++++++++++++++++
 runtime/common/charmonizer.main | 18 ++++++++++++++++++
 runtime/python/cfext/CFBind.c   |  1 +
 3 files changed, 37 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/3764180f/runtime/common/charmonizer.c
----------------------------------------------------------------------
diff --git a/runtime/common/charmonizer.c b/runtime/common/charmonizer.c
index 37cfaf4..102970e 100644
--- a/runtime/common/charmonizer.c
+++ b/runtime/common/charmonizer.c
@@ -8930,6 +8930,15 @@ cfish_MakeFile_write(cfish_MakeFile *self, chaz_CFlags *extra_link_flags) {
         self->lib
             = chaz_MakeFile_add_static_lib(self->makefile, NULL, "clownfish");
         lib_objs = "$(CLOWNFISH_STATIC_LIB_OBJS)";
+
+        if (strcmp(host, "python") == 0) {
+            /* For Python, the static library is linked into a shared
+             * library.
+             */
+            compile_flags = chaz_MakeBinary_get_compile_flags(self->lib);
+            chaz_CFlags_compile_shared_library(compile_flags);
+            chaz_CFlags_add_define(compile_flags, "CFP_CFISH", NULL);
+        }
     }
 
     if (self->host_src_dir) {
@@ -8967,6 +8976,15 @@ cfish_MakeFile_write(cfish_MakeFile *self, chaz_CFlags *extra_link_flags) {
         self->test_lib
             = chaz_MakeFile_add_static_lib(self->makefile, NULL, "testcfish");
         test_lib_objs = "$(TESTCFISH_STATIC_LIB_OBJS)";
+
+        if (strcmp(host, "python") == 0) {
+            /* For Python, the static library is linked into a shared
+             * library.
+             */
+            compile_flags = chaz_MakeBinary_get_compile_flags(self->test_lib);
+            chaz_CFlags_compile_shared_library(compile_flags);
+            chaz_CFlags_add_define(compile_flags, "CFP_TESTCFISH", NULL);
+        }
     }
 
     chaz_MakeBinary_add_src_dir(self->test_lib, self->test_dir);

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/3764180f/runtime/common/charmonizer.main
----------------------------------------------------------------------
diff --git a/runtime/common/charmonizer.main b/runtime/common/charmonizer.main
index 124541c..fc68fd4 100644
--- a/runtime/common/charmonizer.main
+++ b/runtime/common/charmonizer.main
@@ -382,6 +382,15 @@ cfish_MakeFile_write(cfish_MakeFile *self, chaz_CFlags *extra_link_flags) {
         self->lib
             = chaz_MakeFile_add_static_lib(self->makefile, NULL, "clownfish");
         lib_objs = "$(CLOWNFISH_STATIC_LIB_OBJS)";
+
+        if (strcmp(host, "python") == 0) {
+            /* For Python, the static library is linked into a shared
+             * library.
+             */
+            compile_flags = chaz_MakeBinary_get_compile_flags(self->lib);
+            chaz_CFlags_compile_shared_library(compile_flags);
+            chaz_CFlags_add_define(compile_flags, "CFP_CFISH", NULL);
+        }
     }
 
     if (self->host_src_dir) {
@@ -419,6 +428,15 @@ cfish_MakeFile_write(cfish_MakeFile *self, chaz_CFlags *extra_link_flags) {
         self->test_lib
             = chaz_MakeFile_add_static_lib(self->makefile, NULL, "testcfish");
         test_lib_objs = "$(TESTCFISH_STATIC_LIB_OBJS)";
+
+        if (strcmp(host, "python") == 0) {
+            /* For Python, the static library is linked into a shared
+             * library.
+             */
+            compile_flags = chaz_MakeBinary_get_compile_flags(self->test_lib);
+            chaz_CFlags_compile_shared_library(compile_flags);
+            chaz_CFlags_add_define(compile_flags, "CFP_TESTCFISH", NULL);
+        }
     }
 
     chaz_MakeBinary_add_src_dir(self->test_lib, self->test_dir);

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/3764180f/runtime/python/cfext/CFBind.c
----------------------------------------------------------------------
diff --git a/runtime/python/cfext/CFBind.c b/runtime/python/cfext/CFBind.c
index 4c5780e..0703880 100644
--- a/runtime/python/cfext/CFBind.c
+++ b/runtime/python/cfext/CFBind.c
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#define CFP_CFISH
 #define C_CFISH_OBJ
 #define C_CFISH_CLASS
 #define C_CFISH_METHOD


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

Posted by nw...@apache.org.
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);
+}
+
+


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

Posted by nw...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/TestHost.c
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestHost.c b/runtime/core/Clownfish/Test/TestHost.c
deleted file mode 100644
index 6da0efa..0000000
--- a/runtime/core/Clownfish/Test/TestHost.c
+++ /dev/null
@@ -1,125 +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/TestHost.h"
-#include "Clownfish/Class.h"
-#include "Clownfish/String.h"
-
-TestHost*
-TestHost_new() {
-    return (TestHost*)Class_Make_Obj(TESTHOST);
-}
-
-Obj*
-TestHost_Test_Obj_Pos_Arg_IMP(TestHost *self, Obj *arg) {
-    UNUSED_VAR(self);
-    return arg;
-}
-
-Obj*
-TestHost_Test_Obj_Pos_Arg_Def_IMP(TestHost *self, Obj *arg) {
-    UNUSED_VAR(self);
-    return arg;
-}
-
-Obj*
-TestHost_Test_Obj_Label_Arg_IMP(TestHost *self, Obj *arg, bool unused) {
-    UNUSED_VAR(self);
-    UNUSED_VAR(unused);
-    return arg;
-}
-
-Obj*
-TestHost_Test_Obj_Label_Arg_Def_IMP(TestHost *self, Obj *arg, bool unused) {
-    UNUSED_VAR(self);
-    UNUSED_VAR(unused);
-    return arg;
-}
-
-int32_t
-TestHost_Test_Int32_Pos_Arg_IMP(TestHost *self, int32_t arg) {
-    UNUSED_VAR(self);
-    return arg;
-}
-
-int32_t
-TestHost_Test_Int32_Pos_Arg_Def_IMP(TestHost *self, int32_t arg) {
-    UNUSED_VAR(self);
-    return arg;
-}
-
-int32_t
-TestHost_Test_Int32_Label_Arg_IMP(TestHost *self, int32_t arg, bool unused) {
-    UNUSED_VAR(self);
-    UNUSED_VAR(unused);
-    return arg;
-}
-
-int32_t
-TestHost_Test_Int32_Label_Arg_Def_IMP(TestHost *self, int32_t arg,
-                                      bool unused) {
-    UNUSED_VAR(self);
-    UNUSED_VAR(unused);
-    return arg;
-}
-
-bool
-TestHost_Test_Bool_Pos_Arg_IMP(TestHost *self, bool arg) {
-    UNUSED_VAR(self);
-    return arg;
-}
-
-bool
-TestHost_Test_Bool_Pos_Arg_Def_IMP(TestHost *self, bool arg) {
-    UNUSED_VAR(self);
-    return arg;
-}
-
-bool
-TestHost_Test_Bool_Label_Arg_IMP(TestHost *self, bool arg, bool unused) {
-    UNUSED_VAR(self);
-    UNUSED_VAR(unused);
-    return arg;
-}
-
-bool
-TestHost_Test_Bool_Label_Arg_Def_IMP(TestHost *self, bool arg, bool unused) {
-    UNUSED_VAR(self);
-    UNUSED_VAR(unused);
-    return arg;
-}
-
-void
-TestHost_Invoke_Invalid_Callback_From_C_IMP(TestHost *self) {
-    TestHost_Invalid_Callback(self);
-}
-
-String*
-TestHost_Aliased_IMP(TestHost *self) {
-    UNUSED_VAR(self);
-    return Str_newf("C");
-}
-
-String*
-TestHost_Invoke_Aliased_From_C_IMP(TestHost *self) {
-    UNUSED_VAR(self);
-    return TestHost_Aliased(self);
-}
-
-

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/TestHost.cfh
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestHost.cfh b/runtime/core/Clownfish/Test/TestHost.cfh
deleted file mode 100644
index 92bc272..0000000
--- a/runtime/core/Clownfish/Test/TestHost.cfh
+++ /dev/null
@@ -1,81 +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;
-
-/** Clownfish test suite.
- */
-class Clownfish::Test::TestHost {
-    inert incremented TestHost*
-    new();
-
-    Obj*
-    Test_Obj_Pos_Arg(TestHost *self, Obj *arg);
-
-    Obj*
-    Test_Obj_Pos_Arg_Def(TestHost *self, nullable Obj *arg = NULL);
-
-    Obj*
-    Test_Obj_Label_Arg(TestHost *self, Obj *arg, bool unused = false);
-
-    Obj*
-    Test_Obj_Label_Arg_Def(TestHost *self, nullable Obj *arg = NULL,
-                           bool unused = false);
-
-    int32_t
-    Test_Int32_Pos_Arg(TestHost *self, int32_t arg);
-
-    int32_t
-    Test_Int32_Pos_Arg_Def(TestHost *self, int32_t arg = 101);
-
-    int32_t
-    Test_Int32_Label_Arg(TestHost *self, int32_t arg, bool unused = false);
-
-    int32_t
-    Test_Int32_Label_Arg_Def(TestHost *self, int32_t arg = 101,
-                             bool unused = false);
-
-    bool
-    Test_Bool_Pos_Arg(TestHost *self, bool arg);
-
-    bool
-    Test_Bool_Pos_Arg_Def(TestHost *self, bool arg = true);
-
-    bool
-    Test_Bool_Label_Arg(TestHost *self, bool arg, bool unused = false);
-
-    bool
-    Test_Bool_Label_Arg_Def(TestHost *self, bool arg = true,
-                            bool unused = false);
-
-    /** A method that can't be overridden from the host language.
-     */
-    abstract void*
-    Invalid_Callback(TestHost *self);
-
-    void
-    Invoke_Invalid_Callback_From_C(TestHost *self);
-
-    /** A method with a custom host language alias.
-     */
-    incremented String*
-    Aliased(TestHost *self);
-
-    incremented String*
-    Invoke_Aliased_From_C(TestHost* self);
-}
-
-

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/TestLockFreeRegistry.c
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestLockFreeRegistry.c b/runtime/core/Clownfish/Test/TestLockFreeRegistry.c
deleted file mode 100644
index b70ff1f..0000000
--- a/runtime/core/Clownfish/Test/TestLockFreeRegistry.c
+++ /dev/null
@@ -1,168 +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 <stdlib.h>
-#include <string.h>
-
-#define CFISH_USE_SHORT_NAMES
-#define TESTCFISH_USE_SHORT_NAMES
-
-#include "Clownfish/Test/TestLockFreeRegistry.h"
-
-#include "Clownfish/Class.h"
-#include "Clownfish/LockFreeRegistry.h"
-#include "Clownfish/String.h"
-#include "Clownfish/Test.h"
-#include "Clownfish/TestHarness/TestBatchRunner.h"
-#include "Clownfish/TestHarness/TestUtils.h"
-#include "Clownfish/Util/Memory.h"
-
-#define NUM_THREADS 5
-
-typedef struct ThreadArgs {
-    LockFreeRegistry *registry;
-    uint32_t         *nums;
-    uint32_t          num_objs;
-    uint64_t          target_time;
-    uint32_t          succeeded;
-} ThreadArgs;
-
-TestLockFreeRegistry*
-TestLFReg_new() {
-    return (TestLockFreeRegistry*)Class_Make_Obj(TESTLOCKFREEREGISTRY);
-}
-
-static void
-test_all(TestBatchRunner *runner) {
-    LockFreeRegistry *registry = LFReg_new(1);
-    String *foo = Str_newf("foo");
-    String *bar = Str_newf("bar");
-    String *baz = Str_newf("baz");
-    String *foo_dupe = Str_newf("foo");
-
-    TEST_TRUE(runner, LFReg_register(registry, foo, (Obj*)foo),
-              "Register() returns true on success");
-    TEST_FALSE(runner,
-               LFReg_register(registry, foo_dupe, (Obj*)foo_dupe),
-               "Can't Register() keys that test equal");
-
-    TEST_TRUE(runner, LFReg_register(registry, bar, (Obj*)bar),
-              "Register() key with the same Hash_Sum but that isn't Equal");
-
-    TEST_TRUE(runner, LFReg_fetch(registry, foo_dupe) == (Obj*)foo,
-              "Fetch()");
-    TEST_TRUE(runner, LFReg_fetch(registry, bar) == (Obj*)bar,
-              "Fetch() again");
-    TEST_TRUE(runner, LFReg_fetch(registry, baz) == NULL,
-              "Fetch() non-existent key returns NULL");
-
-    DECREF(foo_dupe);
-    DECREF(baz);
-    DECREF(bar);
-    DECREF(foo);
-    LFReg_destroy(registry);
-}
-
-static void
-S_register_many(void *varg) {
-    ThreadArgs *args = (ThreadArgs*)varg;
-
-    // Encourage contention, so that all threads try to register at the same
-    // time.
-
-    // Sleep until target_time.
-    uint64_t time = TestUtils_time();
-    if (args->target_time > time) {
-        TestUtils_usleep(args->target_time - time);
-    }
-
-    TestUtils_thread_yield();
-
-    uint32_t succeeded = 0;
-    for (uint32_t i = 0; i < args->num_objs; i++) {
-        String *obj = Str_newf("%u32", args->nums[i]);
-        if (LFReg_register(args->registry, obj, (Obj*)obj)) {
-            succeeded++;
-        }
-        DECREF(obj);
-    }
-
-    args->succeeded = succeeded;
-}
-
-static void
-test_threads(TestBatchRunner *runner) {
-    if (!TestUtils_has_threads) {
-        SKIP(runner, 1, "No thread support");
-        return;
-    }
-
-    LockFreeRegistry *registry = LFReg_new(32);
-    ThreadArgs thread_args[NUM_THREADS];
-    uint32_t num_objs = 10000;
-
-    for (uint32_t i = 0; i < NUM_THREADS; i++) {
-        uint32_t *nums = (uint32_t*)MALLOCATE(num_objs * sizeof(uint32_t));
-
-        for (uint32_t j = 0; j < num_objs; j++) {
-            nums[j] = j;
-        }
-
-        // Fisher-Yates shuffle.
-        for (uint32_t j = num_objs - 1; j > 0; j--) {
-            uint32_t r = (uint32_t)TestUtils_random_u64() % (j + 1);
-            uint32_t tmp = nums[j];
-            nums[j] = nums[r];
-            nums[r] = tmp;
-        }
-
-        thread_args[i].registry = registry;
-        thread_args[i].nums     = nums;
-        thread_args[i].num_objs = num_objs;
-    }
-
-    Thread *threads[NUM_THREADS];
-    uint64_t target_time = TestUtils_time() + 200 * 1000;
-
-    for (uint32_t i = 0; i < NUM_THREADS; i++) {
-        thread_args[i].target_time = target_time;
-        threads[i]
-            = TestUtils_thread_create(S_register_many, &thread_args[i], NULL);
-    }
-
-    uint32_t total_succeeded = 0;
-
-    for (uint32_t i = 0; i < NUM_THREADS; i++) {
-        TestUtils_thread_join(threads[i]);
-        total_succeeded += thread_args[i].succeeded;
-        FREEMEM(thread_args[i].nums);
-    }
-
-    TEST_INT_EQ(runner, total_succeeded, num_objs,
-                "registered exactly the right number of entries across all"
-                " threads");
-
-    LFReg_destroy(registry);
-}
-
-void
-TestLFReg_Run_IMP(TestLockFreeRegistry *self, TestBatchRunner *runner) {
-    TestBatchRunner_Plan(runner, (TestBatch*)self, 7);
-    test_all(runner);
-    test_threads(runner);
-}
-
-

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/TestLockFreeRegistry.cfh
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestLockFreeRegistry.cfh b/runtime/core/Clownfish/Test/TestLockFreeRegistry.cfh
deleted file mode 100644
index 784f745..0000000
--- a/runtime/core/Clownfish/Test/TestLockFreeRegistry.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::TestLockFreeRegistry nickname TestLFReg
-    inherits Clownfish::TestHarness::TestBatch {
-
-    inert incremented TestLockFreeRegistry*
-    new();
-
-    void
-    Run(TestLockFreeRegistry *self, TestBatchRunner *runner);
-}
-
-

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/TestMethod.c
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestMethod.c b/runtime/core/Clownfish/Test/TestMethod.c
deleted file mode 100644
index 7c39759..0000000
--- a/runtime/core/Clownfish/Test/TestMethod.c
+++ /dev/null
@@ -1,91 +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/TestMethod.h"
-
-#include "Clownfish/Err.h"
-#include "Clownfish/Method.h"
-#include "Clownfish/String.h"
-#include "Clownfish/TestHarness/TestBatchRunner.h"
-
-TestMethod*
-TestMethod_new() {
-    return (TestMethod*)Class_Make_Obj(TESTMETHOD);
-}
-
-static void
-S_set_host_alias(void *context) {
-    Method *method = (Method*)context;
-    Method_Set_Host_Alias(method, SSTR_WRAP_C("foo"));
-}
-
-static void
-test_accessors(TestBatchRunner *runner) {
-    String *name = SSTR_WRAP_C("Frobnicate_Widget");
-    Method *method = Method_new(name, NULL, 0);
-
-    TEST_TRUE(runner, Str_Equals(Method_Get_Name(method), (Obj*)name),
-              "Get_Name");
-
-    String *alias = SSTR_WRAP_C("host_frob");
-    Method_Set_Host_Alias(method, alias);
-    TEST_TRUE(runner, Str_Equals(Method_Get_Host_Alias(method), (Obj*)alias),
-              "Set_Host_Alias");
-    Err *error = Err_trap(S_set_host_alias, method);
-    TEST_TRUE(runner, error != NULL,
-              "Set_Host_Alias can't be called more than once");
-    DECREF(error);
-
-    TEST_FALSE(runner, Method_Is_Excluded_From_Host(method),
-               "Is_Excluded_From_Host");
-
-    Method_Destroy(method);
-}
-
-static void
-test_lower_snake_alias(TestBatchRunner *runner) {
-    String *name = SSTR_WRAP_C("Frobnicate_Widget");
-    Method *method = Method_new(name, NULL, 0);
-
-    {
-        String *alias = Method_lower_snake_alias(method);
-        TEST_TRUE(runner, Str_Equals_Utf8(alias, "frobnicate_widget", 17),
-                  "lower_snake_alias without explicit alias");
-        DECREF(alias);
-    }
-
-    {
-        String *new_alias = SSTR_WRAP_C("host_frob");
-        Method_Set_Host_Alias(method, new_alias);
-        String *alias = Method_lower_snake_alias(method);
-        TEST_TRUE(runner, Str_Equals(alias, (Obj*)new_alias),
-                  "lower_snake_alias with explicit alias");
-        DECREF(alias);
-    }
-
-    Method_Destroy(method);
-}
-
-void
-TestMethod_Run_IMP(TestMethod *self, TestBatchRunner *runner) {
-    TestBatchRunner_Plan(runner, (TestBatch*)self, 6);
-    test_accessors(runner);
-    test_lower_snake_alias(runner);
-}
-

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

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/TestNum.c
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestNum.c b/runtime/core/Clownfish/Test/TestNum.c
deleted file mode 100644
index dfa6769..0000000
--- a/runtime/core/Clownfish/Test/TestNum.c
+++ /dev/null
@@ -1,278 +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 <math.h>
-
-#include "charmony.h"
-
-#include "Clownfish/Test/TestNum.h"
-
-#include "Clownfish/Err.h"
-#include "Clownfish/String.h"
-#include "Clownfish/Num.h"
-#include "Clownfish/Test.h"
-#include "Clownfish/TestHarness/TestBatchRunner.h"
-#include "Clownfish/TestHarness/TestUtils.h"
-#include "Clownfish/Class.h"
-
-TestNum*
-TestNum_new() {
-    return (TestNum*)Class_Make_Obj(TESTNUM);
-}
-
-static void
-test_To_String(TestBatchRunner *runner) {
-    Float   *f64 = Float_new(1.33);
-    Integer *i64 = Int_new(INT64_MAX);
-    String *f64_string = Float_To_String(f64);
-    String *i64_string = Int_To_String(i64);
-
-    TEST_TRUE(runner, Str_Starts_With_Utf8(f64_string, "1.3", 3),
-              "Float_To_String");
-    TEST_TRUE(runner, Str_Equals_Utf8(i64_string, "9223372036854775807", 19),
-              "Int_To_String");
-
-    DECREF(i64_string);
-    DECREF(f64_string);
-    DECREF(i64);
-    DECREF(f64);
-}
-
-static void
-S_float_to_i64(void *context) {
-    Float *f = (Float*)context;
-    Float_To_I64(f);
-}
-
-static void
-test_accessors(TestBatchRunner *runner) {
-    Float   *f64 = Float_new(1.33);
-    Integer *i64 = Int_new(INT64_MIN);
-    double wanted64 = 1.33;
-    double got64;
-
-    got64 = Float_Get_Value(f64);
-    TEST_TRUE(runner, *(int64_t*)&got64 == *(int64_t*)&wanted64,
-              "F64 Get_Value");
-    TEST_TRUE(runner, Float_To_I64(f64) == 1, "Float_To_I64");
-
-    {
-        Float *huge = Float_new(1e40);
-        Err *error = Err_trap(S_float_to_i64, huge);
-        TEST_TRUE(runner, error != NULL,
-                  "Float_To_I64 throws when out of range (+)");
-        DECREF(error);
-        DECREF(huge);
-    }
-
-    {
-        Float *huge = Float_new(-1e40);
-        Err *error = Err_trap(S_float_to_i64, huge);
-        TEST_TRUE(runner, error != NULL,
-                  "Float_To_I64 throws when out of range (-)");
-        DECREF(error);
-        DECREF(huge);
-    }
-
-    TEST_TRUE(runner, Int_Get_Value(i64) == INT64_MIN, "I64 Get_Value");
-    TEST_TRUE(runner, Int_To_F64(i64) == -9223372036854775808.0, "Int_To_F64");
-
-    DECREF(i64);
-    DECREF(f64);
-}
-
-static void
-S_test_compare_float_int(TestBatchRunner *runner, double f64_val,
-                         int64_t i64_val, int32_t result) {
-    Float *f64;
-    Integer *i64;
-
-    f64 = Float_new(f64_val);
-    i64 = Int_new(i64_val);
-    TEST_INT_EQ(runner, Float_Compare_To(f64, (Obj*)i64), result,
-                "Float_Compare_To %f %" PRId64, f64_val, i64_val);
-    TEST_INT_EQ(runner, Int_Compare_To(i64, (Obj*)f64), -result,
-                "Int_Compare_To %" PRId64" %f", i64_val, f64_val);
-    TEST_INT_EQ(runner, Float_Equals(f64, (Obj*)i64), result == 0,
-                "Float_Equals %f %" PRId64, f64_val, i64_val);
-    TEST_INT_EQ(runner, Int_Equals(i64, (Obj*)f64), result == 0,
-                "Int_Equals %" PRId64 " %f", i64_val, f64_val);
-    DECREF(f64);
-    DECREF(i64);
-
-    if (i64_val == INT64_MIN) { return; }
-
-    f64 = Float_new(-f64_val);
-    i64 = Int_new(-i64_val);
-    TEST_INT_EQ(runner, Float_Compare_To(f64, (Obj*)i64), -result,
-                "Float_Compare_To %f %" PRId64, -f64_val, -i64_val);
-    TEST_INT_EQ(runner, Int_Compare_To(i64, (Obj*)f64), result,
-                "Int_Compare_To %" PRId64" %f", -i64_val, -f64_val);
-    TEST_INT_EQ(runner, Float_Equals(f64, (Obj*)i64), result == 0,
-                "Float_Equals %f %" PRId64, -f64_val, -i64_val);
-    TEST_INT_EQ(runner, Int_Equals(i64, (Obj*)f64), result == 0,
-                "Int_Equals %" PRId64 " %f", -i64_val, -f64_val);
-    DECREF(f64);
-    DECREF(i64);
-}
-
-static void
-S_float_compare_to(void *context) {
-    Float *f = (Float*)context;
-    Float_Compare_To(f, (Obj*)OBJ);
-}
-
-static void
-S_int_compare_to(void *context) {
-    Integer *i = (Integer*)context;
-    Int_Compare_To(i, (Obj*)OBJ);
-}
-
-static void
-test_Equals_and_Compare_To(TestBatchRunner *runner) {
-    {
-        Float *f1 = Float_new(1.0);
-        Float *f2 = Float_new(1.0);
-        TEST_TRUE(runner, Float_Compare_To(f1, (Obj*)f2) == 0,
-                  "Float_Compare_To equal");
-        TEST_TRUE(runner, Float_Equals(f1, (Obj*)f2),
-                  "Float_Equals equal");
-        DECREF(f1);
-        DECREF(f2);
-    }
-
-    {
-        Float *f1 = Float_new(1.0);
-        Float *f2 = Float_new(2.0);
-        TEST_TRUE(runner, Float_Compare_To(f1, (Obj*)f2) < 0,
-                  "Float_Compare_To less than");
-        TEST_FALSE(runner, Float_Equals(f1, (Obj*)f2),
-                   "Float_Equals less than");
-        DECREF(f1);
-        DECREF(f2);
-    }
-
-    {
-        Float *f1 = Float_new(1.0);
-        Float *f2 = Float_new(0.0);
-        TEST_TRUE(runner, Float_Compare_To(f1, (Obj*)f2) > 0,
-                  "Float_Compare_To greater than");
-        TEST_FALSE(runner, Float_Equals(f1, (Obj*)f2),
-                   "Float_Equals greater than");
-        DECREF(f1);
-        DECREF(f2);
-    }
-
-    {
-        Float *f = Float_new(1.0);
-        Err *error = Err_trap(S_float_compare_to, f);
-        TEST_TRUE(runner, error != NULL,
-                  "Float_Compare_To with invalid type throws");
-        TEST_FALSE(runner, Float_Equals(f, (Obj*)OBJ),
-                   "Float_Equals with different type");
-        DECREF(error);
-        DECREF(f);
-    }
-
-    {
-        Integer *i1 = Int_new(INT64_C(0x6666666666666666));
-        Integer *i2 = Int_new(INT64_C(0x6666666666666666));
-        TEST_TRUE(runner, Int_Compare_To(i1, (Obj*)i2) == 0,
-                  "Int_Compare_To equal");
-        TEST_TRUE(runner, Int_Equals(i1, (Obj*)i2),
-                  "Int_Equals equal");
-        DECREF(i1);
-        DECREF(i2);
-    }
-
-    {
-        Integer *i1 = Int_new(INT64_C(0x6666666666666666));
-        Integer *i2 = Int_new(INT64_C(0x6666666666666667));
-        TEST_TRUE(runner, Int_Compare_To(i1, (Obj*)i2) < 0,
-                  "Int_Compare_To less than");
-        TEST_FALSE(runner, Int_Equals(i1, (Obj*)i2),
-                   "Int_Equals less than");
-        DECREF(i1);
-        DECREF(i2);
-    }
-
-    {
-        Integer *i1 = Int_new(INT64_C(0x6666666666666666));
-        Integer *i2 = Int_new(INT64_C(0x6666666666666665));
-        TEST_TRUE(runner, Int_Compare_To(i1, (Obj*)i2) > 0,
-                  "Int_Compare_To greater than");
-        TEST_FALSE(runner, Int_Equals(i1, (Obj*)i2),
-                   "Int_Equals greater than");
-        DECREF(i1);
-        DECREF(i2);
-    }
-
-    {
-        Integer *i = Int_new(0);
-        Err *error = Err_trap(S_int_compare_to, i);
-        TEST_TRUE(runner, error != NULL,
-                  "Int_Compare_To with invalid type throws");
-        TEST_FALSE(runner, Int_Equals(i, (Obj*)OBJ),
-                   "Int_Equals with different type");
-        DECREF(error);
-        DECREF(i);
-    }
-
-    // NOTICE: When running these tests on x86/x64, it's best to compile
-    // with -ffloat-store to avoid excess FPU precision which can hide
-    // implementation bugs.
-    S_test_compare_float_int(runner, (double)INT64_MAX * 2.0, INT64_MAX, 1);
-    S_test_compare_float_int(runner, pow(2.0, 60.0), INT64_C(1) << 60, 0);
-    S_test_compare_float_int(runner, pow(2.0, 60.0), (INT64_C(1) << 60) - 1,
-                             1);
-    S_test_compare_float_int(runner, pow(2.0, 60.0), (INT64_C(1) << 60) + 1,
-                             -1);
-    S_test_compare_float_int(runner, pow(2.0, 63.0), INT64_MAX, 1);
-    S_test_compare_float_int(runner, -pow(2.0, 63.0), INT64_MIN, 0);
-    // -9223372036854777856.0 == nextafter(-pow(2, 63), -INFINITY)
-    S_test_compare_float_int(runner, -9223372036854777856.0, INT64_MIN, -1);
-    S_test_compare_float_int(runner, 1.0, 2, -1);
-}
-
-static void
-test_Clone(TestBatchRunner *runner) {
-    Float   *f64 = Float_new(1.33);
-    Integer *i64 = Int_new(INT64_MAX);
-    Float   *f64_dupe = Float_Clone(f64);
-    Integer *i64_dupe = Int_Clone(i64);
-    TEST_TRUE(runner, Float_Equals(f64, (Obj*)f64_dupe),
-              "Float Clone");
-    TEST_TRUE(runner, Int_Equals(i64, (Obj*)i64_dupe),
-              "Integer Clone");
-    DECREF(i64_dupe);
-    DECREF(f64_dupe);
-    DECREF(i64);
-    DECREF(f64);
-}
-
-void
-TestNum_Run_IMP(TestNum *self, TestBatchRunner *runner) {
-    TestBatchRunner_Plan(runner, (TestBatch*)self, 82);
-    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/core/Clownfish/Test/TestNum.cfh
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestNum.cfh b/runtime/core/Clownfish/Test/TestNum.cfh
deleted file mode 100644
index 6d1f663..0000000
--- a/runtime/core/Clownfish/Test/TestNum.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::TestNum
-    inherits Clownfish::TestHarness::TestBatch {
-
-    inert incremented TestNum*
-    new();
-
-    void
-    Run(TestNum *self, TestBatchRunner *runner);
-}
-
-

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/TestObj.c
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestObj.c b/runtime/core/Clownfish/Test/TestObj.c
deleted file mode 100644
index c4cdafe..0000000
--- a/runtime/core/Clownfish/Test/TestObj.c
+++ /dev/null
@@ -1,152 +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 <stdio.h>
-
-#define CFISH_USE_SHORT_NAMES
-#define TESTCFISH_USE_SHORT_NAMES
-
-#include "charmony.h"
-
-#include "Clownfish/Test/TestObj.h"
-
-#include "Clownfish/String.h"
-#include "Clownfish/Err.h"
-#include "Clownfish/Test.h"
-#include "Clownfish/TestHarness/TestBatchRunner.h"
-#include "Clownfish/Class.h"
-
-TestObj*
-TestObj_new() {
-    return (TestObj*)Class_Make_Obj(TESTOBJ);
-}
-
-static Obj*
-S_new_testobj() {
-    String *class_name = SSTR_WRAP_C("TestObj");
-    Obj *obj;
-    Class *klass = Class_fetch_class(class_name);
-    if (!klass) {
-        klass = Class_singleton(class_name, OBJ);
-    }
-    obj = Class_Make_Obj(klass);
-    return Obj_init(obj);
-}
-
-static void
-test_refcounts(TestBatchRunner *runner) {
-    Obj *obj = S_new_testobj();
-
-    TEST_INT_EQ(runner, CFISH_REFCOUNT_NN(obj), 1,
-                "Correct starting refcount");
-
-    obj = CFISH_INCREF_NN(obj);
-    TEST_INT_EQ(runner, CFISH_REFCOUNT_NN(obj), 2, "INCREF_NN");
-
-    CFISH_DECREF_NN(obj);
-    TEST_INT_EQ(runner, CFISH_REFCOUNT_NN(obj), 1, "DECREF_NN");
-
-    DECREF(obj);
-}
-
-static void
-test_To_String(TestBatchRunner *runner) {
-    Obj *testobj = S_new_testobj();
-    String *string = Obj_To_String(testobj);
-    TEST_TRUE(runner, Str_Contains_Utf8(string, "TestObj", 7), "To_String");
-    DECREF(string);
-    DECREF(testobj);
-}
-
-static void
-test_Equals(TestBatchRunner *runner) {
-    Obj *testobj = S_new_testobj();
-    Obj *other   = S_new_testobj();
-
-    TEST_TRUE(runner, Obj_Equals(testobj, testobj),
-              "Equals is true for the same object");
-    TEST_FALSE(runner, Obj_Equals(testobj, other),
-               "Distinct objects are not equal");
-
-    DECREF(testobj);
-    DECREF(other);
-}
-
-static void
-test_is_a(TestBatchRunner *runner) {
-    String *string     = Str_new_from_trusted_utf8("", 0);
-    Class  *str_class  = Str_get_class(string);
-    String *class_name = Str_get_class_name(string);
-
-    TEST_TRUE(runner, Str_is_a(string, STRING), "String is_a String.");
-    TEST_TRUE(runner, Str_is_a(string, OBJ), "String is_a Obj.");
-    TEST_TRUE(runner, str_class == STRING, "get_class");
-    TEST_TRUE(runner, Str_Equals(Class_Get_Name(STRING), (Obj*)class_name),
-              "get_class_name");
-    TEST_FALSE(runner, Obj_is_a(NULL, OBJ), "NULL is not an Obj");
-
-    DECREF(string);
-}
-
-static void
-S_attempt_init(void *context) {
-    Obj_init((Obj*)context);
-}
-
-static void
-S_attempt_Clone(void *context) {
-    Obj_Clone((Obj*)context);
-}
-
-static void
-S_attempt_Compare_To(void *context) {
-    Obj_Compare_To((Obj*)context, (Obj*)context);
-}
-
-static void
-S_verify_abstract_error(TestBatchRunner *runner, Err_Attempt_t routine,
-                        void *context, const char *name) {
-    char message[100];
-    sprintf(message, "%s() is abstract", name);
-    Err *error = Err_trap(routine, context);
-    TEST_TRUE(runner, error != NULL
-              && Err_is_a(error, ERR)
-              && Str_Contains_Utf8(Err_Get_Mess(error), "bstract", 7),
-              message);
-    DECREF(error);
-}
-
-static void
-test_abstract_routines(TestBatchRunner *runner) {
-    Obj *blank = Class_Make_Obj(OBJ);
-    S_verify_abstract_error(runner, S_attempt_init, blank, "init");
-
-    Obj *obj = S_new_testobj();
-    S_verify_abstract_error(runner, S_attempt_Clone,      obj, "Clone");
-    S_verify_abstract_error(runner, S_attempt_Compare_To, obj, "Compare_To");
-    DECREF(obj);
-}
-
-void
-TestObj_Run_IMP(TestObj *self, TestBatchRunner *runner) {
-    TestBatchRunner_Plan(runner, (TestBatch*)self, 14);
-    test_refcounts(runner);
-    test_To_String(runner);
-    test_Equals(runner);
-    test_is_a(runner);
-    test_abstract_routines(runner);
-}
-

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

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/TestPtrHash.c
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestPtrHash.c b/runtime/core/Clownfish/Test/TestPtrHash.c
deleted file mode 100644
index 3bf7003..0000000
--- a/runtime/core/Clownfish/Test/TestPtrHash.c
+++ /dev/null
@@ -1,108 +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 <stdlib.h>
-#include <time.h>
-
-#include "Clownfish/Test/TestPtrHash.h"
-#include "Clownfish/Class.h"
-#include "Clownfish/PtrHash.h"
-#include "Clownfish/TestHarness/TestBatchRunner.h"
-#include "Clownfish/TestHarness/TestUtils.h"
-#include "Clownfish/Util/Memory.h"
-
-TestPtrHash*
-TestPtrHash_new() {
-    return (TestPtrHash*)Class_Make_Obj(TESTPTRHASH);
-}
-
-static void
-test_Store_and_Fetch(TestBatchRunner *runner) {
-    PtrHash *hash = PtrHash_new(100);
-    char dummy[100];
-
-    for (int i = 0; i < 100; i++) {
-        void *key = &dummy[i];
-        PtrHash_Store(hash, key, key);
-    }
-
-    bool all_equal = true;
-    for (int i = 0; i < 100; i++) {
-        void *key = &dummy[i];
-        void *value = PtrHash_Fetch(hash, key);
-        if (value != key) {
-            all_equal = false;
-            break;
-        }
-    }
-    TEST_TRUE(runner, all_equal, "basic Store and Fetch");
-
-    TEST_TRUE(runner, PtrHash_Fetch(hash, &dummy[100]) == NULL,
-              "Fetch against non-existent key returns NULL");
-
-    PtrHash_Store(hash, &dummy[50], dummy);
-    TEST_TRUE(runner, PtrHash_Fetch(hash, &dummy[50]) == dummy,
-              "Store replaces existing value");
-
-    PtrHash_Destroy(hash);
-}
-
-static void
-test_stress(TestBatchRunner *runner) {
-    PtrHash *hash = PtrHash_new(0); // trigger multiple rebuilds.
-    size_t num_elems = 200000;
-    void **keys = (void**)MALLOCATE(num_elems * sizeof(void*));
-
-    for (size_t i = 0; i < num_elems; i++) {
-        size_t index = (size_t)(TestUtils_random_u64() % num_elems);
-        void *key = &keys[index];
-        PtrHash_Store(hash, key, key);
-        keys[i] = key;
-    }
-
-    // Overwrite for good measure.
-    for (size_t i = 0; i < num_elems; i++) {
-        void *key = keys[i];
-        PtrHash_Store(hash, key, key);
-    }
-
-    bool all_equal = true;
-    for (size_t i = 0; i < num_elems; i++) {
-        void *key = keys[i];
-        void *got = PtrHash_Fetch(hash, key);
-        if (got != key) {
-            all_equal = false;
-            break;
-        }
-    }
-    TEST_TRUE(runner, all_equal, "stress test");
-
-    FREEMEM(keys);
-    PtrHash_Destroy(hash);
-}
-
-void
-TestPtrHash_Run_IMP(TestPtrHash *self, TestBatchRunner *runner) {
-    TestBatchRunner_Plan(runner, (TestBatch*)self, 4);
-    srand((unsigned int)time(NULL));
-    test_Store_and_Fetch(runner);
-    test_stress(runner);
-}
-
-

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/TestPtrHash.cfh
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestPtrHash.cfh b/runtime/core/Clownfish/Test/TestPtrHash.cfh
deleted file mode 100644
index 83589cb..0000000
--- a/runtime/core/Clownfish/Test/TestPtrHash.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::TestPtrHash
-    inherits Clownfish::TestHarness::TestBatch {
-
-    inert incremented TestPtrHash*
-    new();
-
-    void
-    Run(TestPtrHash *self, TestBatchRunner *runner);
-}
-
-

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/TestString.c
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestString.c b/runtime/core/Clownfish/Test/TestString.c
deleted file mode 100644
index d89b5fe..0000000
--- a/runtime/core/Clownfish/Test/TestString.c
+++ /dev/null
@@ -1,848 +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 <stdio.h>
-
-#define CFISH_USE_SHORT_NAMES
-#define TESTCFISH_USE_SHORT_NAMES
-
-#include "Clownfish/Test/TestString.h"
-
-#include "Clownfish/String.h"
-#include "Clownfish/Boolean.h"
-#include "Clownfish/ByteBuf.h"
-#include "Clownfish/CharBuf.h"
-#include "Clownfish/Err.h"
-#include "Clownfish/Test.h"
-#include "Clownfish/TestHarness/TestBatchRunner.h"
-#include "Clownfish/TestHarness/TestUtils.h"
-#include "Clownfish/Util/Memory.h"
-#include "Clownfish/Class.h"
-
-#define SMILEY "\xE2\x98\xBA"
-static char smiley[] = { (char)0xE2, (char)0x98, (char)0xBA, 0 };
-static uint32_t smiley_len = 3;
-static int32_t smiley_cp  = 0x263A;
-
-TestString*
-TestStr_new() {
-    return (TestString*)Class_Make_Obj(TESTSTRING);
-}
-
-static String*
-S_get_str(const char *string) {
-    return Str_new_from_utf8(string, strlen(string));
-}
-
-// Surround a smiley with lots of whitespace.
-static String*
-S_smiley_with_whitespace(size_t *num_spaces_ptr) {
-    int32_t spaces[] = {
-        ' ',    '\t',   '\r',   '\n',   0x000B, 0x000C, 0x000D, 0x0085,
-        0x00A0, 0x1680, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005,
-        0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x2028, 0x2029, 0x202F,
-        0x205F, 0x3000
-    };
-    size_t num_spaces = sizeof(spaces) / sizeof(uint32_t);
-
-    CharBuf *buf = CB_new(0);
-    for (size_t i = 0; i < num_spaces; i++) { CB_Cat_Char(buf, spaces[i]); }
-    CB_Cat_Char(buf, smiley_cp);
-    for (size_t i = 0; i < num_spaces; i++) { CB_Cat_Char(buf, spaces[i]); }
-
-    String *retval = CB_To_String(buf);
-    if (num_spaces_ptr) { *num_spaces_ptr = num_spaces; }
-
-    DECREF(buf);
-    return retval;
-}
-
-static void
-test_new(TestBatchRunner *runner) {
-    static char chars[] = "A string " SMILEY " with a smile.";
-
-    {
-        char *buffer = (char*)MALLOCATE(sizeof(chars));
-        strcpy(buffer, chars);
-        String *thief = Str_new_steal_utf8(buffer, sizeof(chars) - 1);
-        TEST_TRUE(runner, Str_Equals_Utf8(thief, chars, sizeof(chars) - 1),
-                  "Str_new_steal_utf8");
-        DECREF(thief);
-    }
-
-    {
-        char *buffer = (char*)MALLOCATE(sizeof(chars));
-        strcpy(buffer, chars);
-        String *thief
-            = Str_new_steal_trusted_utf8(buffer, sizeof(chars) - 1);
-        TEST_TRUE(runner, Str_Equals_Utf8(thief, chars, sizeof(chars) - 1),
-                  "Str_new_steal_trusted_utf8");
-        DECREF(thief);
-    }
-
-    {
-        String *wrapper = Str_new_wrap_utf8(chars, sizeof(chars) - 1);
-        TEST_TRUE(runner, Str_Equals_Utf8(wrapper, chars, sizeof(chars) - 1),
-                  "Str_new_wrap_utf8");
-        DECREF(wrapper);
-    }
-
-    {
-        String *wrapper = Str_new_wrap_trusted_utf8(chars, sizeof(chars) - 1);
-        TEST_TRUE(runner, Str_Equals_Utf8(wrapper, chars, sizeof(chars) - 1),
-                  "Str_new_wrap_trusted_utf8");
-        DECREF(wrapper);
-    }
-
-    {
-        String *smiley_str = Str_new_from_char(smiley_cp);
-        TEST_TRUE(runner, Str_Equals_Utf8(smiley_str, smiley, smiley_len),
-                  "Str_new_from_char");
-        DECREF(smiley_str);
-    }
-}
-
-static void
-test_Cat(TestBatchRunner *runner) {
-    String *wanted = Str_newf("a%s", smiley);
-    String *source;
-    String *got;
-
-    source = S_get_str("");
-    got = Str_Cat(source, wanted);
-    TEST_TRUE(runner, Str_Equals(wanted, (Obj*)got), "Cat");
-    DECREF(got);
-    DECREF(source);
-
-    source = S_get_str("a");
-    got = Str_Cat_Utf8(source, smiley, smiley_len);
-    TEST_TRUE(runner, Str_Equals(wanted, (Obj*)got), "Cat_Utf8");
-    DECREF(got);
-    DECREF(source);
-
-    source = S_get_str("a");
-    got = Str_Cat_Trusted_Utf8(source, smiley, smiley_len);
-    TEST_TRUE(runner, Str_Equals(wanted, (Obj*)got), "Cat_Trusted_Utf8");
-    DECREF(got);
-    DECREF(source);
-
-    DECREF(wanted);
-}
-
-static void
-test_Clone(TestBatchRunner *runner) {
-    String *wanted = S_get_str("foo");
-    String *got    = Str_Clone(wanted);
-    TEST_TRUE(runner, Str_Equals(wanted, (Obj*)got), "Clone");
-    DECREF(got);
-    DECREF(wanted);
-}
-
-static int64_t
-S_find(String *string, String *substring) {
-    StringIterator *iter = Str_Find(string, substring);
-    if (iter == NULL) { return -1; }
-    size_t tick = StrIter_Recede(iter, SIZE_MAX);
-    DECREF(iter);
-    return (int64_t)tick;
-}
-
-static void
-test_Contains_and_Find(TestBatchRunner *runner) {
-    String *string;
-    String *substring = S_get_str("foo");
-    String *empty     = S_get_str("");
-
-    TEST_FALSE(runner, Str_Contains(empty, substring),
-               "Not contained in empty string");
-    TEST_INT_EQ(runner, S_find(empty, substring), -1,
-                "Not found in empty string");
-
-    string = S_get_str("foo");
-    TEST_TRUE(runner, Str_Contains(string, substring),
-              "Contains complete string");
-    TEST_INT_EQ(runner, S_find(string, substring), 0, "Find complete string");
-    TEST_TRUE(runner, Str_Contains(string, empty),
-              "Contains empty string");
-    TEST_INT_EQ(runner, S_find(string, empty), 0, "Find empty string");
-    DECREF(string);
-
-    string = S_get_str("afoo");
-    TEST_TRUE(runner, Str_Contains(string, substring),
-              "Contained after first");
-    TEST_INT_EQ(runner, S_find(string, substring), 1, "Find after first");
-    String *prefix = Str_SubString(string, 0, 3);
-    TEST_FALSE(runner, Str_Contains(prefix, substring), "Don't overrun");
-    DECREF(prefix);
-    DECREF(string);
-
-    string = S_get_str("afood");
-    TEST_TRUE(runner, Str_Contains(string, substring), "Contained in middle");
-    TEST_INT_EQ(runner, S_find(string, substring), 1, "Find in middle");
-    DECREF(string);
-
-    DECREF(empty);
-    DECREF(substring);
-}
-
-static void
-test_Code_Point_At_and_From(TestBatchRunner *runner) {
-    int32_t code_points[] = {
-        'a', smiley_cp, smiley_cp, 'b', smiley_cp, 'c'
-    };
-    uint32_t num_code_points = sizeof(code_points) / sizeof(int32_t);
-    String *string = Str_newf("a%s%sb%sc", smiley, smiley, smiley);
-    uint32_t i;
-
-    for (i = 0; i < num_code_points; i++) {
-        uint32_t from = num_code_points - i;
-        TEST_INT_EQ(runner, Str_Code_Point_At(string, i), code_points[i],
-                    "Code_Point_At %ld", (long)i);
-        TEST_INT_EQ(runner, Str_Code_Point_From(string, from),
-                    code_points[i], "Code_Point_From %ld", (long)from);
-    }
-
-    TEST_INT_EQ(runner, Str_Code_Point_At(string, num_code_points), STR_OOB,
-                "Code_Point_At %ld", (long)num_code_points);
-    TEST_INT_EQ(runner, Str_Code_Point_From(string, 0), STR_OOB,
-                "Code_Point_From 0");
-    TEST_INT_EQ(runner, Str_Code_Point_From(string, num_code_points + 1),
-                STR_OOB, "Code_Point_From %ld", (long)(num_code_points + 1));
-
-    DECREF(string);
-}
-
-static void
-test_SubString(TestBatchRunner *runner) {
-    {
-        String *string = Str_newf("a%s%sb%sc", smiley, smiley, smiley);
-        String *wanted = Str_newf("%sb%s", smiley, smiley);
-        String *got = Str_SubString(string, 2, 3);
-        TEST_TRUE(runner, Str_Equals(wanted, (Obj*)got), "SubString");
-        DECREF(string);
-        DECREF(wanted);
-        DECREF(got);
-    }
-
-    {
-        static const char chars[] = "A string.";
-        String *wrapper = Str_new_wrap_utf8(chars, sizeof(chars) - 1);
-        String *wanted  = Str_newf("string");
-        String *got     = Str_SubString(wrapper, 2, 6);
-        TEST_TRUE(runner, Str_Equals(got, (Obj*)wanted),
-                  "SubString with wrapped buffer");
-        DECREF(wrapper);
-        DECREF(wanted);
-        DECREF(got);
-    }
-}
-
-static void
-test_Trim(TestBatchRunner *runner) {
-    String *ws_smiley = S_smiley_with_whitespace(NULL);
-    String *ws_foo    = S_get_str("  foo  ");
-    String *ws_only   = S_get_str("  \t  \r\n");
-    String *trimmed   = S_get_str("a     b");
-    String *got;
-
-    got = Str_Trim(ws_smiley);
-    TEST_TRUE(runner, Str_Equals_Utf8(got, smiley, smiley_len), "Trim");
-    DECREF(got);
-
-    got = Str_Trim_Top(ws_foo);
-    TEST_TRUE(runner, Str_Equals_Utf8(got, "foo  ", 5), "Trim_Top");
-    DECREF(got);
-
-    got = Str_Trim_Tail(ws_foo);
-    TEST_TRUE(runner, Str_Equals_Utf8(got, "  foo", 5), "Trim_Tail");
-    DECREF(got);
-
-    got = Str_Trim(ws_only);
-    TEST_TRUE(runner, Str_Equals_Utf8(got, "", 0), "Trim with only whitespace");
-    DECREF(got);
-
-    got = Str_Trim_Top(ws_only);
-    TEST_TRUE(runner, Str_Equals_Utf8(got, "", 0),
-              "Trim_Top with only whitespace");
-    DECREF(got);
-
-    got = Str_Trim_Tail(ws_only);
-    TEST_TRUE(runner, Str_Equals_Utf8(got, "", 0),
-              "Trim_Tail with only whitespace");
-    DECREF(got);
-
-    got = Str_Trim(trimmed);
-    TEST_TRUE(runner, Str_Equals(got, (Obj*)trimmed),
-              "Trim doesn't change trimmed string");
-    DECREF(got);
-
-    got = Str_Trim_Top(trimmed);
-    TEST_TRUE(runner, Str_Equals(got, (Obj*)trimmed),
-              "Trim_Top doesn't change trimmed string");
-    DECREF(got);
-
-    got = Str_Trim_Tail(trimmed);
-    TEST_TRUE(runner, Str_Equals(got, (Obj*)trimmed),
-              "Trim_Tail doesn't change trimmed string");
-    DECREF(got);
-
-    DECREF(trimmed);
-    DECREF(ws_only);
-    DECREF(ws_foo);
-    DECREF(ws_smiley);
-}
-
-static void
-test_To_F64(TestBatchRunner *runner) {
-    String *string;
-
-    string = S_get_str("1.5");
-    double difference = 1.5 - Str_To_F64(string);
-    if (difference < 0) { difference = 0 - difference; }
-    TEST_TRUE(runner, difference < 0.001, "To_F64");
-    DECREF(string);
-
-    string = S_get_str("-1.5");
-    difference = 1.5 + Str_To_F64(string);
-    if (difference < 0) { difference = 0 - difference; }
-    TEST_TRUE(runner, difference < 0.001, "To_F64 negative");
-    DECREF(string);
-
-    // TODO: Enable this test when we have real substrings.
-    /*string = S_get_str("1.59");
-    double value_full = Str_To_F64(string);
-    Str_Set_Size(string, 3);
-    double value_short = Str_To_F64(string);
-    TEST_TRUE(runner, value_short < value_full,
-              "TO_F64 doesn't run past end of string");
-    DECREF(string);*/
-}
-
-static void
-test_To_I64(TestBatchRunner *runner) {
-    String *string;
-
-    string = S_get_str("10");
-    TEST_INT_EQ(runner, Str_To_I64(string), 10, "To_I64");
-    DECREF(string);
-
-    string = S_get_str("-10");
-    TEST_INT_EQ(runner, Str_To_I64(string), -10, "To_I64 negative");
-    DECREF(string);
-
-    string = S_get_str("10.");
-    TEST_INT_EQ(runner, Str_To_I64(string), 10, "To_I64 stops at non-digits");
-    DECREF(string);
-
-    string = S_get_str("10" SMILEY);
-    TEST_INT_EQ(runner, Str_To_I64(string), 10, "To_I64 stops at non-ASCII");
-    DECREF(string);
-
-    string = S_get_str("10A");
-    TEST_INT_EQ(runner, Str_To_I64(string), 10,
-              "To_I64 stops at out-of-range digits");
-    DECREF(string);
-}
-
-static void
-test_BaseX_To_I64(TestBatchRunner *runner) {
-    String *string;
-
-    string = S_get_str("-JJ");
-    TEST_INT_EQ(runner, Str_BaseX_To_I64(string, 20), -399,
-              "BaseX_To_I64 base 20");
-    DECREF(string);
-}
-
-static void
-test_To_String(TestBatchRunner *runner) {
-    String *string = Str_newf("Test");
-    String *copy   = Str_To_String(string);
-    TEST_TRUE(runner, Str_Equals(copy, (Obj*)string), "To_String");
-    DECREF(string);
-    DECREF(copy);
-}
-
-static void
-test_To_Utf8(TestBatchRunner *runner) {
-    String *string = Str_newf("a%s%sb%sc", smiley, smiley, smiley);
-    char *buf = Str_To_Utf8(string);
-    TEST_TRUE(runner, strcmp(buf, "a" SMILEY SMILEY "b" SMILEY "c") == 0,
-              "To_Utf8");
-    FREEMEM(buf);
-    DECREF(string);
-}
-
-static void
-test_To_ByteBuf(TestBatchRunner *runner) {
-    String     *string = Str_newf("foo");
-    ByteBuf    *bb     = Str_To_ByteBuf(string);
-    TEST_TRUE(runner, BB_Equals_Bytes(bb, "foo", 3), "To_ByteBuf");
-    DECREF(bb);
-    DECREF(string);
-}
-
-static void
-test_Length(TestBatchRunner *runner) {
-    String *string = Str_newf("a%s%sb%sc", smiley, smiley, smiley);
-    TEST_UINT_EQ(runner, Str_Length(string), 6, "Length");
-    DECREF(string);
-}
-
-static void
-test_Compare_To(TestBatchRunner *runner) {
-    String *abc = Str_newf("a%s%sb%sc", smiley, smiley, smiley);
-    String *ab  = Str_newf("a%s%sb", smiley, smiley);
-    String *ac  = Str_newf("a%s%sc", smiley, smiley);
-
-    TEST_TRUE(runner, Str_Compare_To(abc, (Obj*)abc) == 0,
-              "Compare_To abc abc");
-    TEST_TRUE(runner, Str_Compare_To(ab, (Obj*)abc) < 0,
-              "Compare_To ab abc");
-    TEST_TRUE(runner, Str_Compare_To(abc, (Obj*)ab) > 0,
-              "Compare_To abc ab");
-    TEST_TRUE(runner, Str_Compare_To(ab, (Obj*)ac) < 0,
-              "Compare_To ab ac");
-    TEST_TRUE(runner, Str_Compare_To(ac, (Obj*)ab) > 0,
-              "Compare_To ac ab");
-
-    DECREF(ac);
-    DECREF(ab);
-    DECREF(abc);
-}
-
-static void
-test_Starts_Ends_With(TestBatchRunner *runner) {
-    String *prefix = S_get_str("pre" SMILEY "fix_");
-    String *suffix = S_get_str("_post" SMILEY "fix");
-    String *empty  = S_get_str("");
-
-    TEST_TRUE(runner, Str_Starts_With(suffix, suffix),
-              "Starts_With self returns true");
-    TEST_TRUE(runner, Str_Ends_With(prefix, prefix),
-              "Ends_With self returns true");
-
-    TEST_TRUE(runner, Str_Starts_With(suffix, empty),
-              "Starts_With empty string returns true");
-    TEST_TRUE(runner, Str_Ends_With(prefix, empty),
-              "Ends_With empty string returns true");
-    TEST_FALSE(runner, Str_Starts_With(empty, suffix),
-              "Empty string Starts_With returns false");
-    TEST_FALSE(runner, Str_Ends_With(empty, prefix),
-              "Empty string Ends_With returns false");
-
-    {
-        String *string
-            = S_get_str("pre" SMILEY "fix_string_post" SMILEY "fix");
-        TEST_TRUE(runner, Str_Starts_With(string, prefix),
-                  "Starts_With returns true");
-        TEST_TRUE(runner, Str_Ends_With(string, suffix),
-                  "Ends_With returns true");
-        DECREF(string);
-    }
-
-    {
-        String *string
-            = S_get_str("pre" SMILEY "fix:string:post" SMILEY "fix");
-        TEST_FALSE(runner, Str_Starts_With(string, prefix),
-                   "Starts_With returns false");
-        TEST_FALSE(runner, Str_Ends_With(string, suffix),
-                   "Ends_With returns false");
-        DECREF(string);
-    }
-
-    DECREF(prefix);
-    DECREF(suffix);
-    DECREF(empty);
-}
-
-static void
-test_Starts_Ends_With_Utf8(TestBatchRunner *runner) {
-    String *str = S_get_str("pre" SMILEY "post");
-
-    static const char prefix[] = "pre" SMILEY;
-    static const char postfix[] = SMILEY "post";
-    static const size_t prefix_size = sizeof(prefix) - 1;
-    static const size_t postfix_size = sizeof(postfix) - 1;
-    TEST_TRUE(runner, Str_Starts_With_Utf8(str, prefix, prefix_size),
-              "Starts_With_Utf8 returns true");
-    TEST_TRUE(runner, Str_Ends_With_Utf8(str, postfix, postfix_size),
-              "Ends_With_Utf8 returns true");
-    TEST_FALSE(runner, Str_Starts_With_Utf8(str, postfix, postfix_size),
-              "Starts_With_Utf8 returns false");
-    TEST_FALSE(runner, Str_Ends_With_Utf8(str, prefix, prefix_size),
-              "Ends_With_Utf8 returns false");
-
-    static const char longer[] = "12345678901234567890";
-    static const size_t longer_size = sizeof(longer) - 1;
-    TEST_FALSE(runner, Str_Starts_With_Utf8(str, longer, longer_size),
-              "Starts_With_Utf8 longer str returns false");
-    TEST_FALSE(runner, Str_Ends_With_Utf8(str, longer, longer_size),
-               "Ends_With_Utf8 longer str returns false");
-
-    DECREF(str);
-}
-
-static void
-test_Get_Ptr8(TestBatchRunner *runner) {
-    String *string = S_get_str("Banana");
-
-    const char *ptr8 = Str_Get_Ptr8(string);
-    TEST_TRUE(runner, strcmp(ptr8, "Banana") == 0, "Get_Ptr8");
-
-    size_t size = Str_Get_Size(string);
-    TEST_UINT_EQ(runner, size, 6, "Get_Size");
-
-    DECREF(string);
-}
-
-static void
-test_iterator(TestBatchRunner *runner) {
-    static const int32_t code_points[] = {
-        0x41,
-        0x7F,
-        0x80,
-        0x7FF,
-        0x800,
-        0xFFFF,
-        0x10000,
-        0x10FFFF
-    };
-    static size_t num_code_points
-        = sizeof(code_points) / sizeof(code_points[0]);
-
-    CharBuf *buf = CB_new(0);
-    for (size_t i = 0; i < num_code_points; ++i) {
-        CB_Cat_Char(buf, code_points[i]);
-    }
-    String *string = CB_To_String(buf);
-
-    {
-        StringIterator *iter = Str_Top(string);
-
-        TEST_TRUE(runner, StrIter_Equals(iter, (Obj*)iter),
-                  "StringIterator equal to self");
-        TEST_FALSE(runner, StrIter_Equals(iter, (Obj*)CFISH_TRUE),
-                   "StringIterator not equal non-iterators");
-
-        DECREF(iter);
-    }
-
-    {
-        StringIterator *top  = Str_Top(string);
-        StringIterator *tail = Str_Tail(string);
-
-        TEST_FALSE(runner, StrIter_Equals(top, (Obj*)tail),
-                   "StrIter_Equals returns false");
-
-        TEST_INT_EQ(runner, StrIter_Compare_To(top, (Obj*)tail), -1,
-                    "Compare_To top < tail");
-        TEST_INT_EQ(runner, StrIter_Compare_To(tail, (Obj*)top), 1,
-                    "Compare_To tail > top");
-        TEST_INT_EQ(runner, StrIter_Compare_To(top, (Obj*)top), 0,
-                    "Compare_To top == top");
-
-        StringIterator *clone = StrIter_Clone(top);
-        TEST_TRUE(runner, StrIter_Equals(clone, (Obj*)top), "Clone");
-
-        StrIter_Assign(clone, tail);
-        TEST_TRUE(runner, StrIter_Equals(clone, (Obj*)tail), "Assign");
-
-        String *other = Str_newf("Other string");
-        StringIterator *other_iter = Str_Top(other);
-        TEST_FALSE(runner, StrIter_Equals(other_iter, (Obj*)tail),
-                   "Equals returns false for different strings");
-        StrIter_Assign(clone, other_iter);
-        TEST_TRUE(runner, StrIter_Equals(clone, (Obj*)other_iter),
-                  "Assign iterator with different string");
-
-        DECREF(other);
-        DECREF(other_iter);
-        DECREF(clone);
-        DECREF(top);
-        DECREF(tail);
-    }
-
-    {
-        StringIterator *iter = Str_Top(string);
-
-        for (size_t i = 0; i < num_code_points; ++i) {
-            TEST_TRUE(runner, StrIter_Has_Next(iter), "Has_Next %d", i);
-            int32_t code_point = StrIter_Next(iter);
-            TEST_INT_EQ(runner, code_point, code_points[i], "Next %d", i);
-        }
-
-        TEST_TRUE(runner, !StrIter_Has_Next(iter),
-                  "Has_Next at end of string");
-        TEST_INT_EQ(runner, StrIter_Next(iter), STR_OOB,
-                    "Next at end of string");
-
-        StringIterator *tail = Str_Tail(string);
-        TEST_TRUE(runner, StrIter_Equals(iter, (Obj*)tail), "Equals tail");
-
-        DECREF(tail);
-        DECREF(iter);
-    }
-
-    {
-        StringIterator *iter = Str_Tail(string);
-
-        for (size_t i = num_code_points; i--;) {
-            TEST_TRUE(runner, StrIter_Has_Prev(iter), "Has_Prev %d", i);
-            int32_t code_point = StrIter_Prev(iter);
-            TEST_INT_EQ(runner, code_point, code_points[i], "Prev %d", i);
-        }
-
-        TEST_TRUE(runner, !StrIter_Has_Prev(iter),
-                  "Has_Prev at end of string");
-        TEST_INT_EQ(runner, StrIter_Prev(iter), STR_OOB,
-                    "Prev at start of string");
-
-        StringIterator *top = Str_Top(string);
-        TEST_TRUE(runner, StrIter_Equals(iter, (Obj*)top), "Equals top");
-
-        DECREF(top);
-        DECREF(iter);
-    }
-
-    {
-        StringIterator *iter = Str_Top(string);
-
-        StrIter_Next(iter);
-        TEST_UINT_EQ(runner, StrIter_Advance(iter, 2), 2,
-                     "Advance returns number of code points");
-        TEST_INT_EQ(runner, StrIter_Next(iter), code_points[3],
-                    "Advance works");
-        TEST_UINT_EQ(runner,
-                     StrIter_Advance(iter, 1000000), num_code_points - 4,
-                     "Advance past end of string");
-
-        StrIter_Prev(iter);
-        TEST_UINT_EQ(runner, StrIter_Recede(iter, 2), 2,
-                     "Recede returns number of code points");
-        TEST_INT_EQ(runner, StrIter_Prev(iter), code_points[num_code_points-4],
-                    "Recede works");
-        TEST_UINT_EQ(runner, StrIter_Recede(iter, 1000000), num_code_points - 4,
-                     "Recede past start of string");
-
-        DECREF(iter);
-    }
-
-    DECREF(string);
-    DECREF(buf);
-}
-
-static void
-test_iterator_whitespace(TestBatchRunner *runner) {
-    size_t num_spaces;
-    String *ws_smiley = S_smiley_with_whitespace(&num_spaces);
-
-    {
-        StringIterator *iter = Str_Top(ws_smiley);
-        TEST_UINT_EQ(runner, StrIter_Skip_Whitespace(iter), num_spaces,
-                     "Skip_Whitespace");
-        TEST_UINT_EQ(runner, StrIter_Skip_Whitespace(iter), 0,
-                     "Skip_Whitespace without whitespace");
-        DECREF(iter);
-    }
-
-    {
-        StringIterator *iter = Str_Tail(ws_smiley);
-        TEST_UINT_EQ(runner, StrIter_Skip_Whitespace_Back(iter), num_spaces,
-                     "Skip_Whitespace_Back");
-        TEST_UINT_EQ(runner, StrIter_Skip_Whitespace_Back(iter), 0,
-                     "Skip_Whitespace_Back without whitespace");
-        DECREF(iter);
-    }
-
-    DECREF(ws_smiley);
-}
-
-typedef struct {
-    StringIterator *top;
-    StringIterator *tail;
-} StrIterCropContext;
-
-static void
-S_striter_crop(void *vcontext) {
-    StrIterCropContext *context = (StrIterCropContext*)vcontext;
-    StrIter_crop(context->top, context->tail);
-}
-
-static void
-test_iterator_substring(TestBatchRunner *runner) {
-    String *string = Str_newf("a%sb%sc%sd", smiley, smiley, smiley);
-
-    StringIterator *start = Str_Top(string);
-    StringIterator *end = Str_Tail(string);
-
-    {
-        String *substring = StrIter_crop(start, end);
-        TEST_TRUE(runner, Str_Equals(substring, (Obj*)string),
-                  "StrIter_crop whole string");
-        DECREF(substring);
-    }
-
-    StrIter_Advance(start, 2);
-    StrIter_Recede(end, 2);
-
-    {
-        String *substring = StrIter_crop(start, end);
-        static const char wanted_buf[] = "b" SMILEY "c";
-        static const size_t wanted_size = sizeof(wanted_buf) - 1;
-        String *wanted = Str_new_from_utf8(wanted_buf, wanted_size);
-        TEST_TRUE(runner, Str_Equals(substring, (Obj*)wanted),
-                  "StrIter_crop");
-
-        TEST_TRUE(runner, StrIter_Starts_With(start, wanted),
-                  "Starts_With returns true");
-        TEST_TRUE(runner, StrIter_Ends_With(end, wanted),
-                  "Ends_With returns true");
-        TEST_TRUE(runner,
-                  StrIter_Starts_With_Utf8(start, wanted_buf, wanted_size),
-                  "Starts_With_Utf8 returns true");
-        TEST_TRUE(runner,
-                  StrIter_Ends_With_Utf8(end, wanted_buf, wanted_size),
-                  "Ends_With_Utf8 returns true");
-
-        DECREF(wanted);
-        DECREF(substring);
-    }
-
-    {
-        static const char short_buf[] = "b" SMILEY "x";
-        static const size_t short_size = sizeof(short_buf) - 1;
-        String *short_str = Str_new_from_utf8(short_buf, short_size);
-        TEST_FALSE(runner, StrIter_Starts_With(start, short_str),
-                   "Starts_With returns false");
-        TEST_FALSE(runner, StrIter_Ends_With(start, short_str),
-                   "Ends_With returns false");
-        TEST_FALSE(runner,
-                   StrIter_Starts_With_Utf8(start, short_buf, short_size),
-                   "Starts_With_Utf8 returns false");
-        TEST_FALSE(runner,
-                   StrIter_Ends_With_Utf8(start, short_buf, short_size),
-                   "Ends_With_Utf8 returns false");
-
-        static const char long_buf[] = "b" SMILEY "xxxxxxxxxxxx" SMILEY "c";
-        static const size_t long_size = sizeof(long_buf) - 1;
-        String *long_str = Str_new_from_utf8(long_buf, long_size);
-        TEST_FALSE(runner, StrIter_Starts_With(start, long_str),
-                   "Starts_With long string returns false");
-        TEST_FALSE(runner, StrIter_Ends_With(end, long_str),
-                   "Ends_With long string returns false");
-        TEST_FALSE(runner,
-                   StrIter_Starts_With_Utf8(start, long_buf, long_size),
-                   "Starts_With_Utf8 long string returns false");
-        TEST_FALSE(runner,
-                   StrIter_Ends_With_Utf8(end, long_buf, long_size),
-                   "Ends_With_Utf8 long string returns false");
-
-        DECREF(short_str);
-        DECREF(long_str);
-    }
-
-    {
-        String *substring = StrIter_crop(end, NULL);
-        String *wanted = Str_newf("%sd", smiley);
-        TEST_TRUE(runner, Str_Equals(substring, (Obj*)wanted),
-                  "StrIter_crop with NULL tail");
-        DECREF(wanted);
-        DECREF(substring);
-    }
-
-    {
-        String *substring = StrIter_crop(NULL, start);
-        String *wanted = Str_newf("a%s", smiley);
-        TEST_TRUE(runner, Str_Equals(substring, (Obj*)wanted),
-                  "StrIter_crop with NULL top");
-        DECREF(wanted);
-        DECREF(substring);
-    }
-
-    {
-        StrIterCropContext context;
-        context.top  = NULL;
-        context.tail = NULL;
-        Err *error = Err_trap(S_striter_crop, &context);
-        TEST_TRUE(runner, error != NULL,
-                  "StrIter_crop throws if top and tail are NULL");
-        DECREF(error);
-    }
-
-    {
-        String *other = SSTR_WRAP_C("other");
-        StrIterCropContext context;
-        context.top  = start;
-        context.tail = Str_Tail(other);
-        Err *error = Err_trap(S_striter_crop, &context);
-        TEST_TRUE(runner, error != NULL,
-                  "StrIter_crop throws if string don't match");
-        DECREF(error);
-        DECREF(context.tail);
-    }
-
-    {
-        StrIterCropContext context;
-        context.top  = end;
-        context.tail = start;
-        Err *error = Err_trap(S_striter_crop, &context);
-        TEST_TRUE(runner, error != NULL,
-                  "StrIter_crop throws if top is behind tail");
-        DECREF(error);
-    }
-
-    DECREF(start);
-    DECREF(end);
-    DECREF(string);
-}
-
-void
-TestStr_Run_IMP(TestString *self, TestBatchRunner *runner) {
-    TestBatchRunner_Plan(runner, (TestBatch*)self, 158);
-    test_new(runner);
-    test_Cat(runner);
-    test_Clone(runner);
-    test_Code_Point_At_and_From(runner);
-    test_Contains_and_Find(runner);
-    test_SubString(runner);
-    test_Trim(runner);
-    test_To_F64(runner);
-    test_To_I64(runner);
-    test_BaseX_To_I64(runner);
-    test_To_String(runner);
-    test_To_Utf8(runner);
-    test_To_ByteBuf(runner);
-    test_Length(runner);
-    test_Compare_To(runner);
-    test_Starts_Ends_With(runner);
-    test_Starts_Ends_With_Utf8(runner);
-    test_Get_Ptr8(runner);
-    test_iterator(runner);
-    test_iterator_whitespace(runner);
-    test_iterator_substring(runner);
-}
-
-/*************************** StringCallbackTest ***************************/
-
-bool
-StrCbTest_Unchanged_By_Callback_IMP(StringCallbackTest *self, String *str) {
-    String *before = Str_Clone(str);
-    StrCbTest_Callback(self);
-    return Str_Equals(str, (Obj*)before);
-}
-

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/TestString.cfh
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestString.cfh b/runtime/core/Clownfish/Test/TestString.cfh
deleted file mode 100644
index 88e43c9..0000000
--- a/runtime/core/Clownfish/Test/TestString.cfh
+++ /dev/null
@@ -1,36 +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::TestString nickname TestStr
-    inherits Clownfish::TestHarness::TestBatch {
-
-    inert incremented TestString*
-    new();
-
-    void
-    Run(TestString *self, TestBatchRunner *runner);
-}
-
-abstract class Clownfish::Test::StringCallbackTest nickname StrCbTest {
-    bool
-    Unchanged_By_Callback(StringCallbackTest *self, String *str);
-
-    abstract void
-    Callback(StringCallbackTest *self);
-}
-


[10/14] lucy-clownfish git commit: Separate test binaries

Posted by nw...@apache.org.
Separate test binaries

The C bindings build a separate shared library libcfishtest.so.
Building a static library would simplify things a little but would
require another per-parcel macro on Windows (like
CFP_STATIC_TESTCFISH).

The Perl bindings build a separate XS module Clownfish::Test.

For the Go and Python bindings, a separate static library
libtestcfish.a is built.


Project: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/commit/ef738c21
Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/ef738c21
Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/ef738c21

Branch: refs/heads/master
Commit: ef738c212adbed879cc75ab49675deae4f6e8b04
Parents: a80e405
Author: Nick Wellnhofer <we...@aevum.de>
Authored: Fri Jul 8 13:57:30 2016 +0200
Committer: Nick Wellnhofer <we...@aevum.de>
Committed: Fri Jul 8 14:29:42 2016 +0200

----------------------------------------------------------------------
 runtime/c/.gitignore                            |  21 +--
 runtime/common/charmonizer.c                    | 157 ++++++++++++------
 runtime/common/charmonizer.main                 | 159 +++++++++++++------
 runtime/go/build.go                             |   3 +-
 runtime/perl/.gitignore                         |   2 +
 runtime/perl/buildlib/Clownfish/Build.pm        |   8 +-
 .../perl/buildlib/Clownfish/Build/Binding.pm    |   2 +-
 runtime/perl/lib/Clownfish/Test.pm              |   8 +
 runtime/perl/t/binding/019-obj.t                |   1 +
 runtime/perl/t/binding/023-string.t             |   1 +
 runtime/python/setup.py                         |  11 +-
 11 files changed, 253 insertions(+), 120 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/ef738c21/runtime/c/.gitignore
----------------------------------------------------------------------
diff --git a/runtime/c/.gitignore b/runtime/c/.gitignore
index 641de1e..724f367 100644
--- a/runtime/c/.gitignore
+++ b/runtime/c/.gitignore
@@ -1,18 +1,13 @@
+*.dll
+*.dll.a
+*.dylib
+*.exe
+*.exp
+*.lib
+*.so
+*.so.*
 /Makefile
 /autogen/
-/cfish-*.dll
-/cfish-*.exp
-/cfish-*.lib
 /charmonizer
-/charmonizer.exe
 /charmony.h
-/cygcfish-*.dll
-/libcfish-*.dll
-/libcfish.*.dylib
-/libcfish.a
-/libcfish.a.*
-/libcfish.dylib
-/libcfish.so
-/libcfish.so.*
 /t/test_cfish
-/t/test_cfish.exe

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/ef738c21/runtime/common/charmonizer.c
----------------------------------------------------------------------
diff --git a/runtime/common/charmonizer.c b/runtime/common/charmonizer.c
index af37cfd..37cfaf4 100644
--- a/runtime/common/charmonizer.c
+++ b/runtime/common/charmonizer.c
@@ -8589,7 +8589,8 @@ chaz_VariadicMacros_run(void) {
 
 typedef struct cfish_MakeFile {
     chaz_MakeFile   *makefile;
-    chaz_MakeBinary *binary;
+    chaz_MakeBinary *lib;
+    chaz_MakeBinary *test_lib;
     chaz_MakeVar    *cfh_var;
     chaz_CLI        *cli;
 
@@ -8804,7 +8805,8 @@ cfish_MakeFile_new(chaz_CLI *cli) {
     cfish_MakeFile *self = malloc(sizeof(cfish_MakeFile));
 
     self->makefile = chaz_MakeFile_new();
-    self->binary   = NULL;
+    self->lib      = NULL;
+    self->test_lib = NULL;
     self->cfh_var  = NULL;
     self->cli      = cli;
 
@@ -8851,15 +8853,12 @@ cfish_MakeFile_destroy(cfish_MakeFile *self) {
 
 static void
 cfish_MakeFile_write(cfish_MakeFile *self, chaz_CFlags *extra_link_flags) {
-    static const char *const autogen_src_files[] = {
-        "cfish_parcel.c",
-        "testcfish_parcel.c",
-        NULL
-    };
-
     const char *dir_sep = chaz_OS_dir_sep();
     const char *host    = chaz_CLI_strval(self->cli, "host");
 
+    const char *lib_objs      = NULL;
+    const char *test_lib_objs = NULL;
+
     chaz_MakeVar  *var;
     chaz_MakeRule *rule;
 
@@ -8868,8 +8867,6 @@ cfish_MakeFile_write(cfish_MakeFile *self, chaz_CFlags *extra_link_flags) {
     chaz_CFlags *compile_flags;
     chaz_CFlags *link_flags;
 
-    int i;
-
     printf("Creating Makefile...\n");
 
     /* Directories */
@@ -8903,77 +8900,119 @@ cfish_MakeFile_write(cfish_MakeFile *self, chaz_CFlags *extra_link_flags) {
 
     chaz_CFlags_destroy(makefile_cflags);
 
-    /* Binary. */
+    /* Core library. */
 
     if (strcmp(host, "c") == 0 || strcmp(host, "perl") == 0) {
+        /* Shared library for C and Perl. */
+
         chaz_MakeFile_add_rule(self->makefile, "all", "$(CFISH_SHARED_LIB)");
 
-        self->binary
+        self->lib
             = chaz_MakeFile_add_shared_lib(self->makefile, NULL, "cfish",
                                            cfish_version, cfish_major_version);
-        chaz_MakeFile_add_rule(self->makefile, "$(CFISH_SHARED_LIB_OBJS)",
-                               self->autogen_target);
+        lib_objs = "$(CFISH_SHARED_LIB_OBJS)";
+
+        compile_flags = chaz_MakeBinary_get_compile_flags(self->lib);
+        chaz_CFlags_add_define(compile_flags, "CFP_CFISH", NULL);
+
+        link_flags = chaz_MakeBinary_get_link_flags(self->lib);
+        chaz_CFlags_enable_debugging(link_flags);
+        chaz_CFlags_append(link_flags,
+                           chaz_CFlags_get_string(extra_link_flags));
     }
     else {
+        /* Static library for Go and Python. */
+
         chaz_MakeFile_add_rule(self->makefile, "static",
-                               "$(CLOWNFISH_STATIC_LIB)");
+                               "$(CLOWNFISH_STATIC_LIB)"
+                               " $(TESTCFISH_STATIC_LIB)");
 
-        self->binary
+        self->lib
             = chaz_MakeFile_add_static_lib(self->makefile, NULL, "clownfish");
-        chaz_MakeFile_add_rule(self->makefile, "$(CLOWNFISH_STATIC_LIB_OBJS)",
-                               self->autogen_target);
+        lib_objs = "$(CLOWNFISH_STATIC_LIB_OBJS)";
     }
 
     if (self->host_src_dir) {
-        chaz_MakeBinary_add_src_dir(self->binary, self->host_src_dir);
+        chaz_MakeBinary_add_src_dir(self->lib, self->host_src_dir);
     }
-    chaz_MakeBinary_add_src_dir(self->binary, self->core_dir);
-    chaz_MakeBinary_add_src_dir(self->binary, self->test_dir);
+    chaz_MakeBinary_add_src_dir(self->lib, self->core_dir);
+    chaz_MakeBinary_add_src_file(self->lib, self->autogen_src_dir,
+                                 "cfish_parcel.c");
+
+    /* Test library. */
 
-    compile_flags = chaz_MakeBinary_get_compile_flags(self->binary);
-    chaz_CFlags_add_define(compile_flags, "CFP_CFISH", NULL);
-    chaz_CFlags_add_define(compile_flags, "CFP_TESTCFISH", NULL);
+    if (strcmp(host, "c") == 0 || strcmp(host, "perl") == 0) {
+        /* Shared library for C and Perl. */
+
+        self->test_lib
+            = chaz_MakeFile_add_shared_lib(self->makefile, NULL, "testcfish",
+                                           cfish_version, cfish_major_version);
+        test_lib_objs = "$(TESTCFISH_SHARED_LIB_OBJS)";
 
-    link_flags = chaz_MakeBinary_get_link_flags(self->binary);
-    chaz_CFlags_enable_debugging(link_flags);
-    chaz_CFlags_append(link_flags, chaz_CFlags_get_string(extra_link_flags));
+        compile_flags = chaz_MakeBinary_get_compile_flags(self->test_lib);
+        chaz_CFlags_add_define(compile_flags, "CFP_TESTCFISH", NULL);
 
-    for (i = 0; autogen_src_files[i] != NULL; ++i) {
-        chaz_MakeBinary_add_src_file(self->binary, self->autogen_src_dir,
-                                     autogen_src_files[i]);
+        link_flags = chaz_MakeBinary_get_link_flags(self->test_lib);
+        chaz_CFlags_enable_debugging(link_flags);
+        chaz_CFlags_append(link_flags,
+                           chaz_CFlags_get_string(extra_link_flags));
+        chaz_CFlags_add_shared_lib(link_flags, NULL, "cfish",
+                                   cfish_major_version);
+
+        chaz_MakeBinary_add_prereq(self->test_lib, "$(CFISH_SHARED_LIB)");
+    }
+    else {
+        /* Static library for Go and Python. */
+
+        self->test_lib
+            = chaz_MakeFile_add_static_lib(self->makefile, NULL, "testcfish");
+        test_lib_objs = "$(TESTCFISH_STATIC_LIB_OBJS)";
     }
 
+    chaz_MakeBinary_add_src_dir(self->test_lib, self->test_dir);
+    chaz_MakeBinary_add_src_file(self->test_lib, self->autogen_src_dir,
+                                 "testcfish_parcel.c");
+
     /* Additional rules. */
 
+    /* Object files depend on autogenerated headers. */
+    chaz_MakeFile_add_rule(self->makefile, lib_objs, self->autogen_target);
+    chaz_MakeFile_add_rule(self->makefile, test_lib_objs,
+                           self->autogen_target);
+
     if (strcmp(host, "c") == 0) {
         cfish_MakeFile_write_c_cfc_rules(self);
         cfish_MakeFile_write_c_test_rules(self);
     }
 
+    /* Targets to compile object files for Perl. */
     if (strcmp(host, "perl") == 0) {
-        char *core_objects = chaz_MakeBinary_obj_string(self->binary);
+        char *objects;
+
         chaz_MakeFile_add_rule(self->makefile, "core_objects",
                                "$(CFISH_SHARED_LIB_OBJS)");
-        chaz_ConfWriter_add_def("CORE_OBJECTS", core_objects);
-        free(core_objects);
-    }
+        objects = chaz_MakeBinary_obj_string(self->lib);
+        chaz_ConfWriter_add_def("CORE_OBJECTS", objects);
+        free(objects);
 
-    /* Needed for parallel builds. */
-    for (i = 0; autogen_src_files[i] != NULL; ++i) {
-        char *path = chaz_Util_join(dir_sep, self->autogen_src_dir,
-                                    autogen_src_files[i], NULL);
-        chaz_MakeFile_add_rule(self->makefile, path, self->autogen_target);
-        free(path);
+        chaz_MakeFile_add_rule(self->makefile, "test_objects",
+                               "$(TESTCFISH_SHARED_LIB_OBJS)");
+        objects = chaz_MakeBinary_obj_string(self->test_lib);
+        chaz_ConfWriter_add_def("TEST_OBJECTS", objects);
+        free(objects);
     }
 
-    rule = chaz_MakeFile_clean_rule(self->makefile);
-    chaz_MakeRule_add_recursive_rm_command(rule, "autogen");
-
     chaz_MakeFile_write(self->makefile);
 }
 
 static void
 cfish_MakeFile_write_c_cfc_rules(cfish_MakeFile *self) {
+    static const char *const autogen_src_files[] = {
+        "cfish_parcel.c",
+        "testcfish_parcel.c",
+        NULL
+    };
+
     chaz_MakeRule *rule;
 
     const char *dir_sep  = chaz_OS_dir_sep();
@@ -8983,6 +9022,8 @@ cfish_MakeFile_write_c_cfc_rules(cfish_MakeFile *self) {
     char *cfc_exe;
     char *cfc_command;
 
+    int i;
+
     cfc_dir = chaz_Util_join(dir_sep, self->base_dir, "..", "compiler", "c",
                              NULL);
     cfc_exe = chaz_Util_join("", cfc_dir, dir_sep, "cfc", exe_ext, NULL);
@@ -8995,14 +9036,24 @@ cfish_MakeFile_write_c_cfc_rules(cfish_MakeFile *self) {
     chaz_Make_list_files(self->core_dir, "cfh", S_cfh_file_callback, self);
     chaz_Make_list_files(self->test_dir, "cfh", S_cfh_file_callback, self);
 
-    rule = chaz_MakeFile_add_rule(self->makefile, self->autogen_target, cfc_exe);
+    rule = chaz_MakeFile_add_rule(self->makefile, self->autogen_target,
+                                  cfc_exe);
     chaz_MakeRule_add_prereq(rule, "$(CLOWNFISH_HEADERS)");
     cfc_command = chaz_Util_join("", cfc_exe, " --source=", self->core_dir,
                                  " --source=", self->test_dir,
                                  " --dest=autogen --header=cfc_header", NULL);
     chaz_MakeRule_add_command(rule, cfc_command);
 
+    /* Tell make how autogenerated source files are built. */
+    for (i = 0; autogen_src_files[i] != NULL; ++i) {
+        char *path = chaz_Util_join(dir_sep, self->autogen_src_dir,
+                                    autogen_src_files[i], NULL);
+        chaz_MakeFile_add_rule(self->makefile, path, self->autogen_target);
+        free(path);
+    }
+
     rule = chaz_MakeFile_clean_rule(self->makefile);
+    chaz_MakeRule_add_recursive_rm_command(rule, "autogen");
     chaz_MakeRule_add_make_command(rule, cfc_dir, "clean");
 
     rule = chaz_MakeFile_distclean_rule(self->makefile);
@@ -9016,19 +9067,25 @@ cfish_MakeFile_write_c_cfc_rules(cfish_MakeFile *self) {
 static void
 cfish_MakeFile_write_c_test_rules(cfish_MakeFile *self) {
     chaz_MakeBinary *exe;
-    chaz_CFlags     *link_cflags;
+    chaz_CFlags     *link_flags;
     chaz_MakeRule   *rule;
 
     exe = chaz_MakeFile_add_exe(self->makefile, "t", "test_cfish");
     chaz_MakeBinary_add_src_file(exe, "t", "test_cfish.c");
-    chaz_MakeFile_add_rule(self->makefile, "$(TEST_CFISH_EXE_OBJS)",
-                           self->autogen_target);
-    link_cflags = chaz_MakeBinary_get_link_flags(exe);
-    chaz_CFlags_add_shared_lib(link_cflags, NULL, "cfish",
+
+    link_flags = chaz_MakeBinary_get_link_flags(exe);
+    chaz_CFlags_add_rpath(link_flags, "\"$$PWD\"");
+    chaz_CFlags_add_shared_lib(link_flags, NULL, "testcfish",
                                cfish_major_version);
-    chaz_CFlags_add_rpath(link_cflags, "\"$$PWD\"");
+    chaz_CFlags_add_shared_lib(link_flags, NULL, "cfish",
+                               cfish_major_version);
+
+    chaz_MakeBinary_add_prereq(exe, "$(TESTCFISH_SHARED_LIB)");
     chaz_MakeBinary_add_prereq(exe, "$(CFISH_SHARED_LIB)");
 
+    chaz_MakeFile_add_rule(self->makefile, "$(TEST_CFISH_EXE_OBJS)",
+                           self->autogen_target);
+
     rule = chaz_MakeFile_add_rule(self->makefile, "test", "$(TEST_CFISH_EXE)");
     chaz_MakeRule_add_command(rule, "$(TEST_CFISH_EXE)");
 

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/ef738c21/runtime/common/charmonizer.main
----------------------------------------------------------------------
diff --git a/runtime/common/charmonizer.main b/runtime/common/charmonizer.main
index 7ceb2dd..124541c 100644
--- a/runtime/common/charmonizer.main
+++ b/runtime/common/charmonizer.main
@@ -41,7 +41,8 @@
 
 typedef struct cfish_MakeFile {
     chaz_MakeFile   *makefile;
-    chaz_MakeBinary *binary;
+    chaz_MakeBinary *lib;
+    chaz_MakeBinary *test_lib;
     chaz_MakeVar    *cfh_var;
     chaz_CLI        *cli;
 
@@ -256,7 +257,8 @@ cfish_MakeFile_new(chaz_CLI *cli) {
     cfish_MakeFile *self = malloc(sizeof(cfish_MakeFile));
 
     self->makefile = chaz_MakeFile_new();
-    self->binary   = NULL;
+    self->lib      = NULL;
+    self->test_lib = NULL;
     self->cfh_var  = NULL;
     self->cli      = cli;
 
@@ -303,15 +305,12 @@ cfish_MakeFile_destroy(cfish_MakeFile *self) {
 
 static void
 cfish_MakeFile_write(cfish_MakeFile *self, chaz_CFlags *extra_link_flags) {
-    static const char *const autogen_src_files[] = {
-        "cfish_parcel.c",
-        "testcfish_parcel.c",
-        NULL
-    };
-
     const char *dir_sep = chaz_OS_dir_sep();
     const char *host    = chaz_CLI_strval(self->cli, "host");
 
+    const char *lib_objs      = NULL;
+    const char *test_lib_objs = NULL;
+
     chaz_MakeVar  *var;
     chaz_MakeRule *rule;
 
@@ -320,8 +319,6 @@ cfish_MakeFile_write(cfish_MakeFile *self, chaz_CFlags *extra_link_flags) {
     chaz_CFlags *compile_flags;
     chaz_CFlags *link_flags;
 
-    int i;
-
     printf("Creating Makefile...\n");
 
     /* Directories */
@@ -355,77 +352,119 @@ cfish_MakeFile_write(cfish_MakeFile *self, chaz_CFlags *extra_link_flags) {
 
     chaz_CFlags_destroy(makefile_cflags);
 
-    /* Binary. */
+    /* Core library. */
 
     if (strcmp(host, "c") == 0 || strcmp(host, "perl") == 0) {
+        /* Shared library for C and Perl. */
+
         chaz_MakeFile_add_rule(self->makefile, "all", "$(CFISH_SHARED_LIB)");
 
-        self->binary
+        self->lib
             = chaz_MakeFile_add_shared_lib(self->makefile, NULL, "cfish",
                                            cfish_version, cfish_major_version);
-        chaz_MakeFile_add_rule(self->makefile, "$(CFISH_SHARED_LIB_OBJS)",
-                               self->autogen_target);
+        lib_objs = "$(CFISH_SHARED_LIB_OBJS)";
+
+        compile_flags = chaz_MakeBinary_get_compile_flags(self->lib);
+        chaz_CFlags_add_define(compile_flags, "CFP_CFISH", NULL);
+
+        link_flags = chaz_MakeBinary_get_link_flags(self->lib);
+        chaz_CFlags_enable_debugging(link_flags);
+        chaz_CFlags_append(link_flags,
+                           chaz_CFlags_get_string(extra_link_flags));
     }
     else {
+        /* Static library for Go and Python. */
+
         chaz_MakeFile_add_rule(self->makefile, "static",
-                               "$(CLOWNFISH_STATIC_LIB)");
+                               "$(CLOWNFISH_STATIC_LIB)"
+                               " $(TESTCFISH_STATIC_LIB)");
 
-        self->binary
+        self->lib
             = chaz_MakeFile_add_static_lib(self->makefile, NULL, "clownfish");
-        chaz_MakeFile_add_rule(self->makefile, "$(CLOWNFISH_STATIC_LIB_OBJS)",
-                               self->autogen_target);
+        lib_objs = "$(CLOWNFISH_STATIC_LIB_OBJS)";
     }
 
     if (self->host_src_dir) {
-        chaz_MakeBinary_add_src_dir(self->binary, self->host_src_dir);
+        chaz_MakeBinary_add_src_dir(self->lib, self->host_src_dir);
     }
-    chaz_MakeBinary_add_src_dir(self->binary, self->core_dir);
-    chaz_MakeBinary_add_src_dir(self->binary, self->test_dir);
+    chaz_MakeBinary_add_src_dir(self->lib, self->core_dir);
+    chaz_MakeBinary_add_src_file(self->lib, self->autogen_src_dir,
+                                 "cfish_parcel.c");
 
-    compile_flags = chaz_MakeBinary_get_compile_flags(self->binary);
-    chaz_CFlags_add_define(compile_flags, "CFP_CFISH", NULL);
-    chaz_CFlags_add_define(compile_flags, "CFP_TESTCFISH", NULL);
+    /* Test library. */
 
-    link_flags = chaz_MakeBinary_get_link_flags(self->binary);
-    chaz_CFlags_enable_debugging(link_flags);
-    chaz_CFlags_append(link_flags, chaz_CFlags_get_string(extra_link_flags));
+    if (strcmp(host, "c") == 0 || strcmp(host, "perl") == 0) {
+        /* Shared library for C and Perl. */
 
-    for (i = 0; autogen_src_files[i] != NULL; ++i) {
-        chaz_MakeBinary_add_src_file(self->binary, self->autogen_src_dir,
-                                     autogen_src_files[i]);
+        self->test_lib
+            = chaz_MakeFile_add_shared_lib(self->makefile, NULL, "testcfish",
+                                           cfish_version, cfish_major_version);
+        test_lib_objs = "$(TESTCFISH_SHARED_LIB_OBJS)";
+
+        compile_flags = chaz_MakeBinary_get_compile_flags(self->test_lib);
+        chaz_CFlags_add_define(compile_flags, "CFP_TESTCFISH", NULL);
+
+        link_flags = chaz_MakeBinary_get_link_flags(self->test_lib);
+        chaz_CFlags_enable_debugging(link_flags);
+        chaz_CFlags_append(link_flags,
+                           chaz_CFlags_get_string(extra_link_flags));
+        chaz_CFlags_add_shared_lib(link_flags, NULL, "cfish",
+                                   cfish_major_version);
+
+        chaz_MakeBinary_add_prereq(self->test_lib, "$(CFISH_SHARED_LIB)");
     }
+    else {
+        /* Static library for Go and Python. */
+
+        self->test_lib
+            = chaz_MakeFile_add_static_lib(self->makefile, NULL, "testcfish");
+        test_lib_objs = "$(TESTCFISH_STATIC_LIB_OBJS)";
+    }
+
+    chaz_MakeBinary_add_src_dir(self->test_lib, self->test_dir);
+    chaz_MakeBinary_add_src_file(self->test_lib, self->autogen_src_dir,
+                                 "testcfish_parcel.c");
 
     /* Additional rules. */
 
+    /* Object files depend on autogenerated headers. */
+    chaz_MakeFile_add_rule(self->makefile, lib_objs, self->autogen_target);
+    chaz_MakeFile_add_rule(self->makefile, test_lib_objs,
+                           self->autogen_target);
+
     if (strcmp(host, "c") == 0) {
         cfish_MakeFile_write_c_cfc_rules(self);
         cfish_MakeFile_write_c_test_rules(self);
     }
 
+    /* Targets to compile object files for Perl. */
     if (strcmp(host, "perl") == 0) {
-        char *core_objects = chaz_MakeBinary_obj_string(self->binary);
+        char *objects;
+
         chaz_MakeFile_add_rule(self->makefile, "core_objects",
                                "$(CFISH_SHARED_LIB_OBJS)");
-        chaz_ConfWriter_add_def("CORE_OBJECTS", core_objects);
-        free(core_objects);
-    }
-
-    /* Needed for parallel builds. */
-    for (i = 0; autogen_src_files[i] != NULL; ++i) {
-        char *path = chaz_Util_join(dir_sep, self->autogen_src_dir,
-                                    autogen_src_files[i], NULL);
-        chaz_MakeFile_add_rule(self->makefile, path, self->autogen_target);
-        free(path);
+        objects = chaz_MakeBinary_obj_string(self->lib);
+        chaz_ConfWriter_add_def("CORE_OBJECTS", objects);
+        free(objects);
+
+        chaz_MakeFile_add_rule(self->makefile, "test_objects",
+                               "$(TESTCFISH_SHARED_LIB_OBJS)");
+        objects = chaz_MakeBinary_obj_string(self->test_lib);
+        chaz_ConfWriter_add_def("TEST_OBJECTS", objects);
+        free(objects);
     }
 
-    rule = chaz_MakeFile_clean_rule(self->makefile);
-    chaz_MakeRule_add_recursive_rm_command(rule, "autogen");
-
     chaz_MakeFile_write(self->makefile);
 }
 
 static void
 cfish_MakeFile_write_c_cfc_rules(cfish_MakeFile *self) {
+    static const char *const autogen_src_files[] = {
+        "cfish_parcel.c",
+        "testcfish_parcel.c",
+        NULL
+    };
+
     chaz_MakeRule *rule;
 
     const char *dir_sep  = chaz_OS_dir_sep();
@@ -435,6 +474,8 @@ cfish_MakeFile_write_c_cfc_rules(cfish_MakeFile *self) {
     char *cfc_exe;
     char *cfc_command;
 
+    int i;
+
     cfc_dir = chaz_Util_join(dir_sep, self->base_dir, "..", "compiler", "c",
                              NULL);
     cfc_exe = chaz_Util_join("", cfc_dir, dir_sep, "cfc", exe_ext, NULL);
@@ -447,14 +488,24 @@ cfish_MakeFile_write_c_cfc_rules(cfish_MakeFile *self) {
     chaz_Make_list_files(self->core_dir, "cfh", S_cfh_file_callback, self);
     chaz_Make_list_files(self->test_dir, "cfh", S_cfh_file_callback, self);
 
-    rule = chaz_MakeFile_add_rule(self->makefile, self->autogen_target, cfc_exe);
+    rule = chaz_MakeFile_add_rule(self->makefile, self->autogen_target,
+                                  cfc_exe);
     chaz_MakeRule_add_prereq(rule, "$(CLOWNFISH_HEADERS)");
     cfc_command = chaz_Util_join("", cfc_exe, " --source=", self->core_dir,
                                  " --source=", self->test_dir,
                                  " --dest=autogen --header=cfc_header", NULL);
     chaz_MakeRule_add_command(rule, cfc_command);
 
+    /* Tell make how autogenerated source files are built. */
+    for (i = 0; autogen_src_files[i] != NULL; ++i) {
+        char *path = chaz_Util_join(dir_sep, self->autogen_src_dir,
+                                    autogen_src_files[i], NULL);
+        chaz_MakeFile_add_rule(self->makefile, path, self->autogen_target);
+        free(path);
+    }
+
     rule = chaz_MakeFile_clean_rule(self->makefile);
+    chaz_MakeRule_add_recursive_rm_command(rule, "autogen");
     chaz_MakeRule_add_make_command(rule, cfc_dir, "clean");
 
     rule = chaz_MakeFile_distclean_rule(self->makefile);
@@ -468,19 +519,25 @@ cfish_MakeFile_write_c_cfc_rules(cfish_MakeFile *self) {
 static void
 cfish_MakeFile_write_c_test_rules(cfish_MakeFile *self) {
     chaz_MakeBinary *exe;
-    chaz_CFlags     *link_cflags;
+    chaz_CFlags     *link_flags;
     chaz_MakeRule   *rule;
 
     exe = chaz_MakeFile_add_exe(self->makefile, "t", "test_cfish");
     chaz_MakeBinary_add_src_file(exe, "t", "test_cfish.c");
-    chaz_MakeFile_add_rule(self->makefile, "$(TEST_CFISH_EXE_OBJS)",
-                           self->autogen_target);
-    link_cflags = chaz_MakeBinary_get_link_flags(exe);
-    chaz_CFlags_add_shared_lib(link_cflags, NULL, "cfish",
+
+    link_flags = chaz_MakeBinary_get_link_flags(exe);
+    chaz_CFlags_add_rpath(link_flags, "\"$$PWD\"");
+    chaz_CFlags_add_shared_lib(link_flags, NULL, "testcfish",
                                cfish_major_version);
-    chaz_CFlags_add_rpath(link_cflags, "\"$$PWD\"");
+    chaz_CFlags_add_shared_lib(link_flags, NULL, "cfish",
+                               cfish_major_version);
+
+    chaz_MakeBinary_add_prereq(exe, "$(TESTCFISH_SHARED_LIB)");
     chaz_MakeBinary_add_prereq(exe, "$(CFISH_SHARED_LIB)");
 
+    chaz_MakeFile_add_rule(self->makefile, "$(TEST_CFISH_EXE_OBJS)",
+                           self->autogen_target);
+
     rule = chaz_MakeFile_add_rule(self->makefile, "test", "$(TEST_CFISH_EXE)");
     chaz_MakeRule_add_command(rule, "$(TEST_CFISH_EXE)");
 

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/ef738c21/runtime/go/build.go
----------------------------------------------------------------------
diff --git a/runtime/go/build.go b/runtime/go/build.go
index c6eb9e3..46d70cc 100644
--- a/runtime/go/build.go
+++ b/runtime/go/build.go
@@ -109,7 +109,7 @@ func configure() {
 	if !current(charmonizerC, charmonizerEXE) {
 		runCommand("cc", "-o", charmonizerEXE, charmonizerC)
 	}
-	if !current(charmonizerEXE, charmonyH) {
+	if !current(charmonizerEXE, charmonyH) || !current(charmonizerEXE, "Makefile") {
 		runCommand("./charmonizer", "--cc=cc", "--enable-c", "--host=go",
 			"--enable-makefile", "--disable-threads", "--", "-std=gnu99",
 			"-O2")
@@ -265,6 +265,7 @@ func writeConfigGO() {
 			"// #cgo CFLAGS: -I%s\n"+
 			"// #cgo CFLAGS: -I%s/autogen/include\n"+
 			"// #cgo LDFLAGS: -L%s\n"+
+			"// #cgo LDFLAGS: -ltestcfish\n"+
 			"// #cgo LDFLAGS: -lclownfish\n"+
 			"import \"C\"\n",
 		buildDir, buildDir, buildDir, buildDir)

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/ef738c21/runtime/perl/.gitignore
----------------------------------------------------------------------
diff --git a/runtime/perl/.gitignore b/runtime/perl/.gitignore
index 4e0ff0f..7c4733f 100644
--- a/runtime/perl/.gitignore
+++ b/runtime/perl/.gitignore
@@ -14,6 +14,8 @@
 /charmony.h
 /lib/Clownfish.c
 /lib/Clownfish.xs
+/lib/Clownfish/Test.c
+/lib/Clownfish/Test.xs
 /ppport.h
 /typemap
 

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/ef738c21/runtime/perl/buildlib/Clownfish/Build.pm
----------------------------------------------------------------------
diff --git a/runtime/perl/buildlib/Clownfish/Build.pm b/runtime/perl/buildlib/Clownfish/Build.pm
index 81b5fbf..0eef5c3 100644
--- a/runtime/perl/buildlib/Clownfish/Build.pm
+++ b/runtime/perl/buildlib/Clownfish/Build.pm
@@ -77,10 +77,16 @@ sub new {
         modules => [
             {
                 name          => 'Clownfish',
-                parcels       => [ 'Clownfish', 'TestClownfish' ],
+                parcels       => [ 'Clownfish' ],
                 make_target   => 'core_objects',
                 c_source_dirs => [ $XS_SOURCE_DIR ],
             },
+            {
+                name          => 'Clownfish::Test',
+                parcels       => [ 'TestClownfish' ],
+                make_target   => 'test_objects',
+                xs_prereqs    => [ 'Clownfish' ],
+            },
         ],
     };
     my $self = $class->SUPER::new( recursive_test_files => 1, %args );

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/ef738c21/runtime/perl/buildlib/Clownfish/Build/Binding.pm
----------------------------------------------------------------------
diff --git a/runtime/perl/buildlib/Clownfish/Build/Binding.pm b/runtime/perl/buildlib/Clownfish/Build/Binding.pm
index 8da4d83..71b0ff8 100644
--- a/runtime/perl/buildlib/Clownfish/Build/Binding.pm
+++ b/runtime/perl/buildlib/Clownfish/Build/Binding.pm
@@ -67,7 +67,7 @@ END_XS_CODE
 
 sub bind_test {
     my $xs_code = <<'END_XS_CODE';
-MODULE = Clownfish   PACKAGE = Clownfish::Test
+MODULE = Clownfish::Test   PACKAGE = Clownfish::Test
 
 SV*
 create_test_suite()

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/ef738c21/runtime/perl/lib/Clownfish/Test.pm
----------------------------------------------------------------------
diff --git a/runtime/perl/lib/Clownfish/Test.pm b/runtime/perl/lib/Clownfish/Test.pm
index e637cd3..55aeb94 100644
--- a/runtime/perl/lib/Clownfish/Test.pm
+++ b/runtime/perl/lib/Clownfish/Test.pm
@@ -18,6 +18,14 @@ use Clownfish;
 our $VERSION = '0.005000';
 $VERSION = eval $VERSION;
 
+sub dl_load_flags { 1 }
+
+BEGIN {
+    require DynaLoader;
+    our @ISA = qw( DynaLoader );
+    bootstrap Clownfish::Test '0.5.0';
+}
+
 sub run_tests {
     my $class_name = shift;
     my $formatter  = Clownfish::TestHarness::TestFormatterTAP->new();

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/ef738c21/runtime/perl/t/binding/019-obj.t
----------------------------------------------------------------------
diff --git a/runtime/perl/t/binding/019-obj.t b/runtime/perl/t/binding/019-obj.t
index 3d22c4e..c5eec3b 100644
--- a/runtime/perl/t/binding/019-obj.t
+++ b/runtime/perl/t/binding/019-obj.t
@@ -17,6 +17,7 @@ use strict;
 use warnings;
 
 use Test::More tests => 25;
+use Clownfish::Test;
 
 package TestObj;
 use base qw( Clownfish::Obj );

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/ef738c21/runtime/perl/t/binding/023-string.t
----------------------------------------------------------------------
diff --git a/runtime/perl/t/binding/023-string.t b/runtime/perl/t/binding/023-string.t
index 4f839c5..cae8476 100644
--- a/runtime/perl/t/binding/023-string.t
+++ b/runtime/perl/t/binding/023-string.t
@@ -53,6 +53,7 @@ is( $buf, $wanted, 'iter next' );
 
 {
     package MyStringCallbackTest;
+    use Clownfish::Test;
     use base qw(Clownfish::Test::StringCallbackTest);
 
     our $string_ref;

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/ef738c21/runtime/python/setup.py
----------------------------------------------------------------------
diff --git a/runtime/python/setup.py b/runtime/python/setup.py
index 717ce50..8860d3b 100644
--- a/runtime/python/setup.py
+++ b/runtime/python/setup.py
@@ -58,8 +58,10 @@ CHARMONIZER_C        = os.path.join(COMMON_SOURCE_DIR, 'charmonizer.c')
 CHARMONIZER_EXE_NAME = compiler.executable_filename('charmonizer')
 CHARMONIZER_EXE_PATH = os.path.join(os.curdir, CHARMONIZER_EXE_NAME)
 CHARMONY_H_PATH      = 'charmony.h'
-LIBCLOWNFISH_NAME    = 'libclownfish.a' # TODO portability
-LIBCLOWNFISH_PATH    = os.path.abspath(os.path.join(os.curdir, LIBCLOWNFISH_NAME))
+CORELIB_NAME         = 'libclownfish.a' # TODO portability
+CORELIB_PATH         = os.path.abspath(os.path.join(os.curdir, CORELIB_NAME))
+TESTLIB_NAME         = 'libtestcfish.a' # TODO portability
+TESTLIB_PATH         = os.path.abspath(os.path.join(os.curdir, TESTLIB_NAME))
 AUTOGEN_INCLUDE      = os.path.join('autogen', 'include')
 CFC_DIR              = os.path.join(BASE_DIR, 'compiler', 'python')
 CFC_BUILD_DIR        = ext_build_dir(os.path.join(CFC_DIR))
@@ -198,7 +200,10 @@ clownfish_extension = Extension('clownfish._clownfish',
                                     CFEXT_DIR,
                                     os.curdir,
                                  ],
-                                 extra_link_args = [LIBCLOWNFISH_PATH],
+                                 extra_link_args = [
+                                    CORELIB_PATH,
+                                    TESTLIB_PATH,
+                                 ],
                                  sources = c_filepaths)
 
 setup(name = 'clownfish',


[13/14] lucy-clownfish git commit: Fix Makefile paths for Perl CPAN build

Posted by nw...@apache.org.
Fix Makefile paths for Perl CPAN build


Project: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/commit/a45abf04
Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/a45abf04
Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/a45abf04

Branch: refs/heads/master
Commit: a45abf04a63b7a9d0a2fc40af79216d8778d4d84
Parents: 3764180
Author: Nick Wellnhofer <we...@aevum.de>
Authored: Fri Jul 8 13:57:47 2016 +0200
Committer: Nick Wellnhofer <we...@aevum.de>
Committed: Fri Jul 8 14:37:20 2016 +0200

----------------------------------------------------------------------
 runtime/common/charmonizer.c    | 19 ++++++++++++++-----
 runtime/common/charmonizer.main | 19 ++++++++++++++-----
 2 files changed, 28 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/a45abf04/runtime/common/charmonizer.c
----------------------------------------------------------------------
diff --git a/runtime/common/charmonizer.c b/runtime/common/charmonizer.c
index 102970e..27753cc 100644
--- a/runtime/common/charmonizer.c
+++ b/runtime/common/charmonizer.c
@@ -8801,7 +8801,8 @@ S_link_flags(chaz_CLI *cli) {
 static cfish_MakeFile*
 cfish_MakeFile_new(chaz_CLI *cli) {
     const char *dir_sep = chaz_OS_dir_sep();
-
+    char *cfcore_filename = chaz_Util_join(dir_sep, "cfcore", "Clownfish.cfp",
+                                           NULL);
     cfish_MakeFile *self = malloc(sizeof(cfish_MakeFile));
 
     self->makefile = chaz_MakeFile_new();
@@ -8810,10 +8811,17 @@ cfish_MakeFile_new(chaz_CLI *cli) {
     self->cfh_var  = NULL;
     self->cli      = cli;
 
-    /* TODO: Detect base directory. */
-    self->base_dir = "..";
-    self->core_dir = chaz_Util_join(dir_sep, self->base_dir, "core", NULL);
-    self->test_dir = chaz_Util_join(dir_sep, self->base_dir, "test", NULL);
+    if (chaz_Util_can_open_file(cfcore_filename)) {
+        self->base_dir = ".";
+        self->core_dir = chaz_Util_strdup("cfcore");
+        self->test_dir = chaz_Util_strdup("cftest");
+    }
+    else {
+        self->base_dir = "..";
+        self->core_dir = chaz_Util_join(dir_sep, "..", "core", NULL);
+        self->test_dir = chaz_Util_join(dir_sep, "..", "test", NULL);
+    }
+
     self->autogen_src_dir = chaz_Util_join(dir_sep, "autogen", "source", NULL);
     self->autogen_inc_dir
         = chaz_Util_join(dir_sep, "autogen", "include", NULL);
@@ -8835,6 +8843,7 @@ cfish_MakeFile_new(chaz_CLI *cli) {
         self->host_src_dir = NULL;
     }
 
+    free(cfcore_filename);
     return self;
 }
 

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/a45abf04/runtime/common/charmonizer.main
----------------------------------------------------------------------
diff --git a/runtime/common/charmonizer.main b/runtime/common/charmonizer.main
index fc68fd4..681d263 100644
--- a/runtime/common/charmonizer.main
+++ b/runtime/common/charmonizer.main
@@ -253,7 +253,8 @@ S_link_flags(chaz_CLI *cli) {
 static cfish_MakeFile*
 cfish_MakeFile_new(chaz_CLI *cli) {
     const char *dir_sep = chaz_OS_dir_sep();
-
+    char *cfcore_filename = chaz_Util_join(dir_sep, "cfcore", "Clownfish.cfp",
+                                           NULL);
     cfish_MakeFile *self = malloc(sizeof(cfish_MakeFile));
 
     self->makefile = chaz_MakeFile_new();
@@ -262,10 +263,17 @@ cfish_MakeFile_new(chaz_CLI *cli) {
     self->cfh_var  = NULL;
     self->cli      = cli;
 
-    /* TODO: Detect base directory. */
-    self->base_dir = "..";
-    self->core_dir = chaz_Util_join(dir_sep, self->base_dir, "core", NULL);
-    self->test_dir = chaz_Util_join(dir_sep, self->base_dir, "test", NULL);
+    if (chaz_Util_can_open_file(cfcore_filename)) {
+        self->base_dir = ".";
+        self->core_dir = chaz_Util_strdup("cfcore");
+        self->test_dir = chaz_Util_strdup("cftest");
+    }
+    else {
+        self->base_dir = "..";
+        self->core_dir = chaz_Util_join(dir_sep, "..", "core", NULL);
+        self->test_dir = chaz_Util_join(dir_sep, "..", "test", NULL);
+    }
+
     self->autogen_src_dir = chaz_Util_join(dir_sep, "autogen", "source", NULL);
     self->autogen_inc_dir
         = chaz_Util_join(dir_sep, "autogen", "include", NULL);
@@ -287,6 +295,7 @@ cfish_MakeFile_new(chaz_CLI *cli) {
         self->host_src_dir = NULL;
     }
 
+    free(cfcore_filename);
     return self;
 }
 


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

Posted by nw...@apache.org.
Move tests to separate directory


Project: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/commit/8ba4e619
Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/8ba4e619
Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/8ba4e619

Branch: refs/heads/master
Commit: 8ba4e61987f00db2624818c4fa1a6f9c1b4f79b8
Parents: 5bda4da
Author: Nick Wellnhofer <we...@aevum.de>
Authored: Fri Jul 8 13:57:13 2016 +0200
Committer: Nick Wellnhofer <we...@aevum.de>
Committed: Fri Jul 8 14:25:36 2016 +0200

----------------------------------------------------------------------
 runtime/common/charmonizer.c                    |   7 +
 runtime/common/charmonizer.main                 |   7 +
 runtime/core/Clownfish/Test.c                   |  70 --
 runtime/core/Clownfish/Test.cfh                 |  26 -
 runtime/core/Clownfish/Test/TestBlob.c          | 147 ----
 runtime/core/Clownfish/Test/TestBlob.cfh        |  28 -
 runtime/core/Clownfish/Test/TestBoolean.c       |  99 ---
 runtime/core/Clownfish/Test/TestBoolean.cfh     |  29 -
 runtime/core/Clownfish/Test/TestByteBuf.c       | 218 -----
 runtime/core/Clownfish/Test/TestByteBuf.cfh     |  29 -
 runtime/core/Clownfish/Test/TestCharBuf.c       | 394 ---------
 runtime/core/Clownfish/Test/TestCharBuf.cfh     |  29 -
 runtime/core/Clownfish/Test/TestClass.c         | 158 ----
 runtime/core/Clownfish/Test/TestClass.cfh       |  28 -
 runtime/core/Clownfish/Test/TestErr.c           | 193 -----
 runtime/core/Clownfish/Test/TestErr.cfh         |  28 -
 runtime/core/Clownfish/Test/TestHash.c          | 325 -------
 runtime/core/Clownfish/Test/TestHash.cfh        |  29 -
 runtime/core/Clownfish/Test/TestHashIterator.c  | 253 ------
 .../core/Clownfish/Test/TestHashIterator.cfh    |  29 -
 runtime/core/Clownfish/Test/TestHost.c          | 125 ---
 runtime/core/Clownfish/Test/TestHost.cfh        |  81 --
 .../core/Clownfish/Test/TestLockFreeRegistry.c  | 168 ----
 .../Clownfish/Test/TestLockFreeRegistry.cfh     |  29 -
 runtime/core/Clownfish/Test/TestMethod.c        |  91 --
 runtime/core/Clownfish/Test/TestMethod.cfh      |  28 -
 runtime/core/Clownfish/Test/TestNum.c           | 278 ------
 runtime/core/Clownfish/Test/TestNum.cfh         |  29 -
 runtime/core/Clownfish/Test/TestObj.c           | 152 ----
 runtime/core/Clownfish/Test/TestObj.cfh         |  28 -
 runtime/core/Clownfish/Test/TestPtrHash.c       | 108 ---
 runtime/core/Clownfish/Test/TestPtrHash.cfh     |  29 -
 runtime/core/Clownfish/Test/TestString.c        | 848 -------------------
 runtime/core/Clownfish/Test/TestString.cfh      |  36 -
 runtime/core/Clownfish/Test/TestVector.c        | 572 -------------
 runtime/core/Clownfish/Test/TestVector.cfh      |  29 -
 runtime/core/Clownfish/Test/Util/TestAtomic.c   |  65 --
 runtime/core/Clownfish/Test/Util/TestAtomic.cfh |  29 -
 runtime/core/Clownfish/Test/Util/TestMemory.c   | 119 ---
 runtime/core/Clownfish/Test/Util/TestMemory.cfh |  29 -
 .../core/Clownfish/Test/Util/TestStringHelper.c | 373 --------
 .../Clownfish/Test/Util/TestStringHelper.cfh    |  29 -
 runtime/core/TestClownfish.c                    |  22 -
 runtime/core/TestClownfish.cfp                  |   8 -
 runtime/go/build.go                             |   1 +
 runtime/perl/buildlib/Clownfish/Build.pm        |   6 +-
 runtime/python/setup.py                         |   2 +
 runtime/test/Clownfish/Test.c                   |  70 ++
 runtime/test/Clownfish/Test.cfh                 |  26 +
 runtime/test/Clownfish/Test/TestBlob.c          | 147 ++++
 runtime/test/Clownfish/Test/TestBlob.cfh        |  28 +
 runtime/test/Clownfish/Test/TestBoolean.c       |  99 +++
 runtime/test/Clownfish/Test/TestBoolean.cfh     |  29 +
 runtime/test/Clownfish/Test/TestByteBuf.c       | 218 +++++
 runtime/test/Clownfish/Test/TestByteBuf.cfh     |  29 +
 runtime/test/Clownfish/Test/TestCharBuf.c       | 394 +++++++++
 runtime/test/Clownfish/Test/TestCharBuf.cfh     |  29 +
 runtime/test/Clownfish/Test/TestClass.c         | 158 ++++
 runtime/test/Clownfish/Test/TestClass.cfh       |  28 +
 runtime/test/Clownfish/Test/TestErr.c           | 193 +++++
 runtime/test/Clownfish/Test/TestErr.cfh         |  28 +
 runtime/test/Clownfish/Test/TestHash.c          | 325 +++++++
 runtime/test/Clownfish/Test/TestHash.cfh        |  29 +
 runtime/test/Clownfish/Test/TestHashIterator.c  | 253 ++++++
 .../test/Clownfish/Test/TestHashIterator.cfh    |  29 +
 runtime/test/Clownfish/Test/TestHost.c          | 125 +++
 runtime/test/Clownfish/Test/TestHost.cfh        |  81 ++
 .../test/Clownfish/Test/TestLockFreeRegistry.c  | 168 ++++
 .../Clownfish/Test/TestLockFreeRegistry.cfh     |  29 +
 runtime/test/Clownfish/Test/TestMethod.c        |  91 ++
 runtime/test/Clownfish/Test/TestMethod.cfh      |  28 +
 runtime/test/Clownfish/Test/TestNum.c           | 278 ++++++
 runtime/test/Clownfish/Test/TestNum.cfh         |  29 +
 runtime/test/Clownfish/Test/TestObj.c           | 152 ++++
 runtime/test/Clownfish/Test/TestObj.cfh         |  28 +
 runtime/test/Clownfish/Test/TestPtrHash.c       | 108 +++
 runtime/test/Clownfish/Test/TestPtrHash.cfh     |  29 +
 runtime/test/Clownfish/Test/TestString.c        | 848 +++++++++++++++++++
 runtime/test/Clownfish/Test/TestString.cfh      |  36 +
 runtime/test/Clownfish/Test/TestVector.c        | 572 +++++++++++++
 runtime/test/Clownfish/Test/TestVector.cfh      |  29 +
 runtime/test/Clownfish/Test/Util/TestAtomic.c   |  65 ++
 runtime/test/Clownfish/Test/Util/TestAtomic.cfh |  29 +
 runtime/test/Clownfish/Test/Util/TestMemory.c   | 119 +++
 runtime/test/Clownfish/Test/Util/TestMemory.cfh |  29 +
 .../test/Clownfish/Test/Util/TestStringHelper.c | 373 ++++++++
 .../Clownfish/Test/Util/TestStringHelper.cfh    |  29 +
 runtime/test/TestClownfish.c                    |  22 +
 runtime/test/TestClownfish.cfp                  |   8 +
 89 files changed, 5439 insertions(+), 5418 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/common/charmonizer.c
----------------------------------------------------------------------
diff --git a/runtime/common/charmonizer.c b/runtime/common/charmonizer.c
index 7448e25..af37cfd 100644
--- a/runtime/common/charmonizer.c
+++ b/runtime/common/charmonizer.c
@@ -8597,6 +8597,7 @@ typedef struct cfish_MakeFile {
     const char  *base_dir;
     const char  *host_src_dir;
     char        *core_dir;
+    char        *test_dir;
     char        *autogen_src_dir;
     char        *autogen_inc_dir;
     char        *autogen_target;
@@ -8807,8 +8808,10 @@ cfish_MakeFile_new(chaz_CLI *cli) {
     self->cfh_var  = NULL;
     self->cli      = cli;
 
+    /* TODO: Detect base directory. */
     self->base_dir = "..";
     self->core_dir = chaz_Util_join(dir_sep, self->base_dir, "core", NULL);
+    self->test_dir = chaz_Util_join(dir_sep, self->base_dir, "test", NULL);
     self->autogen_src_dir = chaz_Util_join(dir_sep, "autogen", "source", NULL);
     self->autogen_inc_dir
         = chaz_Util_join(dir_sep, "autogen", "include", NULL);
@@ -8838,6 +8841,7 @@ cfish_MakeFile_destroy(cfish_MakeFile *self) {
     chaz_MakeFile_destroy(self->makefile);
 
     free(self->core_dir);
+    free(self->test_dir);
     free(self->autogen_inc_dir);
     free(self->autogen_src_dir);
     free(self->autogen_target);
@@ -8924,6 +8928,7 @@ cfish_MakeFile_write(cfish_MakeFile *self, chaz_CFlags *extra_link_flags) {
         chaz_MakeBinary_add_src_dir(self->binary, self->host_src_dir);
     }
     chaz_MakeBinary_add_src_dir(self->binary, self->core_dir);
+    chaz_MakeBinary_add_src_dir(self->binary, self->test_dir);
 
     compile_flags = chaz_MakeBinary_get_compile_flags(self->binary);
     chaz_CFlags_add_define(compile_flags, "CFP_CFISH", NULL);
@@ -8988,10 +8993,12 @@ cfish_MakeFile_write_c_cfc_rules(cfish_MakeFile *self) {
     self->cfh_var = chaz_MakeFile_add_var(self->makefile, "CLOWNFISH_HEADERS",
                                           NULL);
     chaz_Make_list_files(self->core_dir, "cfh", S_cfh_file_callback, self);
+    chaz_Make_list_files(self->test_dir, "cfh", S_cfh_file_callback, self);
 
     rule = chaz_MakeFile_add_rule(self->makefile, self->autogen_target, cfc_exe);
     chaz_MakeRule_add_prereq(rule, "$(CLOWNFISH_HEADERS)");
     cfc_command = chaz_Util_join("", cfc_exe, " --source=", self->core_dir,
+                                 " --source=", self->test_dir,
                                  " --dest=autogen --header=cfc_header", NULL);
     chaz_MakeRule_add_command(rule, cfc_command);
 

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/common/charmonizer.main
----------------------------------------------------------------------
diff --git a/runtime/common/charmonizer.main b/runtime/common/charmonizer.main
index e777c52..7ceb2dd 100644
--- a/runtime/common/charmonizer.main
+++ b/runtime/common/charmonizer.main
@@ -49,6 +49,7 @@ typedef struct cfish_MakeFile {
     const char  *base_dir;
     const char  *host_src_dir;
     char        *core_dir;
+    char        *test_dir;
     char        *autogen_src_dir;
     char        *autogen_inc_dir;
     char        *autogen_target;
@@ -259,8 +260,10 @@ cfish_MakeFile_new(chaz_CLI *cli) {
     self->cfh_var  = NULL;
     self->cli      = cli;
 
+    /* TODO: Detect base directory. */
     self->base_dir = "..";
     self->core_dir = chaz_Util_join(dir_sep, self->base_dir, "core", NULL);
+    self->test_dir = chaz_Util_join(dir_sep, self->base_dir, "test", NULL);
     self->autogen_src_dir = chaz_Util_join(dir_sep, "autogen", "source", NULL);
     self->autogen_inc_dir
         = chaz_Util_join(dir_sep, "autogen", "include", NULL);
@@ -290,6 +293,7 @@ cfish_MakeFile_destroy(cfish_MakeFile *self) {
     chaz_MakeFile_destroy(self->makefile);
 
     free(self->core_dir);
+    free(self->test_dir);
     free(self->autogen_inc_dir);
     free(self->autogen_src_dir);
     free(self->autogen_target);
@@ -376,6 +380,7 @@ cfish_MakeFile_write(cfish_MakeFile *self, chaz_CFlags *extra_link_flags) {
         chaz_MakeBinary_add_src_dir(self->binary, self->host_src_dir);
     }
     chaz_MakeBinary_add_src_dir(self->binary, self->core_dir);
+    chaz_MakeBinary_add_src_dir(self->binary, self->test_dir);
 
     compile_flags = chaz_MakeBinary_get_compile_flags(self->binary);
     chaz_CFlags_add_define(compile_flags, "CFP_CFISH", NULL);
@@ -440,10 +445,12 @@ cfish_MakeFile_write_c_cfc_rules(cfish_MakeFile *self) {
     self->cfh_var = chaz_MakeFile_add_var(self->makefile, "CLOWNFISH_HEADERS",
                                           NULL);
     chaz_Make_list_files(self->core_dir, "cfh", S_cfh_file_callback, self);
+    chaz_Make_list_files(self->test_dir, "cfh", S_cfh_file_callback, self);
 
     rule = chaz_MakeFile_add_rule(self->makefile, self->autogen_target, cfc_exe);
     chaz_MakeRule_add_prereq(rule, "$(CLOWNFISH_HEADERS)");
     cfc_command = chaz_Util_join("", cfc_exe, " --source=", self->core_dir,
+                                 " --source=", self->test_dir,
                                  " --dest=autogen --header=cfc_header", NULL);
     chaz_MakeRule_add_command(rule, cfc_command);
 

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test.c
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test.c b/runtime/core/Clownfish/Test.c
deleted file mode 100644
index 4ae6163..0000000
--- a/runtime/core/Clownfish/Test.c
+++ /dev/null
@@ -1,70 +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.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/core/Clownfish/Test.cfh
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test.cfh b/runtime/core/Clownfish/Test.cfh
deleted file mode 100644
index 68dc8bc..0000000
--- a/runtime/core/Clownfish/Test.cfh
+++ /dev/null
@@ -1,26 +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;
-
-/** 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/core/Clownfish/Test/TestBlob.c
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestBlob.c b/runtime/core/Clownfish/Test/TestBlob.c
deleted file mode 100644
index 5776f47..0000000
--- a/runtime/core/Clownfish/Test/TestBlob.c
+++ /dev/null
@@ -1,147 +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/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/core/Clownfish/Test/TestBlob.cfh
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestBlob.cfh b/runtime/core/Clownfish/Test/TestBlob.cfh
deleted file mode 100644
index 4d3d4b6..0000000
--- a/runtime/core/Clownfish/Test/TestBlob.cfh
+++ /dev/null
@@ -1,28 +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::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/core/Clownfish/Test/TestBoolean.c
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestBoolean.c b/runtime/core/Clownfish/Test/TestBoolean.c
deleted file mode 100644
index 8835fe1..0000000
--- a/runtime/core/Clownfish/Test/TestBoolean.c
+++ /dev/null
@@ -1,99 +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/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/core/Clownfish/Test/TestBoolean.cfh
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestBoolean.cfh b/runtime/core/Clownfish/Test/TestBoolean.cfh
deleted file mode 100644
index 43702db..0000000
--- a/runtime/core/Clownfish/Test/TestBoolean.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::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/core/Clownfish/Test/TestByteBuf.c
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestByteBuf.c b/runtime/core/Clownfish/Test/TestByteBuf.c
deleted file mode 100644
index 7f8fc50..0000000
--- a/runtime/core/Clownfish/Test/TestByteBuf.c
+++ /dev/null
@@ -1,218 +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/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/core/Clownfish/Test/TestByteBuf.cfh
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestByteBuf.cfh b/runtime/core/Clownfish/Test/TestByteBuf.cfh
deleted file mode 100644
index d27e715..0000000
--- a/runtime/core/Clownfish/Test/TestByteBuf.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::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/core/Clownfish/Test/TestCharBuf.c
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestCharBuf.c b/runtime/core/Clownfish/Test/TestCharBuf.c
deleted file mode 100644
index 4329987..0000000
--- a/runtime/core/Clownfish/Test/TestCharBuf.c
+++ /dev/null
@@ -1,394 +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 <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/core/Clownfish/Test/TestCharBuf.cfh
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestCharBuf.cfh b/runtime/core/Clownfish/Test/TestCharBuf.cfh
deleted file mode 100644
index d568c14..0000000
--- a/runtime/core/Clownfish/Test/TestCharBuf.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::TestCharBuf nickname TestCB
-    inherits Clownfish::TestHarness::TestBatch {
-
-    inert incremented TestCharBuf*
-    new();
-
-    void
-    Run(TestCharBuf *self, TestBatchRunner *runner);
-}
-
-

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/TestClass.c
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestClass.c b/runtime/core/Clownfish/Test/TestClass.c
deleted file mode 100644
index a6646f8..0000000
--- a/runtime/core/Clownfish/Test/TestClass.c
+++ /dev/null
@@ -1,158 +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 <stdio.h>
-
-#define C_CFISH_BOOLEAN
-#define C_CFISH_CLASS
-#define CFISH_USE_SHORT_NAMES
-#define TESTCFISH_USE_SHORT_NAMES
-
-#include "charmony.h"
-
-#include <string.h>
-
-#include "Clownfish/Test/TestClass.h"
-
-#include "Clownfish/Boolean.h"
-#include "Clownfish/Class.h"
-#include "Clownfish/Method.h"
-#include "Clownfish/String.h"
-#include "Clownfish/TestHarness/TestBatchRunner.h"
-#include "Clownfish/Util/Memory.h"
-#include "Clownfish/Vector.h"
-
-TestClass*
-TestClass_new() {
-    return (TestClass*)Class_Make_Obj(TESTCLASS);
-}
-
-#if DEBUG_CLASS_CONTENTS
-
-#include <stdio.h>
-
-static void
-S_memdump(void *vptr, size_t size) {
-    unsigned char *ptr = (unsigned char*)vptr;
-    for (size_t i = 0; i < size; i++) {
-        printf("%02X ", ptr[i]);
-    }
-    printf("\n");
-}
-
-#endif /* DEBUG_CLASS_CONTENTS */
-
-static void
-test_bootstrap_idempotence(TestBatchRunner *runner) {
-    Class    *bool_class        = BOOLEAN;
-    uint32_t  bool_class_size   = BOOLEAN->class_alloc_size;
-    uint32_t  bool_ivars_offset = cfish_Bool_IVARS_OFFSET;
-    Boolean  *true_singleton    = Bool_true_singleton;
-
-    char *bool_class_contents = (char*)MALLOCATE(bool_class_size);
-    memcpy(bool_class_contents, BOOLEAN, bool_class_size);
-
-    // Force another bootstrap run.
-    cfish_bootstrap_internal(1);
-
-#if DEBUG_CLASS_CONTENTS
-    printf("Before\n");
-    S_memdump(bool_class_contents, bool_class_size);
-    printf("After\n");
-    S_memdump(BOOLEAN, bool_class_size);
-#endif
-
-    TEST_TRUE(runner, bool_class == BOOLEAN,
-              "Boolean class pointer unchanged");
-    TEST_TRUE(runner,
-              memcmp(bool_class_contents, BOOLEAN, bool_class_size) == 0,
-              "Boolean class unchanged");
-    TEST_TRUE(runner, bool_ivars_offset == cfish_Bool_IVARS_OFFSET,
-              "Boolean ivars offset unchanged");
-    TEST_TRUE(runner, true_singleton == Bool_true_singleton,
-              "Boolean singleton unchanged");
-
-    FREEMEM(bool_class_contents);
-}
-
-static String*
-MyObj_To_String_IMP(Obj *self) {
-    UNUSED_VAR(self);
-    return Str_newf("delta");
-}
-
-static void
-test_simple_subclass(TestBatchRunner *runner) {
-    String *class_name = SSTR_WRAP_C("Clownfish::Test::MyObj");
-    Class *subclass = Class_singleton(class_name, OBJ);
-
-    TEST_TRUE(runner, Str_Equals(Class_Get_Name(subclass), (Obj*)class_name),
-              "Get_Name");
-    TEST_TRUE(runner, Class_Get_Parent(subclass) == OBJ, "Get_Parent");
-
-    Obj *obj = Class_Make_Obj(subclass);
-    TEST_TRUE(runner, Obj_is_a(obj, subclass), "Make_Obj");
-
-    Class_Override(subclass, (cfish_method_t)MyObj_To_String_IMP,
-                   CFISH_Obj_To_String_OFFSET);
-    String *str = Obj_To_String(obj);
-    TEST_TRUE(runner, Str_Equals_Utf8(str, "delta", 5), "Override");
-    DECREF(str);
-
-    DECREF(obj);
-}
-
-static void
-test_add_alias_to_registry(TestBatchRunner *runner) {
-    static const char alias[] = "Clownfish::Test::ObjAlias";
-    bool added;
-
-    added = Class_add_alias_to_registry(OBJ, alias, sizeof(alias) - 1);
-    TEST_TRUE(runner, added, "add_alias_to_registry returns true");
-    Class *klass = Class_fetch_class(SSTR_WRAP_C(alias));
-    TEST_TRUE(runner, klass == OBJ, "add_alias_to_registry works");
-
-    added = Class_add_alias_to_registry(CLASS, alias, sizeof(alias) - 1);
-    TEST_FALSE(runner, added, "add_alias_to_registry returns false");
-}
-
-static void
-test_Get_Methods(TestBatchRunner *runner) {
-    Vector *methods = Class_Get_Methods(OBJ);
-    Method *destroy = NULL;
-
-    for (size_t i = 0, size = Vec_Get_Size(methods); i < size; i++) {
-        Method *method = (Method*)Vec_Fetch(methods, i);
-
-        if (Str_Equals_Utf8(Method_Get_Name(method), "Destroy", 7)) {
-            destroy = method;
-        }
-    }
-
-    TEST_TRUE(runner, destroy != NULL, "Destroy method found");
-
-    DECREF(methods);
-}
-
-void
-TestClass_Run_IMP(TestClass *self, TestBatchRunner *runner) {
-    TestBatchRunner_Plan(runner, (TestBatch*)self, 12);
-    test_bootstrap_idempotence(runner);
-    test_simple_subclass(runner);
-    test_add_alias_to_registry(runner);
-    test_Get_Methods(runner);
-}
-

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

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/TestErr.c
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestErr.c b/runtime/core/Clownfish/Test/TestErr.c
deleted file mode 100644
index 46ecb1e..0000000
--- a/runtime/core/Clownfish/Test/TestErr.c
+++ /dev/null
@@ -1,193 +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/TestErr.h"
-
-#include "Clownfish/String.h"
-#include "Clownfish/Err.h"
-#include "Clownfish/Test.h"
-#include "Clownfish/TestHarness/TestBatchRunner.h"
-#include "Clownfish/TestHarness/TestUtils.h"
-#include "Clownfish/Class.h"
-
-TestErr*
-TestErr_new() {
-    return (TestErr*)Class_Make_Obj(TESTERR);
-}
-
-static void
-test_To_String(TestBatchRunner *runner) {
-    String *message = Str_newf("oops");
-    Err *error = Err_new(message);
-    String *string = Err_To_String(error);
-    TEST_TRUE(runner, Str_Equals(message, (Obj*)string),
-              "Stringifies as message");
-    DECREF(string);
-    DECREF(error);
-}
-
-static void
-test_Cat_Mess(TestBatchRunner *runner) {
-    Err *error = Err_new(Str_newf("alpha"));
-    Err_Cat_Mess(error, SSTR_WRAP_C("\nbeta"));
-    String *mess = Err_Get_Mess(error);
-    TEST_TRUE(runner, Str_Equals_Utf8(mess, "alpha\nbeta", 10), "Cat_Mess");
-    DECREF(error);
-}
-
-static void
-test_Add_Frame(TestBatchRunner *runner) {
-    {
-        Err *error = Err_new(Str_newf("alpha"));
-        Err_Add_Frame(error, "source.c", 128, "function");
-        String *mess = Err_Get_Mess(error);
-        const char *expected = "alpha\n\tfunction at source.c line 128\n";
-        TEST_TRUE(runner, Str_Equals_Utf8(mess, expected, strlen(expected)),
-                  "Add_Frame");
-        DECREF(error);
-    }
-
-    {
-        Err *error = Err_new(Str_newf("alpha\n"));
-        Err_Add_Frame(error, "source.c", 128, "function");
-        String *mess = Err_Get_Mess(error);
-        const char *expected = "alpha\n\tfunction at source.c line 128\n";
-        TEST_TRUE(runner, Str_Equals_Utf8(mess, expected, strlen(expected)),
-                  "Add_Frame with trailing newline");
-        DECREF(error);
-    }
-
-    {
-        Err *error = Err_new(Str_newf("alpha"));
-        Err_Add_Frame(error, "source.c", 128, NULL);
-        String *mess = Err_Get_Mess(error);
-        const char *expected = "alpha\n\tat source.c line 128\n";
-        TEST_TRUE(runner, Str_Equals_Utf8(mess, expected, strlen(expected)),
-                  "Add_Frame without func");
-        DECREF(error);
-    }
-}
-
-static void
-S_rethrow(void *context) {
-    Err *error = (Err*)context;
-    Err_rethrow(error, "src.c", 12, "fn");
-}
-
-static void
-test_rethrow(TestBatchRunner *runner) {
-    Err *error = Err_new(Str_newf("error"));
-    Err *rethrown = Err_trap(S_rethrow, error);
-    String *mess = Err_Get_Mess(rethrown);
-    const char *expected = "error\n\tfn at src.c line 12\n";
-    TEST_TRUE(runner, Str_Starts_With_Utf8(mess, expected, strlen(expected)),
-              "rethrow");
-    DECREF(error);
-}
-
-static void
-S_invalid_downcast(void *context) {
-    Obj *obj = (Obj*)context;
-    DOWNCAST(obj, ERR);
-}
-
-static void
-test_downcast(TestBatchRunner *runner) {
-    Obj *obj = (Obj*)Str_newf("gamma");
-
-    TEST_TRUE(runner, DOWNCAST(obj, STRING) != NULL, "downcast");
-
-    TEST_TRUE(runner, DOWNCAST(NULL, STRING) == NULL, "downcast NULL");
-
-    Err *error = Err_trap(S_invalid_downcast, obj);
-    TEST_TRUE(runner, error != NULL, "downcast throws");
-    DECREF(error);
-
-    DECREF(obj);
-}
-
-static void
-S_invalid_certify(void *context) {
-    Obj *obj = (Obj*)context;
-    CERTIFY(obj, ERR);
-}
-
-static void
-test_certify(TestBatchRunner *runner) {
-    Obj *obj = (Obj*)Str_newf("epsilon");
-    Err *error;
-
-    TEST_TRUE(runner, CERTIFY(obj, STRING) != NULL, "certify");
-
-    error = Err_trap(S_invalid_certify, NULL);
-    TEST_TRUE(runner, error != NULL, "certify NULL");
-    DECREF(error);
-
-    error = Err_trap(S_invalid_certify, obj);
-    TEST_TRUE(runner, error != NULL, "certify throws");
-    DECREF(error);
-
-    DECREF(obj);
-}
-
-static void
-S_err_thread(void *arg) {
-    TestBatchRunner *runner = (TestBatchRunner*)arg;
-
-    TEST_TRUE(runner, Err_get_error() == NULL,
-              "global error in thread initialized to null");
-
-    Err_set_error(Err_new(Str_newf("thread")));
-    String *mess = Err_Get_Mess(Err_get_error());
-    TEST_TRUE(runner, Str_Equals_Utf8(mess, "thread", 6),
-              "set_error in thread works");
-}
-
-static void
-test_threads(TestBatchRunner *runner) {
-    if (!TestUtils_has_threads) {
-        SKIP(runner, 3, "no thread support");
-        return;
-    }
-
-    Err_set_error(Err_new(Str_newf("main")));
-
-    void *runtime = TestUtils_clone_host_runtime();
-    Thread *thread = TestUtils_thread_create(S_err_thread, runner, runtime);
-    TestUtils_thread_join(thread);
-    TestUtils_destroy_host_runtime(runtime);
-
-    String *mess = Err_Get_Mess(Err_get_error());
-    TEST_TRUE(runner, Str_Equals_Utf8(mess, "main", 4),
-              "thread doesn't clobber global error");
-}
-
-void
-TestErr_Run_IMP(TestErr *self, TestBatchRunner *runner) {
-    TestBatchRunner_Plan(runner, (TestBatch*)self, 15);
-    test_To_String(runner);
-    test_Cat_Mess(runner);
-    test_Add_Frame(runner);
-    test_rethrow(runner);
-    test_downcast(runner);
-    test_certify(runner);
-    test_threads(runner);
-}
-
-

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

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/TestHash.c
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestHash.c b/runtime/core/Clownfish/Test/TestHash.c
deleted file mode 100644
index b00d891..0000000
--- a/runtime/core/Clownfish/Test/TestHash.c
+++ /dev/null
@@ -1,325 +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 <stdlib.h>
-#include <time.h>
-
-#define CFISH_USE_SHORT_NAMES
-#define TESTCFISH_USE_SHORT_NAMES
-#define C_CFISH_HASH
-
-#include "Clownfish/Test/TestHash.h"
-
-#include "Clownfish/String.h"
-#include "Clownfish/Boolean.h"
-#include "Clownfish/Hash.h"
-#include "Clownfish/Test.h"
-#include "Clownfish/TestHarness/TestBatchRunner.h"
-#include "Clownfish/TestHarness/TestUtils.h"
-#include "Clownfish/Vector.h"
-#include "Clownfish/Class.h"
-
-TestHash*
-TestHash_new() {
-    return (TestHash*)Class_Make_Obj(TESTHASH);
-}
-
-static void
-test_Equals(TestBatchRunner *runner) {
-    Hash *hash  = Hash_new(0);
-    Hash *other = Hash_new(0);
-    String *stuff = SSTR_WRAP_C("stuff");
-
-    TEST_TRUE(runner, Hash_Equals(hash, (Obj*)other),
-              "Empty hashes are equal");
-
-    Hash_Store_Utf8(hash, "foo", 3, (Obj*)CFISH_TRUE);
-    TEST_FALSE(runner, Hash_Equals(hash, (Obj*)other),
-               "Add one pair and Equals returns false");
-
-    Hash_Store_Utf8(other, "foo", 3, (Obj*)CFISH_TRUE);
-    TEST_TRUE(runner, Hash_Equals(hash, (Obj*)other),
-              "Add a matching pair and Equals returns true");
-
-    Hash_Store_Utf8(other, "foo", 3, INCREF(stuff));
-    TEST_FALSE(runner, Hash_Equals(hash, (Obj*)other),
-               "Non-matching value spoils Equals");
-
-    DECREF(hash);
-    DECREF(other);
-}
-
-static void
-test_Store_and_Fetch(TestBatchRunner *runner) {
-    Hash          *hash         = Hash_new(100);
-    Hash          *dupe         = Hash_new(100);
-    const size_t   starting_cap = Hash_Get_Capacity(hash);
-    Vector        *expected     = Vec_new(100);
-    Vector        *got          = Vec_new(100);
-    String        *twenty       = SSTR_WRAP_C("20");
-    String        *forty        = SSTR_WRAP_C("40");
-    String        *foo          = SSTR_WRAP_C("foo");
-
-    for (int32_t i = 0; i < 100; i++) {
-        String *str = Str_newf("%i32", i);
-        Hash_Store(hash, str, (Obj*)str);
-        Hash_Store(dupe, str, INCREF(str));
-        Vec_Push(expected, INCREF(str));
-    }
-    TEST_TRUE(runner, Hash_Equals(hash, (Obj*)dupe), "Equals");
-
-    TEST_UINT_EQ(runner, Hash_Get_Capacity(hash), starting_cap,
-                 "Initial capacity sufficient (no rebuilds)");
-
-    for (size_t i = 0; i < 100; i++) {
-        String *key  = (String*)Vec_Fetch(expected, i);
-        Obj    *elem = Hash_Fetch(hash, key);
-        Vec_Push(got, (Obj*)INCREF(elem));
-    }
-
-    TEST_TRUE(runner, Vec_Equals(got, (Obj*)expected),
-              "basic Store and Fetch");
-    TEST_UINT_EQ(runner, Hash_Get_Size(hash), 100,
-                 "size incremented properly by Hash_Store");
-
-    TEST_TRUE(runner, Hash_Fetch(hash, foo) == NULL,
-              "Fetch against non-existent key returns NULL");
-
-    String *twelve = (String*)Hash_Fetch_Utf8(hash, "12", 2);
-    TEST_TRUE(runner, Str_Equals_Utf8(twelve, "12", 2), "Fetch_Utf8");
-
-    Obj *stored_foo = INCREF(foo);
-    Hash_Store(hash, forty, stored_foo);
-    TEST_TRUE(runner, Str_Equals(foo, Hash_Fetch(hash, forty)),
-              "Hash_Store replaces existing value");
-    TEST_FALSE(runner, Hash_Equals(hash, (Obj*)dupe),
-               "replacement value spoils equals");
-    TEST_UINT_EQ(runner, Hash_Get_Size(hash), 100,
-                 "size unaffected after value replaced");
-
-    TEST_TRUE(runner, Hash_Delete(hash, forty) == stored_foo,
-              "Delete returns value");
-    DECREF(stored_foo);
-    TEST_UINT_EQ(runner, Hash_Get_Size(hash), 99,
-                 "size decremented by successful Delete");
-    TEST_TRUE(runner, Hash_Delete(hash, forty) == NULL,
-              "Delete returns NULL when key not found");
-    TEST_UINT_EQ(runner, Hash_Get_Size(hash), 99,
-                 "size not decremented by unsuccessful Delete");
-    DECREF(Hash_Delete(dupe, forty));
-    TEST_TRUE(runner, Vec_Equals(got, (Obj*)expected), "Equals after Delete");
-
-    Obj *forty_one = Hash_Delete_Utf8(hash, "41", 2);
-    TEST_TRUE(runner, forty_one != NULL, "Delete_Utf8");
-    TEST_UINT_EQ(runner, Hash_Get_Size(hash), 98,
-                 "Delete_Utf8 decrements size");
-    DECREF(forty_one);
-
-    Hash_Clear(hash);
-    TEST_TRUE(runner, Hash_Fetch(hash, twenty) == NULL, "Clear");
-    TEST_TRUE(runner, Hash_Get_Size(hash) == 0, "size is 0 after Clear");
-
-    Hash_Clear(hash);
-    Hash_Store(hash, forty, NULL);
-    TEST_TRUE(runner, Hash_Fetch(hash, forty) == NULL, "Store NULL");
-    TEST_TRUE(runner, Hash_Get_Size(hash) == 1, "Size after Store NULL");
-    TEST_TRUE(runner, Hash_Delete(hash, forty) == NULL, "Delete NULL value");
-    TEST_TRUE(runner, Hash_Get_Size(hash) == 0,
-              "Size after Deleting NULL val");
-
-    DECREF(hash);
-    DECREF(dupe);
-    DECREF(got);
-    DECREF(expected);
-}
-
-static void
-test_Keys_Values(TestBatchRunner *runner) {
-    Hash     *hash     = Hash_new(0); // trigger multiple rebuilds.
-    Vector   *expected = Vec_new(100);
-    Vector   *keys;
-    Vector   *values;
-
-    for (uint32_t i = 0; i < 500; i++) {
-        String *str = Str_newf("%u32", i);
-        Hash_Store(hash, str, (Obj*)str);
-        Vec_Push(expected, INCREF(str));
-    }
-
-    Vec_Sort(expected);
-
-    keys   = Hash_Keys(hash);
-    values = Hash_Values(hash);
-    Vec_Sort(keys);
-    Vec_Sort(values);
-    TEST_TRUE(runner, Vec_Equals(keys, (Obj*)expected), "Keys");
-    TEST_TRUE(runner, Vec_Equals(values, (Obj*)expected), "Values");
-    Vec_Clear(keys);
-    Vec_Clear(values);
-
-    {
-        String *forty = SSTR_WRAP_C("40");
-        String *nope  = SSTR_WRAP_C("nope");
-        TEST_TRUE(runner, Hash_Has_Key(hash, forty), "Has_Key");
-        TEST_FALSE(runner, Hash_Has_Key(hash, nope),
-                   "Has_Key returns false for non-existent key");
-    }
-
-    DECREF(hash);
-    DECREF(expected);
-    DECREF(keys);
-    DECREF(values);
-}
-
-static void
-test_stress(TestBatchRunner *runner) {
-    Hash     *hash     = Hash_new(0); // trigger multiple rebuilds.
-    Vector   *expected = Vec_new(1000);
-    Vector   *keys;
-    Vector   *values;
-
-    for (uint32_t i = 0; i < 1000; i++) {
-        String *str = TestUtils_random_string((size_t)(rand() % 1200));
-        while (Hash_Fetch(hash, str)) {
-            DECREF(str);
-            str = TestUtils_random_string((size_t)(rand() % 1200));
-        }
-        Hash_Store(hash, str, (Obj*)str);
-        Vec_Push(expected, INCREF(str));
-    }
-
-    Vec_Sort(expected);
-
-    // Overwrite for good measure.
-    for (uint32_t i = 0; i < 1000; i++) {
-        String *str = (String*)Vec_Fetch(expected, i);
-        Hash_Store(hash, str, INCREF(str));
-    }
-
-    keys   = Hash_Keys(hash);
-    values = Hash_Values(hash);
-    Vec_Sort(keys);
-    Vec_Sort(values);
-    TEST_TRUE(runner, Vec_Equals(keys, (Obj*)expected), "stress Keys");
-    TEST_TRUE(runner, Vec_Equals(values, (Obj*)expected), "stress Values");
-
-    DECREF(keys);
-    DECREF(values);
-    DECREF(expected);
-    DECREF(hash);
-}
-
-static void
-test_collision(TestBatchRunner *runner) {
-    Hash   *hash = Hash_new(0);
-    String *one  = Str_newf("A");
-    String *two  = Str_newf("P{2}|=~-ULE/d");
-
-    TEST_TRUE(runner, Str_Hash_Sum(one) == Str_Hash_Sum(two),
-              "Keys have the same hash sum");
-
-    Hash_Store(hash, one, INCREF(one));
-    Hash_Store(hash, two, INCREF(two));
-    String *elem = (String*)Hash_Fetch(hash, two);
-    TEST_TRUE(runner, elem == two, "Fetch works with collisions");
-
-    DECREF(one);
-    DECREF(two);
-    DECREF(hash);
-}
-
-static void
-test_store_skips_tombstone(TestBatchRunner *runner) {
-    Hash *hash = Hash_new(0);
-    size_t mask = Hash_Get_Capacity(hash) - 1;
-
-    String *one = Str_newf("one");
-    size_t slot = Str_Hash_Sum(one) & mask;
-
-    // Find a colliding key.
-    String *two = NULL;
-    for (int i = 0; i < 100000; i++) {
-        two = Str_newf("%i32", i);
-        if (slot == (Str_Hash_Sum(two) & mask)) {
-            break;
-        }
-        DECREF(two);
-        two = NULL;
-    }
-
-    Hash_Store(hash, one, (Obj*)CFISH_TRUE);
-    Hash_Store(hash, two, (Obj*)CFISH_TRUE);
-    Hash_Delete(hash, one);
-    Hash_Store(hash, two, (Obj*)CFISH_TRUE);
-
-    TEST_UINT_EQ(runner, Hash_Get_Size(hash), 1, "Store skips tombstone");
-
-    DECREF(one);
-    DECREF(two);
-    DECREF(hash);
-}
-
-static void
-test_threshold_accounting(TestBatchRunner *runner) {
-    Hash   *hash = Hash_new(20);
-    String *key  = Str_newf("key");
-
-    size_t threshold = hash->threshold;
-    Hash_Store(hash, key, (Obj*)CFISH_TRUE);
-    Hash_Delete(hash, key);
-    TEST_UINT_EQ(runner, hash->threshold, threshold - 1,
-                 "Tombstone creation decreases threshold");
-
-    Hash_Store(hash, key, (Obj*)CFISH_TRUE);
-    TEST_UINT_EQ(runner, hash->threshold, threshold,
-                 "Tombstone destruction increases threshold");
-
-    DECREF(key);
-    DECREF(hash);
-}
-
-static void
-test_tombstone_identification(TestBatchRunner *runner) {
-    Hash   *hash = Hash_new(20);
-    String *key  = Str_newf("P{2}|=~-U@!y>");
-
-    // Tombstones have a zero hash_sum.
-    TEST_UINT_EQ(runner, Str_Hash_Sum(key), 0, "Key has zero hash sum");
-
-    Hash_Store(hash, key, (Obj*)CFISH_TRUE);
-    Hash_Delete(hash, key);
-    TEST_TRUE(runner, Hash_Fetch(hash, key) == NULL,
-              "Key with zero hash sum isn't mistaken for tombstone");
-
-    DECREF(key);
-    DECREF(hash);
-}
-
-void
-TestHash_Run_IMP(TestHash *self, TestBatchRunner *runner) {
-    TestBatchRunner_Plan(runner, (TestBatch*)self, 39);
-    srand((unsigned int)time((time_t*)NULL));
-    test_Equals(runner);
-    test_Store_and_Fetch(runner);
-    test_Keys_Values(runner);
-    test_stress(runner);
-    test_collision(runner);
-    test_store_skips_tombstone(runner);
-    test_threshold_accounting(runner);
-    test_tombstone_identification(runner);
-}
-
-

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

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/TestHashIterator.c
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestHashIterator.c b/runtime/core/Clownfish/Test/TestHashIterator.c
deleted file mode 100644
index fac7d69..0000000
--- a/runtime/core/Clownfish/Test/TestHashIterator.c
+++ /dev/null
@@ -1,253 +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 <stdlib.h>
-#include <time.h>
-
-#define CFISH_USE_SHORT_NAMES
-#define TESTCFISH_USE_SHORT_NAMES
-
-#include "Clownfish/Test/TestHashIterator.h"
-
-#include "Clownfish/Err.h"
-#include "Clownfish/String.h"
-#include "Clownfish/Hash.h"
-#include "Clownfish/HashIterator.h"
-#include "Clownfish/Test.h"
-#include "Clownfish/Vector.h"
-#include "Clownfish/TestHarness/TestBatchRunner.h"
-#include "Clownfish/TestHarness/TestUtils.h"
-#include "Clownfish/Class.h"
-
-TestHashIterator*
-TestHashIterator_new() {
-    return (TestHashIterator*)Class_Make_Obj(TESTHASHITERATOR);
-}
-
-static void
-test_Next(TestBatchRunner *runner) {
-    Hash     *hash     = Hash_new(0); // trigger multiple rebuilds.
-    Vector   *expected = Vec_new(100);
-    Vector   *keys     = Vec_new(500);
-    Vector   *values   = Vec_new(500);
-
-    for (uint32_t i = 0; i < 500; i++) {
-        String *str = Str_newf("%u32", i);
-        Hash_Store(hash, str, (Obj*)str);
-        Vec_Push(expected, INCREF(str));
-    }
-
-    Vec_Sort(expected);
-
-    {
-        HashIterator *iter = HashIter_new(hash);
-        while (HashIter_Next(iter)) {
-            String *key = HashIter_Get_Key(iter);
-            Obj *value = HashIter_Get_Value(iter);
-            Vec_Push(keys, INCREF(key));
-            Vec_Push(values, INCREF(value));
-        }
-        TEST_TRUE(runner, !HashIter_Next(iter),
-                  "Next continues to return false after iteration finishes.");
-
-        DECREF(iter);
-    }
-
-    Vec_Sort(keys);
-    Vec_Sort(values);
-    TEST_TRUE(runner, Vec_Equals(keys, (Obj*)expected), "Keys from Iter");
-    TEST_TRUE(runner, Vec_Equals(values, (Obj*)expected), "Values from Iter");
-
-    DECREF(hash);
-    DECREF(expected);
-    DECREF(keys);
-    DECREF(values);
-}
-
-static void
-S_invoke_Next(void *context) {
-    HashIterator *iter = (HashIterator*)context;
-    HashIter_Next(iter);
-}
-
-static void
-S_invoke_Get_Key(void *context) {
-    HashIterator *iter = (HashIterator*)context;
-    HashIter_Get_Key(iter);
-}
-
-static void
-S_invoke_Get_Value(void *context) {
-    HashIterator *iter = (HashIterator*)context;
-    HashIter_Get_Value(iter);
-}
-
-static void
-test_empty(TestBatchRunner *runner) {
-    Hash         *hash = Hash_new(0);
-    HashIterator *iter = HashIter_new(hash);
-
-    TEST_TRUE(runner, !HashIter_Next(iter),
-              "First call to next false on empty hash iteration");
-
-    Err *get_key_error = Err_trap(S_invoke_Get_Key, iter);
-    TEST_TRUE(runner, get_key_error != NULL,
-              "Get_Key throws exception on empty hash.");
-    DECREF(get_key_error);
-
-    Err *get_value_error = Err_trap(S_invoke_Get_Value, iter);
-    TEST_TRUE(runner, get_value_error != NULL,
-              "Get_Value throws exception on empty hash.");
-    DECREF(get_value_error);
-
-    DECREF(hash);
-    DECREF(iter);
-}
-
-static void
-test_Get_Key_and_Get_Value(TestBatchRunner *runner) {
-    Hash   *hash = Hash_new(0);
-    String *str  = Str_newf("foo");
-    Hash_Store(hash, str, (Obj*)str);
-    bool ok;
-
-    HashIterator *iter = HashIter_new(hash);
-    DECREF(hash);
-
-    Err *get_key_error = Err_trap(S_invoke_Get_Key, iter);
-    TEST_TRUE(runner, get_key_error != NULL,
-              "Get_Key throws exception before first call to Next.");
-    ok = Str_Contains_Utf8(Err_Get_Mess(get_key_error), "before", 6);
-    TEST_TRUE(runner, ok, "Get_Key before Next throws correct message");
-    DECREF(get_key_error);
-
-    Err *get_value_error = Err_trap(S_invoke_Get_Value, iter);
-    TEST_TRUE(runner, get_value_error != NULL,
-              "Get_Value throws exception before first call to Next.");
-    ok = Str_Contains_Utf8(Err_Get_Mess(get_value_error), "before", 6);
-    TEST_TRUE(runner, ok, "Get_Value before Next throws correct message");
-    DECREF(get_value_error);
-
-    HashIter_Next(iter);
-    TEST_TRUE(runner, HashIter_Get_Key(iter) != NULL,
-              "Get_Key during iteration.");
-    TEST_TRUE(runner, HashIter_Get_Value(iter) != NULL,
-              "Get_Value during iteration.");
-
-    HashIter_Next(iter);
-    get_key_error = Err_trap(S_invoke_Get_Key, iter);
-    TEST_TRUE(runner, get_key_error != NULL,
-              "Get_Key throws exception after end of iteration.");
-    ok = Str_Contains_Utf8(Err_Get_Mess(get_key_error), "after", 5);
-    TEST_TRUE(runner, ok, "Get_Key after end throws correct message");
-    DECREF(get_key_error);
-
-    get_value_error = Err_trap(S_invoke_Get_Value, iter);
-    TEST_TRUE(runner, get_value_error != NULL,
-              "Get_Value throws exception after end of iteration.");
-    ok = Str_Contains_Utf8(Err_Get_Mess(get_value_error), "after", 5);
-    TEST_TRUE(runner, ok, "Get_Value after end throws correct message");
-    DECREF(get_value_error);
-
-
-    DECREF(iter);
-}
-
-static void
-test_illegal_modification(TestBatchRunner *runner) {
-    Hash *hash = Hash_new(0);
-
-    for (uint32_t i = 0; i < 3; i++) {
-        String *str = Str_newf("%u32", i);
-        Hash_Store(hash, str, (Obj*)str);
-    }
-
-    HashIterator *iter = HashIter_new(hash);
-    HashIter_Next(iter);
-
-    for (uint32_t i = 0; i < 100; i++) {
-        String *str = Str_newf("foo %u32", i);
-        Hash_Store(hash, str, (Obj*)str);
-    }
-
-    Err *next_error = Err_trap(S_invoke_Next, iter);
-    TEST_TRUE(runner, next_error != NULL,
-              "Next on resized hash throws exception.");
-    DECREF(next_error);
-
-    Err *get_key_error = Err_trap(S_invoke_Get_Key, iter);
-    TEST_TRUE(runner, get_key_error != NULL,
-              "Get_Key on resized hash throws exception.");
-    DECREF(get_key_error);
-
-    Err *get_value_error = Err_trap(S_invoke_Get_Value, iter);
-    TEST_TRUE(runner, get_value_error != NULL,
-              "Get_Value on resized hash throws exception.");
-    DECREF(get_value_error);
-
-    DECREF(hash);
-    DECREF(iter);
-}
-
-static void
-test_tombstone(TestBatchRunner *runner) {
-    {
-        Hash   *hash = Hash_new(0);
-        String *str  = Str_newf("foo");
-        Hash_Store(hash, str, INCREF(str));
-        DECREF(Hash_Delete(hash, str));
-        DECREF(str);
-
-        HashIterator *iter = HashIter_new(hash);
-        TEST_TRUE(runner, !HashIter_Next(iter), "Next advances past tombstones.");
-
-        DECREF(iter);
-        DECREF(hash);
-    }
-
-    {
-        Hash   *hash = Hash_new(0);
-        String *str  = Str_newf("foo");
-        Hash_Store(hash, str, INCREF(str));
-
-        HashIterator *iter = HashIter_new(hash);
-        HashIter_Next(iter);
-        DECREF(Hash_Delete(hash, str));
-
-
-        Err *get_key_error = Err_trap(S_invoke_Get_Key, iter);
-        TEST_TRUE(runner, get_key_error != NULL,
-                  "Get_Key doesn't return tombstone and throws error.");
-        DECREF(get_key_error);
-
-        DECREF(str);
-        DECREF(iter);
-        DECREF(hash);
-    }
-}
-
-void
-TestHashIterator_Run_IMP(TestHashIterator *self, TestBatchRunner *runner) {
-    TestBatchRunner_Plan(runner, (TestBatch*)self, 21);
-    srand((unsigned int)time((time_t*)NULL));
-    test_Next(runner);
-    test_empty(runner);
-    test_Get_Key_and_Get_Value(runner);
-    test_illegal_modification(runner);
-    test_tombstone(runner);
-}
-
-

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/core/Clownfish/Test/TestHashIterator.cfh
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Test/TestHashIterator.cfh b/runtime/core/Clownfish/Test/TestHashIterator.cfh
deleted file mode 100644
index 4765f76..0000000
--- a/runtime/core/Clownfish/Test/TestHashIterator.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::TestHashIterator
-    inherits Clownfish::TestHarness::TestBatch {
-
-    inert incremented TestHashIterator*
-    new();
-
-    void
-    Run(TestHashIterator *self, TestBatchRunner *runner);
-}
-
-


[14/14] lucy-clownfish git commit: Merge branch 'separate-tests'

Posted by nw...@apache.org.
Merge branch 'separate-tests'

This brings the size of the Clownfish library down to 95 KB on
32-bit Ubuntu (with -fno-asynchronous-unwind-tables), 88 KB on
64-bit Windows, and 82 KB on OS X.

Fixes CLOWNFISH-80.


Project: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/commit/0be314b9
Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/0be314b9
Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/0be314b9

Branch: refs/heads/master
Commit: 0be314b9d4d306f019a6b79c6bd9d90118679af3
Parents: 56f1982 a45abf0
Author: Nick Wellnhofer <we...@aevum.de>
Authored: Mon Jul 11 13:24:03 2016 +0200
Committer: Nick Wellnhofer <we...@aevum.de>
Committed: Mon Jul 11 13:24:03 2016 +0200

----------------------------------------------------------------------
 compiler/perl/Build.PL                          |   2 +-
 compiler/perl/lib/Clownfish/CFC/Perl/Build.pm   |  28 +-
 runtime/c/.gitignore                            |  21 +-
 runtime/common/charmonizer.c                    | 195 +++--
 runtime/common/charmonizer.main                 | 197 +++--
 runtime/core/Clownfish/LockFreeRegistry.h       |  10 +-
 runtime/core/Clownfish/PtrHash.h                |  10 +-
 runtime/core/Clownfish/Test.c                   |  70 --
 runtime/core/Clownfish/Test.cfh                 |  26 -
 runtime/core/Clownfish/Test/TestBlob.c          | 147 ----
 runtime/core/Clownfish/Test/TestBlob.cfh        |  28 -
 runtime/core/Clownfish/Test/TestBoolean.c       |  99 ---
 runtime/core/Clownfish/Test/TestBoolean.cfh     |  29 -
 runtime/core/Clownfish/Test/TestByteBuf.c       | 218 -----
 runtime/core/Clownfish/Test/TestByteBuf.cfh     |  29 -
 runtime/core/Clownfish/Test/TestCharBuf.c       | 394 ---------
 runtime/core/Clownfish/Test/TestCharBuf.cfh     |  29 -
 runtime/core/Clownfish/Test/TestClass.c         | 158 ----
 runtime/core/Clownfish/Test/TestClass.cfh       |  28 -
 runtime/core/Clownfish/Test/TestErr.c           | 193 -----
 runtime/core/Clownfish/Test/TestErr.cfh         |  28 -
 runtime/core/Clownfish/Test/TestHash.c          | 325 -------
 runtime/core/Clownfish/Test/TestHash.cfh        |  29 -
 runtime/core/Clownfish/Test/TestHashIterator.c  | 253 ------
 .../core/Clownfish/Test/TestHashIterator.cfh    |  29 -
 runtime/core/Clownfish/Test/TestHost.c          | 125 ---
 runtime/core/Clownfish/Test/TestHost.cfh        |  81 --
 .../core/Clownfish/Test/TestLockFreeRegistry.c  | 168 ----
 .../Clownfish/Test/TestLockFreeRegistry.cfh     |  29 -
 runtime/core/Clownfish/Test/TestMethod.c        |  91 --
 runtime/core/Clownfish/Test/TestMethod.cfh      |  28 -
 runtime/core/Clownfish/Test/TestNum.c           | 278 ------
 runtime/core/Clownfish/Test/TestNum.cfh         |  29 -
 runtime/core/Clownfish/Test/TestObj.c           | 152 ----
 runtime/core/Clownfish/Test/TestObj.cfh         |  28 -
 runtime/core/Clownfish/Test/TestPtrHash.c       | 108 ---
 runtime/core/Clownfish/Test/TestPtrHash.cfh     |  29 -
 runtime/core/Clownfish/Test/TestString.c        | 848 -------------------
 runtime/core/Clownfish/Test/TestString.cfh      |  36 -
 runtime/core/Clownfish/Test/TestVector.c        | 572 -------------
 runtime/core/Clownfish/Test/TestVector.cfh      |  29 -
 runtime/core/Clownfish/Test/Util/TestAtomic.c   |  65 --
 runtime/core/Clownfish/Test/Util/TestAtomic.cfh |  29 -
 runtime/core/Clownfish/Test/Util/TestMemory.c   | 119 ---
 runtime/core/Clownfish/Test/Util/TestMemory.cfh |  29 -
 .../core/Clownfish/Test/Util/TestStringHelper.c | 373 --------
 .../Clownfish/Test/Util/TestStringHelper.cfh    |  29 -
 runtime/core/Clownfish/Util/Atomic.h            |   4 +-
 runtime/core/TestClownfish.c                    |  22 -
 runtime/core/TestClownfish.cfp                  |   8 -
 runtime/go/build.go                             |   4 +-
 runtime/perl/.gitignore                         |   2 +
 runtime/perl/Build.PL                           |   2 +-
 runtime/perl/buildlib/Clownfish/Build.pm        |  26 +-
 .../perl/buildlib/Clownfish/Build/Binding.pm    |   2 +-
 runtime/perl/lib/Clownfish/Test.pm              |   8 +
 runtime/perl/t/binding/019-obj.t                |   1 +
 runtime/perl/t/binding/023-string.t             |   1 +
 runtime/python/cfext/CFBind.c                   |   1 +
 runtime/python/setup.py                         |  13 +-
 runtime/test/Clownfish/Test.c                   |  70 ++
 runtime/test/Clownfish/Test.cfh                 |  26 +
 runtime/test/Clownfish/Test/TestBlob.c          | 147 ++++
 runtime/test/Clownfish/Test/TestBlob.cfh        |  28 +
 runtime/test/Clownfish/Test/TestBoolean.c       |  99 +++
 runtime/test/Clownfish/Test/TestBoolean.cfh     |  29 +
 runtime/test/Clownfish/Test/TestByteBuf.c       | 218 +++++
 runtime/test/Clownfish/Test/TestByteBuf.cfh     |  29 +
 runtime/test/Clownfish/Test/TestCharBuf.c       | 394 +++++++++
 runtime/test/Clownfish/Test/TestCharBuf.cfh     |  29 +
 runtime/test/Clownfish/Test/TestClass.c         | 164 ++++
 runtime/test/Clownfish/Test/TestClass.cfh       |  28 +
 runtime/test/Clownfish/Test/TestErr.c           | 193 +++++
 runtime/test/Clownfish/Test/TestErr.cfh         |  28 +
 runtime/test/Clownfish/Test/TestHash.c          | 325 +++++++
 runtime/test/Clownfish/Test/TestHash.cfh        |  29 +
 runtime/test/Clownfish/Test/TestHashIterator.c  | 253 ++++++
 .../test/Clownfish/Test/TestHashIterator.cfh    |  29 +
 runtime/test/Clownfish/Test/TestHost.c          | 125 +++
 runtime/test/Clownfish/Test/TestHost.cfh        |  81 ++
 .../test/Clownfish/Test/TestLockFreeRegistry.c  | 168 ++++
 .../Clownfish/Test/TestLockFreeRegistry.cfh     |  29 +
 runtime/test/Clownfish/Test/TestMethod.c        |  91 ++
 runtime/test/Clownfish/Test/TestMethod.cfh      |  28 +
 runtime/test/Clownfish/Test/TestNum.c           | 278 ++++++
 runtime/test/Clownfish/Test/TestNum.cfh         |  29 +
 runtime/test/Clownfish/Test/TestObj.c           | 152 ++++
 runtime/test/Clownfish/Test/TestObj.cfh         |  28 +
 runtime/test/Clownfish/Test/TestPtrHash.c       | 108 +++
 runtime/test/Clownfish/Test/TestPtrHash.cfh     |  29 +
 runtime/test/Clownfish/Test/TestString.c        | 848 +++++++++++++++++++
 runtime/test/Clownfish/Test/TestString.cfh      |  36 +
 runtime/test/Clownfish/Test/TestVector.c        | 572 +++++++++++++
 runtime/test/Clownfish/Test/TestVector.cfh      |  29 +
 runtime/test/Clownfish/Test/Util/TestAtomic.c   |  65 ++
 runtime/test/Clownfish/Test/Util/TestAtomic.cfh |  29 +
 runtime/test/Clownfish/Test/Util/TestMemory.c   | 119 +++
 runtime/test/Clownfish/Test/Util/TestMemory.cfh |  29 +
 .../test/Clownfish/Test/Util/TestStringHelper.c | 373 ++++++++
 .../Clownfish/Test/Util/TestStringHelper.cfh    |  29 +
 runtime/test/TestClownfish.c                    |  22 +
 runtime/test/TestClownfish.cfp                  |   8 +
 102 files changed, 5792 insertions(+), 5575 deletions(-)
----------------------------------------------------------------------



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

Posted by nw...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestClass.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestClass.c b/runtime/test/Clownfish/Test/TestClass.c
new file mode 100644
index 0000000..a6646f8
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestClass.c
@@ -0,0 +1,158 @@
+/* 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 <stdio.h>
+
+#define C_CFISH_BOOLEAN
+#define C_CFISH_CLASS
+#define CFISH_USE_SHORT_NAMES
+#define TESTCFISH_USE_SHORT_NAMES
+
+#include "charmony.h"
+
+#include <string.h>
+
+#include "Clownfish/Test/TestClass.h"
+
+#include "Clownfish/Boolean.h"
+#include "Clownfish/Class.h"
+#include "Clownfish/Method.h"
+#include "Clownfish/String.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Clownfish/Util/Memory.h"
+#include "Clownfish/Vector.h"
+
+TestClass*
+TestClass_new() {
+    return (TestClass*)Class_Make_Obj(TESTCLASS);
+}
+
+#if DEBUG_CLASS_CONTENTS
+
+#include <stdio.h>
+
+static void
+S_memdump(void *vptr, size_t size) {
+    unsigned char *ptr = (unsigned char*)vptr;
+    for (size_t i = 0; i < size; i++) {
+        printf("%02X ", ptr[i]);
+    }
+    printf("\n");
+}
+
+#endif /* DEBUG_CLASS_CONTENTS */
+
+static void
+test_bootstrap_idempotence(TestBatchRunner *runner) {
+    Class    *bool_class        = BOOLEAN;
+    uint32_t  bool_class_size   = BOOLEAN->class_alloc_size;
+    uint32_t  bool_ivars_offset = cfish_Bool_IVARS_OFFSET;
+    Boolean  *true_singleton    = Bool_true_singleton;
+
+    char *bool_class_contents = (char*)MALLOCATE(bool_class_size);
+    memcpy(bool_class_contents, BOOLEAN, bool_class_size);
+
+    // Force another bootstrap run.
+    cfish_bootstrap_internal(1);
+
+#if DEBUG_CLASS_CONTENTS
+    printf("Before\n");
+    S_memdump(bool_class_contents, bool_class_size);
+    printf("After\n");
+    S_memdump(BOOLEAN, bool_class_size);
+#endif
+
+    TEST_TRUE(runner, bool_class == BOOLEAN,
+              "Boolean class pointer unchanged");
+    TEST_TRUE(runner,
+              memcmp(bool_class_contents, BOOLEAN, bool_class_size) == 0,
+              "Boolean class unchanged");
+    TEST_TRUE(runner, bool_ivars_offset == cfish_Bool_IVARS_OFFSET,
+              "Boolean ivars offset unchanged");
+    TEST_TRUE(runner, true_singleton == Bool_true_singleton,
+              "Boolean singleton unchanged");
+
+    FREEMEM(bool_class_contents);
+}
+
+static String*
+MyObj_To_String_IMP(Obj *self) {
+    UNUSED_VAR(self);
+    return Str_newf("delta");
+}
+
+static void
+test_simple_subclass(TestBatchRunner *runner) {
+    String *class_name = SSTR_WRAP_C("Clownfish::Test::MyObj");
+    Class *subclass = Class_singleton(class_name, OBJ);
+
+    TEST_TRUE(runner, Str_Equals(Class_Get_Name(subclass), (Obj*)class_name),
+              "Get_Name");
+    TEST_TRUE(runner, Class_Get_Parent(subclass) == OBJ, "Get_Parent");
+
+    Obj *obj = Class_Make_Obj(subclass);
+    TEST_TRUE(runner, Obj_is_a(obj, subclass), "Make_Obj");
+
+    Class_Override(subclass, (cfish_method_t)MyObj_To_String_IMP,
+                   CFISH_Obj_To_String_OFFSET);
+    String *str = Obj_To_String(obj);
+    TEST_TRUE(runner, Str_Equals_Utf8(str, "delta", 5), "Override");
+    DECREF(str);
+
+    DECREF(obj);
+}
+
+static void
+test_add_alias_to_registry(TestBatchRunner *runner) {
+    static const char alias[] = "Clownfish::Test::ObjAlias";
+    bool added;
+
+    added = Class_add_alias_to_registry(OBJ, alias, sizeof(alias) - 1);
+    TEST_TRUE(runner, added, "add_alias_to_registry returns true");
+    Class *klass = Class_fetch_class(SSTR_WRAP_C(alias));
+    TEST_TRUE(runner, klass == OBJ, "add_alias_to_registry works");
+
+    added = Class_add_alias_to_registry(CLASS, alias, sizeof(alias) - 1);
+    TEST_FALSE(runner, added, "add_alias_to_registry returns false");
+}
+
+static void
+test_Get_Methods(TestBatchRunner *runner) {
+    Vector *methods = Class_Get_Methods(OBJ);
+    Method *destroy = NULL;
+
+    for (size_t i = 0, size = Vec_Get_Size(methods); i < size; i++) {
+        Method *method = (Method*)Vec_Fetch(methods, i);
+
+        if (Str_Equals_Utf8(Method_Get_Name(method), "Destroy", 7)) {
+            destroy = method;
+        }
+    }
+
+    TEST_TRUE(runner, destroy != NULL, "Destroy method found");
+
+    DECREF(methods);
+}
+
+void
+TestClass_Run_IMP(TestClass *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 12);
+    test_bootstrap_idempotence(runner);
+    test_simple_subclass(runner);
+    test_add_alias_to_registry(runner);
+    test_Get_Methods(runner);
+}
+

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

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestErr.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestErr.c b/runtime/test/Clownfish/Test/TestErr.c
new file mode 100644
index 0000000..46ecb1e
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestErr.c
@@ -0,0 +1,193 @@
+/* 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/TestErr.h"
+
+#include "Clownfish/String.h"
+#include "Clownfish/Err.h"
+#include "Clownfish/Test.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Clownfish/TestHarness/TestUtils.h"
+#include "Clownfish/Class.h"
+
+TestErr*
+TestErr_new() {
+    return (TestErr*)Class_Make_Obj(TESTERR);
+}
+
+static void
+test_To_String(TestBatchRunner *runner) {
+    String *message = Str_newf("oops");
+    Err *error = Err_new(message);
+    String *string = Err_To_String(error);
+    TEST_TRUE(runner, Str_Equals(message, (Obj*)string),
+              "Stringifies as message");
+    DECREF(string);
+    DECREF(error);
+}
+
+static void
+test_Cat_Mess(TestBatchRunner *runner) {
+    Err *error = Err_new(Str_newf("alpha"));
+    Err_Cat_Mess(error, SSTR_WRAP_C("\nbeta"));
+    String *mess = Err_Get_Mess(error);
+    TEST_TRUE(runner, Str_Equals_Utf8(mess, "alpha\nbeta", 10), "Cat_Mess");
+    DECREF(error);
+}
+
+static void
+test_Add_Frame(TestBatchRunner *runner) {
+    {
+        Err *error = Err_new(Str_newf("alpha"));
+        Err_Add_Frame(error, "source.c", 128, "function");
+        String *mess = Err_Get_Mess(error);
+        const char *expected = "alpha\n\tfunction at source.c line 128\n";
+        TEST_TRUE(runner, Str_Equals_Utf8(mess, expected, strlen(expected)),
+                  "Add_Frame");
+        DECREF(error);
+    }
+
+    {
+        Err *error = Err_new(Str_newf("alpha\n"));
+        Err_Add_Frame(error, "source.c", 128, "function");
+        String *mess = Err_Get_Mess(error);
+        const char *expected = "alpha\n\tfunction at source.c line 128\n";
+        TEST_TRUE(runner, Str_Equals_Utf8(mess, expected, strlen(expected)),
+                  "Add_Frame with trailing newline");
+        DECREF(error);
+    }
+
+    {
+        Err *error = Err_new(Str_newf("alpha"));
+        Err_Add_Frame(error, "source.c", 128, NULL);
+        String *mess = Err_Get_Mess(error);
+        const char *expected = "alpha\n\tat source.c line 128\n";
+        TEST_TRUE(runner, Str_Equals_Utf8(mess, expected, strlen(expected)),
+                  "Add_Frame without func");
+        DECREF(error);
+    }
+}
+
+static void
+S_rethrow(void *context) {
+    Err *error = (Err*)context;
+    Err_rethrow(error, "src.c", 12, "fn");
+}
+
+static void
+test_rethrow(TestBatchRunner *runner) {
+    Err *error = Err_new(Str_newf("error"));
+    Err *rethrown = Err_trap(S_rethrow, error);
+    String *mess = Err_Get_Mess(rethrown);
+    const char *expected = "error\n\tfn at src.c line 12\n";
+    TEST_TRUE(runner, Str_Starts_With_Utf8(mess, expected, strlen(expected)),
+              "rethrow");
+    DECREF(error);
+}
+
+static void
+S_invalid_downcast(void *context) {
+    Obj *obj = (Obj*)context;
+    DOWNCAST(obj, ERR);
+}
+
+static void
+test_downcast(TestBatchRunner *runner) {
+    Obj *obj = (Obj*)Str_newf("gamma");
+
+    TEST_TRUE(runner, DOWNCAST(obj, STRING) != NULL, "downcast");
+
+    TEST_TRUE(runner, DOWNCAST(NULL, STRING) == NULL, "downcast NULL");
+
+    Err *error = Err_trap(S_invalid_downcast, obj);
+    TEST_TRUE(runner, error != NULL, "downcast throws");
+    DECREF(error);
+
+    DECREF(obj);
+}
+
+static void
+S_invalid_certify(void *context) {
+    Obj *obj = (Obj*)context;
+    CERTIFY(obj, ERR);
+}
+
+static void
+test_certify(TestBatchRunner *runner) {
+    Obj *obj = (Obj*)Str_newf("epsilon");
+    Err *error;
+
+    TEST_TRUE(runner, CERTIFY(obj, STRING) != NULL, "certify");
+
+    error = Err_trap(S_invalid_certify, NULL);
+    TEST_TRUE(runner, error != NULL, "certify NULL");
+    DECREF(error);
+
+    error = Err_trap(S_invalid_certify, obj);
+    TEST_TRUE(runner, error != NULL, "certify throws");
+    DECREF(error);
+
+    DECREF(obj);
+}
+
+static void
+S_err_thread(void *arg) {
+    TestBatchRunner *runner = (TestBatchRunner*)arg;
+
+    TEST_TRUE(runner, Err_get_error() == NULL,
+              "global error in thread initialized to null");
+
+    Err_set_error(Err_new(Str_newf("thread")));
+    String *mess = Err_Get_Mess(Err_get_error());
+    TEST_TRUE(runner, Str_Equals_Utf8(mess, "thread", 6),
+              "set_error in thread works");
+}
+
+static void
+test_threads(TestBatchRunner *runner) {
+    if (!TestUtils_has_threads) {
+        SKIP(runner, 3, "no thread support");
+        return;
+    }
+
+    Err_set_error(Err_new(Str_newf("main")));
+
+    void *runtime = TestUtils_clone_host_runtime();
+    Thread *thread = TestUtils_thread_create(S_err_thread, runner, runtime);
+    TestUtils_thread_join(thread);
+    TestUtils_destroy_host_runtime(runtime);
+
+    String *mess = Err_Get_Mess(Err_get_error());
+    TEST_TRUE(runner, Str_Equals_Utf8(mess, "main", 4),
+              "thread doesn't clobber global error");
+}
+
+void
+TestErr_Run_IMP(TestErr *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 15);
+    test_To_String(runner);
+    test_Cat_Mess(runner);
+    test_Add_Frame(runner);
+    test_rethrow(runner);
+    test_downcast(runner);
+    test_certify(runner);
+    test_threads(runner);
+}
+
+

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

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestHash.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestHash.c b/runtime/test/Clownfish/Test/TestHash.c
new file mode 100644
index 0000000..b00d891
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestHash.c
@@ -0,0 +1,325 @@
+/* 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 <stdlib.h>
+#include <time.h>
+
+#define CFISH_USE_SHORT_NAMES
+#define TESTCFISH_USE_SHORT_NAMES
+#define C_CFISH_HASH
+
+#include "Clownfish/Test/TestHash.h"
+
+#include "Clownfish/String.h"
+#include "Clownfish/Boolean.h"
+#include "Clownfish/Hash.h"
+#include "Clownfish/Test.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Clownfish/TestHarness/TestUtils.h"
+#include "Clownfish/Vector.h"
+#include "Clownfish/Class.h"
+
+TestHash*
+TestHash_new() {
+    return (TestHash*)Class_Make_Obj(TESTHASH);
+}
+
+static void
+test_Equals(TestBatchRunner *runner) {
+    Hash *hash  = Hash_new(0);
+    Hash *other = Hash_new(0);
+    String *stuff = SSTR_WRAP_C("stuff");
+
+    TEST_TRUE(runner, Hash_Equals(hash, (Obj*)other),
+              "Empty hashes are equal");
+
+    Hash_Store_Utf8(hash, "foo", 3, (Obj*)CFISH_TRUE);
+    TEST_FALSE(runner, Hash_Equals(hash, (Obj*)other),
+               "Add one pair and Equals returns false");
+
+    Hash_Store_Utf8(other, "foo", 3, (Obj*)CFISH_TRUE);
+    TEST_TRUE(runner, Hash_Equals(hash, (Obj*)other),
+              "Add a matching pair and Equals returns true");
+
+    Hash_Store_Utf8(other, "foo", 3, INCREF(stuff));
+    TEST_FALSE(runner, Hash_Equals(hash, (Obj*)other),
+               "Non-matching value spoils Equals");
+
+    DECREF(hash);
+    DECREF(other);
+}
+
+static void
+test_Store_and_Fetch(TestBatchRunner *runner) {
+    Hash          *hash         = Hash_new(100);
+    Hash          *dupe         = Hash_new(100);
+    const size_t   starting_cap = Hash_Get_Capacity(hash);
+    Vector        *expected     = Vec_new(100);
+    Vector        *got          = Vec_new(100);
+    String        *twenty       = SSTR_WRAP_C("20");
+    String        *forty        = SSTR_WRAP_C("40");
+    String        *foo          = SSTR_WRAP_C("foo");
+
+    for (int32_t i = 0; i < 100; i++) {
+        String *str = Str_newf("%i32", i);
+        Hash_Store(hash, str, (Obj*)str);
+        Hash_Store(dupe, str, INCREF(str));
+        Vec_Push(expected, INCREF(str));
+    }
+    TEST_TRUE(runner, Hash_Equals(hash, (Obj*)dupe), "Equals");
+
+    TEST_UINT_EQ(runner, Hash_Get_Capacity(hash), starting_cap,
+                 "Initial capacity sufficient (no rebuilds)");
+
+    for (size_t i = 0; i < 100; i++) {
+        String *key  = (String*)Vec_Fetch(expected, i);
+        Obj    *elem = Hash_Fetch(hash, key);
+        Vec_Push(got, (Obj*)INCREF(elem));
+    }
+
+    TEST_TRUE(runner, Vec_Equals(got, (Obj*)expected),
+              "basic Store and Fetch");
+    TEST_UINT_EQ(runner, Hash_Get_Size(hash), 100,
+                 "size incremented properly by Hash_Store");
+
+    TEST_TRUE(runner, Hash_Fetch(hash, foo) == NULL,
+              "Fetch against non-existent key returns NULL");
+
+    String *twelve = (String*)Hash_Fetch_Utf8(hash, "12", 2);
+    TEST_TRUE(runner, Str_Equals_Utf8(twelve, "12", 2), "Fetch_Utf8");
+
+    Obj *stored_foo = INCREF(foo);
+    Hash_Store(hash, forty, stored_foo);
+    TEST_TRUE(runner, Str_Equals(foo, Hash_Fetch(hash, forty)),
+              "Hash_Store replaces existing value");
+    TEST_FALSE(runner, Hash_Equals(hash, (Obj*)dupe),
+               "replacement value spoils equals");
+    TEST_UINT_EQ(runner, Hash_Get_Size(hash), 100,
+                 "size unaffected after value replaced");
+
+    TEST_TRUE(runner, Hash_Delete(hash, forty) == stored_foo,
+              "Delete returns value");
+    DECREF(stored_foo);
+    TEST_UINT_EQ(runner, Hash_Get_Size(hash), 99,
+                 "size decremented by successful Delete");
+    TEST_TRUE(runner, Hash_Delete(hash, forty) == NULL,
+              "Delete returns NULL when key not found");
+    TEST_UINT_EQ(runner, Hash_Get_Size(hash), 99,
+                 "size not decremented by unsuccessful Delete");
+    DECREF(Hash_Delete(dupe, forty));
+    TEST_TRUE(runner, Vec_Equals(got, (Obj*)expected), "Equals after Delete");
+
+    Obj *forty_one = Hash_Delete_Utf8(hash, "41", 2);
+    TEST_TRUE(runner, forty_one != NULL, "Delete_Utf8");
+    TEST_UINT_EQ(runner, Hash_Get_Size(hash), 98,
+                 "Delete_Utf8 decrements size");
+    DECREF(forty_one);
+
+    Hash_Clear(hash);
+    TEST_TRUE(runner, Hash_Fetch(hash, twenty) == NULL, "Clear");
+    TEST_TRUE(runner, Hash_Get_Size(hash) == 0, "size is 0 after Clear");
+
+    Hash_Clear(hash);
+    Hash_Store(hash, forty, NULL);
+    TEST_TRUE(runner, Hash_Fetch(hash, forty) == NULL, "Store NULL");
+    TEST_TRUE(runner, Hash_Get_Size(hash) == 1, "Size after Store NULL");
+    TEST_TRUE(runner, Hash_Delete(hash, forty) == NULL, "Delete NULL value");
+    TEST_TRUE(runner, Hash_Get_Size(hash) == 0,
+              "Size after Deleting NULL val");
+
+    DECREF(hash);
+    DECREF(dupe);
+    DECREF(got);
+    DECREF(expected);
+}
+
+static void
+test_Keys_Values(TestBatchRunner *runner) {
+    Hash     *hash     = Hash_new(0); // trigger multiple rebuilds.
+    Vector   *expected = Vec_new(100);
+    Vector   *keys;
+    Vector   *values;
+
+    for (uint32_t i = 0; i < 500; i++) {
+        String *str = Str_newf("%u32", i);
+        Hash_Store(hash, str, (Obj*)str);
+        Vec_Push(expected, INCREF(str));
+    }
+
+    Vec_Sort(expected);
+
+    keys   = Hash_Keys(hash);
+    values = Hash_Values(hash);
+    Vec_Sort(keys);
+    Vec_Sort(values);
+    TEST_TRUE(runner, Vec_Equals(keys, (Obj*)expected), "Keys");
+    TEST_TRUE(runner, Vec_Equals(values, (Obj*)expected), "Values");
+    Vec_Clear(keys);
+    Vec_Clear(values);
+
+    {
+        String *forty = SSTR_WRAP_C("40");
+        String *nope  = SSTR_WRAP_C("nope");
+        TEST_TRUE(runner, Hash_Has_Key(hash, forty), "Has_Key");
+        TEST_FALSE(runner, Hash_Has_Key(hash, nope),
+                   "Has_Key returns false for non-existent key");
+    }
+
+    DECREF(hash);
+    DECREF(expected);
+    DECREF(keys);
+    DECREF(values);
+}
+
+static void
+test_stress(TestBatchRunner *runner) {
+    Hash     *hash     = Hash_new(0); // trigger multiple rebuilds.
+    Vector   *expected = Vec_new(1000);
+    Vector   *keys;
+    Vector   *values;
+
+    for (uint32_t i = 0; i < 1000; i++) {
+        String *str = TestUtils_random_string((size_t)(rand() % 1200));
+        while (Hash_Fetch(hash, str)) {
+            DECREF(str);
+            str = TestUtils_random_string((size_t)(rand() % 1200));
+        }
+        Hash_Store(hash, str, (Obj*)str);
+        Vec_Push(expected, INCREF(str));
+    }
+
+    Vec_Sort(expected);
+
+    // Overwrite for good measure.
+    for (uint32_t i = 0; i < 1000; i++) {
+        String *str = (String*)Vec_Fetch(expected, i);
+        Hash_Store(hash, str, INCREF(str));
+    }
+
+    keys   = Hash_Keys(hash);
+    values = Hash_Values(hash);
+    Vec_Sort(keys);
+    Vec_Sort(values);
+    TEST_TRUE(runner, Vec_Equals(keys, (Obj*)expected), "stress Keys");
+    TEST_TRUE(runner, Vec_Equals(values, (Obj*)expected), "stress Values");
+
+    DECREF(keys);
+    DECREF(values);
+    DECREF(expected);
+    DECREF(hash);
+}
+
+static void
+test_collision(TestBatchRunner *runner) {
+    Hash   *hash = Hash_new(0);
+    String *one  = Str_newf("A");
+    String *two  = Str_newf("P{2}|=~-ULE/d");
+
+    TEST_TRUE(runner, Str_Hash_Sum(one) == Str_Hash_Sum(two),
+              "Keys have the same hash sum");
+
+    Hash_Store(hash, one, INCREF(one));
+    Hash_Store(hash, two, INCREF(two));
+    String *elem = (String*)Hash_Fetch(hash, two);
+    TEST_TRUE(runner, elem == two, "Fetch works with collisions");
+
+    DECREF(one);
+    DECREF(two);
+    DECREF(hash);
+}
+
+static void
+test_store_skips_tombstone(TestBatchRunner *runner) {
+    Hash *hash = Hash_new(0);
+    size_t mask = Hash_Get_Capacity(hash) - 1;
+
+    String *one = Str_newf("one");
+    size_t slot = Str_Hash_Sum(one) & mask;
+
+    // Find a colliding key.
+    String *two = NULL;
+    for (int i = 0; i < 100000; i++) {
+        two = Str_newf("%i32", i);
+        if (slot == (Str_Hash_Sum(two) & mask)) {
+            break;
+        }
+        DECREF(two);
+        two = NULL;
+    }
+
+    Hash_Store(hash, one, (Obj*)CFISH_TRUE);
+    Hash_Store(hash, two, (Obj*)CFISH_TRUE);
+    Hash_Delete(hash, one);
+    Hash_Store(hash, two, (Obj*)CFISH_TRUE);
+
+    TEST_UINT_EQ(runner, Hash_Get_Size(hash), 1, "Store skips tombstone");
+
+    DECREF(one);
+    DECREF(two);
+    DECREF(hash);
+}
+
+static void
+test_threshold_accounting(TestBatchRunner *runner) {
+    Hash   *hash = Hash_new(20);
+    String *key  = Str_newf("key");
+
+    size_t threshold = hash->threshold;
+    Hash_Store(hash, key, (Obj*)CFISH_TRUE);
+    Hash_Delete(hash, key);
+    TEST_UINT_EQ(runner, hash->threshold, threshold - 1,
+                 "Tombstone creation decreases threshold");
+
+    Hash_Store(hash, key, (Obj*)CFISH_TRUE);
+    TEST_UINT_EQ(runner, hash->threshold, threshold,
+                 "Tombstone destruction increases threshold");
+
+    DECREF(key);
+    DECREF(hash);
+}
+
+static void
+test_tombstone_identification(TestBatchRunner *runner) {
+    Hash   *hash = Hash_new(20);
+    String *key  = Str_newf("P{2}|=~-U@!y>");
+
+    // Tombstones have a zero hash_sum.
+    TEST_UINT_EQ(runner, Str_Hash_Sum(key), 0, "Key has zero hash sum");
+
+    Hash_Store(hash, key, (Obj*)CFISH_TRUE);
+    Hash_Delete(hash, key);
+    TEST_TRUE(runner, Hash_Fetch(hash, key) == NULL,
+              "Key with zero hash sum isn't mistaken for tombstone");
+
+    DECREF(key);
+    DECREF(hash);
+}
+
+void
+TestHash_Run_IMP(TestHash *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 39);
+    srand((unsigned int)time((time_t*)NULL));
+    test_Equals(runner);
+    test_Store_and_Fetch(runner);
+    test_Keys_Values(runner);
+    test_stress(runner);
+    test_collision(runner);
+    test_store_skips_tombstone(runner);
+    test_threshold_accounting(runner);
+    test_tombstone_identification(runner);
+}
+
+

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

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestHashIterator.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestHashIterator.c b/runtime/test/Clownfish/Test/TestHashIterator.c
new file mode 100644
index 0000000..fac7d69
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestHashIterator.c
@@ -0,0 +1,253 @@
+/* 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 <stdlib.h>
+#include <time.h>
+
+#define CFISH_USE_SHORT_NAMES
+#define TESTCFISH_USE_SHORT_NAMES
+
+#include "Clownfish/Test/TestHashIterator.h"
+
+#include "Clownfish/Err.h"
+#include "Clownfish/String.h"
+#include "Clownfish/Hash.h"
+#include "Clownfish/HashIterator.h"
+#include "Clownfish/Test.h"
+#include "Clownfish/Vector.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Clownfish/TestHarness/TestUtils.h"
+#include "Clownfish/Class.h"
+
+TestHashIterator*
+TestHashIterator_new() {
+    return (TestHashIterator*)Class_Make_Obj(TESTHASHITERATOR);
+}
+
+static void
+test_Next(TestBatchRunner *runner) {
+    Hash     *hash     = Hash_new(0); // trigger multiple rebuilds.
+    Vector   *expected = Vec_new(100);
+    Vector   *keys     = Vec_new(500);
+    Vector   *values   = Vec_new(500);
+
+    for (uint32_t i = 0; i < 500; i++) {
+        String *str = Str_newf("%u32", i);
+        Hash_Store(hash, str, (Obj*)str);
+        Vec_Push(expected, INCREF(str));
+    }
+
+    Vec_Sort(expected);
+
+    {
+        HashIterator *iter = HashIter_new(hash);
+        while (HashIter_Next(iter)) {
+            String *key = HashIter_Get_Key(iter);
+            Obj *value = HashIter_Get_Value(iter);
+            Vec_Push(keys, INCREF(key));
+            Vec_Push(values, INCREF(value));
+        }
+        TEST_TRUE(runner, !HashIter_Next(iter),
+                  "Next continues to return false after iteration finishes.");
+
+        DECREF(iter);
+    }
+
+    Vec_Sort(keys);
+    Vec_Sort(values);
+    TEST_TRUE(runner, Vec_Equals(keys, (Obj*)expected), "Keys from Iter");
+    TEST_TRUE(runner, Vec_Equals(values, (Obj*)expected), "Values from Iter");
+
+    DECREF(hash);
+    DECREF(expected);
+    DECREF(keys);
+    DECREF(values);
+}
+
+static void
+S_invoke_Next(void *context) {
+    HashIterator *iter = (HashIterator*)context;
+    HashIter_Next(iter);
+}
+
+static void
+S_invoke_Get_Key(void *context) {
+    HashIterator *iter = (HashIterator*)context;
+    HashIter_Get_Key(iter);
+}
+
+static void
+S_invoke_Get_Value(void *context) {
+    HashIterator *iter = (HashIterator*)context;
+    HashIter_Get_Value(iter);
+}
+
+static void
+test_empty(TestBatchRunner *runner) {
+    Hash         *hash = Hash_new(0);
+    HashIterator *iter = HashIter_new(hash);
+
+    TEST_TRUE(runner, !HashIter_Next(iter),
+              "First call to next false on empty hash iteration");
+
+    Err *get_key_error = Err_trap(S_invoke_Get_Key, iter);
+    TEST_TRUE(runner, get_key_error != NULL,
+              "Get_Key throws exception on empty hash.");
+    DECREF(get_key_error);
+
+    Err *get_value_error = Err_trap(S_invoke_Get_Value, iter);
+    TEST_TRUE(runner, get_value_error != NULL,
+              "Get_Value throws exception on empty hash.");
+    DECREF(get_value_error);
+
+    DECREF(hash);
+    DECREF(iter);
+}
+
+static void
+test_Get_Key_and_Get_Value(TestBatchRunner *runner) {
+    Hash   *hash = Hash_new(0);
+    String *str  = Str_newf("foo");
+    Hash_Store(hash, str, (Obj*)str);
+    bool ok;
+
+    HashIterator *iter = HashIter_new(hash);
+    DECREF(hash);
+
+    Err *get_key_error = Err_trap(S_invoke_Get_Key, iter);
+    TEST_TRUE(runner, get_key_error != NULL,
+              "Get_Key throws exception before first call to Next.");
+    ok = Str_Contains_Utf8(Err_Get_Mess(get_key_error), "before", 6);
+    TEST_TRUE(runner, ok, "Get_Key before Next throws correct message");
+    DECREF(get_key_error);
+
+    Err *get_value_error = Err_trap(S_invoke_Get_Value, iter);
+    TEST_TRUE(runner, get_value_error != NULL,
+              "Get_Value throws exception before first call to Next.");
+    ok = Str_Contains_Utf8(Err_Get_Mess(get_value_error), "before", 6);
+    TEST_TRUE(runner, ok, "Get_Value before Next throws correct message");
+    DECREF(get_value_error);
+
+    HashIter_Next(iter);
+    TEST_TRUE(runner, HashIter_Get_Key(iter) != NULL,
+              "Get_Key during iteration.");
+    TEST_TRUE(runner, HashIter_Get_Value(iter) != NULL,
+              "Get_Value during iteration.");
+
+    HashIter_Next(iter);
+    get_key_error = Err_trap(S_invoke_Get_Key, iter);
+    TEST_TRUE(runner, get_key_error != NULL,
+              "Get_Key throws exception after end of iteration.");
+    ok = Str_Contains_Utf8(Err_Get_Mess(get_key_error), "after", 5);
+    TEST_TRUE(runner, ok, "Get_Key after end throws correct message");
+    DECREF(get_key_error);
+
+    get_value_error = Err_trap(S_invoke_Get_Value, iter);
+    TEST_TRUE(runner, get_value_error != NULL,
+              "Get_Value throws exception after end of iteration.");
+    ok = Str_Contains_Utf8(Err_Get_Mess(get_value_error), "after", 5);
+    TEST_TRUE(runner, ok, "Get_Value after end throws correct message");
+    DECREF(get_value_error);
+
+
+    DECREF(iter);
+}
+
+static void
+test_illegal_modification(TestBatchRunner *runner) {
+    Hash *hash = Hash_new(0);
+
+    for (uint32_t i = 0; i < 3; i++) {
+        String *str = Str_newf("%u32", i);
+        Hash_Store(hash, str, (Obj*)str);
+    }
+
+    HashIterator *iter = HashIter_new(hash);
+    HashIter_Next(iter);
+
+    for (uint32_t i = 0; i < 100; i++) {
+        String *str = Str_newf("foo %u32", i);
+        Hash_Store(hash, str, (Obj*)str);
+    }
+
+    Err *next_error = Err_trap(S_invoke_Next, iter);
+    TEST_TRUE(runner, next_error != NULL,
+              "Next on resized hash throws exception.");
+    DECREF(next_error);
+
+    Err *get_key_error = Err_trap(S_invoke_Get_Key, iter);
+    TEST_TRUE(runner, get_key_error != NULL,
+              "Get_Key on resized hash throws exception.");
+    DECREF(get_key_error);
+
+    Err *get_value_error = Err_trap(S_invoke_Get_Value, iter);
+    TEST_TRUE(runner, get_value_error != NULL,
+              "Get_Value on resized hash throws exception.");
+    DECREF(get_value_error);
+
+    DECREF(hash);
+    DECREF(iter);
+}
+
+static void
+test_tombstone(TestBatchRunner *runner) {
+    {
+        Hash   *hash = Hash_new(0);
+        String *str  = Str_newf("foo");
+        Hash_Store(hash, str, INCREF(str));
+        DECREF(Hash_Delete(hash, str));
+        DECREF(str);
+
+        HashIterator *iter = HashIter_new(hash);
+        TEST_TRUE(runner, !HashIter_Next(iter), "Next advances past tombstones.");
+
+        DECREF(iter);
+        DECREF(hash);
+    }
+
+    {
+        Hash   *hash = Hash_new(0);
+        String *str  = Str_newf("foo");
+        Hash_Store(hash, str, INCREF(str));
+
+        HashIterator *iter = HashIter_new(hash);
+        HashIter_Next(iter);
+        DECREF(Hash_Delete(hash, str));
+
+
+        Err *get_key_error = Err_trap(S_invoke_Get_Key, iter);
+        TEST_TRUE(runner, get_key_error != NULL,
+                  "Get_Key doesn't return tombstone and throws error.");
+        DECREF(get_key_error);
+
+        DECREF(str);
+        DECREF(iter);
+        DECREF(hash);
+    }
+}
+
+void
+TestHashIterator_Run_IMP(TestHashIterator *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 21);
+    srand((unsigned int)time((time_t*)NULL));
+    test_Next(runner);
+    test_empty(runner);
+    test_Get_Key_and_Get_Value(runner);
+    test_illegal_modification(runner);
+    test_tombstone(runner);
+}
+
+

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

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestHost.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestHost.c b/runtime/test/Clownfish/Test/TestHost.c
new file mode 100644
index 0000000..6da0efa
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestHost.c
@@ -0,0 +1,125 @@
+/* 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/TestHost.h"
+#include "Clownfish/Class.h"
+#include "Clownfish/String.h"
+
+TestHost*
+TestHost_new() {
+    return (TestHost*)Class_Make_Obj(TESTHOST);
+}
+
+Obj*
+TestHost_Test_Obj_Pos_Arg_IMP(TestHost *self, Obj *arg) {
+    UNUSED_VAR(self);
+    return arg;
+}
+
+Obj*
+TestHost_Test_Obj_Pos_Arg_Def_IMP(TestHost *self, Obj *arg) {
+    UNUSED_VAR(self);
+    return arg;
+}
+
+Obj*
+TestHost_Test_Obj_Label_Arg_IMP(TestHost *self, Obj *arg, bool unused) {
+    UNUSED_VAR(self);
+    UNUSED_VAR(unused);
+    return arg;
+}
+
+Obj*
+TestHost_Test_Obj_Label_Arg_Def_IMP(TestHost *self, Obj *arg, bool unused) {
+    UNUSED_VAR(self);
+    UNUSED_VAR(unused);
+    return arg;
+}
+
+int32_t
+TestHost_Test_Int32_Pos_Arg_IMP(TestHost *self, int32_t arg) {
+    UNUSED_VAR(self);
+    return arg;
+}
+
+int32_t
+TestHost_Test_Int32_Pos_Arg_Def_IMP(TestHost *self, int32_t arg) {
+    UNUSED_VAR(self);
+    return arg;
+}
+
+int32_t
+TestHost_Test_Int32_Label_Arg_IMP(TestHost *self, int32_t arg, bool unused) {
+    UNUSED_VAR(self);
+    UNUSED_VAR(unused);
+    return arg;
+}
+
+int32_t
+TestHost_Test_Int32_Label_Arg_Def_IMP(TestHost *self, int32_t arg,
+                                      bool unused) {
+    UNUSED_VAR(self);
+    UNUSED_VAR(unused);
+    return arg;
+}
+
+bool
+TestHost_Test_Bool_Pos_Arg_IMP(TestHost *self, bool arg) {
+    UNUSED_VAR(self);
+    return arg;
+}
+
+bool
+TestHost_Test_Bool_Pos_Arg_Def_IMP(TestHost *self, bool arg) {
+    UNUSED_VAR(self);
+    return arg;
+}
+
+bool
+TestHost_Test_Bool_Label_Arg_IMP(TestHost *self, bool arg, bool unused) {
+    UNUSED_VAR(self);
+    UNUSED_VAR(unused);
+    return arg;
+}
+
+bool
+TestHost_Test_Bool_Label_Arg_Def_IMP(TestHost *self, bool arg, bool unused) {
+    UNUSED_VAR(self);
+    UNUSED_VAR(unused);
+    return arg;
+}
+
+void
+TestHost_Invoke_Invalid_Callback_From_C_IMP(TestHost *self) {
+    TestHost_Invalid_Callback(self);
+}
+
+String*
+TestHost_Aliased_IMP(TestHost *self) {
+    UNUSED_VAR(self);
+    return Str_newf("C");
+}
+
+String*
+TestHost_Invoke_Aliased_From_C_IMP(TestHost *self) {
+    UNUSED_VAR(self);
+    return TestHost_Aliased(self);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestHost.cfh
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestHost.cfh b/runtime/test/Clownfish/Test/TestHost.cfh
new file mode 100644
index 0000000..92bc272
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestHost.cfh
@@ -0,0 +1,81 @@
+/* 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.
+ */
+class Clownfish::Test::TestHost {
+    inert incremented TestHost*
+    new();
+
+    Obj*
+    Test_Obj_Pos_Arg(TestHost *self, Obj *arg);
+
+    Obj*
+    Test_Obj_Pos_Arg_Def(TestHost *self, nullable Obj *arg = NULL);
+
+    Obj*
+    Test_Obj_Label_Arg(TestHost *self, Obj *arg, bool unused = false);
+
+    Obj*
+    Test_Obj_Label_Arg_Def(TestHost *self, nullable Obj *arg = NULL,
+                           bool unused = false);
+
+    int32_t
+    Test_Int32_Pos_Arg(TestHost *self, int32_t arg);
+
+    int32_t
+    Test_Int32_Pos_Arg_Def(TestHost *self, int32_t arg = 101);
+
+    int32_t
+    Test_Int32_Label_Arg(TestHost *self, int32_t arg, bool unused = false);
+
+    int32_t
+    Test_Int32_Label_Arg_Def(TestHost *self, int32_t arg = 101,
+                             bool unused = false);
+
+    bool
+    Test_Bool_Pos_Arg(TestHost *self, bool arg);
+
+    bool
+    Test_Bool_Pos_Arg_Def(TestHost *self, bool arg = true);
+
+    bool
+    Test_Bool_Label_Arg(TestHost *self, bool arg, bool unused = false);
+
+    bool
+    Test_Bool_Label_Arg_Def(TestHost *self, bool arg = true,
+                            bool unused = false);
+
+    /** A method that can't be overridden from the host language.
+     */
+    abstract void*
+    Invalid_Callback(TestHost *self);
+
+    void
+    Invoke_Invalid_Callback_From_C(TestHost *self);
+
+    /** A method with a custom host language alias.
+     */
+    incremented String*
+    Aliased(TestHost *self);
+
+    incremented String*
+    Invoke_Aliased_From_C(TestHost* self);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestLockFreeRegistry.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestLockFreeRegistry.c b/runtime/test/Clownfish/Test/TestLockFreeRegistry.c
new file mode 100644
index 0000000..b70ff1f
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestLockFreeRegistry.c
@@ -0,0 +1,168 @@
+/* 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 <stdlib.h>
+#include <string.h>
+
+#define CFISH_USE_SHORT_NAMES
+#define TESTCFISH_USE_SHORT_NAMES
+
+#include "Clownfish/Test/TestLockFreeRegistry.h"
+
+#include "Clownfish/Class.h"
+#include "Clownfish/LockFreeRegistry.h"
+#include "Clownfish/String.h"
+#include "Clownfish/Test.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Clownfish/TestHarness/TestUtils.h"
+#include "Clownfish/Util/Memory.h"
+
+#define NUM_THREADS 5
+
+typedef struct ThreadArgs {
+    LockFreeRegistry *registry;
+    uint32_t         *nums;
+    uint32_t          num_objs;
+    uint64_t          target_time;
+    uint32_t          succeeded;
+} ThreadArgs;
+
+TestLockFreeRegistry*
+TestLFReg_new() {
+    return (TestLockFreeRegistry*)Class_Make_Obj(TESTLOCKFREEREGISTRY);
+}
+
+static void
+test_all(TestBatchRunner *runner) {
+    LockFreeRegistry *registry = LFReg_new(1);
+    String *foo = Str_newf("foo");
+    String *bar = Str_newf("bar");
+    String *baz = Str_newf("baz");
+    String *foo_dupe = Str_newf("foo");
+
+    TEST_TRUE(runner, LFReg_register(registry, foo, (Obj*)foo),
+              "Register() returns true on success");
+    TEST_FALSE(runner,
+               LFReg_register(registry, foo_dupe, (Obj*)foo_dupe),
+               "Can't Register() keys that test equal");
+
+    TEST_TRUE(runner, LFReg_register(registry, bar, (Obj*)bar),
+              "Register() key with the same Hash_Sum but that isn't Equal");
+
+    TEST_TRUE(runner, LFReg_fetch(registry, foo_dupe) == (Obj*)foo,
+              "Fetch()");
+    TEST_TRUE(runner, LFReg_fetch(registry, bar) == (Obj*)bar,
+              "Fetch() again");
+    TEST_TRUE(runner, LFReg_fetch(registry, baz) == NULL,
+              "Fetch() non-existent key returns NULL");
+
+    DECREF(foo_dupe);
+    DECREF(baz);
+    DECREF(bar);
+    DECREF(foo);
+    LFReg_destroy(registry);
+}
+
+static void
+S_register_many(void *varg) {
+    ThreadArgs *args = (ThreadArgs*)varg;
+
+    // Encourage contention, so that all threads try to register at the same
+    // time.
+
+    // Sleep until target_time.
+    uint64_t time = TestUtils_time();
+    if (args->target_time > time) {
+        TestUtils_usleep(args->target_time - time);
+    }
+
+    TestUtils_thread_yield();
+
+    uint32_t succeeded = 0;
+    for (uint32_t i = 0; i < args->num_objs; i++) {
+        String *obj = Str_newf("%u32", args->nums[i]);
+        if (LFReg_register(args->registry, obj, (Obj*)obj)) {
+            succeeded++;
+        }
+        DECREF(obj);
+    }
+
+    args->succeeded = succeeded;
+}
+
+static void
+test_threads(TestBatchRunner *runner) {
+    if (!TestUtils_has_threads) {
+        SKIP(runner, 1, "No thread support");
+        return;
+    }
+
+    LockFreeRegistry *registry = LFReg_new(32);
+    ThreadArgs thread_args[NUM_THREADS];
+    uint32_t num_objs = 10000;
+
+    for (uint32_t i = 0; i < NUM_THREADS; i++) {
+        uint32_t *nums = (uint32_t*)MALLOCATE(num_objs * sizeof(uint32_t));
+
+        for (uint32_t j = 0; j < num_objs; j++) {
+            nums[j] = j;
+        }
+
+        // Fisher-Yates shuffle.
+        for (uint32_t j = num_objs - 1; j > 0; j--) {
+            uint32_t r = (uint32_t)TestUtils_random_u64() % (j + 1);
+            uint32_t tmp = nums[j];
+            nums[j] = nums[r];
+            nums[r] = tmp;
+        }
+
+        thread_args[i].registry = registry;
+        thread_args[i].nums     = nums;
+        thread_args[i].num_objs = num_objs;
+    }
+
+    Thread *threads[NUM_THREADS];
+    uint64_t target_time = TestUtils_time() + 200 * 1000;
+
+    for (uint32_t i = 0; i < NUM_THREADS; i++) {
+        thread_args[i].target_time = target_time;
+        threads[i]
+            = TestUtils_thread_create(S_register_many, &thread_args[i], NULL);
+    }
+
+    uint32_t total_succeeded = 0;
+
+    for (uint32_t i = 0; i < NUM_THREADS; i++) {
+        TestUtils_thread_join(threads[i]);
+        total_succeeded += thread_args[i].succeeded;
+        FREEMEM(thread_args[i].nums);
+    }
+
+    TEST_INT_EQ(runner, total_succeeded, num_objs,
+                "registered exactly the right number of entries across all"
+                " threads");
+
+    LFReg_destroy(registry);
+}
+
+void
+TestLFReg_Run_IMP(TestLockFreeRegistry *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 7);
+    test_all(runner);
+    test_threads(runner);
+}
+
+

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

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestMethod.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestMethod.c b/runtime/test/Clownfish/Test/TestMethod.c
new file mode 100644
index 0000000..7c39759
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestMethod.c
@@ -0,0 +1,91 @@
+/* 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/TestMethod.h"
+
+#include "Clownfish/Err.h"
+#include "Clownfish/Method.h"
+#include "Clownfish/String.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+
+TestMethod*
+TestMethod_new() {
+    return (TestMethod*)Class_Make_Obj(TESTMETHOD);
+}
+
+static void
+S_set_host_alias(void *context) {
+    Method *method = (Method*)context;
+    Method_Set_Host_Alias(method, SSTR_WRAP_C("foo"));
+}
+
+static void
+test_accessors(TestBatchRunner *runner) {
+    String *name = SSTR_WRAP_C("Frobnicate_Widget");
+    Method *method = Method_new(name, NULL, 0);
+
+    TEST_TRUE(runner, Str_Equals(Method_Get_Name(method), (Obj*)name),
+              "Get_Name");
+
+    String *alias = SSTR_WRAP_C("host_frob");
+    Method_Set_Host_Alias(method, alias);
+    TEST_TRUE(runner, Str_Equals(Method_Get_Host_Alias(method), (Obj*)alias),
+              "Set_Host_Alias");
+    Err *error = Err_trap(S_set_host_alias, method);
+    TEST_TRUE(runner, error != NULL,
+              "Set_Host_Alias can't be called more than once");
+    DECREF(error);
+
+    TEST_FALSE(runner, Method_Is_Excluded_From_Host(method),
+               "Is_Excluded_From_Host");
+
+    Method_Destroy(method);
+}
+
+static void
+test_lower_snake_alias(TestBatchRunner *runner) {
+    String *name = SSTR_WRAP_C("Frobnicate_Widget");
+    Method *method = Method_new(name, NULL, 0);
+
+    {
+        String *alias = Method_lower_snake_alias(method);
+        TEST_TRUE(runner, Str_Equals_Utf8(alias, "frobnicate_widget", 17),
+                  "lower_snake_alias without explicit alias");
+        DECREF(alias);
+    }
+
+    {
+        String *new_alias = SSTR_WRAP_C("host_frob");
+        Method_Set_Host_Alias(method, new_alias);
+        String *alias = Method_lower_snake_alias(method);
+        TEST_TRUE(runner, Str_Equals(alias, (Obj*)new_alias),
+                  "lower_snake_alias with explicit alias");
+        DECREF(alias);
+    }
+
+    Method_Destroy(method);
+}
+
+void
+TestMethod_Run_IMP(TestMethod *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 6);
+    test_accessors(runner);
+    test_lower_snake_alias(runner);
+}
+

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

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestNum.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestNum.c b/runtime/test/Clownfish/Test/TestNum.c
new file mode 100644
index 0000000..dfa6769
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestNum.c
@@ -0,0 +1,278 @@
+/* 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 <math.h>
+
+#include "charmony.h"
+
+#include "Clownfish/Test/TestNum.h"
+
+#include "Clownfish/Err.h"
+#include "Clownfish/String.h"
+#include "Clownfish/Num.h"
+#include "Clownfish/Test.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Clownfish/TestHarness/TestUtils.h"
+#include "Clownfish/Class.h"
+
+TestNum*
+TestNum_new() {
+    return (TestNum*)Class_Make_Obj(TESTNUM);
+}
+
+static void
+test_To_String(TestBatchRunner *runner) {
+    Float   *f64 = Float_new(1.33);
+    Integer *i64 = Int_new(INT64_MAX);
+    String *f64_string = Float_To_String(f64);
+    String *i64_string = Int_To_String(i64);
+
+    TEST_TRUE(runner, Str_Starts_With_Utf8(f64_string, "1.3", 3),
+              "Float_To_String");
+    TEST_TRUE(runner, Str_Equals_Utf8(i64_string, "9223372036854775807", 19),
+              "Int_To_String");
+
+    DECREF(i64_string);
+    DECREF(f64_string);
+    DECREF(i64);
+    DECREF(f64);
+}
+
+static void
+S_float_to_i64(void *context) {
+    Float *f = (Float*)context;
+    Float_To_I64(f);
+}
+
+static void
+test_accessors(TestBatchRunner *runner) {
+    Float   *f64 = Float_new(1.33);
+    Integer *i64 = Int_new(INT64_MIN);
+    double wanted64 = 1.33;
+    double got64;
+
+    got64 = Float_Get_Value(f64);
+    TEST_TRUE(runner, *(int64_t*)&got64 == *(int64_t*)&wanted64,
+              "F64 Get_Value");
+    TEST_TRUE(runner, Float_To_I64(f64) == 1, "Float_To_I64");
+
+    {
+        Float *huge = Float_new(1e40);
+        Err *error = Err_trap(S_float_to_i64, huge);
+        TEST_TRUE(runner, error != NULL,
+                  "Float_To_I64 throws when out of range (+)");
+        DECREF(error);
+        DECREF(huge);
+    }
+
+    {
+        Float *huge = Float_new(-1e40);
+        Err *error = Err_trap(S_float_to_i64, huge);
+        TEST_TRUE(runner, error != NULL,
+                  "Float_To_I64 throws when out of range (-)");
+        DECREF(error);
+        DECREF(huge);
+    }
+
+    TEST_TRUE(runner, Int_Get_Value(i64) == INT64_MIN, "I64 Get_Value");
+    TEST_TRUE(runner, Int_To_F64(i64) == -9223372036854775808.0, "Int_To_F64");
+
+    DECREF(i64);
+    DECREF(f64);
+}
+
+static void
+S_test_compare_float_int(TestBatchRunner *runner, double f64_val,
+                         int64_t i64_val, int32_t result) {
+    Float *f64;
+    Integer *i64;
+
+    f64 = Float_new(f64_val);
+    i64 = Int_new(i64_val);
+    TEST_INT_EQ(runner, Float_Compare_To(f64, (Obj*)i64), result,
+                "Float_Compare_To %f %" PRId64, f64_val, i64_val);
+    TEST_INT_EQ(runner, Int_Compare_To(i64, (Obj*)f64), -result,
+                "Int_Compare_To %" PRId64" %f", i64_val, f64_val);
+    TEST_INT_EQ(runner, Float_Equals(f64, (Obj*)i64), result == 0,
+                "Float_Equals %f %" PRId64, f64_val, i64_val);
+    TEST_INT_EQ(runner, Int_Equals(i64, (Obj*)f64), result == 0,
+                "Int_Equals %" PRId64 " %f", i64_val, f64_val);
+    DECREF(f64);
+    DECREF(i64);
+
+    if (i64_val == INT64_MIN) { return; }
+
+    f64 = Float_new(-f64_val);
+    i64 = Int_new(-i64_val);
+    TEST_INT_EQ(runner, Float_Compare_To(f64, (Obj*)i64), -result,
+                "Float_Compare_To %f %" PRId64, -f64_val, -i64_val);
+    TEST_INT_EQ(runner, Int_Compare_To(i64, (Obj*)f64), result,
+                "Int_Compare_To %" PRId64" %f", -i64_val, -f64_val);
+    TEST_INT_EQ(runner, Float_Equals(f64, (Obj*)i64), result == 0,
+                "Float_Equals %f %" PRId64, -f64_val, -i64_val);
+    TEST_INT_EQ(runner, Int_Equals(i64, (Obj*)f64), result == 0,
+                "Int_Equals %" PRId64 " %f", -i64_val, -f64_val);
+    DECREF(f64);
+    DECREF(i64);
+}
+
+static void
+S_float_compare_to(void *context) {
+    Float *f = (Float*)context;
+    Float_Compare_To(f, (Obj*)OBJ);
+}
+
+static void
+S_int_compare_to(void *context) {
+    Integer *i = (Integer*)context;
+    Int_Compare_To(i, (Obj*)OBJ);
+}
+
+static void
+test_Equals_and_Compare_To(TestBatchRunner *runner) {
+    {
+        Float *f1 = Float_new(1.0);
+        Float *f2 = Float_new(1.0);
+        TEST_TRUE(runner, Float_Compare_To(f1, (Obj*)f2) == 0,
+                  "Float_Compare_To equal");
+        TEST_TRUE(runner, Float_Equals(f1, (Obj*)f2),
+                  "Float_Equals equal");
+        DECREF(f1);
+        DECREF(f2);
+    }
+
+    {
+        Float *f1 = Float_new(1.0);
+        Float *f2 = Float_new(2.0);
+        TEST_TRUE(runner, Float_Compare_To(f1, (Obj*)f2) < 0,
+                  "Float_Compare_To less than");
+        TEST_FALSE(runner, Float_Equals(f1, (Obj*)f2),
+                   "Float_Equals less than");
+        DECREF(f1);
+        DECREF(f2);
+    }
+
+    {
+        Float *f1 = Float_new(1.0);
+        Float *f2 = Float_new(0.0);
+        TEST_TRUE(runner, Float_Compare_To(f1, (Obj*)f2) > 0,
+                  "Float_Compare_To greater than");
+        TEST_FALSE(runner, Float_Equals(f1, (Obj*)f2),
+                   "Float_Equals greater than");
+        DECREF(f1);
+        DECREF(f2);
+    }
+
+    {
+        Float *f = Float_new(1.0);
+        Err *error = Err_trap(S_float_compare_to, f);
+        TEST_TRUE(runner, error != NULL,
+                  "Float_Compare_To with invalid type throws");
+        TEST_FALSE(runner, Float_Equals(f, (Obj*)OBJ),
+                   "Float_Equals with different type");
+        DECREF(error);
+        DECREF(f);
+    }
+
+    {
+        Integer *i1 = Int_new(INT64_C(0x6666666666666666));
+        Integer *i2 = Int_new(INT64_C(0x6666666666666666));
+        TEST_TRUE(runner, Int_Compare_To(i1, (Obj*)i2) == 0,
+                  "Int_Compare_To equal");
+        TEST_TRUE(runner, Int_Equals(i1, (Obj*)i2),
+                  "Int_Equals equal");
+        DECREF(i1);
+        DECREF(i2);
+    }
+
+    {
+        Integer *i1 = Int_new(INT64_C(0x6666666666666666));
+        Integer *i2 = Int_new(INT64_C(0x6666666666666667));
+        TEST_TRUE(runner, Int_Compare_To(i1, (Obj*)i2) < 0,
+                  "Int_Compare_To less than");
+        TEST_FALSE(runner, Int_Equals(i1, (Obj*)i2),
+                   "Int_Equals less than");
+        DECREF(i1);
+        DECREF(i2);
+    }
+
+    {
+        Integer *i1 = Int_new(INT64_C(0x6666666666666666));
+        Integer *i2 = Int_new(INT64_C(0x6666666666666665));
+        TEST_TRUE(runner, Int_Compare_To(i1, (Obj*)i2) > 0,
+                  "Int_Compare_To greater than");
+        TEST_FALSE(runner, Int_Equals(i1, (Obj*)i2),
+                   "Int_Equals greater than");
+        DECREF(i1);
+        DECREF(i2);
+    }
+
+    {
+        Integer *i = Int_new(0);
+        Err *error = Err_trap(S_int_compare_to, i);
+        TEST_TRUE(runner, error != NULL,
+                  "Int_Compare_To with invalid type throws");
+        TEST_FALSE(runner, Int_Equals(i, (Obj*)OBJ),
+                   "Int_Equals with different type");
+        DECREF(error);
+        DECREF(i);
+    }
+
+    // NOTICE: When running these tests on x86/x64, it's best to compile
+    // with -ffloat-store to avoid excess FPU precision which can hide
+    // implementation bugs.
+    S_test_compare_float_int(runner, (double)INT64_MAX * 2.0, INT64_MAX, 1);
+    S_test_compare_float_int(runner, pow(2.0, 60.0), INT64_C(1) << 60, 0);
+    S_test_compare_float_int(runner, pow(2.0, 60.0), (INT64_C(1) << 60) - 1,
+                             1);
+    S_test_compare_float_int(runner, pow(2.0, 60.0), (INT64_C(1) << 60) + 1,
+                             -1);
+    S_test_compare_float_int(runner, pow(2.0, 63.0), INT64_MAX, 1);
+    S_test_compare_float_int(runner, -pow(2.0, 63.0), INT64_MIN, 0);
+    // -9223372036854777856.0 == nextafter(-pow(2, 63), -INFINITY)
+    S_test_compare_float_int(runner, -9223372036854777856.0, INT64_MIN, -1);
+    S_test_compare_float_int(runner, 1.0, 2, -1);
+}
+
+static void
+test_Clone(TestBatchRunner *runner) {
+    Float   *f64 = Float_new(1.33);
+    Integer *i64 = Int_new(INT64_MAX);
+    Float   *f64_dupe = Float_Clone(f64);
+    Integer *i64_dupe = Int_Clone(i64);
+    TEST_TRUE(runner, Float_Equals(f64, (Obj*)f64_dupe),
+              "Float Clone");
+    TEST_TRUE(runner, Int_Equals(i64, (Obj*)i64_dupe),
+              "Integer Clone");
+    DECREF(i64_dupe);
+    DECREF(f64_dupe);
+    DECREF(i64);
+    DECREF(f64);
+}
+
+void
+TestNum_Run_IMP(TestNum *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 82);
+    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/TestNum.cfh
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestNum.cfh b/runtime/test/Clownfish/Test/TestNum.cfh
new file mode 100644
index 0000000..6d1f663
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestNum.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::TestNum
+    inherits Clownfish::TestHarness::TestBatch {
+
+    inert incremented TestNum*
+    new();
+
+    void
+    Run(TestNum *self, TestBatchRunner *runner);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestObj.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestObj.c b/runtime/test/Clownfish/Test/TestObj.c
new file mode 100644
index 0000000..c4cdafe
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestObj.c
@@ -0,0 +1,152 @@
+/* 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 <stdio.h>
+
+#define CFISH_USE_SHORT_NAMES
+#define TESTCFISH_USE_SHORT_NAMES
+
+#include "charmony.h"
+
+#include "Clownfish/Test/TestObj.h"
+
+#include "Clownfish/String.h"
+#include "Clownfish/Err.h"
+#include "Clownfish/Test.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Clownfish/Class.h"
+
+TestObj*
+TestObj_new() {
+    return (TestObj*)Class_Make_Obj(TESTOBJ);
+}
+
+static Obj*
+S_new_testobj() {
+    String *class_name = SSTR_WRAP_C("TestObj");
+    Obj *obj;
+    Class *klass = Class_fetch_class(class_name);
+    if (!klass) {
+        klass = Class_singleton(class_name, OBJ);
+    }
+    obj = Class_Make_Obj(klass);
+    return Obj_init(obj);
+}
+
+static void
+test_refcounts(TestBatchRunner *runner) {
+    Obj *obj = S_new_testobj();
+
+    TEST_INT_EQ(runner, CFISH_REFCOUNT_NN(obj), 1,
+                "Correct starting refcount");
+
+    obj = CFISH_INCREF_NN(obj);
+    TEST_INT_EQ(runner, CFISH_REFCOUNT_NN(obj), 2, "INCREF_NN");
+
+    CFISH_DECREF_NN(obj);
+    TEST_INT_EQ(runner, CFISH_REFCOUNT_NN(obj), 1, "DECREF_NN");
+
+    DECREF(obj);
+}
+
+static void
+test_To_String(TestBatchRunner *runner) {
+    Obj *testobj = S_new_testobj();
+    String *string = Obj_To_String(testobj);
+    TEST_TRUE(runner, Str_Contains_Utf8(string, "TestObj", 7), "To_String");
+    DECREF(string);
+    DECREF(testobj);
+}
+
+static void
+test_Equals(TestBatchRunner *runner) {
+    Obj *testobj = S_new_testobj();
+    Obj *other   = S_new_testobj();
+
+    TEST_TRUE(runner, Obj_Equals(testobj, testobj),
+              "Equals is true for the same object");
+    TEST_FALSE(runner, Obj_Equals(testobj, other),
+               "Distinct objects are not equal");
+
+    DECREF(testobj);
+    DECREF(other);
+}
+
+static void
+test_is_a(TestBatchRunner *runner) {
+    String *string     = Str_new_from_trusted_utf8("", 0);
+    Class  *str_class  = Str_get_class(string);
+    String *class_name = Str_get_class_name(string);
+
+    TEST_TRUE(runner, Str_is_a(string, STRING), "String is_a String.");
+    TEST_TRUE(runner, Str_is_a(string, OBJ), "String is_a Obj.");
+    TEST_TRUE(runner, str_class == STRING, "get_class");
+    TEST_TRUE(runner, Str_Equals(Class_Get_Name(STRING), (Obj*)class_name),
+              "get_class_name");
+    TEST_FALSE(runner, Obj_is_a(NULL, OBJ), "NULL is not an Obj");
+
+    DECREF(string);
+}
+
+static void
+S_attempt_init(void *context) {
+    Obj_init((Obj*)context);
+}
+
+static void
+S_attempt_Clone(void *context) {
+    Obj_Clone((Obj*)context);
+}
+
+static void
+S_attempt_Compare_To(void *context) {
+    Obj_Compare_To((Obj*)context, (Obj*)context);
+}
+
+static void
+S_verify_abstract_error(TestBatchRunner *runner, Err_Attempt_t routine,
+                        void *context, const char *name) {
+    char message[100];
+    sprintf(message, "%s() is abstract", name);
+    Err *error = Err_trap(routine, context);
+    TEST_TRUE(runner, error != NULL
+              && Err_is_a(error, ERR)
+              && Str_Contains_Utf8(Err_Get_Mess(error), "bstract", 7),
+              message);
+    DECREF(error);
+}
+
+static void
+test_abstract_routines(TestBatchRunner *runner) {
+    Obj *blank = Class_Make_Obj(OBJ);
+    S_verify_abstract_error(runner, S_attempt_init, blank, "init");
+
+    Obj *obj = S_new_testobj();
+    S_verify_abstract_error(runner, S_attempt_Clone,      obj, "Clone");
+    S_verify_abstract_error(runner, S_attempt_Compare_To, obj, "Compare_To");
+    DECREF(obj);
+}
+
+void
+TestObj_Run_IMP(TestObj *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 14);
+    test_refcounts(runner);
+    test_To_String(runner);
+    test_Equals(runner);
+    test_is_a(runner);
+    test_abstract_routines(runner);
+}
+

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

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestPtrHash.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestPtrHash.c b/runtime/test/Clownfish/Test/TestPtrHash.c
new file mode 100644
index 0000000..3bf7003
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestPtrHash.c
@@ -0,0 +1,108 @@
+/* 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 <stdlib.h>
+#include <time.h>
+
+#include "Clownfish/Test/TestPtrHash.h"
+#include "Clownfish/Class.h"
+#include "Clownfish/PtrHash.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Clownfish/TestHarness/TestUtils.h"
+#include "Clownfish/Util/Memory.h"
+
+TestPtrHash*
+TestPtrHash_new() {
+    return (TestPtrHash*)Class_Make_Obj(TESTPTRHASH);
+}
+
+static void
+test_Store_and_Fetch(TestBatchRunner *runner) {
+    PtrHash *hash = PtrHash_new(100);
+    char dummy[100];
+
+    for (int i = 0; i < 100; i++) {
+        void *key = &dummy[i];
+        PtrHash_Store(hash, key, key);
+    }
+
+    bool all_equal = true;
+    for (int i = 0; i < 100; i++) {
+        void *key = &dummy[i];
+        void *value = PtrHash_Fetch(hash, key);
+        if (value != key) {
+            all_equal = false;
+            break;
+        }
+    }
+    TEST_TRUE(runner, all_equal, "basic Store and Fetch");
+
+    TEST_TRUE(runner, PtrHash_Fetch(hash, &dummy[100]) == NULL,
+              "Fetch against non-existent key returns NULL");
+
+    PtrHash_Store(hash, &dummy[50], dummy);
+    TEST_TRUE(runner, PtrHash_Fetch(hash, &dummy[50]) == dummy,
+              "Store replaces existing value");
+
+    PtrHash_Destroy(hash);
+}
+
+static void
+test_stress(TestBatchRunner *runner) {
+    PtrHash *hash = PtrHash_new(0); // trigger multiple rebuilds.
+    size_t num_elems = 200000;
+    void **keys = (void**)MALLOCATE(num_elems * sizeof(void*));
+
+    for (size_t i = 0; i < num_elems; i++) {
+        size_t index = (size_t)(TestUtils_random_u64() % num_elems);
+        void *key = &keys[index];
+        PtrHash_Store(hash, key, key);
+        keys[i] = key;
+    }
+
+    // Overwrite for good measure.
+    for (size_t i = 0; i < num_elems; i++) {
+        void *key = keys[i];
+        PtrHash_Store(hash, key, key);
+    }
+
+    bool all_equal = true;
+    for (size_t i = 0; i < num_elems; i++) {
+        void *key = keys[i];
+        void *got = PtrHash_Fetch(hash, key);
+        if (got != key) {
+            all_equal = false;
+            break;
+        }
+    }
+    TEST_TRUE(runner, all_equal, "stress test");
+
+    FREEMEM(keys);
+    PtrHash_Destroy(hash);
+}
+
+void
+TestPtrHash_Run_IMP(TestPtrHash *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 4);
+    srand((unsigned int)time(NULL));
+    test_Store_and_Fetch(runner);
+    test_stress(runner);
+}
+
+

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


[04/14] lucy-clownfish git commit: Rename core directory to "cfcore" for CPAN tarball

Posted by nw...@apache.org.
Rename core directory to "cfcore" for CPAN tarball


Project: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/commit/5bda4dad
Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/5bda4dad
Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/5bda4dad

Branch: refs/heads/master
Commit: 5bda4dadd15edf1cd9c0b1d171b99dcf935a6c68
Parents: 56f1982
Author: Nick Wellnhofer <we...@aevum.de>
Authored: Fri Jul 8 13:57:06 2016 +0200
Committer: Nick Wellnhofer <we...@aevum.de>
Committed: Fri Jul 8 14:25:36 2016 +0200

----------------------------------------------------------------------
 compiler/perl/lib/Clownfish/CFC/Perl/Build.pm |  2 +-
 runtime/perl/buildlib/Clownfish/Build.pm      | 12 +++++++-----
 2 files changed, 8 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/5bda4dad/compiler/perl/lib/Clownfish/CFC/Perl/Build.pm
----------------------------------------------------------------------
diff --git a/compiler/perl/lib/Clownfish/CFC/Perl/Build.pm b/compiler/perl/lib/Clownfish/CFC/Perl/Build.pm
index 804ba87..6d0f584 100644
--- a/compiler/perl/lib/Clownfish/CFC/Perl/Build.pm
+++ b/compiler/perl/lib/Clownfish/CFC/Perl/Build.pm
@@ -48,7 +48,7 @@ else {
 # development, the files are accessed from their original locations.
 
 my @BASE_PATH;
-push(@BASE_PATH, updir()) unless -e 'core';
+push(@BASE_PATH, updir()) unless -d 'core' || -d 'cfcore';
 
 my $AUTOGEN_DIR  = 'autogen';
 my $LIB_DIR      = 'lib';

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/5bda4dad/runtime/perl/buildlib/Clownfish/Build.pm
----------------------------------------------------------------------
diff --git a/runtime/perl/buildlib/Clownfish/Build.pm b/runtime/perl/buildlib/Clownfish/Build.pm
index 3d1b8c3..9506662 100644
--- a/runtime/perl/buildlib/Clownfish/Build.pm
+++ b/runtime/perl/buildlib/Clownfish/Build.pm
@@ -21,7 +21,7 @@ package Clownfish::Build;
 my $IS_CPAN_DIST;
 
 BEGIN {
-    $IS_CPAN_DIST = -e 'core';
+    $IS_CPAN_DIST = -e 'cfcore';
 
     if (!$IS_CPAN_DIST) {
         unshift @INC,
@@ -49,17 +49,19 @@ use Cwd qw( getcwd );
 my @BASE_PATH = __PACKAGE__->cf_base_path;
 
 my $COMMON_SOURCE_DIR = catdir( @BASE_PATH, 'common' );
-my $CORE_SOURCE_DIR   = catdir( @BASE_PATH, 'core' );
 my $CFC_DIR           = catdir( @BASE_PATH, updir(), 'compiler', 'perl' );
 my $XS_SOURCE_DIR = 'xs';
 my $CFC_BUILD     = catfile( $CFC_DIR, 'Build' );
 my $LIB_DIR       = 'lib';
+my $CORE_SOURCE_DIR;
 my $CHARMONIZER_C;
 if ($IS_CPAN_DIST) {
-    $CHARMONIZER_C = 'charmonizer.c';
+    $CORE_SOURCE_DIR = 'cfcore';
+    $CHARMONIZER_C   = 'charmonizer.c';
 }
 else {
-    $CHARMONIZER_C = catfile( $COMMON_SOURCE_DIR, 'charmonizer.c' );
+    $CORE_SOURCE_DIR = catdir( @BASE_PATH, 'core' );
+    $CHARMONIZER_C   = catfile( $COMMON_SOURCE_DIR, 'charmonizer.c' );
 }
 
 sub new {
@@ -314,7 +316,7 @@ sub ACTION_dist {
         '../../LICENSE'         => 'LICENSE',
         '../../NOTICE'          => 'NOTICE',
         '../../README.md'       => 'README.md',
-        $CORE_SOURCE_DIR        => 'core',
+        $CORE_SOURCE_DIR        => 'cfcore',
         $CHARMONIZER_C          => 'charmonizer.c',
     );
     print "Copying files...\n";


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

Posted by nw...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestString.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestString.c b/runtime/test/Clownfish/Test/TestString.c
new file mode 100644
index 0000000..d89b5fe
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestString.c
@@ -0,0 +1,848 @@
+/* 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
+
+#include "Clownfish/Test/TestString.h"
+
+#include "Clownfish/String.h"
+#include "Clownfish/Boolean.h"
+#include "Clownfish/ByteBuf.h"
+#include "Clownfish/CharBuf.h"
+#include "Clownfish/Err.h"
+#include "Clownfish/Test.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Clownfish/TestHarness/TestUtils.h"
+#include "Clownfish/Util/Memory.h"
+#include "Clownfish/Class.h"
+
+#define SMILEY "\xE2\x98\xBA"
+static char smiley[] = { (char)0xE2, (char)0x98, (char)0xBA, 0 };
+static uint32_t smiley_len = 3;
+static int32_t smiley_cp  = 0x263A;
+
+TestString*
+TestStr_new() {
+    return (TestString*)Class_Make_Obj(TESTSTRING);
+}
+
+static String*
+S_get_str(const char *string) {
+    return Str_new_from_utf8(string, strlen(string));
+}
+
+// Surround a smiley with lots of whitespace.
+static String*
+S_smiley_with_whitespace(size_t *num_spaces_ptr) {
+    int32_t spaces[] = {
+        ' ',    '\t',   '\r',   '\n',   0x000B, 0x000C, 0x000D, 0x0085,
+        0x00A0, 0x1680, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005,
+        0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x2028, 0x2029, 0x202F,
+        0x205F, 0x3000
+    };
+    size_t num_spaces = sizeof(spaces) / sizeof(uint32_t);
+
+    CharBuf *buf = CB_new(0);
+    for (size_t i = 0; i < num_spaces; i++) { CB_Cat_Char(buf, spaces[i]); }
+    CB_Cat_Char(buf, smiley_cp);
+    for (size_t i = 0; i < num_spaces; i++) { CB_Cat_Char(buf, spaces[i]); }
+
+    String *retval = CB_To_String(buf);
+    if (num_spaces_ptr) { *num_spaces_ptr = num_spaces; }
+
+    DECREF(buf);
+    return retval;
+}
+
+static void
+test_new(TestBatchRunner *runner) {
+    static char chars[] = "A string " SMILEY " with a smile.";
+
+    {
+        char *buffer = (char*)MALLOCATE(sizeof(chars));
+        strcpy(buffer, chars);
+        String *thief = Str_new_steal_utf8(buffer, sizeof(chars) - 1);
+        TEST_TRUE(runner, Str_Equals_Utf8(thief, chars, sizeof(chars) - 1),
+                  "Str_new_steal_utf8");
+        DECREF(thief);
+    }
+
+    {
+        char *buffer = (char*)MALLOCATE(sizeof(chars));
+        strcpy(buffer, chars);
+        String *thief
+            = Str_new_steal_trusted_utf8(buffer, sizeof(chars) - 1);
+        TEST_TRUE(runner, Str_Equals_Utf8(thief, chars, sizeof(chars) - 1),
+                  "Str_new_steal_trusted_utf8");
+        DECREF(thief);
+    }
+
+    {
+        String *wrapper = Str_new_wrap_utf8(chars, sizeof(chars) - 1);
+        TEST_TRUE(runner, Str_Equals_Utf8(wrapper, chars, sizeof(chars) - 1),
+                  "Str_new_wrap_utf8");
+        DECREF(wrapper);
+    }
+
+    {
+        String *wrapper = Str_new_wrap_trusted_utf8(chars, sizeof(chars) - 1);
+        TEST_TRUE(runner, Str_Equals_Utf8(wrapper, chars, sizeof(chars) - 1),
+                  "Str_new_wrap_trusted_utf8");
+        DECREF(wrapper);
+    }
+
+    {
+        String *smiley_str = Str_new_from_char(smiley_cp);
+        TEST_TRUE(runner, Str_Equals_Utf8(smiley_str, smiley, smiley_len),
+                  "Str_new_from_char");
+        DECREF(smiley_str);
+    }
+}
+
+static void
+test_Cat(TestBatchRunner *runner) {
+    String *wanted = Str_newf("a%s", smiley);
+    String *source;
+    String *got;
+
+    source = S_get_str("");
+    got = Str_Cat(source, wanted);
+    TEST_TRUE(runner, Str_Equals(wanted, (Obj*)got), "Cat");
+    DECREF(got);
+    DECREF(source);
+
+    source = S_get_str("a");
+    got = Str_Cat_Utf8(source, smiley, smiley_len);
+    TEST_TRUE(runner, Str_Equals(wanted, (Obj*)got), "Cat_Utf8");
+    DECREF(got);
+    DECREF(source);
+
+    source = S_get_str("a");
+    got = Str_Cat_Trusted_Utf8(source, smiley, smiley_len);
+    TEST_TRUE(runner, Str_Equals(wanted, (Obj*)got), "Cat_Trusted_Utf8");
+    DECREF(got);
+    DECREF(source);
+
+    DECREF(wanted);
+}
+
+static void
+test_Clone(TestBatchRunner *runner) {
+    String *wanted = S_get_str("foo");
+    String *got    = Str_Clone(wanted);
+    TEST_TRUE(runner, Str_Equals(wanted, (Obj*)got), "Clone");
+    DECREF(got);
+    DECREF(wanted);
+}
+
+static int64_t
+S_find(String *string, String *substring) {
+    StringIterator *iter = Str_Find(string, substring);
+    if (iter == NULL) { return -1; }
+    size_t tick = StrIter_Recede(iter, SIZE_MAX);
+    DECREF(iter);
+    return (int64_t)tick;
+}
+
+static void
+test_Contains_and_Find(TestBatchRunner *runner) {
+    String *string;
+    String *substring = S_get_str("foo");
+    String *empty     = S_get_str("");
+
+    TEST_FALSE(runner, Str_Contains(empty, substring),
+               "Not contained in empty string");
+    TEST_INT_EQ(runner, S_find(empty, substring), -1,
+                "Not found in empty string");
+
+    string = S_get_str("foo");
+    TEST_TRUE(runner, Str_Contains(string, substring),
+              "Contains complete string");
+    TEST_INT_EQ(runner, S_find(string, substring), 0, "Find complete string");
+    TEST_TRUE(runner, Str_Contains(string, empty),
+              "Contains empty string");
+    TEST_INT_EQ(runner, S_find(string, empty), 0, "Find empty string");
+    DECREF(string);
+
+    string = S_get_str("afoo");
+    TEST_TRUE(runner, Str_Contains(string, substring),
+              "Contained after first");
+    TEST_INT_EQ(runner, S_find(string, substring), 1, "Find after first");
+    String *prefix = Str_SubString(string, 0, 3);
+    TEST_FALSE(runner, Str_Contains(prefix, substring), "Don't overrun");
+    DECREF(prefix);
+    DECREF(string);
+
+    string = S_get_str("afood");
+    TEST_TRUE(runner, Str_Contains(string, substring), "Contained in middle");
+    TEST_INT_EQ(runner, S_find(string, substring), 1, "Find in middle");
+    DECREF(string);
+
+    DECREF(empty);
+    DECREF(substring);
+}
+
+static void
+test_Code_Point_At_and_From(TestBatchRunner *runner) {
+    int32_t code_points[] = {
+        'a', smiley_cp, smiley_cp, 'b', smiley_cp, 'c'
+    };
+    uint32_t num_code_points = sizeof(code_points) / sizeof(int32_t);
+    String *string = Str_newf("a%s%sb%sc", smiley, smiley, smiley);
+    uint32_t i;
+
+    for (i = 0; i < num_code_points; i++) {
+        uint32_t from = num_code_points - i;
+        TEST_INT_EQ(runner, Str_Code_Point_At(string, i), code_points[i],
+                    "Code_Point_At %ld", (long)i);
+        TEST_INT_EQ(runner, Str_Code_Point_From(string, from),
+                    code_points[i], "Code_Point_From %ld", (long)from);
+    }
+
+    TEST_INT_EQ(runner, Str_Code_Point_At(string, num_code_points), STR_OOB,
+                "Code_Point_At %ld", (long)num_code_points);
+    TEST_INT_EQ(runner, Str_Code_Point_From(string, 0), STR_OOB,
+                "Code_Point_From 0");
+    TEST_INT_EQ(runner, Str_Code_Point_From(string, num_code_points + 1),
+                STR_OOB, "Code_Point_From %ld", (long)(num_code_points + 1));
+
+    DECREF(string);
+}
+
+static void
+test_SubString(TestBatchRunner *runner) {
+    {
+        String *string = Str_newf("a%s%sb%sc", smiley, smiley, smiley);
+        String *wanted = Str_newf("%sb%s", smiley, smiley);
+        String *got = Str_SubString(string, 2, 3);
+        TEST_TRUE(runner, Str_Equals(wanted, (Obj*)got), "SubString");
+        DECREF(string);
+        DECREF(wanted);
+        DECREF(got);
+    }
+
+    {
+        static const char chars[] = "A string.";
+        String *wrapper = Str_new_wrap_utf8(chars, sizeof(chars) - 1);
+        String *wanted  = Str_newf("string");
+        String *got     = Str_SubString(wrapper, 2, 6);
+        TEST_TRUE(runner, Str_Equals(got, (Obj*)wanted),
+                  "SubString with wrapped buffer");
+        DECREF(wrapper);
+        DECREF(wanted);
+        DECREF(got);
+    }
+}
+
+static void
+test_Trim(TestBatchRunner *runner) {
+    String *ws_smiley = S_smiley_with_whitespace(NULL);
+    String *ws_foo    = S_get_str("  foo  ");
+    String *ws_only   = S_get_str("  \t  \r\n");
+    String *trimmed   = S_get_str("a     b");
+    String *got;
+
+    got = Str_Trim(ws_smiley);
+    TEST_TRUE(runner, Str_Equals_Utf8(got, smiley, smiley_len), "Trim");
+    DECREF(got);
+
+    got = Str_Trim_Top(ws_foo);
+    TEST_TRUE(runner, Str_Equals_Utf8(got, "foo  ", 5), "Trim_Top");
+    DECREF(got);
+
+    got = Str_Trim_Tail(ws_foo);
+    TEST_TRUE(runner, Str_Equals_Utf8(got, "  foo", 5), "Trim_Tail");
+    DECREF(got);
+
+    got = Str_Trim(ws_only);
+    TEST_TRUE(runner, Str_Equals_Utf8(got, "", 0), "Trim with only whitespace");
+    DECREF(got);
+
+    got = Str_Trim_Top(ws_only);
+    TEST_TRUE(runner, Str_Equals_Utf8(got, "", 0),
+              "Trim_Top with only whitespace");
+    DECREF(got);
+
+    got = Str_Trim_Tail(ws_only);
+    TEST_TRUE(runner, Str_Equals_Utf8(got, "", 0),
+              "Trim_Tail with only whitespace");
+    DECREF(got);
+
+    got = Str_Trim(trimmed);
+    TEST_TRUE(runner, Str_Equals(got, (Obj*)trimmed),
+              "Trim doesn't change trimmed string");
+    DECREF(got);
+
+    got = Str_Trim_Top(trimmed);
+    TEST_TRUE(runner, Str_Equals(got, (Obj*)trimmed),
+              "Trim_Top doesn't change trimmed string");
+    DECREF(got);
+
+    got = Str_Trim_Tail(trimmed);
+    TEST_TRUE(runner, Str_Equals(got, (Obj*)trimmed),
+              "Trim_Tail doesn't change trimmed string");
+    DECREF(got);
+
+    DECREF(trimmed);
+    DECREF(ws_only);
+    DECREF(ws_foo);
+    DECREF(ws_smiley);
+}
+
+static void
+test_To_F64(TestBatchRunner *runner) {
+    String *string;
+
+    string = S_get_str("1.5");
+    double difference = 1.5 - Str_To_F64(string);
+    if (difference < 0) { difference = 0 - difference; }
+    TEST_TRUE(runner, difference < 0.001, "To_F64");
+    DECREF(string);
+
+    string = S_get_str("-1.5");
+    difference = 1.5 + Str_To_F64(string);
+    if (difference < 0) { difference = 0 - difference; }
+    TEST_TRUE(runner, difference < 0.001, "To_F64 negative");
+    DECREF(string);
+
+    // TODO: Enable this test when we have real substrings.
+    /*string = S_get_str("1.59");
+    double value_full = Str_To_F64(string);
+    Str_Set_Size(string, 3);
+    double value_short = Str_To_F64(string);
+    TEST_TRUE(runner, value_short < value_full,
+              "TO_F64 doesn't run past end of string");
+    DECREF(string);*/
+}
+
+static void
+test_To_I64(TestBatchRunner *runner) {
+    String *string;
+
+    string = S_get_str("10");
+    TEST_INT_EQ(runner, Str_To_I64(string), 10, "To_I64");
+    DECREF(string);
+
+    string = S_get_str("-10");
+    TEST_INT_EQ(runner, Str_To_I64(string), -10, "To_I64 negative");
+    DECREF(string);
+
+    string = S_get_str("10.");
+    TEST_INT_EQ(runner, Str_To_I64(string), 10, "To_I64 stops at non-digits");
+    DECREF(string);
+
+    string = S_get_str("10" SMILEY);
+    TEST_INT_EQ(runner, Str_To_I64(string), 10, "To_I64 stops at non-ASCII");
+    DECREF(string);
+
+    string = S_get_str("10A");
+    TEST_INT_EQ(runner, Str_To_I64(string), 10,
+              "To_I64 stops at out-of-range digits");
+    DECREF(string);
+}
+
+static void
+test_BaseX_To_I64(TestBatchRunner *runner) {
+    String *string;
+
+    string = S_get_str("-JJ");
+    TEST_INT_EQ(runner, Str_BaseX_To_I64(string, 20), -399,
+              "BaseX_To_I64 base 20");
+    DECREF(string);
+}
+
+static void
+test_To_String(TestBatchRunner *runner) {
+    String *string = Str_newf("Test");
+    String *copy   = Str_To_String(string);
+    TEST_TRUE(runner, Str_Equals(copy, (Obj*)string), "To_String");
+    DECREF(string);
+    DECREF(copy);
+}
+
+static void
+test_To_Utf8(TestBatchRunner *runner) {
+    String *string = Str_newf("a%s%sb%sc", smiley, smiley, smiley);
+    char *buf = Str_To_Utf8(string);
+    TEST_TRUE(runner, strcmp(buf, "a" SMILEY SMILEY "b" SMILEY "c") == 0,
+              "To_Utf8");
+    FREEMEM(buf);
+    DECREF(string);
+}
+
+static void
+test_To_ByteBuf(TestBatchRunner *runner) {
+    String     *string = Str_newf("foo");
+    ByteBuf    *bb     = Str_To_ByteBuf(string);
+    TEST_TRUE(runner, BB_Equals_Bytes(bb, "foo", 3), "To_ByteBuf");
+    DECREF(bb);
+    DECREF(string);
+}
+
+static void
+test_Length(TestBatchRunner *runner) {
+    String *string = Str_newf("a%s%sb%sc", smiley, smiley, smiley);
+    TEST_UINT_EQ(runner, Str_Length(string), 6, "Length");
+    DECREF(string);
+}
+
+static void
+test_Compare_To(TestBatchRunner *runner) {
+    String *abc = Str_newf("a%s%sb%sc", smiley, smiley, smiley);
+    String *ab  = Str_newf("a%s%sb", smiley, smiley);
+    String *ac  = Str_newf("a%s%sc", smiley, smiley);
+
+    TEST_TRUE(runner, Str_Compare_To(abc, (Obj*)abc) == 0,
+              "Compare_To abc abc");
+    TEST_TRUE(runner, Str_Compare_To(ab, (Obj*)abc) < 0,
+              "Compare_To ab abc");
+    TEST_TRUE(runner, Str_Compare_To(abc, (Obj*)ab) > 0,
+              "Compare_To abc ab");
+    TEST_TRUE(runner, Str_Compare_To(ab, (Obj*)ac) < 0,
+              "Compare_To ab ac");
+    TEST_TRUE(runner, Str_Compare_To(ac, (Obj*)ab) > 0,
+              "Compare_To ac ab");
+
+    DECREF(ac);
+    DECREF(ab);
+    DECREF(abc);
+}
+
+static void
+test_Starts_Ends_With(TestBatchRunner *runner) {
+    String *prefix = S_get_str("pre" SMILEY "fix_");
+    String *suffix = S_get_str("_post" SMILEY "fix");
+    String *empty  = S_get_str("");
+
+    TEST_TRUE(runner, Str_Starts_With(suffix, suffix),
+              "Starts_With self returns true");
+    TEST_TRUE(runner, Str_Ends_With(prefix, prefix),
+              "Ends_With self returns true");
+
+    TEST_TRUE(runner, Str_Starts_With(suffix, empty),
+              "Starts_With empty string returns true");
+    TEST_TRUE(runner, Str_Ends_With(prefix, empty),
+              "Ends_With empty string returns true");
+    TEST_FALSE(runner, Str_Starts_With(empty, suffix),
+              "Empty string Starts_With returns false");
+    TEST_FALSE(runner, Str_Ends_With(empty, prefix),
+              "Empty string Ends_With returns false");
+
+    {
+        String *string
+            = S_get_str("pre" SMILEY "fix_string_post" SMILEY "fix");
+        TEST_TRUE(runner, Str_Starts_With(string, prefix),
+                  "Starts_With returns true");
+        TEST_TRUE(runner, Str_Ends_With(string, suffix),
+                  "Ends_With returns true");
+        DECREF(string);
+    }
+
+    {
+        String *string
+            = S_get_str("pre" SMILEY "fix:string:post" SMILEY "fix");
+        TEST_FALSE(runner, Str_Starts_With(string, prefix),
+                   "Starts_With returns false");
+        TEST_FALSE(runner, Str_Ends_With(string, suffix),
+                   "Ends_With returns false");
+        DECREF(string);
+    }
+
+    DECREF(prefix);
+    DECREF(suffix);
+    DECREF(empty);
+}
+
+static void
+test_Starts_Ends_With_Utf8(TestBatchRunner *runner) {
+    String *str = S_get_str("pre" SMILEY "post");
+
+    static const char prefix[] = "pre" SMILEY;
+    static const char postfix[] = SMILEY "post";
+    static const size_t prefix_size = sizeof(prefix) - 1;
+    static const size_t postfix_size = sizeof(postfix) - 1;
+    TEST_TRUE(runner, Str_Starts_With_Utf8(str, prefix, prefix_size),
+              "Starts_With_Utf8 returns true");
+    TEST_TRUE(runner, Str_Ends_With_Utf8(str, postfix, postfix_size),
+              "Ends_With_Utf8 returns true");
+    TEST_FALSE(runner, Str_Starts_With_Utf8(str, postfix, postfix_size),
+              "Starts_With_Utf8 returns false");
+    TEST_FALSE(runner, Str_Ends_With_Utf8(str, prefix, prefix_size),
+              "Ends_With_Utf8 returns false");
+
+    static const char longer[] = "12345678901234567890";
+    static const size_t longer_size = sizeof(longer) - 1;
+    TEST_FALSE(runner, Str_Starts_With_Utf8(str, longer, longer_size),
+              "Starts_With_Utf8 longer str returns false");
+    TEST_FALSE(runner, Str_Ends_With_Utf8(str, longer, longer_size),
+               "Ends_With_Utf8 longer str returns false");
+
+    DECREF(str);
+}
+
+static void
+test_Get_Ptr8(TestBatchRunner *runner) {
+    String *string = S_get_str("Banana");
+
+    const char *ptr8 = Str_Get_Ptr8(string);
+    TEST_TRUE(runner, strcmp(ptr8, "Banana") == 0, "Get_Ptr8");
+
+    size_t size = Str_Get_Size(string);
+    TEST_UINT_EQ(runner, size, 6, "Get_Size");
+
+    DECREF(string);
+}
+
+static void
+test_iterator(TestBatchRunner *runner) {
+    static const int32_t code_points[] = {
+        0x41,
+        0x7F,
+        0x80,
+        0x7FF,
+        0x800,
+        0xFFFF,
+        0x10000,
+        0x10FFFF
+    };
+    static size_t num_code_points
+        = sizeof(code_points) / sizeof(code_points[0]);
+
+    CharBuf *buf = CB_new(0);
+    for (size_t i = 0; i < num_code_points; ++i) {
+        CB_Cat_Char(buf, code_points[i]);
+    }
+    String *string = CB_To_String(buf);
+
+    {
+        StringIterator *iter = Str_Top(string);
+
+        TEST_TRUE(runner, StrIter_Equals(iter, (Obj*)iter),
+                  "StringIterator equal to self");
+        TEST_FALSE(runner, StrIter_Equals(iter, (Obj*)CFISH_TRUE),
+                   "StringIterator not equal non-iterators");
+
+        DECREF(iter);
+    }
+
+    {
+        StringIterator *top  = Str_Top(string);
+        StringIterator *tail = Str_Tail(string);
+
+        TEST_FALSE(runner, StrIter_Equals(top, (Obj*)tail),
+                   "StrIter_Equals returns false");
+
+        TEST_INT_EQ(runner, StrIter_Compare_To(top, (Obj*)tail), -1,
+                    "Compare_To top < tail");
+        TEST_INT_EQ(runner, StrIter_Compare_To(tail, (Obj*)top), 1,
+                    "Compare_To tail > top");
+        TEST_INT_EQ(runner, StrIter_Compare_To(top, (Obj*)top), 0,
+                    "Compare_To top == top");
+
+        StringIterator *clone = StrIter_Clone(top);
+        TEST_TRUE(runner, StrIter_Equals(clone, (Obj*)top), "Clone");
+
+        StrIter_Assign(clone, tail);
+        TEST_TRUE(runner, StrIter_Equals(clone, (Obj*)tail), "Assign");
+
+        String *other = Str_newf("Other string");
+        StringIterator *other_iter = Str_Top(other);
+        TEST_FALSE(runner, StrIter_Equals(other_iter, (Obj*)tail),
+                   "Equals returns false for different strings");
+        StrIter_Assign(clone, other_iter);
+        TEST_TRUE(runner, StrIter_Equals(clone, (Obj*)other_iter),
+                  "Assign iterator with different string");
+
+        DECREF(other);
+        DECREF(other_iter);
+        DECREF(clone);
+        DECREF(top);
+        DECREF(tail);
+    }
+
+    {
+        StringIterator *iter = Str_Top(string);
+
+        for (size_t i = 0; i < num_code_points; ++i) {
+            TEST_TRUE(runner, StrIter_Has_Next(iter), "Has_Next %d", i);
+            int32_t code_point = StrIter_Next(iter);
+            TEST_INT_EQ(runner, code_point, code_points[i], "Next %d", i);
+        }
+
+        TEST_TRUE(runner, !StrIter_Has_Next(iter),
+                  "Has_Next at end of string");
+        TEST_INT_EQ(runner, StrIter_Next(iter), STR_OOB,
+                    "Next at end of string");
+
+        StringIterator *tail = Str_Tail(string);
+        TEST_TRUE(runner, StrIter_Equals(iter, (Obj*)tail), "Equals tail");
+
+        DECREF(tail);
+        DECREF(iter);
+    }
+
+    {
+        StringIterator *iter = Str_Tail(string);
+
+        for (size_t i = num_code_points; i--;) {
+            TEST_TRUE(runner, StrIter_Has_Prev(iter), "Has_Prev %d", i);
+            int32_t code_point = StrIter_Prev(iter);
+            TEST_INT_EQ(runner, code_point, code_points[i], "Prev %d", i);
+        }
+
+        TEST_TRUE(runner, !StrIter_Has_Prev(iter),
+                  "Has_Prev at end of string");
+        TEST_INT_EQ(runner, StrIter_Prev(iter), STR_OOB,
+                    "Prev at start of string");
+
+        StringIterator *top = Str_Top(string);
+        TEST_TRUE(runner, StrIter_Equals(iter, (Obj*)top), "Equals top");
+
+        DECREF(top);
+        DECREF(iter);
+    }
+
+    {
+        StringIterator *iter = Str_Top(string);
+
+        StrIter_Next(iter);
+        TEST_UINT_EQ(runner, StrIter_Advance(iter, 2), 2,
+                     "Advance returns number of code points");
+        TEST_INT_EQ(runner, StrIter_Next(iter), code_points[3],
+                    "Advance works");
+        TEST_UINT_EQ(runner,
+                     StrIter_Advance(iter, 1000000), num_code_points - 4,
+                     "Advance past end of string");
+
+        StrIter_Prev(iter);
+        TEST_UINT_EQ(runner, StrIter_Recede(iter, 2), 2,
+                     "Recede returns number of code points");
+        TEST_INT_EQ(runner, StrIter_Prev(iter), code_points[num_code_points-4],
+                    "Recede works");
+        TEST_UINT_EQ(runner, StrIter_Recede(iter, 1000000), num_code_points - 4,
+                     "Recede past start of string");
+
+        DECREF(iter);
+    }
+
+    DECREF(string);
+    DECREF(buf);
+}
+
+static void
+test_iterator_whitespace(TestBatchRunner *runner) {
+    size_t num_spaces;
+    String *ws_smiley = S_smiley_with_whitespace(&num_spaces);
+
+    {
+        StringIterator *iter = Str_Top(ws_smiley);
+        TEST_UINT_EQ(runner, StrIter_Skip_Whitespace(iter), num_spaces,
+                     "Skip_Whitespace");
+        TEST_UINT_EQ(runner, StrIter_Skip_Whitespace(iter), 0,
+                     "Skip_Whitespace without whitespace");
+        DECREF(iter);
+    }
+
+    {
+        StringIterator *iter = Str_Tail(ws_smiley);
+        TEST_UINT_EQ(runner, StrIter_Skip_Whitespace_Back(iter), num_spaces,
+                     "Skip_Whitespace_Back");
+        TEST_UINT_EQ(runner, StrIter_Skip_Whitespace_Back(iter), 0,
+                     "Skip_Whitespace_Back without whitespace");
+        DECREF(iter);
+    }
+
+    DECREF(ws_smiley);
+}
+
+typedef struct {
+    StringIterator *top;
+    StringIterator *tail;
+} StrIterCropContext;
+
+static void
+S_striter_crop(void *vcontext) {
+    StrIterCropContext *context = (StrIterCropContext*)vcontext;
+    StrIter_crop(context->top, context->tail);
+}
+
+static void
+test_iterator_substring(TestBatchRunner *runner) {
+    String *string = Str_newf("a%sb%sc%sd", smiley, smiley, smiley);
+
+    StringIterator *start = Str_Top(string);
+    StringIterator *end = Str_Tail(string);
+
+    {
+        String *substring = StrIter_crop(start, end);
+        TEST_TRUE(runner, Str_Equals(substring, (Obj*)string),
+                  "StrIter_crop whole string");
+        DECREF(substring);
+    }
+
+    StrIter_Advance(start, 2);
+    StrIter_Recede(end, 2);
+
+    {
+        String *substring = StrIter_crop(start, end);
+        static const char wanted_buf[] = "b" SMILEY "c";
+        static const size_t wanted_size = sizeof(wanted_buf) - 1;
+        String *wanted = Str_new_from_utf8(wanted_buf, wanted_size);
+        TEST_TRUE(runner, Str_Equals(substring, (Obj*)wanted),
+                  "StrIter_crop");
+
+        TEST_TRUE(runner, StrIter_Starts_With(start, wanted),
+                  "Starts_With returns true");
+        TEST_TRUE(runner, StrIter_Ends_With(end, wanted),
+                  "Ends_With returns true");
+        TEST_TRUE(runner,
+                  StrIter_Starts_With_Utf8(start, wanted_buf, wanted_size),
+                  "Starts_With_Utf8 returns true");
+        TEST_TRUE(runner,
+                  StrIter_Ends_With_Utf8(end, wanted_buf, wanted_size),
+                  "Ends_With_Utf8 returns true");
+
+        DECREF(wanted);
+        DECREF(substring);
+    }
+
+    {
+        static const char short_buf[] = "b" SMILEY "x";
+        static const size_t short_size = sizeof(short_buf) - 1;
+        String *short_str = Str_new_from_utf8(short_buf, short_size);
+        TEST_FALSE(runner, StrIter_Starts_With(start, short_str),
+                   "Starts_With returns false");
+        TEST_FALSE(runner, StrIter_Ends_With(start, short_str),
+                   "Ends_With returns false");
+        TEST_FALSE(runner,
+                   StrIter_Starts_With_Utf8(start, short_buf, short_size),
+                   "Starts_With_Utf8 returns false");
+        TEST_FALSE(runner,
+                   StrIter_Ends_With_Utf8(start, short_buf, short_size),
+                   "Ends_With_Utf8 returns false");
+
+        static const char long_buf[] = "b" SMILEY "xxxxxxxxxxxx" SMILEY "c";
+        static const size_t long_size = sizeof(long_buf) - 1;
+        String *long_str = Str_new_from_utf8(long_buf, long_size);
+        TEST_FALSE(runner, StrIter_Starts_With(start, long_str),
+                   "Starts_With long string returns false");
+        TEST_FALSE(runner, StrIter_Ends_With(end, long_str),
+                   "Ends_With long string returns false");
+        TEST_FALSE(runner,
+                   StrIter_Starts_With_Utf8(start, long_buf, long_size),
+                   "Starts_With_Utf8 long string returns false");
+        TEST_FALSE(runner,
+                   StrIter_Ends_With_Utf8(end, long_buf, long_size),
+                   "Ends_With_Utf8 long string returns false");
+
+        DECREF(short_str);
+        DECREF(long_str);
+    }
+
+    {
+        String *substring = StrIter_crop(end, NULL);
+        String *wanted = Str_newf("%sd", smiley);
+        TEST_TRUE(runner, Str_Equals(substring, (Obj*)wanted),
+                  "StrIter_crop with NULL tail");
+        DECREF(wanted);
+        DECREF(substring);
+    }
+
+    {
+        String *substring = StrIter_crop(NULL, start);
+        String *wanted = Str_newf("a%s", smiley);
+        TEST_TRUE(runner, Str_Equals(substring, (Obj*)wanted),
+                  "StrIter_crop with NULL top");
+        DECREF(wanted);
+        DECREF(substring);
+    }
+
+    {
+        StrIterCropContext context;
+        context.top  = NULL;
+        context.tail = NULL;
+        Err *error = Err_trap(S_striter_crop, &context);
+        TEST_TRUE(runner, error != NULL,
+                  "StrIter_crop throws if top and tail are NULL");
+        DECREF(error);
+    }
+
+    {
+        String *other = SSTR_WRAP_C("other");
+        StrIterCropContext context;
+        context.top  = start;
+        context.tail = Str_Tail(other);
+        Err *error = Err_trap(S_striter_crop, &context);
+        TEST_TRUE(runner, error != NULL,
+                  "StrIter_crop throws if string don't match");
+        DECREF(error);
+        DECREF(context.tail);
+    }
+
+    {
+        StrIterCropContext context;
+        context.top  = end;
+        context.tail = start;
+        Err *error = Err_trap(S_striter_crop, &context);
+        TEST_TRUE(runner, error != NULL,
+                  "StrIter_crop throws if top is behind tail");
+        DECREF(error);
+    }
+
+    DECREF(start);
+    DECREF(end);
+    DECREF(string);
+}
+
+void
+TestStr_Run_IMP(TestString *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 158);
+    test_new(runner);
+    test_Cat(runner);
+    test_Clone(runner);
+    test_Code_Point_At_and_From(runner);
+    test_Contains_and_Find(runner);
+    test_SubString(runner);
+    test_Trim(runner);
+    test_To_F64(runner);
+    test_To_I64(runner);
+    test_BaseX_To_I64(runner);
+    test_To_String(runner);
+    test_To_Utf8(runner);
+    test_To_ByteBuf(runner);
+    test_Length(runner);
+    test_Compare_To(runner);
+    test_Starts_Ends_With(runner);
+    test_Starts_Ends_With_Utf8(runner);
+    test_Get_Ptr8(runner);
+    test_iterator(runner);
+    test_iterator_whitespace(runner);
+    test_iterator_substring(runner);
+}
+
+/*************************** StringCallbackTest ***************************/
+
+bool
+StrCbTest_Unchanged_By_Callback_IMP(StringCallbackTest *self, String *str) {
+    String *before = Str_Clone(str);
+    StrCbTest_Callback(self);
+    return Str_Equals(str, (Obj*)before);
+}
+

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestString.cfh
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestString.cfh b/runtime/test/Clownfish/Test/TestString.cfh
new file mode 100644
index 0000000..88e43c9
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestString.cfh
@@ -0,0 +1,36 @@
+/* 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::TestString nickname TestStr
+    inherits Clownfish::TestHarness::TestBatch {
+
+    inert incremented TestString*
+    new();
+
+    void
+    Run(TestString *self, TestBatchRunner *runner);
+}
+
+abstract class Clownfish::Test::StringCallbackTest nickname StrCbTest {
+    bool
+    Unchanged_By_Callback(StringCallbackTest *self, String *str);
+
+    abstract void
+    Callback(StringCallbackTest *self);
+}
+

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/8ba4e619/runtime/test/Clownfish/Test/TestVector.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestVector.c b/runtime/test/Clownfish/Test/TestVector.c
new file mode 100644
index 0000000..17a7f98
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestVector.c
@@ -0,0 +1,572 @@
+/* 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/test/Clownfish/Test/TestVector.cfh
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/TestVector.cfh b/runtime/test/Clownfish/Test/TestVector.cfh
new file mode 100644
index 0000000..090b8b9
--- /dev/null
+++ b/runtime/test/Clownfish/Test/TestVector.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::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/test/Clownfish/Test/Util/TestAtomic.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/Util/TestAtomic.c b/runtime/test/Clownfish/Test/Util/TestAtomic.c
new file mode 100644
index 0000000..f87279a
--- /dev/null
+++ b/runtime/test/Clownfish/Test/Util/TestAtomic.c
@@ -0,0 +1,65 @@
+/* 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/test/Clownfish/Test/Util/TestAtomic.cfh
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/Util/TestAtomic.cfh b/runtime/test/Clownfish/Test/Util/TestAtomic.cfh
new file mode 100644
index 0000000..2788342
--- /dev/null
+++ b/runtime/test/Clownfish/Test/Util/TestAtomic.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::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/test/Clownfish/Test/Util/TestMemory.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/Util/TestMemory.c b/runtime/test/Clownfish/Test/Util/TestMemory.c
new file mode 100644
index 0000000..8151c72
--- /dev/null
+++ b/runtime/test/Clownfish/Test/Util/TestMemory.c
@@ -0,0 +1,119 @@
+/* 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/test/Clownfish/Test/Util/TestMemory.cfh
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/Util/TestMemory.cfh b/runtime/test/Clownfish/Test/Util/TestMemory.cfh
new file mode 100644
index 0000000..d0b5803
--- /dev/null
+++ b/runtime/test/Clownfish/Test/Util/TestMemory.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::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/test/Clownfish/Test/Util/TestStringHelper.c
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/Util/TestStringHelper.c b/runtime/test/Clownfish/Test/Util/TestStringHelper.c
new file mode 100644
index 0000000..2a873fd
--- /dev/null
+++ b/runtime/test/Clownfish/Test/Util/TestStringHelper.c
@@ -0,0 +1,373 @@
+/* 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/test/Clownfish/Test/Util/TestStringHelper.cfh
----------------------------------------------------------------------
diff --git a/runtime/test/Clownfish/Test/Util/TestStringHelper.cfh b/runtime/test/Clownfish/Test/Util/TestStringHelper.cfh
new file mode 100644
index 0000000..752c553
--- /dev/null
+++ b/runtime/test/Clownfish/Test/Util/TestStringHelper.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::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/test/TestClownfish.c
----------------------------------------------------------------------
diff --git a/runtime/test/TestClownfish.c b/runtime/test/TestClownfish.c
new file mode 100644
index 0000000..e9ec7a3
--- /dev/null
+++ b/runtime/test/TestClownfish.c
@@ -0,0 +1,22 @@
+/* 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/test/TestClownfish.cfp
----------------------------------------------------------------------
diff --git a/runtime/test/TestClownfish.cfp b/runtime/test/TestClownfish.cfp
new file mode 100644
index 0000000..6db4b4a
--- /dev/null
+++ b/runtime/test/TestClownfish.cfp
@@ -0,0 +1,8 @@
+{
+    "name": "TestClownfish",
+    "nickname": "TestCfish",
+    "version": "v0.5.0",
+    "prerequisites": {
+        "Clownfish": "v0.5.0"
+    }
+}


[03/14] lucy-clownfish git commit: Make some internal symbols visible

Posted by nw...@apache.org.
Make some internal symbols visible

Otherwise the separate test binary can't see them.


Project: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/commit/83c90450
Tree: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/tree/83c90450
Diff: http://git-wip-us.apache.org/repos/asf/lucy-clownfish/diff/83c90450

Branch: refs/heads/master
Commit: 83c9045079e5e5e21b487feaaeb5a506108b24b3
Parents: 8ba4e61
Author: Nick Wellnhofer <we...@aevum.de>
Authored: Fri Jul 8 13:57:18 2016 +0200
Committer: Nick Wellnhofer <we...@aevum.de>
Committed: Fri Jul 8 14:25:36 2016 +0200

----------------------------------------------------------------------
 runtime/core/Clownfish/LockFreeRegistry.h | 10 ++++++----
 runtime/core/Clownfish/PtrHash.h          | 10 ++++++----
 runtime/core/Clownfish/Util/Atomic.h      |  4 ++--
 3 files changed, 14 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/83c90450/runtime/core/Clownfish/LockFreeRegistry.h
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/LockFreeRegistry.h b/runtime/core/Clownfish/LockFreeRegistry.h
index 065c8bf..ac2f74d 100644
--- a/runtime/core/Clownfish/LockFreeRegistry.h
+++ b/runtime/core/Clownfish/LockFreeRegistry.h
@@ -19,6 +19,8 @@
 
 #include <stddef.h>
 
+#include "cfish_parcel.h"
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -31,17 +33,17 @@ struct cfish_String;
 
 typedef struct cfish_LockFreeRegistry cfish_LockFreeRegistry;
 
-cfish_LockFreeRegistry*
+CFISH_VISIBLE cfish_LockFreeRegistry*
 cfish_LFReg_new(size_t capacity);
 
-void
+CFISH_VISIBLE void
 cfish_LFReg_destroy(cfish_LockFreeRegistry *self);
 
-bool
+CFISH_VISIBLE bool
 cfish_LFReg_register(cfish_LockFreeRegistry *self, struct cfish_String *key,
                      struct cfish_Obj *value);
 
-struct cfish_Obj*
+CFISH_VISIBLE struct cfish_Obj*
 cfish_LFReg_fetch(cfish_LockFreeRegistry *self, struct cfish_String *key);
 
 #ifdef CFISH_USE_SHORT_NAMES

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/83c90450/runtime/core/Clownfish/PtrHash.h
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/PtrHash.h b/runtime/core/Clownfish/PtrHash.h
index 4a88bc4..03d5f95 100644
--- a/runtime/core/Clownfish/PtrHash.h
+++ b/runtime/core/Clownfish/PtrHash.h
@@ -19,22 +19,24 @@
 
 #include <stddef.h>
 
+#include "cfish_parcel.h"
+
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 typedef struct cfish_PtrHash cfish_PtrHash;
 
-cfish_PtrHash*
+CFISH_VISIBLE cfish_PtrHash*
 cfish_PtrHash_new(size_t min_cap);
 
-void
+CFISH_VISIBLE void
 CFISH_PtrHash_Destroy(cfish_PtrHash *self);
 
-void
+CFISH_VISIBLE void
 CFISH_PtrHash_Store(cfish_PtrHash *self, void *key, void *value);
 
-void*
+CFISH_VISIBLE void*
 CFISH_PtrHash_Fetch(cfish_PtrHash *self, void *key);
 
 #ifdef CFISH_USE_SHORT_NAMES

http://git-wip-us.apache.org/repos/asf/lucy-clownfish/blob/83c90450/runtime/core/Clownfish/Util/Atomic.h
----------------------------------------------------------------------
diff --git a/runtime/core/Clownfish/Util/Atomic.h b/runtime/core/Clownfish/Util/Atomic.h
index 8b2f8e6..88d8de9 100644
--- a/runtime/core/Clownfish/Util/Atomic.h
+++ b/runtime/core/Clownfish/Util/Atomic.h
@@ -57,7 +57,7 @@ cfish_Atomic_cas_ptr(void *volatile *target, void *old_value, void *new_value) {
 /********************************** Windows *******************************/
 #elif defined(CHY_HAS_WINDOWS_H)
 
-bool
+CFISH_VISIBLE bool
 cfish_Atomic_wrapped_cas_ptr(void *volatile *target, void *old_value,
                             void *new_value);
 
@@ -87,7 +87,7 @@ cfish_Atomic_cas_ptr(void *volatile *target, void *old_value, void *new_value) {
 #elif defined(CHY_HAS_PTHREAD_H)
 #include <pthread.h>
 
-extern pthread_mutex_t cfish_Atomic_mutex;
+extern CFISH_VISIBLE pthread_mutex_t cfish_Atomic_mutex;
 
 static CFISH_INLINE bool
 cfish_Atomic_cas_ptr(void *volatile *target, void *old_value, void *new_value) {