http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Store/TestFolder.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Store/TestFolder.c 
b/test/Lucy/Test/Store/TestFolder.c
new file mode 100644
index 0000000..e94d474
--- /dev/null
+++ b/test/Lucy/Test/Store/TestFolder.c
@@ -0,0 +1,540 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define C_LUCY_RAMFOLDER
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+
+#include "Clownfish/Blob.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/Store/TestFolder.h"
+#include "Lucy/Store/DirHandle.h"
+#include "Lucy/Store/FileHandle.h"
+#include "Lucy/Store/InStream.h"
+#include "Lucy/Store/OutStream.h"
+#include "Lucy/Store/RAMFolder.h"
+
+static String *foo               = NULL;
+static String *bar               = NULL;
+static String *baz               = NULL;
+static String *boffo             = NULL;
+static String *banana            = NULL;
+static String *foo_bar           = NULL;
+static String *foo_bar_baz       = NULL;
+static String *foo_bar_baz_boffo = NULL;
+static String *foo_boffo         = NULL;
+static String *foo_foo           = NULL;
+static String *nope              = NULL;
+
+TestFolder*
+TestFolder_new() {
+    return (TestFolder*)Class_Make_Obj(TESTFOLDER);
+}
+
+static void
+S_init_strings(void) {
+    foo               = Str_newf("foo");
+    bar               = Str_newf("bar");
+    baz               = Str_newf("baz");
+    boffo             = Str_newf("boffo");
+    banana            = Str_newf("banana");
+    foo_bar           = Str_newf("foo/bar");
+    foo_bar_baz       = Str_newf("foo/bar/baz");
+    foo_bar_baz_boffo = Str_newf("foo/bar/baz/boffo");
+    foo_boffo         = Str_newf("foo/boffo");
+    foo_foo           = Str_newf("foo/foo");
+    nope              = Str_newf("nope");
+}
+
+static void
+S_destroy_strings(void) {
+    DECREF(foo);
+    DECREF(bar);
+    DECREF(baz);
+    DECREF(boffo);
+    DECREF(banana);
+    DECREF(foo_bar);
+    DECREF(foo_bar_baz);
+    DECREF(foo_bar_baz_boffo);
+    DECREF(foo_boffo);
+    DECREF(foo_foo);
+    DECREF(nope);
+}
+
+static void
+test_Exists(TestBatchRunner *runner) {
+    Folder *folder = (Folder*)RAMFolder_new(NULL);
+    FileHandle *fh;
+
+    Folder_MkDir(folder, foo);
+    Folder_MkDir(folder, foo_bar);
+    fh = Folder_Open_FileHandle(folder, boffo, FH_CREATE | FH_WRITE_ONLY);
+    DECREF(fh);
+    fh = Folder_Open_FileHandle(folder, foo_boffo,
+                                FH_CREATE | FH_WRITE_ONLY);
+    DECREF(fh);
+
+    TEST_TRUE(runner, Folder_Exists(folder, foo), "Dir exists");
+    TEST_TRUE(runner, Folder_Exists(folder, boffo), "File exists");
+    TEST_TRUE(runner, Folder_Exists(folder, foo_bar),
+              "Nested dir exists");
+    TEST_TRUE(runner, Folder_Exists(folder, foo_boffo),
+              "Nested file exists");
+
+    TEST_FALSE(runner, Folder_Exists(folder, banana),
+               "Non-existent entry");
+    TEST_FALSE(runner, Folder_Exists(folder, foo_foo),
+               "Non-existent nested entry");
+
+    DECREF(folder);
+}
+
+static void
+test_Set_Path_and_Get_Path(TestBatchRunner *runner) {
+    Folder *folder = (Folder*)RAMFolder_new(foo);
+    TEST_TRUE(runner, Str_Equals(Folder_Get_Path(folder), (Obj*)foo),
+              "Get_Path");
+    Folder_Set_Path(folder, bar);
+    TEST_TRUE(runner, Str_Equals(Folder_Get_Path(folder), (Obj*)bar),
+              "Set_Path");
+    DECREF(folder);
+}
+
+static void
+test_MkDir_and_Is_Directory(TestBatchRunner *runner) {
+    Folder *folder = (Folder*)RAMFolder_new(NULL);
+    FileHandle *fh;
+
+    TEST_FALSE(runner, Folder_Is_Directory(folder, foo),
+               "Is_Directory() false for non-existent entry");
+
+    TEST_TRUE(runner, Folder_MkDir(folder, foo),
+              "MkDir returns true on success");
+    TEST_TRUE(runner, Folder_Is_Directory(folder, foo),
+              "Is_Directory() true for local folder");
+
+    TEST_FALSE(runner, Folder_Is_Directory(folder, foo_bar_baz),
+               "Is_Directory() false for non-existent deeply nested dir");
+    Err_set_error(NULL);
+    TEST_FALSE(runner, Folder_MkDir(folder, foo_bar_baz),
+               "MkDir for deeply nested dir fails");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "MkDir for deeply nested dir sets global error");
+
+    TEST_TRUE(runner, Folder_MkDir(folder, foo_bar),
+              "MkDir for nested dir");
+    TEST_TRUE(runner, Folder_Is_Directory(folder, foo_bar),
+              "Is_Directory() true for nested dir");
+
+    Err_set_error(NULL);
+    TEST_FALSE(runner, Folder_MkDir(folder, foo_bar),
+               "Overwrite dir with MkDir fails");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Overwrite dir with MkDir sets global error");
+
+    fh = Folder_Open_FileHandle(folder, foo_boffo,
+                                FH_CREATE | FH_WRITE_ONLY);
+    DECREF(fh);
+    Err_set_error(NULL);
+    TEST_FALSE(runner, Folder_MkDir(folder, foo_boffo),
+               "Overwrite file with MkDir fails");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Overwrite file with MkDir sets global error");
+    TEST_FALSE(runner, Folder_Is_Directory(folder, foo_boffo),
+               "Is_Directory() false for nested file");
+
+    DECREF(folder);
+}
+
+static void
+test_Enclosing_Folder_and_Find_Folder(TestBatchRunner *runner) {
+    Folder *folder = (Folder*)RAMFolder_new(NULL);
+    FileHandle *fh;
+
+    Folder_MkDir(folder, foo);
+    Folder_MkDir(folder, foo_bar);
+    Folder_MkDir(folder, foo_bar_baz);
+    fh = Folder_Open_FileHandle(folder, foo_bar_baz_boffo,
+                                FH_CREATE | FH_WRITE_ONLY);
+
+    {
+        Folder *encloser = Folder_Enclosing_Folder(folder, (String*)nope);
+        Folder *found = Folder_Find_Folder(folder, (String*)nope);
+        TEST_TRUE(runner, encloser == folder,
+                  "Enclosing_Folder() - non-existent entry yields parent");
+        TEST_TRUE(runner, found == NULL,
+                  "Find_Folder() - non-existent entry yields NULL");
+    }
+
+    {
+        Folder *encloser = Folder_Enclosing_Folder(folder, foo_bar);
+        Folder *found = Folder_Find_Folder(folder, foo_bar);
+        TEST_TRUE(runner,
+                  encloser
+                  && Folder_is_a(encloser, FOLDER)
+                  && Str_Ends_With(Folder_Get_Path(encloser), foo),
+                  "Enclosing_Folder() - find one directory down");
+        TEST_TRUE(runner,
+                  found
+                  && Folder_is_a(found, FOLDER)
+                  && Str_Ends_With(Folder_Get_Path(found), bar),
+                  "Find_Folder() - 'foo/bar'");
+    }
+
+    {
+        Folder *encloser = Folder_Enclosing_Folder(folder, foo_bar_baz);
+        Folder *found = Folder_Find_Folder(folder, foo_bar_baz);
+        TEST_TRUE(runner,
+                  encloser
+                  && Folder_is_a(encloser, FOLDER)
+                  && Str_Ends_With(Folder_Get_Path(encloser), bar),
+                  "Find two directories down");
+        TEST_TRUE(runner,
+                  found
+                  && Folder_is_a(found, FOLDER)
+                  && Str_Ends_With(Folder_Get_Path(found), baz),
+                  "Find_Folder() - 'foo/bar/baz'");
+    }
+
+    {
+        Folder *encloser
+            = Folder_Enclosing_Folder(folder, foo_bar_baz_boffo);
+        Folder *found = Folder_Find_Folder(folder, foo_bar_baz_boffo);
+        TEST_TRUE(runner,
+                  encloser
+                  && Folder_is_a(encloser, FOLDER)
+                  && Str_Ends_With(Folder_Get_Path(encloser), baz),
+                  "Recurse to find a directory containing a real file");
+        TEST_TRUE(runner, found == NULL,
+                  "Find_Folder() - file instead of folder yields NULL");
+    }
+
+    DECREF(fh);
+    DECREF(folder);
+}
+
+static void
+test_List(TestBatchRunner *runner) {
+    Folder     *folder = (Folder*)RAMFolder_new(NULL);
+    FileHandle *fh;
+    Vector     *list;
+    String     *elem;
+
+    Folder_MkDir(folder, foo);
+    Folder_MkDir(folder, foo_bar);
+    Folder_MkDir(folder, foo_bar_baz);
+    fh = Folder_Open_FileHandle(folder, boffo, FH_CREATE | FH_WRITE_ONLY);
+    DECREF(fh);
+    fh = Folder_Open_FileHandle(folder, banana, FH_CREATE | FH_WRITE_ONLY);
+    DECREF(fh);
+
+    list = Folder_List(folder, NULL);
+    Vec_Sort(list);
+    TEST_UINT_EQ(runner, Vec_Get_Size(list), 3, "List");
+    elem = (String*)DOWNCAST(Vec_Fetch(list, 0), STRING);
+    TEST_TRUE(runner, elem && Str_Equals(elem, (Obj*)banana),
+              "List first file");
+    elem = (String*)DOWNCAST(Vec_Fetch(list, 1), STRING);
+    TEST_TRUE(runner, elem && Str_Equals(elem, (Obj*)boffo),
+              "List second file");
+    elem = (String*)DOWNCAST(Vec_Fetch(list, 2), STRING);
+    TEST_TRUE(runner, elem && Str_Equals(elem, (Obj*)foo), "List dir");
+    DECREF(list);
+
+    list = Folder_List(folder, foo_bar);
+    TEST_UINT_EQ(runner, Vec_Get_Size(list), 1, "List subdirectory contents");
+    elem = (String*)DOWNCAST(Vec_Fetch(list, 0), STRING);
+    TEST_TRUE(runner, elem && Str_Equals(elem, (Obj*)baz),
+              "Just the filename");
+    DECREF(list);
+
+    DECREF(folder);
+}
+
+static void
+test_Open_Dir(TestBatchRunner *runner) {
+    Folder     *folder = (Folder*)RAMFolder_new(NULL);
+    DirHandle  *dh;
+
+    Folder_MkDir(folder, foo);
+    Folder_MkDir(folder, foo_bar);
+
+    dh = Folder_Open_Dir(folder, foo);
+    TEST_TRUE(runner, dh && DH_is_a(dh, DIRHANDLE), "Open_Dir");
+    DECREF(dh);
+    dh = Folder_Open_Dir(folder, foo_bar);
+    TEST_TRUE(runner, dh && DH_is_a(dh, DIRHANDLE), "Open_Dir nested dir");
+    DECREF(dh);
+
+    Err_set_error(NULL);
+    dh = Folder_Open_Dir(folder, bar);
+    TEST_TRUE(runner, dh == NULL,
+              "Open_Dir on non-existent entry fails");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Open_Dir on non-existent entry sets global error");
+
+    Err_set_error(NULL);
+    dh = Folder_Open_Dir(folder, foo_foo);
+    TEST_TRUE(runner, dh == NULL,
+              "Open_Dir on non-existent nested entry fails");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Open_Dir on non-existent nested entry sets global error");
+
+    DECREF(folder);
+}
+
+static void
+test_Open_FileHandle(TestBatchRunner *runner) {
+    Folder     *folder = (Folder*)RAMFolder_new(NULL);
+    FileHandle *fh;
+
+    Folder_MkDir(folder, foo);
+
+    fh = Folder_Open_FileHandle(folder, boffo, FH_CREATE | FH_WRITE_ONLY);
+    TEST_TRUE(runner, fh && FH_is_a(fh, FILEHANDLE), "Open_FileHandle");
+    DECREF(fh);
+
+    fh = Folder_Open_FileHandle(folder, foo_boffo,
+                                FH_CREATE | FH_WRITE_ONLY);
+    TEST_TRUE(runner, fh && FH_is_a(fh, FILEHANDLE),
+              "Open_FileHandle for nested file");
+    DECREF(fh);
+
+    Err_set_error(NULL);
+    fh = Folder_Open_FileHandle(folder, foo, FH_CREATE | FH_WRITE_ONLY);
+    TEST_TRUE(runner, fh == NULL,
+              "Open_FileHandle on existing dir path fails");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Open_FileHandle on existing dir name sets global error");
+
+    Err_set_error(NULL);
+    fh = Folder_Open_FileHandle(folder, foo_bar_baz_boffo,
+                                FH_CREATE | FH_WRITE_ONLY);
+    TEST_TRUE(runner, fh == NULL,
+              "Open_FileHandle for entry within non-existent dir fails");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Open_FileHandle for entry within non-existent dir sets global 
error");
+
+    DECREF(folder);
+}
+
+static void
+test_Open_Out(TestBatchRunner *runner) {
+    Folder    *folder = (Folder*)RAMFolder_new(NULL);
+    OutStream *outstream;
+
+    Folder_MkDir(folder, foo);
+
+    outstream = Folder_Open_Out(folder, boffo);
+    TEST_TRUE(runner, outstream && OutStream_is_a(outstream, OUTSTREAM),
+              "Open_Out");
+    DECREF(outstream);
+
+    outstream = Folder_Open_Out(folder, foo_boffo);
+    TEST_TRUE(runner, outstream && OutStream_is_a(outstream, OUTSTREAM),
+              "Open_Out for nested file");
+    DECREF(outstream);
+
+    Err_set_error(NULL);
+    outstream = Folder_Open_Out(folder, boffo);
+    TEST_TRUE(runner, outstream == NULL,
+              "Open_OutStream on existing file fails");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Open_Out on existing file sets global error");
+
+    Err_set_error(NULL);
+    outstream = Folder_Open_Out(folder, foo);
+    TEST_TRUE(runner, outstream == NULL,
+              "Open_OutStream on existing dir path fails");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Open_Out on existing dir name sets global error");
+
+    Err_set_error(NULL);
+    outstream = Folder_Open_Out(folder, foo_bar_baz_boffo);
+    TEST_TRUE(runner, outstream == NULL,
+              "Open_Out for entry within non-existent dir fails");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Open_Out for entry within non-existent dir sets global error");
+
+    DECREF(folder);
+}
+
+static void
+test_Open_In(TestBatchRunner *runner) {
+    Folder     *folder = (Folder*)RAMFolder_new(NULL);
+    FileHandle *fh;
+    InStream   *instream;
+
+    Folder_MkDir(folder, foo);
+    Folder_MkDir(folder, foo_bar);
+    fh = Folder_Open_FileHandle(folder, boffo, FH_CREATE | FH_WRITE_ONLY);
+    DECREF(fh);
+    fh = Folder_Open_FileHandle(folder, foo_boffo, FH_CREATE | FH_WRITE_ONLY);
+    DECREF(fh);
+
+    instream = Folder_Open_In(folder, boffo);
+    TEST_TRUE(runner, instream && InStream_is_a(instream, INSTREAM),
+              "Open_In");
+    DECREF(instream);
+
+    instream = Folder_Open_In(folder, foo_boffo);
+    TEST_TRUE(runner, instream && InStream_is_a(instream, INSTREAM),
+              "Open_In for nested file");
+    DECREF(instream);
+
+    Err_set_error(NULL);
+    instream = Folder_Open_In(folder, foo);
+    TEST_TRUE(runner, instream == NULL,
+              "Open_InStream on existing dir path fails");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Open_In on existing dir name sets global error");
+
+    Err_set_error(NULL);
+    instream = Folder_Open_In(folder, foo_bar_baz_boffo);
+    TEST_TRUE(runner, instream == NULL,
+              "Open_In for entry within non-existent dir fails");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Open_In for entry within non-existent dir sets global error");
+
+    DECREF(folder);
+}
+
+static void
+test_Delete(TestBatchRunner *runner) {
+    Folder *folder = (Folder*)RAMFolder_new(NULL);
+    FileHandle *fh;
+    bool result;
+
+    Folder_MkDir(folder, foo);
+    Folder_MkDir(folder, foo_bar);
+    fh = Folder_Open_FileHandle(folder, boffo, FH_CREATE | FH_WRITE_ONLY);
+    DECREF(fh);
+    fh = Folder_Open_FileHandle(folder, foo_boffo,
+                                FH_CREATE | FH_WRITE_ONLY);
+    DECREF(fh);
+
+    Err_set_error(NULL);
+    result = Folder_Delete(folder, banana);
+    TEST_FALSE(runner, result, "Delete on non-existent entry returns false");
+
+    Err_set_error(NULL);
+    result = Folder_Delete(folder, foo);
+    TEST_FALSE(runner, result, "Delete on non-empty dir returns false");
+
+    TEST_TRUE(runner, Folder_Delete(folder, foo_boffo),
+              "Delete nested file");
+    TEST_FALSE(runner, Folder_Exists(folder, foo_boffo),
+               "File is really gone");
+    TEST_TRUE(runner, Folder_Delete(folder, foo_bar),
+              "Delete nested dir");
+    TEST_FALSE(runner, Folder_Exists(folder, foo_bar),
+               "Dir is really gone");
+    TEST_TRUE(runner, Folder_Delete(folder, foo), "Delete empty dir");
+    TEST_FALSE(runner, Folder_Exists(folder, foo), "Dir is really gone");
+
+    DECREF(folder);
+}
+
+static void
+test_Delete_Tree(TestBatchRunner *runner) {
+    Folder *folder = (Folder*)RAMFolder_new(NULL);
+    FileHandle *fh;
+    bool result;
+
+    // Create tree to be deleted.
+    Folder_MkDir(folder, foo);
+    Folder_MkDir(folder, foo_bar);
+    Folder_MkDir(folder, foo_bar_baz);
+    fh = Folder_Open_FileHandle(folder, foo_bar_baz_boffo,
+                                FH_CREATE | FH_WRITE_ONLY);
+    DECREF(fh);
+
+    // Create bystanders.
+    Folder_MkDir(folder, bar);
+    fh = Folder_Open_FileHandle(folder, baz, FH_CREATE | FH_WRITE_ONLY);
+    DECREF(fh);
+
+    result = Folder_Delete_Tree(folder, foo);
+    TEST_TRUE(runner, result, "Delete_Tree() succeeded");
+    TEST_FALSE(runner, Folder_Exists(folder, foo), "Tree really gone");
+
+    TEST_TRUE(runner, Folder_Exists(folder, bar),
+              "local dir with same name as nested dir left intact");
+    TEST_TRUE(runner, Folder_Exists(folder, baz),
+              "local file with same name as nested dir left intact");
+
+    // Kill off the bystanders.
+    result = Folder_Delete_Tree(folder, bar);
+    TEST_TRUE(runner, result, "Delete_Tree() on empty dir");
+    result = Folder_Delete_Tree(folder, baz);
+    TEST_TRUE(runner, result, "Delete_Tree() on file");
+
+    // Create new tree to be deleted.
+    Folder_MkDir(folder, foo);
+    Folder_MkDir(folder, foo_bar);
+    Folder_MkDir(folder, foo_bar_baz);
+    fh = Folder_Open_FileHandle(folder, foo_bar_baz_boffo,
+                                FH_CREATE | FH_WRITE_ONLY);
+    DECREF(fh);
+
+    // Remove tree in subdir.
+    result = Folder_Delete_Tree(folder, foo_bar);
+    TEST_TRUE(runner, result, "Delete_Tree() of subdir succeeded");
+    TEST_FALSE(runner, Folder_Exists(folder, foo_bar),
+               "subdir really gone");
+    TEST_TRUE(runner, Folder_Exists(folder, foo),
+              "enclosing dir left intact");
+
+    DECREF(folder);
+}
+
+static void
+test_Slurp_File(TestBatchRunner *runner) {
+    Folder *folder = (Folder*)RAMFolder_new(NULL);
+    FileHandle *fh = Folder_Open_FileHandle(folder, foo,
+                                            FH_CREATE | FH_WRITE_ONLY);
+    Blob *contents;
+
+    FH_Write(fh, "stuff", 5);
+    FH_Close(fh);
+    DECREF(fh);
+    contents = Folder_Slurp_File(folder, foo);
+    TEST_TRUE(runner, Blob_Equals_Bytes(contents, "stuff", 5), "Slurp_File");
+
+    DECREF(contents);
+    DECREF(folder);
+}
+
+void
+TestFolder_Run_IMP(TestFolder *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 79);
+    S_init_strings();
+    test_Exists(runner);
+    test_Set_Path_and_Get_Path(runner);
+    test_MkDir_and_Is_Directory(runner);
+    test_Enclosing_Folder_and_Find_Folder(runner);
+    test_List(runner);
+    test_Open_Dir(runner);
+    test_Open_FileHandle(runner);
+    test_Open_Out(runner);
+    test_Open_In(runner);
+    test_Delete(runner);
+    test_Delete_Tree(runner);
+    test_Slurp_File(runner);
+    S_destroy_strings();
+}
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Store/TestFolder.cfh
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Store/TestFolder.cfh 
b/test/Lucy/Test/Store/TestFolder.cfh
new file mode 100644
index 0000000..6a5ecf4
--- /dev/null
+++ b/test/Lucy/Test/Store/TestFolder.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 TestLucy;
+
+class Lucy::Test::Store::TestFolder
+    inherits Clownfish::TestHarness::TestBatch {
+
+    inert incremented TestFolder*
+    new();
+
+    void
+    Run(TestFolder *self, TestBatchRunner *runner);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Store/TestFolderCommon.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Store/TestFolderCommon.c 
b/test/Lucy/Test/Store/TestFolderCommon.c
new file mode 100644
index 0000000..07a9850
--- /dev/null
+++ b/test/Lucy/Test/Store/TestFolderCommon.c
@@ -0,0 +1,562 @@
+/* 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 TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+
+#include "Lucy/Test.h"
+#include "Lucy/Test/Store/TestFolderCommon.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Store/Folder.h"
+#include "Lucy/Store/DirHandle.h"
+#include "Lucy/Store/FileHandle.h"
+#include "Lucy/Store/InStream.h"
+#include "Lucy/Store/OutStream.h"
+
+#define set_up_t    LUCY_TestFolderCommon_Set_Up_t
+#define tear_down_t LUCY_TestFolderCommon_Tear_Down_t
+
+static String *foo           = NULL;
+static String *bar           = NULL;
+static String *baz           = NULL;
+static String *boffo         = NULL;
+static String *banana        = NULL;
+static String *foo_bar       = NULL;
+static String *foo_bar_baz   = NULL;
+static String *foo_bar_boffo = NULL;
+static String *foo_boffo     = NULL;
+static String *foo_foo       = NULL;
+static String *nope          = NULL;
+static String *nope_nyet     = NULL;
+
+static void
+S_init_strings(void) {
+    foo           = Str_newf("foo");
+    bar           = Str_newf("bar");
+    baz           = Str_newf("baz");
+    boffo         = Str_newf("boffo");
+    banana        = Str_newf("banana");
+    foo_bar       = Str_newf("foo/bar");
+    foo_bar_baz   = Str_newf("foo/bar/baz");
+    foo_bar_boffo = Str_newf("foo/bar/boffo");
+    foo_boffo     = Str_newf("foo/boffo");
+    foo_foo       = Str_newf("foo/foo");
+    nope          = Str_newf("nope");
+    nope_nyet     = Str_newf("nope/nyet");
+}
+
+static void
+S_destroy_strings(void) {
+    DECREF(foo);
+    DECREF(bar);
+    DECREF(baz);
+    DECREF(boffo);
+    DECREF(banana);
+    DECREF(foo_bar);
+    DECREF(foo_bar_baz);
+    DECREF(foo_bar_boffo);
+    DECREF(foo_boffo);
+    DECREF(foo_foo);
+    DECREF(nope);
+    DECREF(nope_nyet);
+}
+
+static void
+test_Local_Exists(TestBatchRunner *runner, set_up_t set_up, tear_down_t 
tear_down) {
+    Folder *folder = set_up();
+    OutStream *outstream = Folder_Open_Out(folder, boffo);
+    DECREF(outstream);
+    Folder_Local_MkDir(folder, foo);
+    outstream = Folder_Open_Out(folder, foo_boffo);
+    DECREF(outstream);
+
+    TEST_TRUE(runner, Folder_Local_Exists(folder, boffo),
+              "Local_Exists() returns true for file");
+    TEST_TRUE(runner, Folder_Local_Exists(folder, foo),
+              "Local_Exists() returns true for dir");
+    TEST_FALSE(runner, Folder_Local_Exists(folder, foo_boffo),
+               "Local_Exists() returns false for nested entry");
+    TEST_FALSE(runner, Folder_Local_Exists(folder, bar),
+               "Local_Exists() returns false for non-existent entry");
+
+    Folder_Delete(folder, foo_boffo);
+    Folder_Delete(folder, foo);
+    Folder_Delete(folder, boffo);
+    DECREF(folder);
+    tear_down();
+}
+
+static void
+test_Local_Is_Directory(TestBatchRunner *runner, set_up_t set_up,
+                        tear_down_t tear_down) {
+    Folder *folder = set_up();
+    OutStream *outstream = Folder_Open_Out(folder, boffo);
+    DECREF(outstream);
+    Folder_Local_MkDir(folder, foo);
+
+    TEST_FALSE(runner, Folder_Local_Is_Directory(folder, boffo),
+               "Local_Is_Directory() returns false for file");
+    TEST_TRUE(runner, Folder_Local_Is_Directory(folder, foo),
+              "Local_Is_Directory() returns true for dir");
+    TEST_FALSE(runner, Folder_Local_Is_Directory(folder, bar),
+               "Local_Is_Directory() returns false for non-existent entry");
+
+    Folder_Delete(folder, boffo);
+    Folder_Delete(folder, foo);
+    DECREF(folder);
+    tear_down();
+}
+
+static void
+test_Local_Find_Folder(TestBatchRunner *runner, set_up_t set_up,
+                       tear_down_t tear_down) {
+    Folder    *folder = set_up();
+    Folder    *local;
+    OutStream *outstream;
+
+    Folder_MkDir(folder, foo);
+    Folder_MkDir(folder, foo_bar);
+    outstream = Folder_Open_Out(folder, boffo);
+    DECREF(outstream);
+    outstream = Folder_Open_Out(folder, foo_boffo);
+    DECREF(outstream);
+
+    local = Folder_Local_Find_Folder(folder, nope);
+    TEST_TRUE(runner, local == NULL, "Non-existent entry yields NULL");
+
+    String *empty = SSTR_BLANK();
+    local = Folder_Local_Find_Folder(folder, empty);
+    TEST_TRUE(runner, local == NULL, "Empty string yields NULL");
+
+    local = Folder_Local_Find_Folder(folder, foo_bar);
+    TEST_TRUE(runner, local == NULL, "nested folder yields NULL");
+
+    local = Folder_Local_Find_Folder(folder, foo_boffo);
+    TEST_TRUE(runner, local == NULL, "nested file yields NULL");
+
+    local = Folder_Local_Find_Folder(folder, boffo);
+    TEST_TRUE(runner, local == NULL, "local file yields NULL");
+
+    local = Folder_Local_Find_Folder(folder, bar);
+    TEST_TRUE(runner, local == NULL, "name of nested folder yields NULL");
+
+    local = Folder_Local_Find_Folder(folder, foo);
+    TEST_TRUE(runner,
+              local
+              && Folder_is_a(local, FOLDER)
+              && Str_Ends_With(Folder_Get_Path(local), foo),
+              "Find local directory");
+
+    Folder_Delete(folder, foo_bar);
+    Folder_Delete(folder, foo_boffo);
+    Folder_Delete(folder, foo);
+    Folder_Delete(folder, boffo);
+    DECREF(folder);
+    tear_down();
+}
+
+static void
+test_Local_MkDir(TestBatchRunner *runner, set_up_t set_up, tear_down_t 
tear_down) {
+    Folder *folder = set_up();
+    bool result;
+
+    result = Folder_Local_MkDir(folder, foo);
+    TEST_TRUE(runner, result, "Local_MkDir succeeds and returns true");
+
+    Err_set_error(NULL);
+    result = Folder_Local_MkDir(folder, foo);
+    TEST_FALSE(runner, result,
+               "Local_MkDir returns false when a dir already exists");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Local_MkDir sets global error when a dir already exists");
+    TEST_TRUE(runner, Folder_Exists(folder, foo),
+              "Existing dir untouched after failed Local_MkDir");
+
+    OutStream *outstream = Folder_Open_Out(folder, boffo);
+    DECREF(outstream);
+    Err_set_error(NULL);
+    result = Folder_Local_MkDir(folder, foo);
+    TEST_FALSE(runner, result,
+               "Local_MkDir returns false when a file already exists");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Local_MkDir sets global error when a file already exists");
+    TEST_TRUE(runner, Folder_Exists(folder, boffo) &&
+              !Folder_Local_Is_Directory(folder, boffo),
+              "Existing file untouched after failed Local_MkDir");
+
+    Folder_Delete(folder, foo);
+    Folder_Delete(folder, boffo);
+    DECREF(folder);
+    tear_down();
+}
+
+static void
+test_Local_Open_Dir(TestBatchRunner *runner, set_up_t set_up, tear_down_t 
tear_down) {
+    Folder *folder = set_up();
+    DirHandle *dh = Folder_Local_Open_Dir(folder);
+    TEST_TRUE(runner, dh && DH_is_a(dh, DIRHANDLE),
+              "Local_Open_Dir returns an DirHandle");
+    DECREF(dh);
+    DECREF(folder);
+    tear_down();
+}
+
+static void
+test_Local_Open_FileHandle(TestBatchRunner *runner, set_up_t set_up,
+                           tear_down_t tear_down) {
+    Folder *folder = set_up();
+    FileHandle *fh;
+
+    fh = Folder_Local_Open_FileHandle(folder, boffo,
+                                      FH_CREATE | FH_WRITE_ONLY | 
FH_EXCLUSIVE);
+    TEST_TRUE(runner, fh && FH_is_a(fh, FILEHANDLE),
+              "opened FileHandle");
+    DECREF(fh);
+
+    fh = Folder_Local_Open_FileHandle(folder, boffo,
+                                      FH_CREATE | FH_WRITE_ONLY);
+    TEST_TRUE(runner, fh && FH_is_a(fh, FILEHANDLE),
+              "opened FileHandle for append");
+    DECREF(fh);
+
+    Err_set_error(NULL);
+    fh = Folder_Local_Open_FileHandle(folder, boffo,
+                                      FH_CREATE | FH_WRITE_ONLY | 
FH_EXCLUSIVE);
+    TEST_TRUE(runner, fh == NULL, "FH_EXLUSIVE flag prevents clobber");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "failure due to FH_EXLUSIVE flag sets global error");
+
+    fh = Folder_Local_Open_FileHandle(folder, boffo, FH_READ_ONLY);
+    TEST_TRUE(runner, fh && FH_is_a(fh, FILEHANDLE),
+              "opened FileHandle for reading");
+    DECREF(fh);
+
+    Err_set_error(NULL);
+    fh = Folder_Local_Open_FileHandle(folder, nope, FH_READ_ONLY);
+    TEST_TRUE(runner, fh == NULL,
+              "Can't open non-existent file for reading");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Opening non-existent file for reading sets global error");
+
+    Folder_Delete(folder, boffo);
+    DECREF(folder);
+    tear_down();
+}
+
+static void
+test_Local_Delete(TestBatchRunner *runner, set_up_t set_up, tear_down_t 
tear_down) {
+    Folder *folder = set_up();
+    OutStream *outstream;
+
+    outstream = Folder_Open_Out(folder, boffo);
+    DECREF(outstream);
+    TEST_TRUE(runner, Folder_Local_Delete(folder, boffo),
+              "Local_Delete on file succeeds");
+    TEST_FALSE(runner, Folder_Exists(folder, boffo),
+               "File is really gone");
+
+    Folder_Local_MkDir(folder, foo);
+    outstream = Folder_Open_Out(folder, foo_boffo);
+    DECREF(outstream);
+
+    Err_set_error(NULL);
+    TEST_FALSE(runner, Folder_Local_Delete(folder, foo),
+               "Local_Delete on non-empty dir fails");
+
+    Folder_Delete(folder, foo_boffo);
+    TEST_TRUE(runner, Folder_Local_Delete(folder, foo),
+              "Local_Delete on empty dir succeeds");
+    // FIXME: This test sometimes fails on Windows.
+    TEST_FALSE(runner, Folder_Exists(folder, foo),
+               "Dir is really gone");
+
+    DECREF(folder);
+    tear_down();
+}
+
+static void
+test_Rename(TestBatchRunner *runner, set_up_t set_up, tear_down_t tear_down) {
+    Folder *folder = set_up();
+    OutStream *outstream;
+    bool result;
+
+    Folder_MkDir(folder, foo);
+    Folder_MkDir(folder, foo_bar);
+    outstream = Folder_Open_Out(folder, boffo);
+    OutStream_Close(outstream);
+    DECREF(outstream);
+
+    // Move files.
+
+    result = Folder_Rename(folder, boffo, banana);
+    TEST_TRUE(runner, result, "Rename succeeds and returns true");
+    TEST_TRUE(runner, Folder_Exists(folder, banana),
+              "File exists at new path");
+    TEST_FALSE(runner, Folder_Exists(folder, boffo),
+               "File no longer exists at old path");
+
+    result = Folder_Rename(folder, banana, foo_bar_boffo);
+    TEST_TRUE(runner, result, "Rename to file in nested dir");
+    TEST_TRUE(runner, Folder_Exists(folder, foo_bar_boffo),
+              "File exists at new path");
+    TEST_FALSE(runner, Folder_Exists(folder, banana),
+               "File no longer exists at old path");
+
+    result = Folder_Rename(folder, foo_bar_boffo, boffo);
+    TEST_TRUE(runner, result, "Rename from file in nested dir");
+    TEST_TRUE(runner, Folder_Exists(folder, boffo),
+              "File exists at new path");
+    TEST_FALSE(runner, Folder_Exists(folder, foo_bar_boffo),
+               "File no longer exists at old path");
+
+    outstream = Folder_Open_Out(folder, foo_boffo);
+    OutStream_Close(outstream);
+    DECREF(outstream);
+    result = Folder_Rename(folder, boffo, foo_boffo);
+    if (result) {
+        PASS(runner, "Rename clobbers on this system");
+        TEST_TRUE(runner, Folder_Exists(folder, foo_boffo),
+                  "File exists at new path");
+        TEST_FALSE(runner, Folder_Exists(folder, boffo),
+                   "File no longer exists at old path");
+    }
+    else {
+        PASS(runner, "Rename does not clobber on this system");
+        TEST_TRUE(runner, Folder_Exists(folder, foo_boffo),
+                  "File exists at new path");
+        TEST_TRUE(runner, Folder_Exists(folder, boffo),
+                  "File still exists at old path");
+        Folder_Delete(folder, boffo);
+    }
+
+    // Move Dirs.
+
+    Folder_MkDir(folder, baz);
+    result = Folder_Rename(folder, baz, boffo);
+    TEST_TRUE(runner, result, "Rename dir");
+    TEST_TRUE(runner, Folder_Exists(folder, boffo),
+              "Folder exists at new path");
+    TEST_FALSE(runner, Folder_Exists(folder, baz),
+               "Folder no longer exists at old path");
+
+    result = Folder_Rename(folder, boffo, foo_foo);
+    TEST_TRUE(runner, result, "Rename dir into nested subdir");
+    TEST_TRUE(runner, Folder_Exists(folder, foo_foo),
+              "Folder exists at new path");
+    TEST_FALSE(runner, Folder_Exists(folder, boffo),
+               "Folder no longer exists at old path");
+
+    result = Folder_Rename(folder, foo_foo, foo_bar_baz);
+    TEST_TRUE(runner, result, "Rename dir from nested subdir");
+    TEST_TRUE(runner, Folder_Exists(folder, foo_bar_baz),
+              "Folder exists at new path");
+    TEST_FALSE(runner, Folder_Exists(folder, foo_foo),
+               "Folder no longer exists at old path");
+
+    // Test failed clobbers.
+
+    Err_set_error(NULL);
+    result = Folder_Rename(folder, foo_boffo, foo_bar);
+    TEST_FALSE(runner, result, "Rename file clobbering dir fails");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Failed rename sets global error");
+    TEST_TRUE(runner, Folder_Exists(folder, foo_boffo),
+              "File still exists at old path");
+    TEST_TRUE(runner, Folder_Exists(folder, foo_bar),
+              "Dir still exists after failed clobber");
+
+    Err_set_error(NULL);
+    result = Folder_Rename(folder, foo_bar, foo_boffo);
+    TEST_FALSE(runner, result, "Rename dir clobbering file fails");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Failed rename sets global error");
+    TEST_TRUE(runner, Folder_Exists(folder, foo_bar),
+              "Dir still exists at old path");
+    TEST_TRUE(runner, Folder_Exists(folder, foo_boffo),
+              "File still exists after failed clobber");
+
+    // Test that "renaming" succeeds where to and from are the same.
+
+    result = Folder_Rename(folder, foo_boffo, foo_boffo);
+    TEST_TRUE(runner, result, "Renaming file to itself succeeds");
+    TEST_TRUE(runner, Folder_Exists(folder, foo_boffo),
+              "File still exists");
+
+    result = Folder_Rename(folder, foo_bar, foo_bar);
+    // FIXME: This test sometimes fails on Windows with "Permission denied".
+    TEST_TRUE(runner, result, "Renaming dir to itself succeeds");
+    TEST_TRUE(runner, Folder_Exists(folder, foo_bar),
+              "Dir still exists");
+
+    // Invalid filepaths.
+
+    Err_set_error(NULL);
+    result = Folder_Rename(folder, foo_boffo, nope_nyet);
+    TEST_FALSE(runner, result, "Rename into non-existent subdir fails");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Renaming into non-existent subdir sets global error");
+    TEST_TRUE(runner, Folder_Exists(folder, foo_boffo),
+              "Entry still exists at old path");
+
+    Err_set_error(NULL);
+    result = Folder_Rename(folder, nope_nyet, boffo);
+    TEST_FALSE(runner, result, "Rename non-existent file fails");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Renaming non-existent source file sets global error");
+
+    Folder_Delete(folder, foo_bar_baz);
+    Folder_Delete(folder, foo_bar);
+    Folder_Delete(folder, foo_boffo);
+    Folder_Delete(folder, foo);
+    DECREF(folder);
+    tear_down();
+}
+
+static void
+test_Hard_Link(TestBatchRunner *runner, set_up_t set_up, tear_down_t 
tear_down) {
+    Folder *folder = set_up();
+    OutStream *outstream;
+    bool result;
+
+    Folder_MkDir(folder, foo);
+    Folder_MkDir(folder, foo_bar);
+    outstream = Folder_Open_Out(folder, boffo);
+    DECREF(outstream);
+
+    // Link files.
+
+    result = Folder_Hard_Link(folder, boffo, banana);
+    TEST_TRUE(runner, result, "Hard_Link succeeds and returns true");
+    TEST_TRUE(runner, Folder_Exists(folder, banana),
+              "File exists at new path");
+    TEST_TRUE(runner, Folder_Exists(folder, boffo),
+              "File still exists at old path");
+    Folder_Delete(folder, boffo);
+
+    result = Folder_Hard_Link(folder, banana, foo_bar_boffo);
+    TEST_TRUE(runner, result, "Hard_Link to target within nested dir");
+    TEST_TRUE(runner, Folder_Exists(folder, foo_bar_boffo),
+              "File exists at new path");
+    TEST_TRUE(runner, Folder_Exists(folder, banana),
+              "File still exists at old path");
+    Folder_Delete(folder, banana);
+
+    result = Folder_Hard_Link(folder, foo_bar_boffo, foo_boffo);
+    TEST_TRUE(runner, result, "Hard_Link from file in nested dir");
+    TEST_TRUE(runner, Folder_Exists(folder, foo_boffo),
+              "File exists at new path");
+    TEST_TRUE(runner, Folder_Exists(folder, foo_bar_boffo),
+              "File still exists at old path");
+    Folder_Delete(folder, foo_bar_boffo);
+
+    // Invalid clobbers.
+
+    outstream = Folder_Open_Out(folder, boffo);
+    DECREF(outstream);
+    result = Folder_Hard_Link(folder, foo_boffo, boffo);
+    TEST_FALSE(runner, result, "Clobber of file fails");
+    TEST_TRUE(runner, Folder_Exists(folder, boffo),
+              "File still exists at new path");
+    TEST_TRUE(runner, Folder_Exists(folder, foo_boffo),
+              "File still exists at old path");
+    Folder_Delete(folder, boffo);
+
+    Folder_MkDir(folder, baz);
+    result = Folder_Hard_Link(folder, foo_boffo, baz);
+    TEST_FALSE(runner, result, "Clobber of dir fails");
+    TEST_TRUE(runner, Folder_Exists(folder, baz),
+              "Dir still exists at new path");
+    TEST_TRUE(runner, Folder_Exists(folder, foo_boffo),
+              "File still exists at old path");
+    Folder_Delete(folder, baz);
+
+    // Invalid Hard_Link of dir.
+
+    Folder_MkDir(folder, baz);
+    result = Folder_Hard_Link(folder, baz, banana);
+    TEST_FALSE(runner, result, "Hard_Link dir fails");
+    TEST_FALSE(runner, Folder_Exists(folder, banana),
+               "Nothing at new path");
+    // FIXME: This test sometimes fails on Windows.
+    TEST_TRUE(runner, Folder_Exists(folder, baz),
+              "Folder still exists at old path");
+    Folder_Delete(folder, baz);
+
+    // Test that linking to yourself fails.
+
+    result = Folder_Hard_Link(folder, foo_boffo, foo_boffo);
+    TEST_FALSE(runner, result, "Hard_Link file to itself fails");
+    TEST_TRUE(runner, Folder_Exists(folder, foo_boffo),
+              "File still exists");
+
+    // Invalid filepaths.
+
+    Err_set_error(NULL);
+    result = Folder_Rename(folder, foo_boffo, nope_nyet);
+    TEST_FALSE(runner, result, "Hard_Link into non-existent subdir fails");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Hard_Link into non-existent subdir sets global error");
+    TEST_TRUE(runner, Folder_Exists(folder, foo_boffo),
+              "Entry still exists at old path");
+
+    Err_set_error(NULL);
+    result = Folder_Rename(folder, nope_nyet, boffo);
+    TEST_FALSE(runner, result, "Hard_Link non-existent source file fails");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Hard_Link non-existent source file sets global error");
+
+    Folder_Delete(folder, foo_bar);
+    Folder_Delete(folder, foo_boffo);
+    Folder_Delete(folder, foo);
+    DECREF(folder);
+    tear_down();
+}
+
+static void
+test_Close(TestBatchRunner *runner, set_up_t set_up, tear_down_t tear_down) {
+    Folder *folder = set_up();
+    Folder_Close(folder);
+    PASS(runner, "Close() concludes without incident");
+    Folder_Close(folder);
+    Folder_Close(folder);
+    PASS(runner, "Calling Close() multiple times is safe");
+    DECREF(folder);
+    tear_down();
+}
+
+uint32_t
+TestFolderCommon_num_tests() {
+    return 99;
+}
+
+void
+TestFolderCommon_run_tests(TestBatchRunner *runner, set_up_t set_up,
+                           tear_down_t tear_down) {
+    S_init_strings();
+    test_Local_Exists(runner, set_up, tear_down);
+    test_Local_Is_Directory(runner, set_up, tear_down);
+    test_Local_Find_Folder(runner, set_up, tear_down);
+    test_Local_MkDir(runner, set_up, tear_down);
+    test_Local_Open_Dir(runner, set_up, tear_down);
+    test_Local_Open_FileHandle(runner, set_up, tear_down);
+    test_Local_Delete(runner, set_up, tear_down);
+    test_Rename(runner, set_up, tear_down);
+    test_Hard_Link(runner, set_up, tear_down);
+    test_Close(runner, set_up, tear_down);
+    S_destroy_strings();
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Store/TestFolderCommon.cfh
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Store/TestFolderCommon.cfh 
b/test/Lucy/Test/Store/TestFolderCommon.cfh
new file mode 100644
index 0000000..75042f0
--- /dev/null
+++ b/test/Lucy/Test/Store/TestFolderCommon.cfh
@@ -0,0 +1,40 @@
+/* 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 TestLucy;
+
+__C__
+typedef lucy_Folder*
+LUCY_TestFolderCommon_Set_Up_t(void);
+typedef void
+LUCY_TestFolderCommon_Tear_Down_t(void);
+#ifdef LUCY_USE_SHORT_NAMES
+  #define TestFolderCommon_Set_Up_t    LUCY_TestFolderCommon_Set_Up_t
+  #define TestFolderCommon_Tear_Down_t LUCY_TestFolderCommon_Tear_Down_t
+#endif
+__END_C__
+
+inert class Lucy::Test::Store::TestFolderCommon {
+    inert uint32_t
+    num_tests();
+
+    inert void
+    run_tests(TestBatchRunner *runner,
+              LUCY_TestFolderCommon_Set_Up_t set_up,
+              LUCY_TestFolderCommon_Tear_Down_t tear_down);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Store/TestIOChunks.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Store/TestIOChunks.c 
b/test/Lucy/Test/Store/TestIOChunks.c
new file mode 100644
index 0000000..bf35d0b
--- /dev/null
+++ b/test/Lucy/Test/Store/TestIOChunks.c
@@ -0,0 +1,126 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define C_TESTLUCY_TESTINSTREAM
+#define C_LUCY_INSTREAM
+#define C_LUCY_FILEWINDOW
+#include <stdlib.h>
+#include <time.h>
+
+#include "charmony.h"
+
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Clownfish/TestHarness/TestUtils.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/TestUtils.h"
+#include "Lucy/Test/Store/TestIOChunks.h"
+#include "Lucy/Store/InStream.h"
+#include "Lucy/Store/OutStream.h"
+#include "Lucy/Store/RAMFile.h"
+#include "Lucy/Store/RAMFileHandle.h"
+
+TestIOChunks*
+TestIOChunks_new() {
+    return (TestIOChunks*)Class_Make_Obj(TESTIOCHUNKS);
+}
+
+static void
+test_Align(TestBatchRunner *runner) {
+    RAMFile    *file      = RAMFile_new(NULL, false);
+    OutStream  *outstream = OutStream_open((Obj*)file);
+
+    for (int32_t i = 1; i < 32; i++) {
+        int64_t random_bytes = TestUtils_random_u64() % 32;
+        while (random_bytes--) { OutStream_Write_U8(outstream, 0); }
+        TEST_TRUE(runner, (OutStream_Align(outstream, i) % i) == 0,
+                  "Align to %ld", (long)i);
+    }
+    DECREF(file);
+    DECREF(outstream);
+}
+
+static void
+test_Read_Write_Bytes(TestBatchRunner *runner) {
+    RAMFile    *file      = RAMFile_new(NULL, false);
+    OutStream  *outstream = OutStream_open((Obj*)file);
+    InStream   *instream;
+    char        buf[4];
+
+    OutStream_Write_Bytes(outstream, "foo", 4);
+    OutStream_Close(outstream);
+
+    instream = InStream_open((Obj*)file);
+    InStream_Read_Bytes(instream, buf, 4);
+    TEST_TRUE(runner, strcmp(buf, "foo") == 0, "Read_Bytes Write_Bytes");
+
+    DECREF(instream);
+    DECREF(outstream);
+    DECREF(file);
+}
+
+static void
+test_Buf(TestBatchRunner *runner) {
+    RAMFile    *file      = RAMFile_new(NULL, false);
+    OutStream  *outstream = OutStream_open((Obj*)file);
+    size_t      size      = IO_STREAM_BUF_SIZE * 2 + 5;
+    InStream   *instream;
+    const char *buf;
+
+    for (uint32_t i = 0; i < size; i++) {
+        OutStream_Write_U8(outstream, 'a');
+    }
+    OutStream_Close(outstream);
+
+    instream = InStream_open((Obj*)file);
+    InStreamIVARS *const ivars = InStream_IVARS(instream);
+    buf = InStream_Buf(instream, 5);
+    TEST_INT_EQ(runner, ivars->limit - buf, IO_STREAM_BUF_SIZE,
+                "Small request bumped up");
+
+    buf += IO_STREAM_BUF_SIZE - 10; // 10 bytes left in buffer.
+    InStream_Advance_Buf(instream, buf);
+
+    buf = InStream_Buf(instream, 10);
+    TEST_INT_EQ(runner, ivars->limit - buf, 10,
+                "Exact request doesn't trigger refill");
+
+    buf = InStream_Buf(instream, 11);
+    TEST_INT_EQ(runner, ivars->limit - buf, IO_STREAM_BUF_SIZE,
+                "Requesting over limit triggers refill");
+
+    int64_t     expected = InStream_Length(instream) - InStream_Tell(instream);
+    const char *buff     = InStream_Buf(instream, 100000);
+    int64_t     got      = CHY_PTR_TO_I64(ivars->limit) - CHY_PTR_TO_I64(buff);
+    TEST_TRUE(runner, got == expected,
+              "Requests greater than file size get pared down");
+
+    DECREF(instream);
+    DECREF(outstream);
+    DECREF(file);
+}
+
+void
+TestIOChunks_Run_IMP(TestIOChunks *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 36);
+    srand((unsigned int)time((time_t*)NULL));
+    test_Align(runner);
+    test_Read_Write_Bytes(runner);
+    test_Buf(runner);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Store/TestIOChunks.cfh
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Store/TestIOChunks.cfh 
b/test/Lucy/Test/Store/TestIOChunks.cfh
new file mode 100644
index 0000000..68c459a
--- /dev/null
+++ b/test/Lucy/Test/Store/TestIOChunks.cfh
@@ -0,0 +1,31 @@
+/* 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 TestLucy;
+
+/** Tests reading and writing of composite types using InStream/OutStream.
+ */
+class Lucy::Test::Store::TestIOChunks
+    inherits Clownfish::TestHarness::TestBatch {
+
+    inert incremented TestIOChunks*
+    new();
+
+    void
+    Run(TestIOChunks *self, TestBatchRunner *runner);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Store/TestIOPrimitives.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Store/TestIOPrimitives.c 
b/test/Lucy/Test/Store/TestIOPrimitives.c
new file mode 100644
index 0000000..e7d32f2
--- /dev/null
+++ b/test/Lucy/Test/Store/TestIOPrimitives.c
@@ -0,0 +1,518 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define C_TESTLUCY_TESTINSTREAM
+#define C_LUCY_INSTREAM
+#define C_LUCY_FILEWINDOW
+#include <stdlib.h>
+#include <time.h>
+
+#include "charmony.h"
+
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Clownfish/TestHarness/TestUtils.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/TestUtils.h"
+#include "Lucy/Test/Store/TestIOPrimitives.h"
+#include "Lucy/Store/InStream.h"
+#include "Lucy/Store/OutStream.h"
+#include "Lucy/Store/RAMFile.h"
+#include "Lucy/Store/RAMFileHandle.h"
+#include "Lucy/Util/NumberUtils.h"
+
+TestIOPrimitives*
+TestIOPrimitives_new() {
+    return (TestIOPrimitives*)Class_Make_Obj(TESTIOPRIMITIVES);
+}
+
+static void
+test_i8(TestBatchRunner *runner) {
+    RAMFile    *file      = RAMFile_new(NULL, false);
+    OutStream  *outstream = OutStream_open((Obj*)file);
+    InStream   *instream;
+    int i;
+
+    for (i = -128; i < 128; i++) {
+        OutStream_Write_I8(outstream, (int8_t)i);
+    }
+    OutStream_Close(outstream);
+
+    instream = InStream_open((Obj*)file);
+    for (i = -128; i < 128; i++) {
+        if (InStream_Read_I8(instream) != i) { break; }
+    }
+    TEST_INT_EQ(runner, i, 128, "round trip i8 successful for %d out of 256",
+                i + 128);
+
+    DECREF(instream);
+    DECREF(outstream);
+    DECREF(file);
+}
+
+static void
+test_u8(TestBatchRunner *runner) {
+    RAMFile    *file      = RAMFile_new(NULL, false);
+    OutStream  *outstream = OutStream_open((Obj*)file);
+    InStream   *instream;
+    int i;
+
+    for (i = 0; i < 256; i++) {
+        OutStream_Write_U8(outstream, (uint8_t)i);
+    }
+    OutStream_Close(outstream);
+
+    instream = InStream_open((Obj*)file);
+    for (i = 0; i < 256; i++) {
+        if (InStream_Read_U8(instream) != i) { break; }
+    }
+    TEST_INT_EQ(runner, i, 256,
+                "round trip u8 successful for %d out of 256", i);
+
+    DECREF(instream);
+    DECREF(outstream);
+    DECREF(file);
+}
+
+static void
+test_i32(TestBatchRunner *runner) {
+    int64_t    *ints = TestUtils_random_i64s(NULL, 1000, INT32_MIN, INT32_MAX);
+    RAMFile    *file      = RAMFile_new(NULL, false);
+    OutStream  *outstream = OutStream_open((Obj*)file);
+    InStream   *instream;
+    uint32_t i;
+
+    // Test boundaries.
+    ints[0] = INT32_MIN;
+    ints[1] = INT32_MIN + 1;
+    ints[2] = INT32_MAX;
+    ints[3] = INT32_MAX - 1;
+
+    for (i = 0; i < 1000; i++) {
+        OutStream_Write_I32(outstream, (int32_t)ints[i]);
+    }
+    OutStream_Close(outstream);
+
+    instream = InStream_open((Obj*)file);
+    for (i = 0; i < 1000; i++) {
+        int32_t got = InStream_Read_I32(instream);
+        if (got != ints[i]) {
+            FAIL(runner, "i32 round trip failed: %ld, %ld", (long)got,
+                 (long)ints[i]);
+            break;
+        }
+    }
+    if (i == 1000) {
+        PASS(runner, "i32 round trip");
+    }
+
+    DECREF(instream);
+    DECREF(outstream);
+    DECREF(file);
+    FREEMEM(ints);
+}
+
+static void
+test_u32(TestBatchRunner *runner) {
+    uint64_t   *ints = TestUtils_random_u64s(NULL, 1000, 0, UINT32_MAX);
+    RAMFile    *file      = RAMFile_new(NULL, false);
+    OutStream  *outstream = OutStream_open((Obj*)file);
+    InStream   *instream;
+    uint32_t i;
+
+    // Test boundaries.
+    ints[0] = 0;
+    ints[1] = 1;
+    ints[2] = UINT32_MAX;
+    ints[3] = UINT32_MAX - 1;
+
+    for (i = 0; i < 1000; i++) {
+        OutStream_Write_U32(outstream, (uint32_t)ints[i]);
+    }
+    OutStream_Close(outstream);
+
+    instream = InStream_open((Obj*)file);
+    for (i = 0; i < 1000; i++) {
+        uint32_t got = InStream_Read_U32(instream);
+        if (got != ints[i]) {
+            FAIL(runner, "u32 round trip failed: %lu, %lu", (unsigned long)got,
+                 (unsigned long)ints[i]);
+            break;
+        }
+    }
+    if (i == 1000) {
+        PASS(runner, "u32 round trip");
+    }
+
+    DECREF(instream);
+    DECREF(outstream);
+    DECREF(file);
+    FREEMEM(ints);
+}
+
+static void
+test_i64(TestBatchRunner *runner) {
+    int64_t    *ints = TestUtils_random_i64s(NULL, 1000, INT64_MIN, INT64_MAX);
+    RAMFile    *file      = RAMFile_new(NULL, false);
+    OutStream  *outstream = OutStream_open((Obj*)file);
+    InStream   *instream;
+    uint32_t i;
+
+    // Test boundaries.
+    ints[0] = INT64_MIN;
+    ints[1] = INT64_MIN + 1;
+    ints[2] = INT64_MAX;
+    ints[3] = INT64_MAX - 1;
+
+    for (i = 0; i < 1000; i++) {
+        OutStream_Write_I64(outstream, ints[i]);
+    }
+    OutStream_Close(outstream);
+
+    instream = InStream_open((Obj*)file);
+    for (i = 0; i < 1000; i++) {
+        int64_t got = InStream_Read_I64(instream);
+        if (got != ints[i]) {
+            FAIL(runner, "i64 round trip failed: %" PRId64 ", %" PRId64,
+                 got, ints[i]);
+            break;
+        }
+    }
+    if (i == 1000) {
+        PASS(runner, "i64 round trip");
+    }
+
+    DECREF(instream);
+    DECREF(outstream);
+    DECREF(file);
+    FREEMEM(ints);
+}
+
+
+static void
+test_u64(TestBatchRunner *runner) {
+    uint64_t   *ints = TestUtils_random_u64s(NULL, 1000, 0, UINT64_MAX);
+    RAMFile    *file      = RAMFile_new(NULL, false);
+    OutStream  *outstream = OutStream_open((Obj*)file);
+    InStream   *instream;
+    uint32_t i;
+
+    // Test boundaries.
+    ints[0] = 0;
+    ints[1] = 1;
+    ints[2] = UINT64_MAX;
+    ints[3] = UINT64_MAX - 1;
+
+    for (i = 0; i < 1000; i++) {
+        OutStream_Write_U64(outstream, ints[i]);
+    }
+    OutStream_Close(outstream);
+
+    instream = InStream_open((Obj*)file);
+    for (i = 0; i < 1000; i++) {
+        uint64_t got = InStream_Read_U64(instream);
+        if (got != ints[i]) {
+            FAIL(runner, "u64 round trip failed: %" PRIu64 ", %" PRIu64,
+                 got, ints[i]);
+            break;
+        }
+    }
+    if (i == 1000) {
+        PASS(runner, "u64 round trip");
+    }
+
+    DECREF(instream);
+    DECREF(outstream);
+    DECREF(file);
+    FREEMEM(ints);
+}
+
+static void
+test_ci32(TestBatchRunner *runner) {
+    int64_t *ints = TestUtils_random_i64s(NULL, 1000, INT32_MIN, INT32_MAX);
+    RAMFile *file = RAMFile_new(NULL, false);
+    OutStream *outstream = OutStream_open((Obj*)file);
+    InStream *instream;
+    size_t i;
+
+    // Test boundaries.
+    ints[0] = 0;
+    ints[1] = 1;
+    ints[2] = -1;
+    ints[3] = INT32_MAX;
+    ints[4] = INT32_MIN;
+
+    for (i = 0; i < 1000; i++) {
+        OutStream_Write_CI32(outstream, (int32_t)ints[i]);
+    }
+    OutStream_Close(outstream);
+
+    instream = InStream_open((Obj*)file);
+    for (i = 0; i < 1000; i++) {
+        int32_t got = InStream_Read_CI32(instream);
+        if ((int64_t)got != ints[i]) {
+            FAIL(runner, "ci32 round trip failed: %" PRId64 ", %" PRId64,
+                 (int64_t)got, ints[i]);
+            break;
+        }
+    }
+    if (i == 1000) {
+        PASS(runner, "ci32 round trip");
+    }
+
+    DECREF(instream);
+    DECREF(outstream);
+    DECREF(file);
+    FREEMEM(ints);
+}
+
+static void
+test_cu32(TestBatchRunner *runner) {
+    uint64_t   *ints = TestUtils_random_u64s(NULL, 1000, 0, UINT32_MAX);
+    RAMFile    *file      = RAMFile_new(NULL, false);
+    OutStream  *outstream = OutStream_open((Obj*)file);
+    InStream   *instream;
+    uint32_t i;
+
+    // Test boundaries.
+    ints[0] = 0;
+    ints[1] = 1;
+    ints[2] = UINT32_MAX;
+    ints[3] = UINT32_MAX - 1;
+
+    for (i = 0; i < 1000; i++) {
+        OutStream_Write_CU32(outstream, (uint32_t)ints[i]);
+    }
+    OutStream_Close(outstream);
+
+    instream = InStream_open((Obj*)file);
+    for (i = 0; i < 1000; i++) {
+        uint32_t got = InStream_Read_CU32(instream);
+        if (got != ints[i]) {
+            FAIL(runner, "cu32 round trip failed: %lu, %lu", (unsigned 
long)got,
+                 (unsigned long)ints[i]);
+            break;
+        }
+    }
+    if (i == 1000) {
+        PASS(runner, "cu32 round trip");
+    }
+
+    DECREF(instream);
+    DECREF(outstream);
+    DECREF(file);
+    FREEMEM(ints);
+}
+
+static void
+test_ci64(TestBatchRunner *runner) {
+    int64_t *ints = TestUtils_random_i64s(NULL, 1000, INT64_MIN, INT64_MAX);
+    RAMFile *file = RAMFile_new(NULL, false);
+    OutStream *outstream = OutStream_open((Obj*)file);
+    InStream *instream;
+    uint32_t i;
+
+    // Test boundaries.
+    ints[0] = 0;
+    ints[1] = 1;
+    ints[2] = -1;
+    ints[3] = INT64_MAX;
+    ints[4] = INT64_MIN;
+
+    for (i = 0; i < 1000; i++) {
+        OutStream_Write_CI64(outstream, ints[i]);
+    }
+    OutStream_Close(outstream);
+
+    instream = InStream_open((Obj*)file);
+    for (i = 0; i < 1000; i++) {
+        int64_t got = InStream_Read_CI64(instream);
+        if (got != ints[i]) {
+            FAIL(runner, "ci64 round trip failed: %" PRId64 ", %" PRId64,
+                 got, ints[i]);
+            break;
+        }
+    }
+    if (i == 1000) {
+        PASS(runner, "ci64 round trip");
+    }
+
+    DECREF(instream);
+    DECREF(outstream);
+    DECREF(file);
+    FREEMEM(ints);
+}
+
+static void
+test_cu64(TestBatchRunner *runner) {
+    uint64_t   *ints   = TestUtils_random_u64s(NULL, 1000, 0, UINT64_MAX);
+    RAMFile    *file     = RAMFile_new(NULL, false);
+    RAMFile    *raw_file = RAMFile_new(NULL, false);
+    OutStream  *outstream     = OutStream_open((Obj*)file);
+    OutStream  *raw_outstream = OutStream_open((Obj*)raw_file);
+    InStream   *instream;
+    InStream   *raw_instream;
+    uint32_t i;
+
+    // Test boundaries.
+    ints[0] = 0;
+    ints[1] = 1;
+    ints[2] = UINT64_MAX;
+    ints[3] = UINT64_MAX - 1;
+
+    for (i = 0; i < 1000; i++) {
+        OutStream_Write_CU64(outstream, ints[i]);
+        OutStream_Write_CU64(raw_outstream, ints[i]);
+    }
+    OutStream_Close(outstream);
+    OutStream_Close(raw_outstream);
+
+    instream = InStream_open((Obj*)file);
+    for (i = 0; i < 1000; i++) {
+        uint64_t got = InStream_Read_CU64(instream);
+        if (got != ints[i]) {
+            FAIL(runner, "cu64 round trip failed: %" PRIu64 ", %" PRIu64,
+                 got, ints[i]);
+            break;
+        }
+    }
+    if (i == 1000) {
+        PASS(runner, "cu64 round trip");
+    }
+
+    raw_instream = InStream_open((Obj*)raw_file);
+    for (i = 0; i < 1000; i++) {
+        char  buffer[10];
+        const char *buf = buffer;
+        int size = InStream_Read_Raw_C64(raw_instream, buffer);
+        uint64_t got = NumUtil_decode_cu64(&buf);
+        UNUSED_VAR(size);
+        if (got != ints[i]) {
+            FAIL(runner, "Read_Raw_C64 failed: %" PRIu64 ", %" PRIu64,
+                 got, ints[i]);
+            break;
+        }
+    }
+    if (i == 1000) {
+        PASS(runner, "Read_Raw_C64");
+    }
+
+    DECREF(raw_instream);
+    DECREF(instream);
+    DECREF(raw_outstream);
+    DECREF(outstream);
+    DECREF(raw_file);
+    DECREF(file);
+    FREEMEM(ints);
+}
+
+static void
+test_f32(TestBatchRunner *runner) {
+    double     *f64s   = TestUtils_random_f64s(NULL, 1000);
+    float      *values = (float*)MALLOCATE(1000 * sizeof(float));
+    RAMFile    *file      = RAMFile_new(NULL, false);
+    OutStream  *outstream = OutStream_open((Obj*)file);
+    InStream   *instream;
+    uint32_t i;
+
+    // Truncate.
+    for (i = 0; i < 1000; i++) {
+        values[i] = (float)f64s[i];
+    }
+
+    // Test boundaries.
+    values[0] = 0.0f;
+    values[1] = 1.0f;
+
+    for (i = 0; i < 1000; i++) {
+        OutStream_Write_F32(outstream, values[i]);
+    }
+    OutStream_Close(outstream);
+
+    instream = InStream_open((Obj*)file);
+    for (i = 0; i < 1000; i++) {
+        float got = InStream_Read_F32(instream);
+        if (got != values[i]) {
+            FAIL(runner, "f32 round trip failed: %f, %f", got, values[i]);
+            break;
+        }
+    }
+    if (i == 1000) {
+        PASS(runner, "f32 round trip");
+    }
+
+    DECREF(instream);
+    DECREF(outstream);
+    DECREF(file);
+    FREEMEM(values);
+    FREEMEM(f64s);
+}
+
+static void
+test_f64(TestBatchRunner *runner) {
+    double     *values = TestUtils_random_f64s(NULL, 1000);
+    RAMFile    *file      = RAMFile_new(NULL, false);
+    OutStream  *outstream = OutStream_open((Obj*)file);
+    InStream   *instream;
+    uint32_t i;
+
+    // Test boundaries.
+    values[0] = 0.0;
+    values[1] = 1.0;
+
+    for (i = 0; i < 1000; i++) {
+        OutStream_Write_F64(outstream, values[i]);
+    }
+    OutStream_Close(outstream);
+
+    instream = InStream_open((Obj*)file);
+    for (i = 0; i < 1000; i++) {
+        double got = InStream_Read_F64(instream);
+        if (got != values[i]) {
+            FAIL(runner, "f64 round trip failed: %f, %f", got, values[i]);
+            break;
+        }
+    }
+    if (i == 1000) {
+        PASS(runner, "f64 round trip");
+    }
+
+    DECREF(instream);
+    DECREF(outstream);
+    DECREF(file);
+    FREEMEM(values);
+}
+
+void
+TestIOPrimitives_Run_IMP(TestIOPrimitives *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 13);
+    srand((unsigned int)time((time_t*)NULL));
+    test_i8(runner);
+    test_u8(runner);
+    test_i32(runner);
+    test_u32(runner);
+    test_i64(runner);
+    test_u64(runner);
+    test_ci32(runner);
+    test_cu32(runner);
+    test_ci64(runner);
+    test_cu64(runner);
+    test_f32(runner);
+    test_f64(runner);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Store/TestIOPrimitives.cfh
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Store/TestIOPrimitives.cfh 
b/test/Lucy/Test/Store/TestIOPrimitives.cfh
new file mode 100644
index 0000000..c75419a
--- /dev/null
+++ b/test/Lucy/Test/Store/TestIOPrimitives.cfh
@@ -0,0 +1,31 @@
+/* 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 TestLucy;
+
+/** Tests reading and writing of primitive types using InStream/OutStream.
+ */
+class Lucy::Test::Store::TestIOPrimitives
+    inherits Clownfish::TestHarness::TestBatch {
+
+    inert incremented TestIOPrimitives*
+    new();
+
+    void
+    Run(TestIOPrimitives *self, TestBatchRunner *runner);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Store/TestInStream.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Store/TestInStream.c 
b/test/Lucy/Test/Store/TestInStream.c
new file mode 100644
index 0000000..c32f985
--- /dev/null
+++ b/test/Lucy/Test/Store/TestInStream.c
@@ -0,0 +1,225 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define C_TESTLUCY_TESTINSTREAM
+#define C_LUCY_INSTREAM
+#define C_LUCY_FILEWINDOW
+
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/TestUtils.h"
+#include "Lucy/Test/Store/TestInStream.h"
+#include "Lucy/Test/Store/MockFileHandle.h"
+#include "Lucy/Store/FileWindow.h"
+#include "Lucy/Store/InStream.h"
+#include "Lucy/Store/OutStream.h"
+#include "Lucy/Store/RAMFile.h"
+#include "Lucy/Store/RAMFileHandle.h"
+
+TestInStream*
+TestInStream_new() {
+    return (TestInStream*)Class_Make_Obj(TESTINSTREAM);
+}
+
+static void
+test_refill(TestBatchRunner *runner) {
+    RAMFile    *file      = RAMFile_new(NULL, false);
+    OutStream  *outstream = OutStream_open((Obj*)file);
+    InStream   *instream;
+    char        scratch[5];
+    InStreamIVARS *ivars;
+
+    for (int32_t i = 0; i < 1023; i++) {
+        OutStream_Write_U8(outstream, 'x');
+    }
+    OutStream_Write_U8(outstream, 'y');
+    OutStream_Write_U8(outstream, 'z');
+    OutStream_Close(outstream);
+
+    instream = InStream_open((Obj*)file);
+    ivars = InStream_IVARS(instream);
+    InStream_Refill(instream);
+    TEST_INT_EQ(runner, ivars->limit - ivars->buf, IO_STREAM_BUF_SIZE,
+                "Refill");
+    TEST_INT_EQ(runner, (long)InStream_Tell(instream), 0,
+                "Correct file pos after standing-start Refill()");
+    DECREF(instream);
+
+    instream = InStream_open((Obj*)file);
+    ivars = InStream_IVARS(instream);
+    InStream_Fill(instream, 30);
+    TEST_INT_EQ(runner, ivars->limit - ivars->buf, 30, "Fill()");
+    TEST_INT_EQ(runner, (long)InStream_Tell(instream), 0,
+                "Correct file pos after standing-start Fill()");
+    DECREF(instream);
+
+    instream = InStream_open((Obj*)file);
+    ivars = InStream_IVARS(instream);
+    InStream_Read_Bytes(instream, scratch, 5);
+    TEST_INT_EQ(runner, ivars->limit - ivars->buf,
+                IO_STREAM_BUF_SIZE - 5, "small read triggers refill");
+    DECREF(instream);
+
+    instream = InStream_open((Obj*)file);
+    ivars = InStream_IVARS(instream);
+    TEST_INT_EQ(runner, InStream_Read_U8(instream), 'x', "Read_U8");
+    InStream_Seek(instream, 1023);
+    TEST_INT_EQ(runner, (long)FileWindow_IVARS(ivars->window)->offset, 0,
+                "no unnecessary refill on Seek");
+    TEST_INT_EQ(runner, (long)InStream_Tell(instream), 1023, "Seek/Tell");
+    TEST_INT_EQ(runner, InStream_Read_U8(instream), 'y',
+                "correct data after in-buffer Seek()");
+    TEST_INT_EQ(runner, InStream_Read_U8(instream), 'z', "automatic Refill");
+    TEST_TRUE(runner, (FileWindow_IVARS(ivars->window)->offset != 0),
+              "refilled");
+
+    DECREF(instream);
+    DECREF(outstream);
+    DECREF(file);
+}
+
+static void
+test_Clone_and_Reopen(TestBatchRunner *runner) {
+    String        *foo       = SSTR_WRAP_C("foo");
+    String        *bar       = SSTR_WRAP_C("bar");
+    RAMFile       *file      = RAMFile_new(NULL, false);
+    OutStream     *outstream = OutStream_open((Obj*)file);
+    RAMFileHandle *fh;
+    InStream      *instream;
+    InStream      *clone;
+    InStream      *reopened;
+
+    for (uint8_t i = 0; i < 26; i++) {
+        OutStream_Write_U8(outstream, 'a' + i);
+    }
+    OutStream_Close(outstream);
+
+    fh = RAMFH_open(foo, FH_READ_ONLY, file);
+    instream = InStream_open((Obj*)fh);
+    InStream_Seek(instream, 1);
+    TEST_TRUE(runner, Str_Equals(InStream_Get_Filename(instream), (Obj*)foo),
+              "Get_Filename");
+
+    clone    = InStream_Clone(instream);
+    TEST_TRUE(runner, Str_Equals(InStream_Get_Filename(clone), (Obj*)foo),
+              "Clones have same filename");
+    TEST_TRUE(runner, InStream_Length(instream) == InStream_Length(clone),
+              "Clones have same length");
+    TEST_TRUE(runner, InStream_Read_U8(instream) == InStream_Read_U8(clone),
+              "Clones start at same file position");
+
+    reopened = InStream_Reopen(instream, bar, 25, 1);
+    TEST_TRUE(runner, Str_Equals(InStream_Get_Filename(reopened), (Obj*)bar),
+              "Reopened InStreams take new filename");
+    TEST_TRUE(runner, InStream_Read_U8(reopened) == 'z',
+              "Reopened stream starts at supplied offset");
+    TEST_TRUE(runner, InStream_Length(reopened) == 1,
+              "Reopened stream uses supplied length");
+    TEST_TRUE(runner, InStream_Tell(reopened) == 1,
+              "Tell() uses supplied offset for reopened stream");
+    InStream_Seek(reopened, 0);
+    TEST_TRUE(runner, InStream_Read_U8(reopened) == 'z',
+              "Seek() uses supplied offset for reopened stream");
+
+    DECREF(reopened);
+    DECREF(clone);
+    DECREF(instream);
+    DECREF(outstream);
+    DECREF(fh);
+    DECREF(file);
+}
+
+static void
+test_Close(TestBatchRunner *runner) {
+    RAMFile  *file     = RAMFile_new(NULL, false);
+    InStream *instream = InStream_open((Obj*)file);
+    InStream_Close(instream);
+    TEST_TRUE(runner, InStream_IVARS(instream)->file_handle == NULL,
+              "Close decrements FileHandle's refcount");
+    DECREF(instream);
+    DECREF(file);
+}
+
+static void
+test_Seek_and_Tell(TestBatchRunner *runner) {
+    int64_t     gb1      = INT64_C(0x40000000);
+    int64_t     gb3      = gb1 * 3;
+    int64_t     gb6      = gb1 * 6;
+    int64_t     gb12     = gb1 * 12;
+    FileHandle *fh       = (FileHandle*)MockFileHandle_new(NULL, gb12);
+    InStream   *instream = InStream_open((Obj*)fh);
+    InStreamIVARS *const ivars = InStream_IVARS(instream);
+
+    InStream_Buf(instream, 10000);
+    TEST_TRUE(runner, ivars->limit == ((char*)NULL) + 10000,
+              "InStream_Buf sets limit");
+
+    InStream_Seek(instream, gb6);
+    TEST_TRUE(runner, InStream_Tell(instream) == gb6,
+              "Tell after seek forwards outside buffer");
+    TEST_TRUE(runner, ivars->buf == NULL,
+              "Seek forwards outside buffer sets buf to NULL");
+    TEST_TRUE(runner, ivars->limit == NULL,
+              "Seek forwards outside buffer sets limit to NULL");
+    TEST_TRUE(runner, FileWindow_IVARS(ivars->window)->offset == gb6,
+              "Seek forwards outside buffer tracks pos in window offset");
+
+    InStream_Buf(instream, (size_t)gb1);
+    TEST_TRUE(runner, ivars->limit == ((char*)NULL) + gb1,
+              "InStream_Buf sets limit");
+
+    InStream_Seek(instream, gb6 + 10);
+    TEST_TRUE(runner, InStream_Tell(instream) == gb6 + 10,
+              "Tell after seek forwards within buffer");
+    TEST_TRUE(runner, ivars->buf == ((char*)NULL) + 10,
+              "Seek within buffer sets buf");
+    TEST_TRUE(runner, ivars->limit == ((char*)NULL) + gb1,
+              "Seek within buffer leaves limit alone");
+
+    InStream_Seek(instream, gb6 + 1);
+    TEST_TRUE(runner, InStream_Tell(instream) == gb6 + 1,
+              "Tell after seek backwards within buffer");
+    TEST_TRUE(runner, ivars->buf == ((char*)NULL) + 1,
+              "Seek backwards within buffer sets buf");
+    TEST_TRUE(runner, ivars->limit == ((char*)NULL) + gb1,
+              "Seek backwards within buffer leaves limit alone");
+
+    InStream_Seek(instream, gb3);
+    TEST_TRUE(runner, InStream_Tell(instream) == gb3,
+              "Tell after seek backwards outside buffer");
+    TEST_TRUE(runner, ivars->buf == NULL,
+              "Seek backwards outside buffer sets buf to NULL");
+    TEST_TRUE(runner, ivars->limit == NULL,
+              "Seek backwards outside buffer sets limit to NULL");
+    TEST_TRUE(runner, FileWindow_IVARS(ivars->window)->offset == gb3,
+              "Seek backwards outside buffer tracks pos in window offset");
+
+    DECREF(instream);
+    DECREF(fh);
+}
+
+void
+TestInStream_Run_IMP(TestInStream *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 37);
+    test_refill(runner);
+    test_Clone_and_Reopen(runner);
+    test_Close(runner);
+    test_Seek_and_Tell(runner);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Store/TestInStream.cfh
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Store/TestInStream.cfh 
b/test/Lucy/Test/Store/TestInStream.cfh
new file mode 100644
index 0000000..b216fcd
--- /dev/null
+++ b/test/Lucy/Test/Store/TestInStream.cfh
@@ -0,0 +1,33 @@
+/* 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 TestLucy;
+
+/** Tests for basic functionality of InStream using both memory-mapped and
+ * streamed sources.
+ */
+
+class Lucy::Test::Store::TestInStream
+    inherits Clownfish::TestHarness::TestBatch {
+
+    inert incremented TestInStream*
+    new();
+
+    void
+    Run(TestInStream *self, TestBatchRunner *runner);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Store/TestRAMDirHandle.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Store/TestRAMDirHandle.c 
b/test/Lucy/Test/Store/TestRAMDirHandle.c
new file mode 100644
index 0000000..fc6292a
--- /dev/null
+++ b/test/Lucy/Test/Store/TestRAMDirHandle.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 C_LUCY_RAMFOLDER
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/Store/TestRAMDirHandle.h"
+#include "Lucy/Store/FileHandle.h"
+#include "Lucy/Store/RAMFolder.h"
+#include "Lucy/Store/RAMDirHandle.h"
+
+TestRAMDirHandle*
+TestRAMDH_new() {
+    return (TestRAMDirHandle*)Class_Make_Obj(TESTRAMDIRHANDLE);
+}
+
+static void
+test_all(TestBatchRunner *runner) {
+    RAMFolder *folder        = RAMFolder_new(NULL);
+    String    *foo           = SSTR_WRAP_C("foo");
+    String    *boffo         = SSTR_WRAP_C("boffo");
+    String    *foo_boffo     = SSTR_WRAP_C("foo/boffo");
+    bool       saw_foo       = false;
+    bool       saw_boffo     = false;
+    bool       foo_was_dir   = false;
+    bool       boffo_was_dir = false;
+    int        count         = 0;
+
+    // Set up folder contents.
+    RAMFolder_MkDir(folder, foo);
+    FileHandle *fh = RAMFolder_Open_FileHandle(folder, boffo,
+                                               FH_CREATE | FH_WRITE_ONLY);
+    DECREF(fh);
+    fh = RAMFolder_Open_FileHandle(folder, foo_boffo,
+                                   FH_CREATE | FH_WRITE_ONLY);
+    DECREF(fh);
+
+    RAMDirHandle *dh = RAMDH_new(folder);
+    while (RAMDH_Next(dh)) {
+        count++;
+        String *entry = RAMDH_Get_Entry(dh);
+        if (Str_Equals(entry, (Obj*)foo)) {
+            saw_foo = true;
+            foo_was_dir = RAMDH_Entry_Is_Dir(dh);
+        }
+        else if (Str_Equals(entry, (Obj*)boffo)) {
+            saw_boffo = true;
+            boffo_was_dir = RAMDH_Entry_Is_Dir(dh);
+        }
+        DECREF(entry);
+    }
+    TEST_INT_EQ(runner, 2, count, "correct number of entries");
+    TEST_TRUE(runner, saw_foo, "Directory was iterated over");
+    TEST_TRUE(runner, foo_was_dir,
+              "Dir correctly identified by Entry_Is_Dir");
+    TEST_TRUE(runner, saw_boffo, "File was iterated over");
+    TEST_FALSE(runner, boffo_was_dir,
+               "File correctly identified by Entry_Is_Dir");
+
+    uint32_t refcount = REFCOUNT_NN(folder);
+    RAMDH_Close(dh);
+    TEST_INT_EQ(runner, REFCOUNT_NN(folder), refcount - 1,
+                "Folder reference released by Close()");
+
+    DECREF(dh);
+    DECREF(folder);
+}
+
+void
+TestRAMDH_Run_IMP(TestRAMDirHandle *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 6);
+    test_all(runner);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Store/TestRAMDirHandle.cfh
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Store/TestRAMDirHandle.cfh 
b/test/Lucy/Test/Store/TestRAMDirHandle.cfh
new file mode 100644
index 0000000..4adfb31
--- /dev/null
+++ b/test/Lucy/Test/Store/TestRAMDirHandle.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 TestLucy;
+
+class Lucy::Test::Store::TestRAMDirHandle nickname TestRAMDH
+    inherits Clownfish::TestHarness::TestBatch {
+
+    inert incremented TestRAMDirHandle*
+    new();
+
+    void
+    Run(TestRAMDirHandle *self, TestBatchRunner *runner);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Store/TestRAMFileHandle.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Store/TestRAMFileHandle.c 
b/test/Lucy/Test/Store/TestRAMFileHandle.c
new file mode 100644
index 0000000..e208784
--- /dev/null
+++ b/test/Lucy/Test/Store/TestRAMFileHandle.c
@@ -0,0 +1,177 @@
+/* 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 C_TESTLUCY_TESTINSTREAM
+#define C_LUCY_INSTREAM
+#define C_LUCY_FILEWINDOW
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/Store/TestRAMFileHandle.h"
+#include "Lucy/Store/RAMFileHandle.h"
+#include "Lucy/Store/FileWindow.h"
+#include "Lucy/Store/RAMFile.h"
+
+TestRAMFileHandle*
+TestRAMFH_new() {
+    return (TestRAMFileHandle*)Class_Make_Obj(TESTRAMFILEHANDLE);
+}
+
+static void
+test_open(TestBatchRunner *runner) {
+    RAMFileHandle *fh;
+
+    Err_set_error(NULL);
+    fh = RAMFH_open(NULL, FH_WRITE_ONLY, NULL);
+    TEST_TRUE(runner, fh == NULL,
+              "open() without a RAMFile or FH_CREATE returns NULL");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "open() without a RAMFile or FH_CREATE sets error");
+}
+
+static void
+test_Read_Write(TestBatchRunner *runner) {
+    RAMFile *file = RAMFile_new(NULL, false);
+    RAMFileHandle *fh = RAMFH_open(NULL, FH_WRITE_ONLY, file);
+    const char *foo = "foo";
+    const char *bar = "bar";
+    char buffer[12];
+    char *buf = buffer;
+
+    TEST_TRUE(runner, Str_Equals_Utf8(RAMFH_Get_Path(fh), "", 0),
+              "NULL arg as filepath yields empty string");
+
+    TEST_TRUE(runner, RAMFH_Write(fh, foo, 3), "Write returns success");
+    TEST_TRUE(runner, RAMFH_Length(fh) == 3, "Length after one Write");
+    TEST_TRUE(runner, RAMFH_Write(fh, bar, 3), "Write returns success");
+    TEST_TRUE(runner, RAMFH_Length(fh) == 6, "Length after two Writes");
+
+    Err_set_error(NULL);
+    TEST_FALSE(runner, RAMFH_Read(fh, buf, 0, 2),
+               "Reading from a write-only handle returns false");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Reading from a write-only handle sets error");
+
+    // Reopen for reading.
+    DECREF(fh);
+    fh = RAMFH_open(NULL, FH_READ_ONLY, file);
+    TEST_TRUE(runner, RAMFile_Read_Only(file),
+              "FH_READ_ONLY propagates to RAMFile's read_only property");
+
+    TEST_TRUE(runner, RAMFH_Read(fh, buf, 0, 6), "Read returns success");
+    TEST_TRUE(runner, strncmp(buf, "foobar", 6) == 0, "Read/Write");
+    TEST_TRUE(runner, RAMFH_Read(fh, buf, 2, 3), "Read returns success");
+    TEST_TRUE(runner, strncmp(buf, "oba", 3) == 0, "Read with offset");
+
+    Err_set_error(NULL);
+    TEST_FALSE(runner, RAMFH_Read(fh, buf, -1, 4),
+               "Read() with a negative offset returns false");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Read() with a negative offset sets error");
+
+    Err_set_error(NULL);
+    TEST_FALSE(runner, RAMFH_Read(fh, buf, 6, 1),
+               "Read() past EOF returns false");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Read() past EOF sets error");
+
+    Err_set_error(NULL);
+    TEST_FALSE(runner, RAMFH_Write(fh, foo, 3),
+               "Writing to a read-only handle returns false");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Writing to a read-only handle sets error");
+
+    DECREF(fh);
+    DECREF(file);
+}
+
+static void
+test_Grow_and_Get_File(TestBatchRunner *runner) {
+    RAMFileHandle *fh = RAMFH_open(NULL, FH_WRITE_ONLY | FH_CREATE, NULL);
+    RAMFile *ram_file = RAMFH_Get_File(fh);
+    ByteBuf *contents = RAMFile_Get_Contents(ram_file);
+
+    RAMFH_Grow(fh, 100);
+    TEST_TRUE(runner, BB_Get_Capacity(contents) >= 100, "Grow");
+
+    DECREF(fh);
+}
+
+static void
+test_Close(TestBatchRunner *runner) {
+    RAMFileHandle *fh = RAMFH_open(NULL, FH_WRITE_ONLY | FH_CREATE, NULL);
+    TEST_TRUE(runner, RAMFH_Close(fh), "Close returns true");
+    DECREF(fh);
+}
+
+static void
+test_Window(TestBatchRunner *runner) {
+    RAMFile *file = RAMFile_new(NULL, false);
+    RAMFileHandle *fh = RAMFH_open(NULL, FH_WRITE_ONLY, file);
+    FileWindow *window = FileWindow_new();
+    FileWindowIVARS *const window_ivars = FileWindow_IVARS(window);
+
+    for (uint32_t i = 0; i < 1024; i++) {
+        RAMFH_Write(fh, "foo ", 4);
+    }
+    RAMFH_Close(fh);
+
+    // Reopen for reading.
+    DECREF(fh);
+    fh = RAMFH_open(NULL, FH_READ_ONLY, file);
+
+    Err_set_error(NULL);
+    TEST_FALSE(runner, RAMFH_Window(fh, window, -1, 4),
+               "Window() with a negative offset returns false");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Window() with a negative offset sets error");
+
+    Err_set_error(NULL);
+    TEST_FALSE(runner, RAMFH_Window(fh, window, 4000, 1000),
+               "Window() past EOF returns false");
+    TEST_TRUE(runner, Err_get_error() != NULL,
+              "Window() past EOF sets error");
+
+    TEST_TRUE(runner, RAMFH_Window(fh, window, 1021, 2),
+              "Window() returns true");
+    TEST_TRUE(runner, strncmp(window_ivars->buf, "oo", 2) == 0, "Window()");
+
+    TEST_TRUE(runner, RAMFH_Release_Window(fh, window),
+              "Release_Window() returns true");
+    TEST_TRUE(runner, window_ivars->buf == NULL, "Release_Window() resets 
buf");
+    TEST_TRUE(runner, window_ivars->offset == 0, "Release_Window() resets 
offset");
+    TEST_TRUE(runner, window_ivars->len == 0, "Release_Window() resets len");
+
+    DECREF(window);
+    DECREF(fh);
+    DECREF(file);
+}
+
+void
+TestRAMFH_Run_IMP(TestRAMFileHandle *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 32);
+    test_open(runner);
+    test_Read_Write(runner);
+    test_Grow_and_Get_File(runner);
+    test_Close(runner);
+    test_Window(runner);
+}
+
+

Reply via email to