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/15 11:12:00 UTC

[08/22] lucy git commit: Move tests to separate directory

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Analysis/TestSnowballStopFilter.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Analysis/TestSnowballStopFilter.c b/test/Lucy/Test/Analysis/TestSnowballStopFilter.c
new file mode 100644
index 0000000..dc555f1
--- /dev/null
+++ b/test/Lucy/Test/Analysis/TestSnowballStopFilter.c
@@ -0,0 +1,86 @@
+/* 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_TESTSNOWBALLSTOPFILTER
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/Analysis/TestSnowballStopFilter.h"
+#include "Lucy/Analysis/SnowballStopFilter.h"
+
+TestSnowballStopFilter*
+TestSnowStop_new() {
+    return (TestSnowballStopFilter*)Class_Make_Obj(TESTSNOWBALLSTOPFILTER);
+}
+
+static SnowballStopFilter*
+S_make_stopfilter(void *unused, ...) {
+    va_list args;
+    SnowballStopFilter *self = (SnowballStopFilter*)Class_Make_Obj(SNOWBALLSTOPFILTER);
+    Hash *stoplist = Hash_new(0);
+    char *stopword;
+
+    va_start(args, unused);
+    while (NULL != (stopword = va_arg(args, char*))) {
+        Hash_Store_Utf8(stoplist, stopword, strlen(stopword),
+                        (Obj*)Str_newf(""));
+    }
+    va_end(args);
+
+    self = SnowStop_init(self, NULL, stoplist);
+    DECREF(stoplist);
+    return self;
+}
+
+static void
+test_Dump_Load_and_Equals(TestBatchRunner *runner) {
+    SnowballStopFilter *stopfilter =
+        S_make_stopfilter(NULL, "foo", "bar", "baz", NULL);
+    SnowballStopFilter *other =
+        S_make_stopfilter(NULL, "foo", "bar", NULL);
+    Obj *dump       = SnowStop_Dump(stopfilter);
+    Obj *other_dump = SnowStop_Dump(other);
+    SnowballStopFilter *clone       = (SnowballStopFilter*)SnowStop_Load(other, dump);
+    SnowballStopFilter *other_clone = (SnowballStopFilter*)SnowStop_Load(other, other_dump);
+
+    TEST_FALSE(runner,
+               SnowStop_Equals(stopfilter, (Obj*)other),
+               "Equals() false with different stoplist");
+    TEST_TRUE(runner,
+              SnowStop_Equals(stopfilter, (Obj*)clone),
+              "Dump => Load round trip");
+    TEST_TRUE(runner,
+              SnowStop_Equals(other, (Obj*)other_clone),
+              "Dump => Load round trip");
+
+    DECREF(stopfilter);
+    DECREF(dump);
+    DECREF(clone);
+    DECREF(other);
+    DECREF(other_dump);
+    DECREF(other_clone);
+}
+
+void
+TestSnowStop_Run_IMP(TestSnowballStopFilter *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 3);
+    test_Dump_Load_and_Equals(runner);
+}
+
+
+

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

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Analysis/TestStandardTokenizer.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Analysis/TestStandardTokenizer.c b/test/Lucy/Test/Analysis/TestStandardTokenizer.c
new file mode 100644
index 0000000..548bf2f
--- /dev/null
+++ b/test/Lucy/Test/Analysis/TestStandardTokenizer.c
@@ -0,0 +1,140 @@
+/* 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_TESTSTANDARDTOKENIZER
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/Analysis/TestStandardTokenizer.h"
+#include "Lucy/Analysis/StandardTokenizer.h"
+#include "Lucy/Store/FSFolder.h"
+#include "Lucy/Test/TestUtils.h"
+#include "Lucy/Util/Json.h"
+
+TestStandardTokenizer*
+TestStandardTokenizer_new() {
+    return (TestStandardTokenizer*)Class_Make_Obj(TESTSTANDARDTOKENIZER);
+}
+
+static void
+test_Dump_Load_and_Equals(TestBatchRunner *runner) {
+    StandardTokenizer *tokenizer = StandardTokenizer_new();
+    Obj *dump  = StandardTokenizer_Dump(tokenizer);
+    StandardTokenizer *clone
+        = (StandardTokenizer*)StandardTokenizer_Load(tokenizer, dump);
+
+    TEST_TRUE(runner,
+              StandardTokenizer_Equals(tokenizer, (Obj*)clone),
+              "Dump => Load round trip");
+
+    DECREF(tokenizer);
+    DECREF(dump);
+    DECREF(clone);
+}
+
+static void
+test_tokenizer(TestBatchRunner *runner) {
+    StandardTokenizer *tokenizer = StandardTokenizer_new();
+
+    String *word = SSTR_WRAP_C(
+                              " ."
+                              "tha\xCC\x82t's"
+                              ":"
+                              "1,02\xC2\xADZ4.38"
+                              "\xE0\xB8\x81\xC2\xAD\xC2\xAD"
+                              "\xF0\xA0\x80\x80"
+                              "a"
+                              "/");
+    Vector *got = StandardTokenizer_Split(tokenizer, word);
+    String *token = (String*)Vec_Fetch(got, 0);
+    char   *token_str = Str_To_Utf8(token);
+    TEST_TRUE(runner,
+              token
+              && Str_is_a(token, STRING)
+              && Str_Equals_Utf8(token, "tha\xcc\x82t's", 8),
+              "Token: %s", token_str);
+    free(token_str);
+    token = (String*)Vec_Fetch(got, 1);
+    token_str = Str_To_Utf8(token);
+    TEST_TRUE(runner,
+              token
+              && Str_is_a(token, STRING)
+              && Str_Equals_Utf8(token, "1,02\xC2\xADZ4.38", 11),
+              "Token: %s", token_str);
+    free(token_str);
+    token = (String*)Vec_Fetch(got, 2);
+    token_str = Str_To_Utf8(token);
+    TEST_TRUE(runner,
+              token
+              && Str_is_a(token, STRING)
+              && Str_Equals_Utf8(token, "\xE0\xB8\x81\xC2\xAD\xC2\xAD", 7),
+              "Token: %s", token_str);
+    free(token_str);
+    token = (String*)Vec_Fetch(got, 3);
+    token_str = Str_To_Utf8(token);
+    TEST_TRUE(runner,
+              token
+              && Str_is_a(token, STRING)
+              && Str_Equals_Utf8(token, "\xF0\xA0\x80\x80", 4),
+              "Token: %s", token_str);
+    free(token_str);
+    token = (String*)Vec_Fetch(got, 4);
+    token_str = Str_To_Utf8(token);
+    TEST_TRUE(runner,
+              token
+              && Str_is_a(token, STRING)
+              && Str_Equals_Utf8(token, "a", 1),
+              "Token: %s", token_str);
+    free(token_str);
+    DECREF(got);
+
+    FSFolder *modules_folder = TestUtils_modules_folder();
+    if (modules_folder == NULL) {
+        SKIP(runner, 1372, "Can't locate test data");
+    }
+    else {
+        String *path = Str_newf("unicode/ucd/WordBreakTest.json");
+        Vector *tests = (Vector*)Json_slurp_json((Folder*)modules_folder, path);
+        if (!tests) { RETHROW(Err_get_error()); }
+
+        for (size_t i = 0, max = Vec_Get_Size(tests); i < max; i++) {
+            Hash *test = (Hash*)Vec_Fetch(tests, i);
+            String *text = (String*)Hash_Fetch_Utf8(test, "text", 4);
+            Vector *wanted = (Vector*)Hash_Fetch_Utf8(test, "words", 5);
+            Vector *got = StandardTokenizer_Split(tokenizer, text);
+            TEST_TRUE(runner, Vec_Equals(wanted, (Obj*)got), "UCD test #%d",
+                      (int)i + 1);
+            DECREF(got);
+        }
+
+        DECREF(tests);
+        DECREF(modules_folder);
+        DECREF(path);
+    }
+
+    DECREF(tokenizer);
+}
+
+void
+TestStandardTokenizer_Run_IMP(TestStandardTokenizer *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 1378);
+    test_Dump_Load_and_Equals(runner);
+    test_tokenizer(runner);
+}
+
+

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

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Highlight/TestHeatMap.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Highlight/TestHeatMap.c b/test/Lucy/Test/Highlight/TestHeatMap.c
new file mode 100644
index 0000000..6749bc4
--- /dev/null
+++ b/test/Lucy/Test/Highlight/TestHeatMap.c
@@ -0,0 +1,175 @@
+/* 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 "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/Highlight/TestHeatMap.h"
+#include "Lucy/Highlight/HeatMap.h"
+
+#include "Lucy/Search/Span.h"
+
+TestHeatMap*
+TestHeatMap_new() {
+    return (TestHeatMap*)Class_Make_Obj(TESTHEATMAP);
+}
+
+static void
+test_calc_proximity_boost(TestBatchRunner *runner) {
+    Vector  *spans    = Vec_new(0);
+    HeatMap *heat_map = HeatMap_new(spans, 133);
+    Span    *span1    = Span_new(  0, 10, 1.0f);
+    Span    *span2    = Span_new( 10, 10, 1.0f);
+    Span    *span3    = Span_new(  5,  4, 1.0f);
+    Span    *span4    = Span_new(100, 10, 1.0f);
+    Span    *span5    = Span_new(150, 10, 1.0f);
+
+    float big_boost     = HeatMap_Calc_Proximity_Boost(heat_map, span1, span2);
+    float eq_big_boost  = HeatMap_Calc_Proximity_Boost(heat_map, span1, span3);
+    float smaller_boost = HeatMap_Calc_Proximity_Boost(heat_map, span1, span4);
+    float zero_boost    = HeatMap_Calc_Proximity_Boost(heat_map, span1, span5);
+
+    TEST_TRUE(runner, big_boost == eq_big_boost,
+              "overlapping and abutting produce the same proximity boost");
+    TEST_TRUE(runner, big_boost > smaller_boost, "closer is better");
+    TEST_TRUE(runner, zero_boost == 0.0,
+              "distance outside of window yields no prox boost");
+
+    DECREF(span1);
+    DECREF(span2);
+    DECREF(span3);
+    DECREF(span4);
+    DECREF(span5);
+    DECREF(heat_map);
+    DECREF(spans);
+}
+
+static void
+test_flatten_spans(TestBatchRunner *runner) {
+    Vector  *spans    = Vec_new(8);
+    Vector  *wanted   = Vec_new(8);
+    HeatMap *heat_map = HeatMap_new(spans, 133);
+
+    Vector *flattened, *boosts;
+
+    Vec_Push(spans, (Obj*)Span_new(10, 10, 1.0f));
+    Vec_Push(spans, (Obj*)Span_new(16, 14, 2.0f));
+    flattened = HeatMap_Flatten_Spans(heat_map, spans);
+    Vec_Push(wanted, (Obj*)Span_new(10,  6, 1.0f));
+    Vec_Push(wanted, (Obj*)Span_new(16,  4, 3.0f));
+    Vec_Push(wanted, (Obj*)Span_new(20, 10, 2.0f));
+    TEST_TRUE(runner, Vec_Equals(flattened, (Obj*)wanted),
+              "flatten two overlapping spans");
+    Vec_Clear(wanted);
+    boosts = HeatMap_Generate_Proximity_Boosts(heat_map, spans);
+    Vec_Push(wanted, (Obj*)Span_new(10, 20, 3.0f));
+    TEST_TRUE(runner, Vec_Equals(boosts, (Obj*)wanted),
+              "prox boosts for overlap");
+    Vec_Clear(wanted);
+    Vec_Clear(spans);
+    DECREF(boosts);
+    DECREF(flattened);
+
+    Vec_Push(spans, (Obj*)Span_new(10, 10, 1.0f));
+    Vec_Push(spans, (Obj*)Span_new(16, 14, 2.0f));
+    Vec_Push(spans, (Obj*)Span_new(50,  1, 1.0f));
+    flattened = HeatMap_Flatten_Spans(heat_map, spans);
+    Vec_Push(wanted, (Obj*)Span_new(10,  6, 1.0f));
+    Vec_Push(wanted, (Obj*)Span_new(16,  4, 3.0f));
+    Vec_Push(wanted, (Obj*)Span_new(20, 10, 2.0f));
+    Vec_Push(wanted, (Obj*)Span_new(50,  1, 1.0f));
+    TEST_TRUE(runner, Vec_Equals(flattened, (Obj*)wanted),
+              "flatten two overlapping spans, leave hole, then third span");
+    Vec_Clear(wanted);
+    boosts = HeatMap_Generate_Proximity_Boosts(heat_map, spans);
+    TEST_TRUE(runner, Vec_Get_Size(boosts) == 2 + 1,
+              "boosts generated for each unique pair, since all were in range");
+    Vec_Clear(spans);
+    DECREF(boosts);
+    DECREF(flattened);
+
+    Vec_Push(spans, (Obj*)Span_new(10, 10, 1.0f));
+    Vec_Push(spans, (Obj*)Span_new(14,  4, 4.0f));
+    Vec_Push(spans, (Obj*)Span_new(16, 14, 2.0f));
+    flattened = HeatMap_Flatten_Spans(heat_map, spans);
+    Vec_Push(wanted, (Obj*)Span_new(10,  4, 1.0f));
+    Vec_Push(wanted, (Obj*)Span_new(14,  2, 5.0f));
+    Vec_Push(wanted, (Obj*)Span_new(16,  2, 7.0f));
+    Vec_Push(wanted, (Obj*)Span_new(18,  2, 3.0f));
+    Vec_Push(wanted, (Obj*)Span_new(20, 10, 2.0f));
+    TEST_TRUE(runner, Vec_Equals(flattened, (Obj*)wanted),
+              "flatten three overlapping spans");
+    Vec_Clear(wanted);
+    boosts = HeatMap_Generate_Proximity_Boosts(heat_map, spans);
+    TEST_TRUE(runner, Vec_Get_Size(boosts) == 2 + 1,
+              "boosts generated for each unique pair, since all were in range");
+    Vec_Clear(spans);
+    DECREF(boosts);
+    DECREF(flattened);
+
+    Vec_Push(spans, (Obj*)Span_new(10, 10,  1.0f));
+    Vec_Push(spans, (Obj*)Span_new(16, 14,  4.0f));
+    Vec_Push(spans, (Obj*)Span_new(16, 14,  2.0f));
+    Vec_Push(spans, (Obj*)Span_new(30, 10, 10.0f));
+    flattened = HeatMap_Flatten_Spans(heat_map, spans);
+    Vec_Push(wanted, (Obj*)Span_new(10,  6,  1.0f));
+    Vec_Push(wanted, (Obj*)Span_new(16,  4,  7.0f));
+    Vec_Push(wanted, (Obj*)Span_new(20, 10,  6.0f));
+    Vec_Push(wanted, (Obj*)Span_new(30, 10, 10.0f));
+    TEST_TRUE(runner, Vec_Equals(flattened, (Obj*)wanted),
+              "flatten 4 spans, middle two have identical range");
+    Vec_Clear(wanted);
+    boosts = HeatMap_Generate_Proximity_Boosts(heat_map, spans);
+    TEST_TRUE(runner, Vec_Get_Size(boosts) == 3 + 2 + 1,
+              "boosts generated for each unique pair, since all were in range");
+    Vec_Clear(spans);
+    DECREF(boosts);
+    DECREF(flattened);
+
+    Vec_Push(spans, (Obj*)Span_new( 10, 10,  1.0f));
+    Vec_Push(spans, (Obj*)Span_new( 16,  4,  4.0f));
+    Vec_Push(spans, (Obj*)Span_new( 16, 14,  2.0f));
+    Vec_Push(spans, (Obj*)Span_new(230, 10, 10.0f));
+    flattened = HeatMap_Flatten_Spans(heat_map, spans);
+    Vec_Push(wanted, (Obj*)Span_new( 10,  6,  1.0f));
+    Vec_Push(wanted, (Obj*)Span_new( 16,  4,  7.0f));
+    Vec_Push(wanted, (Obj*)Span_new( 20, 10,  2.0f));
+    Vec_Push(wanted, (Obj*)Span_new(230, 10, 10.0f));
+    TEST_TRUE(runner, Vec_Equals(flattened, (Obj*)wanted),
+              "flatten 4 spans, middle two have identical starts but different ends");
+    Vec_Clear(wanted);
+    boosts = HeatMap_Generate_Proximity_Boosts(heat_map, spans);
+    TEST_TRUE(runner, Vec_Get_Size(boosts) == 2 + 1,
+              "boosts not generated for out of range span");
+    Vec_Clear(spans);
+    DECREF(boosts);
+    DECREF(flattened);
+
+    DECREF(heat_map);
+    DECREF(wanted);
+    DECREF(spans);
+}
+
+void
+TestHeatMap_Run_IMP(TestHeatMap *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 13);
+    test_calc_proximity_boost(runner);
+    test_flatten_spans(runner);
+}
+
+

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

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Highlight/TestHighlighter.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Highlight/TestHighlighter.c b/test/Lucy/Test/Highlight/TestHighlighter.c
new file mode 100644
index 0000000..602b32f
--- /dev/null
+++ b/test/Lucy/Test/Highlight/TestHighlighter.c
@@ -0,0 +1,446 @@
+/* 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_TESTHIGHLIGHTER
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/Highlight/TestHighlighter.h"
+#include "Lucy/Highlight/Highlighter.h"
+
+#include "Lucy/Analysis/StandardTokenizer.h"
+#include "Lucy/Document/Doc.h"
+#include "Lucy/Document/HitDoc.h"
+#include "Lucy/Highlight/HeatMap.h"
+#include "Lucy/Index/Indexer.h"
+#include "Lucy/Plan/FullTextType.h"
+#include "Lucy/Plan/Schema.h"
+#include "Lucy/Search/Hits.h"
+#include "Lucy/Search/IndexSearcher.h"
+#include "Lucy/Search/Span.h"
+#include "Lucy/Search/TermQuery.h"
+#include "Lucy/Store/RAMFolder.h"
+
+#define PHI      "\xCE\xA6"
+#define ELLIPSIS "\xE2\x80\xA6"
+
+#define TEST_STRING \
+    "1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 " \
+    "1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 " \
+    "1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 " \
+    "1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 " \
+    PHI " a b c d x y z h i j k " \
+    "6 7 8 9 0 6 7 8 9 0 6 7 8 9 0 6 7 8 9 0 6 7 8 9 0 " \
+    "6 7 8 9 0 6 7 8 9 0 6 7 8 9 0 6 7 8 9 0 6 7 8 9 0 " \
+    "6 7 8 9 0 6 7 8 9 0 6 7 8 9 0 6 7 8 9 0 6 7 8 9 0 " \
+    "6 7 8 9 0 6 7 8 9 0 6 7 8 9 0 6 7 8 9 0 6 7 8 9 0 " \
+    "6 7 8 9 0 6 7 8 9 0 6 7 8 9 0 6 7 8 9 0 6 7 8 9 0 "
+#define TEST_STRING_LEN 425
+
+TestHighlighter*
+TestHighlighter_new() {
+    return (TestHighlighter*)Class_Make_Obj(TESTHIGHLIGHTER);
+}
+
+static void
+test_Raw_Excerpt(TestBatchRunner *runner, Searcher *searcher, Obj *query) {
+    String *content = SSTR_WRAP_C("content");
+    Highlighter *highlighter = Highlighter_new(searcher, query, content, 6);
+    int32_t top;
+    String *raw_excerpt;
+
+    String *field_val = SSTR_WRAP_C("Ook.  Urk.  Ick.  ");
+    Vector *spans = Vec_new(1);
+    Vec_Push(spans, (Obj*)Span_new(0, 18, 1.0f));
+    HeatMap *heat_map = HeatMap_new(spans, 133);
+    DECREF(spans);
+    raw_excerpt = Highlighter_Raw_Excerpt(highlighter, field_val, &top,
+                                          heat_map);
+    char *raw_str = Str_To_Utf8(raw_excerpt);
+    TEST_TRUE(runner,
+              Str_Equals_Utf8(raw_excerpt, "Ook.", 4),
+              "Raw_Excerpt at top %s", raw_str);
+    free(raw_str);
+    TEST_TRUE(runner,
+              top == 0,
+              "top is 0");
+    DECREF(raw_excerpt);
+    DECREF(heat_map);
+
+    spans = Vec_new(1);
+    Vec_Push(spans, (Obj*)Span_new(6, 12, 1.0f));
+    heat_map = HeatMap_new(spans, 133);
+    DECREF(spans);
+    raw_excerpt = Highlighter_Raw_Excerpt(highlighter, field_val, &top,
+                                          heat_map);
+    TEST_TRUE(runner,
+              Str_Equals_Utf8(raw_excerpt, "Urk.", 4),
+              "Raw_Excerpt in middle, with 2 bounds");
+    TEST_TRUE(runner,
+              top == 6,
+              "top in the middle modified by Raw_Excerpt");
+    DECREF(raw_excerpt);
+    DECREF(heat_map);
+
+    field_val = SSTR_WRAP_C("Ook urk ick i.");
+    spans     = Vec_new(1);
+    Vec_Push(spans, (Obj*)Span_new(12, 1, 1.0f));
+    heat_map = HeatMap_new(spans, 133);
+    DECREF(spans);
+    raw_excerpt = Highlighter_Raw_Excerpt(highlighter, field_val, &top,
+                                          heat_map);
+    TEST_TRUE(runner,
+              Str_Equals_Utf8(raw_excerpt, ELLIPSIS " i.", 6),
+              "Ellipsis at top");
+    TEST_TRUE(runner,
+              top == 10,
+              "top correct when leading ellipsis inserted");
+    DECREF(heat_map);
+    DECREF(raw_excerpt);
+
+    field_val = SSTR_WRAP_C("Urk.  Iz no good.");
+    spans     = Vec_new(1);
+    Vec_Push(spans, (Obj*)Span_new(6, 2, 1.0f));
+    heat_map = HeatMap_new(spans, 133);
+    DECREF(spans);
+    raw_excerpt = Highlighter_Raw_Excerpt(highlighter, field_val, &top,
+                                          heat_map);
+    TEST_TRUE(runner,
+              Str_Equals_Utf8(raw_excerpt, "Iz no" ELLIPSIS, 8),
+              "Ellipsis at end");
+    TEST_TRUE(runner,
+              top == 6,
+              "top trimmed");
+    DECREF(heat_map);
+    DECREF(raw_excerpt);
+
+    // Words longer than excerpt len
+
+    field_val = SSTR_WRAP_C("abc/def/ghi/jkl/mno");
+
+    spans = Vec_new(1);
+    Vec_Push(spans, (Obj*)Span_new(0, 3, 1.0f));
+    heat_map = HeatMap_new(spans, 133);
+    DECREF(spans);
+    raw_excerpt = Highlighter_Raw_Excerpt(highlighter, field_val, &top,
+                                          heat_map);
+    TEST_TRUE(runner,
+              Str_Equals_Utf8(raw_excerpt, "abc/d" ELLIPSIS, 8),
+              "Long word at top");
+    DECREF(heat_map);
+    DECREF(raw_excerpt);
+
+    spans = Vec_new(1);
+    Vec_Push(spans, (Obj*)Span_new(8, 3, 1.0f));
+    heat_map = HeatMap_new(spans, 133);
+    DECREF(spans);
+    raw_excerpt = Highlighter_Raw_Excerpt(highlighter, field_val, &top,
+                                          heat_map);
+    TEST_TRUE(runner,
+              Str_Equals_Utf8(raw_excerpt, ELLIPSIS " f/g" ELLIPSIS, 10),
+              "Long word in middle");
+    DECREF(heat_map);
+    DECREF(raw_excerpt);
+
+    DECREF(highlighter);
+}
+
+static void
+test_Highlight_Excerpt(TestBatchRunner *runner, Searcher *searcher, Obj *query) {
+    String *content = SSTR_WRAP_C("content");
+    Highlighter *highlighter = Highlighter_new(searcher, query, content, 3);
+    String *highlighted;
+
+    Vector *spans = Vec_new(1);
+    Vec_Push(spans, (Obj*)Span_new(2, 1, 0.0f));
+    String *raw_excerpt = SSTR_WRAP_C("a b c");
+    highlighted = Highlighter_Highlight_Excerpt(highlighter, spans,
+                                                raw_excerpt, 0);
+    TEST_TRUE(runner,
+              Str_Equals_Utf8(highlighted, "a <strong>b</strong> c", 22),
+              "basic Highlight_Excerpt");
+    DECREF(highlighted);
+    DECREF(spans);
+
+    spans = Vec_new(2);
+    Vec_Push(spans, (Obj*)Span_new(0, 1, 1.0f));
+    Vec_Push(spans, (Obj*)Span_new(10, 10, 1.0f));
+    raw_excerpt = SSTR_WRAP_C(PHI);
+    highlighted = Highlighter_Highlight_Excerpt(highlighter, spans,
+                                                raw_excerpt, 0);
+    TEST_TRUE(runner,
+              Str_Equals_Utf8(highlighted, "<strong>&#934;</strong>", 23),
+              "don't surround spans off end of raw excerpt.");
+    DECREF(highlighted);
+    DECREF(spans);
+
+    spans = Vec_new(1);
+    Vec_Push(spans, (Obj*)Span_new(3, 1, 1.0f));
+    raw_excerpt = SSTR_WRAP_C(PHI " " PHI " " PHI);
+    highlighted = Highlighter_Highlight_Excerpt(highlighter, spans,
+                                                raw_excerpt, 1);
+    TEST_TRUE(runner,
+              Str_Equals_Utf8(highlighted,
+                            "&#934; <strong>&#934;</strong> &#934;", 37),
+              "Highlight_Excerpt pays attention to offset");
+    DECREF(highlighted);
+    DECREF(spans);
+
+    spans = Vec_new(4);
+    Vec_Push(spans, (Obj*)Span_new(2, 10, 1.0f));
+    Vec_Push(spans, (Obj*)Span_new(2,  4, 1.0f));
+    Vec_Push(spans, (Obj*)Span_new(8,  9, 1.0f));
+    Vec_Push(spans, (Obj*)Span_new(8,  4, 1.0f));
+    raw_excerpt = SSTR_WRAP_C(PHI " Oook. Urk. Ick. " PHI);
+    highlighted = Highlighter_Highlight_Excerpt(highlighter, spans,
+                                                raw_excerpt, 0);
+    TEST_TRUE(runner,
+              Str_Equals_Utf8(highlighted,
+                            "&#934; <strong>Oook. Urk. Ick.</strong> &#934;",
+                            46),
+              "Highlight_Excerpt works with overlapping spans");
+    DECREF(highlighted);
+    DECREF(spans);
+
+    DECREF(highlighter);
+}
+
+static void
+test_Create_Excerpt(TestBatchRunner *runner, Searcher *searcher, Obj *query,
+                    Hits *hits) {
+    String *content = SSTR_WRAP_C("content");
+    Highlighter *highlighter = Highlighter_new(searcher, query, content, 200);
+
+    HitDoc *hit = Hits_Next(hits);
+    String *excerpt = Highlighter_Create_Excerpt(highlighter, hit);
+    TEST_TRUE(runner,
+              Str_Contains_Utf8(excerpt,
+                                "<strong>&#934;</strong> a b c d <strong>x y z</strong>",
+                                54),
+              "highlighter tagged phrase and single term");
+    DECREF(excerpt);
+
+    String *pre_tag = SSTR_WRAP_C("\x1B[1m");
+    Highlighter_Set_Pre_Tag(highlighter, pre_tag);
+    String *post_tag = SSTR_WRAP_C("\x1B[0m");
+    Highlighter_Set_Post_Tag(highlighter, post_tag);
+    excerpt = Highlighter_Create_Excerpt(highlighter, hit);
+    TEST_TRUE(runner,
+              Str_Contains_Utf8(excerpt,
+                                "\x1B[1m&#934;\x1B[0m a b c d \x1B[1mx y z\x1B[0m",
+                                36),
+              "set_pre_tag and set_post_tag");
+    DECREF(excerpt);
+    DECREF(hit);
+
+    hit = Hits_Next(hits);
+    excerpt = Highlighter_Create_Excerpt(highlighter, hit);
+    TEST_TRUE(runner,
+              Str_Contains_Utf8(excerpt, "x", 1),
+              "excerpt field with partial hit doesn't cause highlighter freakout");
+    DECREF(excerpt);
+    DECREF(hit);
+    DECREF(highlighter);
+
+    query = (Obj*)SSTR_WRAP_C("x \"x y z\" AND b");
+    hits = Searcher_Hits(searcher, query, 0, 10, NULL);
+    highlighter = Highlighter_new(searcher, query, content, 200);
+    hit = Hits_Next(hits);
+    excerpt = Highlighter_Create_Excerpt(highlighter, hit);
+    TEST_TRUE(runner,
+              Str_Contains_Utf8(excerpt,
+                                "<strong>b</strong> c d <strong>x y z</strong>",
+                                45),
+              "query with same word in both phrase and term doesn't cause freakout");
+    DECREF(excerpt);
+    DECREF(hit);
+    DECREF(highlighter);
+    DECREF(hits);
+
+    query = (Obj*)SSTR_WRAP_C("blind");
+    hits = Searcher_Hits(searcher, query, 0, 10, NULL);
+    highlighter = Highlighter_new(searcher, query, content, 200);
+    hit = Hits_Next(hits);
+    excerpt = Highlighter_Create_Excerpt(highlighter, hit);
+    TEST_TRUE(runner,
+              Str_Contains_Utf8(excerpt, "&quot;", 6),
+              "HTML entity encoded properly");
+    DECREF(excerpt);
+    DECREF(hit);
+    DECREF(highlighter);
+    DECREF(hits);
+
+    query = (Obj*)SSTR_WRAP_C("why");
+    hits = Searcher_Hits(searcher, query, 0, 10, NULL);
+    highlighter = Highlighter_new(searcher, query, content, 200);
+    hit = Hits_Next(hits);
+    excerpt = Highlighter_Create_Excerpt(highlighter, hit);
+    TEST_FALSE(runner,
+               Str_Contains_Utf8(excerpt, "&#934;", 6),
+               "no ellipsis for short excerpt");
+    DECREF(excerpt);
+    DECREF(hit);
+    DECREF(highlighter);
+    DECREF(hits);
+
+    Obj *term = (Obj*)SSTR_WRAP_C("x");
+    query = (Obj*)TermQuery_new(content, term);
+    hits = Searcher_Hits(searcher, query, 0, 10, NULL);
+    hit = Hits_Next(hits);
+    highlighter = Highlighter_new(searcher, query, content, 200);
+    excerpt = Highlighter_Create_Excerpt(highlighter, hit);
+    TEST_TRUE(runner,
+              Str_Contains_Utf8(excerpt, "strong", 5),
+              "specify field highlights correct field...");
+    DECREF(excerpt);
+    DECREF(highlighter);
+    String *alt = SSTR_WRAP_C("alt");
+    highlighter = Highlighter_new(searcher, query, alt, 200);
+    excerpt = Highlighter_Create_Excerpt(highlighter, hit);
+    TEST_FALSE(runner,
+               Str_Contains_Utf8(excerpt, "strong", 5),
+               "... but not another field");
+    DECREF(excerpt);
+    DECREF(highlighter);
+    DECREF(hit);
+    DECREF(hits);
+    DECREF(query);
+}
+
+static void
+test_highlighting(TestBatchRunner *runner) {
+    Schema *schema = Schema_new();
+    StandardTokenizer *tokenizer = StandardTokenizer_new();
+    FullTextType *plain_type = FullTextType_new((Analyzer*)tokenizer);
+    FullTextType_Set_Highlightable(plain_type, true);
+    FullTextType *dunked_type = FullTextType_new((Analyzer*)tokenizer);
+    FullTextType_Set_Highlightable(dunked_type, true);
+    FullTextType_Set_Boost(dunked_type, 0.1f);
+    String *content = SSTR_WRAP_C("content");
+    Schema_Spec_Field(schema, content, (FieldType*)plain_type);
+    String *alt = SSTR_WRAP_C("alt");
+    Schema_Spec_Field(schema, alt, (FieldType*)dunked_type);
+    DECREF(plain_type);
+    DECREF(dunked_type);
+    DECREF(tokenizer);
+
+    RAMFolder *folder = RAMFolder_new(NULL);
+    Indexer *indexer = Indexer_new(schema, (Obj*)folder, NULL, 0);
+
+    Doc *doc = Doc_new(NULL, 0);
+    String *string = SSTR_WRAP_C(TEST_STRING);
+    Doc_Store(doc, content, (Obj*)string);
+    Indexer_Add_Doc(indexer, doc, 1.0f);
+    DECREF(doc);
+
+    doc = Doc_new(NULL, 0);
+    string = SSTR_WRAP_C("\"I see,\" said the blind man.");
+    Doc_Store(doc, content, (Obj*)string);
+    Indexer_Add_Doc(indexer, doc, 1.0f);
+    DECREF(doc);
+
+    doc = Doc_new(NULL, 0);
+    string = SSTR_WRAP_C("x but not why or 2ee");
+    Doc_Store(doc, content, (Obj*)string);
+    string = SSTR_WRAP_C(TEST_STRING " and extra stuff so it scores lower");
+    Doc_Store(doc, alt, (Obj*)string);
+    Indexer_Add_Doc(indexer, doc, 1.0f);
+    DECREF(doc);
+
+    Indexer_Commit(indexer);
+    DECREF(indexer);
+
+    Searcher *searcher = (Searcher*)IxSearcher_new((Obj*)folder);
+    Obj *query = (Obj*)SSTR_WRAP_C("\"x y z\" AND " PHI);
+    Hits *hits = Searcher_Hits(searcher, query, 0, 10, NULL);
+
+    test_Raw_Excerpt(runner, searcher, query);
+    test_Highlight_Excerpt(runner, searcher, query);
+    test_Create_Excerpt(runner, searcher, query, hits);
+
+    DECREF(hits);
+    DECREF(searcher);
+    DECREF(folder);
+    DECREF(schema);
+}
+
+static void
+test_hl_selection(TestBatchRunner *runner) {
+    Schema *schema = Schema_new();
+    StandardTokenizer *tokenizer = StandardTokenizer_new();
+    FullTextType *plain_type = FullTextType_new((Analyzer*)tokenizer);
+    FullTextType_Set_Highlightable(plain_type, true);
+    String *content = SSTR_WRAP_C("content");
+    Schema_Spec_Field(schema, content, (FieldType*)plain_type);
+    DECREF(plain_type);
+    DECREF(tokenizer);
+
+    RAMFolder *folder = RAMFolder_new(NULL);
+    Indexer *indexer = Indexer_new(schema, (Obj*)folder, NULL, 0);
+
+    static char test_string[] =
+        "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla. "
+        "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla. "
+        "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla. "
+        "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla. "
+        "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla. "
+        "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla NNN bla. "
+        "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla. "
+        "bla bla bla MMM bla bla bla bla bla bla bla bla bla bla bla bla. "
+        "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla. "
+        "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla. "
+        "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla. "
+        "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla. "
+        "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla. ";
+    Doc *doc = Doc_new(NULL, 0);
+    String *string = SSTR_WRAP_C(test_string);
+    Doc_Store(doc, content, (Obj*)string);
+    Indexer_Add_Doc(indexer, doc, 1.0f);
+    DECREF(doc);
+
+    Indexer_Commit(indexer);
+    DECREF(indexer);
+
+    Searcher *searcher = (Searcher*)IxSearcher_new((Obj*)folder);
+    Obj *query = (Obj*)SSTR_WRAP_C("NNN MMM");
+    Highlighter *highlighter = Highlighter_new(searcher, query, content, 200);
+    Hits *hits = Searcher_Hits(searcher, query, 0, 10, NULL);
+    HitDoc *hit = Hits_Next(hits);
+    String *excerpt = Highlighter_Create_Excerpt(highlighter, hit);
+    String *mmm = SSTR_WRAP_C("MMM");
+    String *nnn = SSTR_WRAP_C("NNN");
+    TEST_TRUE(runner, Str_Contains(excerpt, mmm) || Str_Contains(excerpt, nnn),
+              "Sentence boundary algo doesn't chop terms");
+
+    DECREF(excerpt);
+    DECREF(hit);
+    DECREF(hits);
+    DECREF(highlighter);
+    DECREF(searcher);
+    DECREF(folder);
+    DECREF(schema);
+}
+
+void
+TestHighlighter_Run_IMP(TestHighlighter *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 23);
+    test_highlighting(runner);
+    test_hl_selection(runner);
+}
+
+

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

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Index/TestDocWriter.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Index/TestDocWriter.c b/test/Lucy/Test/Index/TestDocWriter.c
new file mode 100644
index 0000000..218a547
--- /dev/null
+++ b/test/Lucy/Test/Index/TestDocWriter.c
@@ -0,0 +1,37 @@
+/* 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_TESTDOCWRITER
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/Index/TestDocWriter.h"
+#include "Lucy/Index/DocWriter.h"
+
+TestDocWriter*
+TestDocWriter_new() {
+    return (TestDocWriter*)Class_Make_Obj(TESTDOCWRITER);
+}
+
+void
+TestDocWriter_Run_IMP(TestDocWriter *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 1);
+    PASS(runner, "placeholder");
+}
+
+

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

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Index/TestHighlightWriter.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Index/TestHighlightWriter.c b/test/Lucy/Test/Index/TestHighlightWriter.c
new file mode 100644
index 0000000..848e2ff
--- /dev/null
+++ b/test/Lucy/Test/Index/TestHighlightWriter.c
@@ -0,0 +1,37 @@
+/* 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_TESTHIGHLIGHTWRITER
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/Index/TestHighlightWriter.h"
+#include "Lucy/Index/HighlightWriter.h"
+
+TestHighlightWriter*
+TestHLWriter_new() {
+    return (TestHighlightWriter*)Class_Make_Obj(TESTHIGHLIGHTWRITER);
+}
+
+void
+TestHLWriter_Run_IMP(TestHighlightWriter *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 1);
+    PASS(runner, "Placeholder");
+}
+
+

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

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Index/TestIndexManager.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Index/TestIndexManager.c b/test/Lucy/Test/Index/TestIndexManager.c
new file mode 100644
index 0000000..87b1247
--- /dev/null
+++ b/test/Lucy/Test/Index/TestIndexManager.c
@@ -0,0 +1,63 @@
+/* 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 "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/Index/TestIndexManager.h"
+#include "Lucy/Index/IndexManager.h"
+
+TestIndexManager*
+TestIxManager_new() {
+    return (TestIndexManager*)Class_Make_Obj(TESTINDEXMANAGER);
+}
+
+static void
+test_Choose_Sparse(TestBatchRunner *runner) {
+    IndexManager *manager = IxManager_new(NULL, NULL);
+
+    for (uint32_t num_segs = 2; num_segs < 20; num_segs++) {
+        I32Array *doc_counts = I32Arr_new_blank(num_segs);
+        for (uint32_t j = 0; j < num_segs; j++) {
+            I32Arr_Set(doc_counts, j, 1000);
+        }
+        uint32_t threshold = IxManager_Choose_Sparse(manager, doc_counts);
+        TEST_TRUE(runner, threshold != 1,
+                  "Either don't merge, or merge two segments: %u segs, thresh %u",
+                  (unsigned)num_segs, (unsigned)threshold);
+
+        if (num_segs != 12 && num_segs != 13) {  // when 2 is correct
+            I32Arr_Set(doc_counts, 0, 1);
+            threshold = IxManager_Choose_Sparse(manager, doc_counts);
+            TEST_TRUE(runner, threshold != 2,
+                      "Don't include big next seg: %u segs, thresh %u",
+                      (unsigned)num_segs, (unsigned)threshold);
+        }
+
+        DECREF(doc_counts);
+    }
+
+    DECREF(manager);
+}
+
+void
+TestIxManager_Run_IMP(TestIndexManager *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 34);
+    test_Choose_Sparse(runner);
+}
+

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

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Index/TestPolyReader.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Index/TestPolyReader.c b/test/Lucy/Test/Index/TestPolyReader.c
new file mode 100644
index 0000000..e8b8a67
--- /dev/null
+++ b/test/Lucy/Test/Index/TestPolyReader.c
@@ -0,0 +1,53 @@
+/* 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_TESTPOLYREADER
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/Index/TestPolyReader.h"
+#include "Lucy/Index/PolyReader.h"
+
+TestPolyReader*
+TestPolyReader_new() {
+    return (TestPolyReader*)Class_Make_Obj(TESTPOLYREADER);
+}
+
+static void
+test_sub_tick(TestBatchRunner *runner) {
+    uint32_t num_segs = 255;
+    int32_t *ints = (int32_t*)MALLOCATE(num_segs * sizeof(int32_t));
+    uint32_t i;
+    for (i = 0; i < num_segs; i++) {
+        ints[i] = (int32_t)i;
+    }
+    I32Array *offsets = I32Arr_new(ints, num_segs);
+    for (i = 1; i < num_segs; i++) {
+        if (PolyReader_sub_tick(offsets, (int32_t)i) != i - 1) { break; }
+    }
+    TEST_UINT_EQ(runner, i, num_segs, "got all sub_tick() calls right");
+    DECREF(offsets);
+    FREEMEM(ints);
+}
+
+void
+TestPolyReader_Run_IMP(TestPolyReader *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 1);
+    test_sub_tick(runner);
+}
+

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

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Index/TestPostingListWriter.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Index/TestPostingListWriter.c b/test/Lucy/Test/Index/TestPostingListWriter.c
new file mode 100644
index 0000000..2b3c2f7
--- /dev/null
+++ b/test/Lucy/Test/Index/TestPostingListWriter.c
@@ -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.
+ */
+
+#define C_TESTLUCY_TESTPOSTINGLISTWRITER
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/Index/TestPostingListWriter.h"
+
+TestPostingListWriter*
+TestPListWriter_new() {
+    return (TestPostingListWriter*)Class_Make_Obj(TESTPOSTINGLISTWRITER);
+}
+
+void
+TestPListWriter_Run_IMP(TestPostingListWriter *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 1);
+    PASS(runner, "Placeholder");
+}
+
+

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

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Index/TestSegWriter.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Index/TestSegWriter.c b/test/Lucy/Test/Index/TestSegWriter.c
new file mode 100644
index 0000000..d5e67cc
--- /dev/null
+++ b/test/Lucy/Test/Index/TestSegWriter.c
@@ -0,0 +1,37 @@
+/* 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_TESTSEGWRITER
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/Index/TestSegWriter.h"
+#include "Lucy/Index/SegWriter.h"
+
+TestSegWriter*
+TestSegWriter_new() {
+    return (TestSegWriter*)Class_Make_Obj(TESTSEGWRITER);
+}
+
+void
+TestSegWriter_Run_IMP(TestSegWriter *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 1);
+    PASS(runner, "placeholder");
+}
+
+

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

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Index/TestSegment.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Index/TestSegment.c b/test/Lucy/Test/Index/TestSegment.c
new file mode 100644
index 0000000..87a07af
--- /dev/null
+++ b/test/Lucy/Test/Index/TestSegment.c
@@ -0,0 +1,169 @@
+/* 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_TESTSEG
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/Index/TestSegment.h"
+#include "Lucy/Index/Segment.h"
+#include "Lucy/Store/RAMFolder.h"
+
+TestSegment*
+TestSeg_new() {
+    return (TestSegment*)Class_Make_Obj(TESTSEGMENT);
+}
+
+static void
+test_fields(TestBatchRunner *runner) {
+    Segment *segment = Seg_new(1);
+    String *foo = SSTR_WRAP_C("foo");
+    String *bar = SSTR_WRAP_C("bar");
+    String *baz = SSTR_WRAP_C("baz");
+    int32_t field_num;
+
+    field_num = Seg_Add_Field(segment, foo);
+    TEST_TRUE(runner, field_num == 1,
+              "Add_Field returns field number, and field numbers start at 1");
+    field_num = Seg_Add_Field(segment, bar);
+    TEST_TRUE(runner, field_num == 2, "add a second field");
+    field_num = Seg_Add_Field(segment, foo);
+    TEST_TRUE(runner, field_num == 1,
+              "Add_Field returns existing field number if field is already known");
+
+    TEST_TRUE(runner, Str_Equals(bar, (Obj*)Seg_Field_Name(segment, 2)),
+              "Field_Name");
+    TEST_TRUE(runner, Seg_Field_Name(segment, 3) == NULL,
+              "Field_Name returns NULL for unknown field number");
+    TEST_TRUE(runner, Seg_Field_Num(segment, bar) == 2,
+              "Field_Num");
+    TEST_TRUE(runner, Seg_Field_Num(segment, baz) == 0,
+              "Field_Num returns 0 for unknown field name");
+
+    DECREF(segment);
+}
+
+static void
+test_metadata_storage(TestBatchRunner *runner) {
+    Segment *segment = Seg_new(1);
+    String *got;
+
+    Seg_Store_Metadata_Utf8(segment, "foo", 3, (Obj*)Str_newf("bar"));
+    got = (String*)Seg_Fetch_Metadata_Utf8(segment, "foo", 3);
+    TEST_TRUE(runner,
+              got
+              && Str_is_a(got, STRING)
+              && Str_Equals_Utf8(got, "bar", 3),
+              "metadata round trip"
+             );
+    DECREF(segment);
+}
+
+static void
+test_seg_name_and_num(TestBatchRunner *runner) {
+    Segment *segment_z = Seg_new(35);
+    String *seg_z_name = Seg_num_to_name(35);
+    TEST_TRUE(runner, Seg_Get_Number(segment_z) == INT64_C(35), "Get_Number");
+    TEST_TRUE(runner, Str_Equals_Utf8(Seg_Get_Name(segment_z), "seg_z", 5),
+              "Get_Name");
+    TEST_TRUE(runner, Str_Equals_Utf8(seg_z_name, "seg_z", 5),
+              "num_to_name");
+    DECREF(seg_z_name);
+    DECREF(segment_z);
+}
+
+static void
+test_count(TestBatchRunner *runner) {
+    Segment *segment = Seg_new(100);
+
+    TEST_TRUE(runner, Seg_Get_Count(segment) == 0, "count starts off at 0");
+    Seg_Set_Count(segment, 120);
+    TEST_TRUE(runner, Seg_Get_Count(segment) == 120, "Set_Count");
+    TEST_TRUE(runner, Seg_Increment_Count(segment, 10) == 130,
+              "Increment_Count");
+
+    DECREF(segment);
+}
+
+static void
+test_Compare_To(TestBatchRunner *runner) {
+    Segment *segment_1      = Seg_new(1);
+    Segment *segment_2      = Seg_new(2);
+    Segment *also_segment_2 = Seg_new(2);
+
+    TEST_TRUE(runner, Seg_Compare_To(segment_1, (Obj*)segment_2) < 0,
+              "Compare_To 1 < 2");
+    TEST_TRUE(runner, Seg_Compare_To(segment_2, (Obj*)segment_1) > 0,
+              "Compare_To 1 < 2");
+    TEST_TRUE(runner, Seg_Compare_To(segment_1, (Obj*)segment_1) == 0,
+              "Compare_To identity");
+    TEST_TRUE(runner, Seg_Compare_To(segment_2, (Obj*)also_segment_2) == 0,
+              "Compare_To 2 == 2");
+
+    DECREF(segment_1);
+    DECREF(segment_2);
+    DECREF(also_segment_2);
+}
+
+static void
+test_Write_File_and_Read_File(TestBatchRunner *runner) {
+    RAMFolder *folder  = RAMFolder_new(NULL);
+    Segment   *segment = Seg_new(100);
+    Segment   *got     = Seg_new(100);
+    String    *meta;
+    String    *flotsam = SSTR_WRAP_C("flotsam");
+    String    *jetsam  = SSTR_WRAP_C("jetsam");
+
+    Seg_Set_Count(segment, 111);
+    Seg_Store_Metadata_Utf8(segment, "foo", 3, (Obj*)Str_newf("bar"));
+    Seg_Add_Field(segment, flotsam);
+    Seg_Add_Field(segment, jetsam);
+
+    RAMFolder_MkDir(folder, Seg_Get_Name(segment));
+    Seg_Write_File(segment, (Folder*)folder);
+    Seg_Read_File(got, (Folder*)folder);
+
+    TEST_TRUE(runner, Seg_Get_Count(got) == Seg_Get_Count(segment),
+              "Round-trip count through file");
+    TEST_TRUE(runner,
+              Seg_Field_Num(got, jetsam) == Seg_Field_Num(segment, jetsam),
+              "Round trip field names through file");
+    meta = (String*)Seg_Fetch_Metadata_Utf8(got, "foo", 3);
+    TEST_TRUE(runner,
+              meta
+              && Str_is_a(meta, STRING)
+              && Str_Equals_Utf8(meta, "bar", 3),
+              "Round trip metadata through file");
+
+    DECREF(got);
+    DECREF(segment);
+    DECREF(folder);
+}
+
+void
+TestSeg_Run_IMP(TestSegment *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 21);
+    test_fields(runner);
+    test_metadata_storage(runner);
+    test_seg_name_and_num(runner);
+    test_count(runner);
+    test_Compare_To(runner);
+    test_Write_File_and_Read_File(runner);
+}
+
+

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

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Index/TestSnapshot.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Index/TestSnapshot.c b/test/Lucy/Test/Index/TestSnapshot.c
new file mode 100644
index 0000000..bb86992
--- /dev/null
+++ b/test/Lucy/Test/Index/TestSnapshot.c
@@ -0,0 +1,112 @@
+/* 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 "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/Index/TestSnapshot.h"
+#include "Lucy/Index/Snapshot.h"
+#include "Lucy/Store/RAMFolder.h"
+
+TestSnapshot*
+TestSnapshot_new() {
+    return (TestSnapshot*)Class_Make_Obj(TESTSNAPSHOT);
+}
+
+static void
+test_Add_and_Delete(TestBatchRunner *runner) {
+    Snapshot *snapshot = Snapshot_new();
+    String *foo = SSTR_WRAP_C("foo");
+    String *bar = SSTR_WRAP_C("bar");
+
+    Snapshot_Add_Entry(snapshot, foo);
+    Snapshot_Add_Entry(snapshot, foo); // redundant
+    Vector *entries = Snapshot_List(snapshot);
+    TEST_INT_EQ(runner, Snapshot_Num_Entries(snapshot), 1,
+                "One entry added");
+    TEST_TRUE(runner, Str_Equals(foo, Vec_Fetch(entries, 0)), "correct entry");
+    DECREF(entries);
+
+    Snapshot_Add_Entry(snapshot, bar);
+    TEST_INT_EQ(runner, Snapshot_Num_Entries(snapshot), 2,
+                "second entry added");
+    Snapshot_Delete_Entry(snapshot, foo);
+    TEST_INT_EQ(runner, Snapshot_Num_Entries(snapshot), 1, "Delete_Entry");
+
+    DECREF(snapshot);
+}
+
+static void
+test_path_handling(TestBatchRunner *runner) {
+    Snapshot *snapshot = Snapshot_new();
+    Folder   *folder   = (Folder*)RAMFolder_new(NULL);
+    String   *snap     = SSTR_WRAP_C("snap");
+    String   *crackle  = SSTR_WRAP_C("crackle");
+
+    Snapshot_Write_File(snapshot, folder, snap);
+    TEST_TRUE(runner, Str_Equals(snap, (Obj*)Snapshot_Get_Path(snapshot)),
+              "Write_File() sets path as a side effect");
+
+    Folder_Rename(folder, snap, crackle);
+    Snapshot_Read_File(snapshot, folder, crackle);
+    TEST_TRUE(runner, Str_Equals(crackle, (Obj*)Snapshot_Get_Path(snapshot)),
+              "Read_File() sets path as a side effect");
+
+    Snapshot_Set_Path(snapshot, snap);
+    TEST_TRUE(runner, Str_Equals(snap, (Obj*)Snapshot_Get_Path(snapshot)),
+              "Set_Path()");
+
+    DECREF(folder);
+    DECREF(snapshot);
+}
+
+static void
+test_Read_File_and_Write_File(TestBatchRunner *runner) {
+    Snapshot *snapshot = Snapshot_new();
+    Folder   *folder   = (Folder*)RAMFolder_new(NULL);
+    String   *snap     = SSTR_WRAP_C("snap");
+    String   *foo      = SSTR_WRAP_C("foo");
+
+    Snapshot_Add_Entry(snapshot, foo);
+    Snapshot_Write_File(snapshot, folder, snap);
+
+    Snapshot *dupe = Snapshot_new();
+    Snapshot *read_retval = Snapshot_Read_File(dupe, folder, snap);
+    TEST_TRUE(runner, dupe == read_retval, "Read_File() returns the object");
+
+    Vector *orig_list = Snapshot_List(snapshot);
+    Vector *dupe_list = Snapshot_List(dupe);
+    TEST_TRUE(runner, Vec_Equals(orig_list, (Obj*)dupe_list),
+              "Round trip through Write_File() and Read_File()");
+
+    DECREF(orig_list);
+    DECREF(dupe_list);
+    DECREF(dupe);
+    DECREF(snapshot);
+    DECREF(folder);
+}
+
+void
+TestSnapshot_Run_IMP(TestSnapshot *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 9);
+    test_Add_and_Delete(runner);
+    test_path_handling(runner);
+    test_Read_File_and_Write_File(runner);
+}
+
+

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

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Index/TestSortWriter.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Index/TestSortWriter.c b/test/Lucy/Test/Index/TestSortWriter.c
new file mode 100644
index 0000000..539cc9e
--- /dev/null
+++ b/test/Lucy/Test/Index/TestSortWriter.c
@@ -0,0 +1,323 @@
+/* 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>
+
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+
+#include "Lucy/Test/Index/TestSortWriter.h"
+
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Analysis/StandardTokenizer.h"
+#include "Lucy/Document/Doc.h"
+#include "Lucy/Document/HitDoc.h"
+#include "Lucy/Index/DocReader.h"
+#include "Lucy/Index/Indexer.h"
+#include "Lucy/Index/IndexManager.h"
+#include "Lucy/Index/PolyReader.h"
+#include "Lucy/Index/Segment.h"
+#include "Lucy/Index/SegReader.h"
+#include "Lucy/Index/SortCache.h"
+#include "Lucy/Index/SortReader.h"
+#include "Lucy/Index/SortWriter.h"
+#include "Lucy/Plan/FullTextType.h"
+#include "Lucy/Plan/Schema.h"
+#include "Lucy/Plan/StringType.h"
+#include "Lucy/Store/RAMFolder.h"
+
+static String *name_str;
+static String *speed_str;
+static String *weight_str;
+static String *home_str;
+static String *cat_str;
+static String *wheels_str;
+static String *unused_str;
+static String *nope_str;
+
+TestSortWriter*
+TestSortWriter_new() {
+    return (TestSortWriter*)Class_Make_Obj(TESTSORTWRITER);
+}
+
+static void
+S_init_strings() {
+    name_str   = Str_newf("name");
+    speed_str  = Str_newf("speed");
+    weight_str = Str_newf("weight");
+    home_str   = Str_newf("home");
+    cat_str    = Str_newf("cat");
+    wheels_str = Str_newf("wheels");
+    unused_str = Str_newf("unused");
+    nope_str   = Str_newf("nope");
+}
+
+static void
+S_destroy_strings() {
+    DECREF(name_str);
+    DECREF(speed_str);
+    DECREF(weight_str);
+    DECREF(home_str);
+    DECREF(cat_str);
+    DECREF(wheels_str);
+    DECREF(unused_str);
+    DECREF(nope_str);
+}
+
+static Schema*
+S_create_schema() {
+    Schema *schema = Schema_new();
+
+    StandardTokenizer *tokenizer = StandardTokenizer_new();
+    FullTextType *full_text_type = FullTextType_new((Analyzer*)tokenizer);
+    FullTextType_Set_Sortable(full_text_type, true);
+
+    StringType *string_type = StringType_new();
+    StringType_Set_Sortable(string_type, true);
+
+    StringType *unsortable = StringType_new();
+
+    Schema_Spec_Field(schema, name_str,   (FieldType*)full_text_type);
+    Schema_Spec_Field(schema, speed_str,  (FieldType*)string_type);
+    Schema_Spec_Field(schema, weight_str, (FieldType*)string_type);
+    Schema_Spec_Field(schema, home_str,   (FieldType*)string_type);
+    Schema_Spec_Field(schema, cat_str,    (FieldType*)string_type);
+    Schema_Spec_Field(schema, wheels_str, (FieldType*)string_type);
+    Schema_Spec_Field(schema, unused_str, (FieldType*)string_type);
+    Schema_Spec_Field(schema, nope_str,   (FieldType*)unsortable);
+
+    DECREF(unsortable);
+    DECREF(string_type);
+    DECREF(full_text_type);
+    DECREF(tokenizer);
+
+    return schema;
+}
+
+static void
+S_store_field(Doc *doc, String *field, const char *value) {
+    if (value) {
+        String *string = SSTR_WRAP_C(value);
+        Doc_Store(doc, field, (Obj*)string);
+    }
+}
+
+static void
+S_add_doc(Indexer *indexer, const char *name, const char *speed,
+              const char *weight, const char *home, const char *wheels,
+              const char *nope) {
+    Doc *doc   = Doc_new(NULL, 0);
+
+    S_store_field(doc, name_str,   name);
+    S_store_field(doc, speed_str,  speed);
+    S_store_field(doc, weight_str, weight);
+    S_store_field(doc, home_str,   home);
+    S_store_field(doc, cat_str,    "vehicle");
+    S_store_field(doc, wheels_str, wheels);
+    S_store_field(doc, nope_str,   nope);
+
+    Indexer_Add_Doc(indexer, doc, 1.0f);
+
+    DECREF(doc);
+}
+
+static void
+S_test_sort_cache(TestBatchRunner *runner, RAMFolder *folder,
+                  SegReader *seg_reader, const char *gen, bool is_used,
+                  String *field) {
+    Segment *segment   = SegReader_Get_Segment(seg_reader);
+    int32_t  field_num = Seg_Field_Num(segment, field);
+    char    *field_str = Str_To_Utf8(field);
+    String  *filename  = Str_newf("seg_%s/sort-%i32.ord", gen, field_num);
+    if (is_used) {
+        TEST_TRUE(runner, RAMFolder_Exists(folder, filename),
+                  "sort files written for %s", field_str);
+    }
+    else {
+        TEST_TRUE(runner, !RAMFolder_Exists(folder, filename),
+                  "no sort files written for %s", field_str);
+    }
+    DECREF(filename);
+
+    if (!is_used) {
+        free(field_str);
+        return;
+    }
+
+    SortReader *sort_reader
+        = (SortReader*)SegReader_Obtain(seg_reader,
+                                        Class_Get_Name(SORTREADER));
+    DocReader *doc_reader
+        = (DocReader*)SegReader_Obtain(seg_reader, Class_Get_Name(DOCREADER));
+    SortCache *sort_cache
+        = SortReader_Fetch_Sort_Cache(sort_reader, field);
+
+    int32_t doc_max = SegReader_Doc_Max(seg_reader);
+    for (int32_t doc_id = 1; doc_id <= doc_max; ++doc_id) {
+        int32_t  ord         = SortCache_Ordinal(sort_cache, doc_id);
+        Obj     *cache_value = SortCache_Value(sort_cache, ord);
+        HitDoc  *doc         = DocReader_Fetch_Doc(doc_reader, doc_id);
+        Obj     *doc_value   = HitDoc_Extract(doc, field);
+
+        bool is_equal;
+        if (cache_value == NULL || doc_value == NULL) {
+            is_equal = (cache_value == doc_value);
+        }
+        else {
+            is_equal = Obj_Equals(cache_value, doc_value);
+        }
+        TEST_TRUE(runner, is_equal, "correct cached value field %s doc %d",
+                  field_str, doc_id);
+
+        DECREF(doc_value);
+        DECREF(doc);
+        DECREF(cache_value);
+    }
+
+    free(field_str);
+}
+
+static void
+test_sort_writer(TestBatchRunner *runner) {
+    Schema    *schema  = S_create_schema();
+    RAMFolder *folder  = RAMFolder_new(NULL);
+
+    {
+        // Add vehicles.
+        Indexer *indexer = Indexer_new(schema, (Obj*)folder, NULL, 0);
+
+        S_add_doc(indexer, "airplane", "0200", "8000", "air", "3", "nyet");
+        S_add_doc(indexer, "bike", "0015", "0025", "land", "2", NULL);
+        S_add_doc(indexer, "car", "0070", "3000", "land",  "4", NULL);
+
+        Indexer_Commit(indexer);
+        DECREF(indexer);
+    }
+
+    {
+        PolyReader *poly_reader = PolyReader_open((Obj*)folder, NULL, NULL);
+        Vector     *seg_readers = PolyReader_Get_Seg_Readers(poly_reader);
+        SegReader  *seg_reader  = (SegReader*)Vec_Fetch(seg_readers, 0);
+
+        S_test_sort_cache(runner, folder, seg_reader, "1", true,  name_str);
+        S_test_sort_cache(runner, folder, seg_reader, "1", true,  speed_str);
+        S_test_sort_cache(runner, folder, seg_reader, "1", true,  weight_str);
+        S_test_sort_cache(runner, folder, seg_reader, "1", true,  home_str);
+        S_test_sort_cache(runner, folder, seg_reader, "1", true,  cat_str);
+        S_test_sort_cache(runner, folder, seg_reader, "1", true,  wheels_str);
+        S_test_sort_cache(runner, folder, seg_reader, "1", false, unused_str);
+        S_test_sort_cache(runner, folder, seg_reader, "1", false, nope_str);
+
+        DECREF(poly_reader);
+    }
+
+    {
+        // Add a second segment.
+        NonMergingIndexManager *manager = NMIxManager_new();
+        Indexer *indexer
+            = Indexer_new(schema, (Obj*)folder, (IndexManager*)manager, 0);
+        // no "wheels" field -- test NULL/undef
+        S_add_doc(indexer, "dirigible", "0040", "0000", "air", NULL, NULL);
+        Indexer_Commit(indexer);
+        DECREF(indexer);
+        DECREF(manager);
+    }
+
+    {
+        // Consolidate everything, to test merging.
+        Indexer *indexer = Indexer_new(schema, (Obj*)folder, NULL, 0);
+        String *bike_str = SSTR_WRAP_C("bike");
+        Indexer_Delete_By_Term(indexer, name_str, (Obj*)bike_str);
+        // no "wheels" field -- test NULL/undef
+        S_add_doc(indexer, "elephant", "0020", "6000", "land", NULL, NULL);
+        Indexer_Optimize(indexer);
+        Indexer_Commit(indexer);
+        DECREF(indexer);
+    }
+
+    {
+        Vector *filenames = RAMFolder_List_R(folder, NULL);
+        int num_old_seg_files = 0;
+        for (size_t i = 0, size = Vec_Get_Size(filenames); i < size; ++i) {
+            String *filename = (String*)Vec_Fetch(filenames, i);
+            if (Str_Contains_Utf8(filename, "seg_1", 5)
+                || Str_Contains_Utf8(filename, "seg_2", 5)
+               ) {
+                ++num_old_seg_files;
+            }
+        }
+        TEST_INT_EQ(runner, num_old_seg_files, 0,
+                    "all files from earlier segments zapped");
+        DECREF(filenames);
+    }
+
+    {
+        PolyReader *poly_reader = PolyReader_open((Obj*)folder, NULL, NULL);
+        Vector     *seg_readers = PolyReader_Get_Seg_Readers(poly_reader);
+        SegReader  *seg_reader  = (SegReader*)Vec_Fetch(seg_readers, 0);
+
+        S_test_sort_cache(runner, folder, seg_reader, "3", true, name_str);
+        S_test_sort_cache(runner, folder, seg_reader, "3", true, speed_str);
+        S_test_sort_cache(runner, folder, seg_reader, "3", true, weight_str);
+        S_test_sort_cache(runner, folder, seg_reader, "3", true, home_str);
+        S_test_sort_cache(runner, folder, seg_reader, "3", true, cat_str);
+        S_test_sort_cache(runner, folder, seg_reader, "3", true, wheels_str);
+
+        DECREF(poly_reader);
+    }
+
+    DECREF(folder);
+    DECREF(schema);
+}
+
+void
+TestSortWriter_Run_IMP(TestSortWriter *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 57);
+
+    // Force frequent flushes.
+    SortWriter_set_default_mem_thresh(100);
+
+    S_init_strings();
+    test_sort_writer(runner);
+    S_destroy_strings();
+}
+
+NonMergingIndexManager*
+NMIxManager_new() {
+    NonMergingIndexManager *self
+        = (NonMergingIndexManager*)Class_Make_Obj(NONMERGINGINDEXMANAGER);
+    return NMIxManager_init(self);
+}
+
+NonMergingIndexManager*
+NMIxManager_init(NonMergingIndexManager *self) {
+    IxManager_init((IndexManager*)self, NULL, NULL);
+    return self;
+}
+
+Vector*
+NMIxManager_Recycle_IMP(NonMergingIndexManager *self, PolyReader *reader,
+                        lucy_DeletionsWriter *del_writer, int64_t cutoff,
+                        bool optimize) {
+    UNUSED_VAR(self);
+    UNUSED_VAR(reader);
+    UNUSED_VAR(del_writer);
+    UNUSED_VAR(cutoff);
+    UNUSED_VAR(optimize);
+    return Vec_new(0);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Index/TestSortWriter.cfh
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Index/TestSortWriter.cfh b/test/Lucy/Test/Index/TestSortWriter.cfh
new file mode 100644
index 0000000..5aa63ab
--- /dev/null
+++ b/test/Lucy/Test/Index/TestSortWriter.cfh
@@ -0,0 +1,43 @@
+/* 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::Index::TestSortWriter
+    inherits Clownfish::TestHarness::TestBatch {
+
+    inert incremented TestSortWriter*
+    new();
+
+    void
+    Run(TestSortWriter *self, TestBatchRunner *runner);
+}
+
+class Lucy::Test::Index::NonMergingIndexManager nickname NMIxManager
+    inherits Lucy::Index::IndexManager {
+
+    public inert incremented NonMergingIndexManager*
+    new();
+
+    public inert NonMergingIndexManager*
+    init(NonMergingIndexManager *self);
+
+    public incremented Vector*
+    Recycle(NonMergingIndexManager *self, PolyReader *reader,
+            DeletionsWriter *del_writer, int64_t cutoff,
+            bool optimize = false);
+}
+