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:11:58 UTC

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

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Search/TestQueryParser.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Search/TestQueryParser.c b/test/Lucy/Test/Search/TestQueryParser.c
new file mode 100644
index 0000000..57dcd17
--- /dev/null
+++ b/test/Lucy/Test/Search/TestQueryParser.c
@@ -0,0 +1,81 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define C_TESTLUCY_TESTQUERYPARSER
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+#include <string.h>
+
+#include "Clownfish/TestHarness/TestUtils.h"
+#include "Lucy/Test/Search/TestQueryParser.h"
+#include "Lucy/Test/TestUtils.h"
+#include "Lucy/Search/TermQuery.h"
+#include "Lucy/Search/PhraseQuery.h"
+#include "Lucy/Search/LeafQuery.h"
+#include "Lucy/Search/ANDQuery.h"
+#include "Lucy/Search/NOTQuery.h"
+#include "Lucy/Search/ORQuery.h"
+
+TestQueryParser*
+TestQP_new(const char *query_string, Query *tree, Query *expanded,
+           uint32_t num_hits) {
+    TestQueryParser *self
+        = (TestQueryParser*)Class_Make_Obj(TESTQUERYPARSER);
+    return TestQP_init(self, query_string, tree, expanded, num_hits);
+}
+
+TestQueryParser*
+TestQP_init(TestQueryParser *self, const char *query_string, Query *tree,
+            Query *expanded, uint32_t num_hits) {
+    TestQueryParserIVARS *const ivars = TestQP_IVARS(self);
+    ivars->query_string = query_string ? TestUtils_get_str(query_string) : NULL;
+    ivars->tree         = tree     ? tree     : NULL;
+    ivars->expanded     = expanded ? expanded : NULL;
+    ivars->num_hits     = num_hits;
+    return self;
+}
+
+void
+TestQP_Destroy_IMP(TestQueryParser *self) {
+    TestQueryParserIVARS *const ivars = TestQP_IVARS(self);
+    DECREF(ivars->query_string);
+    DECREF(ivars->tree);
+    DECREF(ivars->expanded);
+    SUPER_DESTROY(self, TESTQUERYPARSER);
+}
+
+String*
+TestQP_Get_Query_String_IMP(TestQueryParser *self) {
+    return TestQP_IVARS(self)->query_string;
+}
+
+Query*
+TestQP_Get_Tree_IMP(TestQueryParser *self) {
+    return TestQP_IVARS(self)->tree;
+}
+
+Query*
+TestQP_Get_Expanded_IMP(TestQueryParser *self) {
+    return TestQP_IVARS(self)->expanded;
+}
+
+uint32_t
+TestQP_Get_Num_Hits_IMP(TestQueryParser *self) {
+    return TestQP_IVARS(self)->num_hits;
+}
+
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Search/TestQueryParser.cfh
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Search/TestQueryParser.cfh b/test/Lucy/Test/Search/TestQueryParser.cfh
new file mode 100644
index 0000000..4c900d5
--- /dev/null
+++ b/test/Lucy/Test/Search/TestQueryParser.cfh
@@ -0,0 +1,61 @@
+/* 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;
+
+/** Test case object for QueryParser unit tests.
+ */
+
+class Lucy::Test::Search::TestQueryParser nickname TestQP
+    inherits Clownfish::Obj {
+
+    String  *query_string;
+    Query   *tree;
+    Query   *expanded;
+    uint32_t num_hits;
+
+    /** Note that unlike most Clownfish constructors, this routine will consume one
+     * reference count each for `tree`, and `expanded`.
+     */
+    inert incremented TestQueryParser*
+    new(const char *query_string = NULL, Query *tree = NULL,
+        Query *expanded = NULL, uint32_t num_hits);
+
+    inert TestQueryParser*
+    init(TestQueryParser *self, const char *query_string = NULL,
+         Query *tree = NULL, Query *expanded = NULL, uint32_t num_hits);
+
+    nullable String*
+    Get_Query_String(TestQueryParser *self);
+
+    nullable Query*
+    Get_Tree(TestQueryParser *self);
+
+    nullable Query*
+    Get_Expanded(TestQueryParser *self);
+
+    uint32_t
+    Get_Num_Hits(TestQueryParser *self);
+
+    public void
+    Destroy(TestQueryParser *self);
+}
+
+__C__
+
+__END_C__
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Search/TestQueryParserLogic.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Search/TestQueryParserLogic.c b/test/Lucy/Test/Search/TestQueryParserLogic.c
new file mode 100644
index 0000000..22104fd
--- /dev/null
+++ b/test/Lucy/Test/Search/TestQueryParserLogic.c
@@ -0,0 +1,976 @@
+/* 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_TESTQUERYPARSERLOGIC
+#define C_TESTLUCY_TESTQUERYPARSER
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+#include <string.h>
+#include <stdlib.h>
+
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/Search/TestQueryParserLogic.h"
+#include "Lucy/Test/Search/TestQueryParser.h"
+#include "Lucy/Test/TestSchema.h"
+#include "Lucy/Test/TestUtils.h"
+#include "Lucy/Analysis/Analyzer.h"
+#include "Lucy/Document/Doc.h"
+#include "Lucy/Index/Indexer.h"
+#include "Lucy/Search/Hits.h"
+#include "Lucy/Search/IndexSearcher.h"
+#include "Lucy/Search/QueryParser.h"
+#include "Lucy/Search/TermQuery.h"
+#include "Lucy/Search/PhraseQuery.h"
+#include "Lucy/Search/LeafQuery.h"
+#include "Lucy/Search/ANDQuery.h"
+#include "Lucy/Search/MatchAllQuery.h"
+#include "Lucy/Search/NOTQuery.h"
+#include "Lucy/Search/NoMatchQuery.h"
+#include "Lucy/Search/ORQuery.h"
+#include "Lucy/Search/RequiredOptionalQuery.h"
+#include "Lucy/Store/RAMFolder.h"
+
+#define make_leaf_query   (Query*)TestUtils_make_leaf_query
+#define make_not_query    (Query*)TestUtils_make_not_query
+#define make_poly_query   (Query*)TestUtils_make_poly_query
+
+TestQueryParserLogic*
+TestQPLogic_new() {
+    return (TestQueryParserLogic*)Class_Make_Obj(TESTQUERYPARSERLOGIC);
+}
+
+static TestQueryParser*
+logical_test_empty_phrase(uint32_t boolop) {
+    Query   *tree = make_leaf_query(NULL, "\"\"");
+    UNUSED_VAR(boolop);
+    return TestQP_new("\"\"", tree, NULL, 0);
+}
+
+static TestQueryParser*
+logical_test_empty_parens(uint32_t boolop) {
+    Query   *tree   = make_poly_query(boolop, NULL);
+    return TestQP_new("()", tree, NULL, 0);
+}
+
+static TestQueryParser*
+logical_test_nested_empty_parens(uint32_t boolop) {
+    Query   *inner   = make_poly_query(boolop, NULL);
+    Query   *tree    = make_poly_query(boolop, inner, NULL);
+    return TestQP_new("(())", tree, NULL, 0);
+}
+
+static TestQueryParser*
+logical_test_nested_empty_phrase(uint32_t boolop) {
+    Query   *leaf   = make_leaf_query(NULL, "\"\"");
+    Query   *tree   = make_poly_query(boolop, leaf, NULL);
+    return TestQP_new("(\"\")", tree, NULL, 0);
+}
+
+static TestQueryParser*
+logical_test_simple_term(uint32_t boolop) {
+    Query   *tree   = make_leaf_query(NULL, "b");
+    UNUSED_VAR(boolop);
+    return TestQP_new("b", tree, NULL, 3);
+}
+
+static TestQueryParser*
+logical_test_one_nested_term(uint32_t boolop) {
+    Query   *leaf   = make_leaf_query(NULL, "a");
+    Query   *tree   = make_poly_query(boolop, leaf, NULL);
+    return TestQP_new("(a)", tree, NULL, 4);
+}
+
+static TestQueryParser*
+logical_test_one_term_phrase(uint32_t boolop) {
+    Query   *tree   = make_leaf_query(NULL, "\"a\"");
+    UNUSED_VAR(boolop);
+    return TestQP_new("\"a\"", tree, NULL, 4);
+}
+
+static TestQueryParser*
+logical_test_two_terms(uint32_t boolop) {
+    Query   *a_leaf    = make_leaf_query(NULL, "a");
+    Query   *b_leaf    = make_leaf_query(NULL, "b");
+    Query   *tree      = make_poly_query(boolop, a_leaf, b_leaf, NULL);
+    uint32_t num_hits  = boolop == BOOLOP_OR ? 4 : 3;
+    return TestQP_new("a b", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_two_terms_nested(uint32_t boolop) {
+    Query   *a_leaf     = make_leaf_query(NULL, "a");
+    Query   *b_leaf     = make_leaf_query(NULL, "b");
+    Query   *tree       = make_poly_query(boolop, a_leaf, b_leaf, NULL);
+    uint32_t num_hits   = boolop == BOOLOP_OR ? 4 : 3;
+    return TestQP_new("(a b)", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_one_term_one_single_term_phrase(uint32_t boolop) {
+    Query   *a_leaf    = make_leaf_query(NULL, "a");
+    Query   *b_leaf    = make_leaf_query(NULL, "\"b\"");
+    Query   *tree      = make_poly_query(boolop, a_leaf, b_leaf, NULL);
+    uint32_t num_hits  = boolop == BOOLOP_OR ? 4 : 3;
+    return TestQP_new("a \"b\"", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_two_terms_one_nested(uint32_t boolop) {
+    Query   *a_leaf    = make_leaf_query(NULL, "a");
+    Query   *b_leaf    = make_leaf_query(NULL, "b");
+    Query   *b_tree    = make_poly_query(boolop, b_leaf, NULL);
+    Query   *tree      = make_poly_query(boolop, a_leaf, b_tree, NULL);
+    uint32_t num_hits  = boolop == BOOLOP_OR ? 4 : 3;
+    return TestQP_new("a (b)", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_one_term_one_nested_single_term_phrase(uint32_t boolop) {
+    Query   *a_leaf    = make_leaf_query(NULL, "a");
+    Query   *b_leaf    = make_leaf_query(NULL, "\"b\"");
+    Query   *b_tree    = make_poly_query(boolop, b_leaf, NULL);
+    Query   *tree      = make_poly_query(boolop, a_leaf, b_tree, NULL);
+    uint32_t num_hits  = boolop == BOOLOP_OR ? 4 : 3;
+    return TestQP_new("a (\"b\")", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_phrase(uint32_t boolop) {
+    Query   *tree    = make_leaf_query(NULL, "\"a b\"");
+    UNUSED_VAR(boolop);
+    return TestQP_new("\"a b\"", tree, NULL, 3);
+}
+
+static TestQueryParser*
+logical_test_nested_phrase(uint32_t boolop) {
+    Query   *leaf   = make_leaf_query(NULL, "\"a b\"");
+    Query   *tree   = make_poly_query(boolop, leaf, NULL);
+    return TestQP_new("(\"a b\")", tree, NULL, 3);
+}
+
+static TestQueryParser*
+logical_test_three_terms(uint32_t boolop) {
+    Query   *a_leaf   = make_leaf_query(NULL, "a");
+    Query   *b_leaf   = make_leaf_query(NULL, "b");
+    Query   *c_leaf   = make_leaf_query(NULL, "c");
+    Query   *tree     = make_poly_query(boolop, a_leaf, b_leaf,
+                                        c_leaf, NULL);
+    uint32_t num_hits = boolop == BOOLOP_OR ? 4 : 2;
+    return TestQP_new("a b c", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_three_terms_two_nested(uint32_t boolop) {
+    Query   *a_leaf     = make_leaf_query(NULL, "a");
+    Query   *b_leaf     = make_leaf_query(NULL, "b");
+    Query   *c_leaf     = make_leaf_query(NULL, "c");
+    Query   *inner_tree = make_poly_query(boolop, b_leaf, c_leaf, NULL);
+    Query   *tree       = make_poly_query(boolop, a_leaf, inner_tree, NULL);
+    uint32_t num_hits   = boolop == BOOLOP_OR ? 4 : 2;
+    return TestQP_new("a (b c)", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_one_term_one_phrase(uint32_t boolop) {
+    Query   *a_leaf   = make_leaf_query(NULL, "a");
+    Query   *bc_leaf  = make_leaf_query(NULL, "\"b c\"");
+    Query   *tree     = make_poly_query(boolop, a_leaf, bc_leaf, NULL);
+    uint32_t num_hits = boolop == BOOLOP_OR ? 4 : 2;
+    return TestQP_new("a \"b c\"", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_one_term_one_nested_phrase(uint32_t boolop) {
+    Query   *a_leaf     = make_leaf_query(NULL, "a");
+    Query   *bc_leaf    = make_leaf_query(NULL, "\"b c\"");
+    Query   *inner_tree = make_poly_query(boolop, bc_leaf, NULL);
+    Query   *tree       = make_poly_query(boolop, a_leaf, inner_tree, NULL);
+    uint32_t num_hits   = boolop == BOOLOP_OR ? 4 : 2;
+    return TestQP_new("a (\"b c\")", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_long_phrase(uint32_t boolop) {
+    Query   *tree   = make_leaf_query(NULL, "\"a b c\"");
+    UNUSED_VAR(boolop);
+    return TestQP_new("\"a b c\"", tree, NULL, 2);
+}
+
+static TestQueryParser*
+logical_test_pure_negation(uint32_t boolop) {
+    Query   *leaf   = make_leaf_query(NULL, "x");
+    Query   *tree   = make_not_query(leaf);
+    UNUSED_VAR(boolop);
+    return TestQP_new("-x", tree, NULL, 0);
+}
+
+static TestQueryParser*
+logical_test_double_negative(uint32_t boolop) {
+    Query   *tree   = make_leaf_query(NULL, "a");
+    UNUSED_VAR(boolop);
+    return TestQP_new("--a", tree, NULL, 4);
+}
+
+static TestQueryParser*
+logical_test_triple_negative(uint32_t boolop) {
+    Query   *leaf   = make_leaf_query(NULL, "a");
+    Query   *tree   = make_not_query(leaf);
+    UNUSED_VAR(boolop);
+    return TestQP_new("---a", tree, NULL, 0);
+}
+
+// Technically, this should produce an acceptably small result set, but it's
+// too difficult to prune -- so QParser_Prune just lops it because it's a
+// top-level NOTQuery.
+static TestQueryParser*
+logical_test_nested_negations(uint32_t boolop) {
+    Query *query = make_leaf_query(NULL, "a");
+    query = make_poly_query(boolop, query, NULL);
+    query = make_not_query(query);
+    query = make_poly_query(BOOLOP_AND, query, NULL);
+    query = make_not_query(query);
+    return TestQP_new("-(-(a))", query, NULL, 0);
+}
+
+static TestQueryParser*
+logical_test_two_terms_one_required(uint32_t boolop) {
+    Query   *a_query = make_leaf_query(NULL, "a");
+    Query   *b_query = make_leaf_query(NULL, "b");
+    Query   *tree;
+    if (boolop == BOOLOP_AND) {
+        tree = make_poly_query(boolop, a_query, b_query, NULL);
+    }
+    else {
+        tree = (Query*)ReqOptQuery_new(b_query, a_query);
+        DECREF(b_query);
+        DECREF(a_query);
+    }
+    return TestQP_new("a +b", tree, NULL, 3);
+}
+
+static TestQueryParser*
+logical_test_intersection(uint32_t boolop) {
+    Query   *a_query = make_leaf_query(NULL, "a");
+    Query   *b_query = make_leaf_query(NULL, "b");
+    Query   *tree    = make_poly_query(BOOLOP_AND, a_query, b_query, NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("a AND b", tree, NULL, 3);
+}
+
+static TestQueryParser*
+logical_test_three_way_intersection(uint32_t boolop) {
+    Query *a_query = make_leaf_query(NULL, "a");
+    Query *b_query = make_leaf_query(NULL, "b");
+    Query *c_query = make_leaf_query(NULL, "c");
+    Query *tree    = make_poly_query(BOOLOP_AND, a_query, b_query,
+                                     c_query, NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("a AND b AND c", tree, NULL, 2);
+}
+
+static TestQueryParser*
+logical_test_union(uint32_t boolop) {
+    Query   *a_query = make_leaf_query(NULL, "a");
+    Query   *b_query = make_leaf_query(NULL, "b");
+    Query   *tree    = make_poly_query(BOOLOP_OR, a_query, b_query, NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("a OR b", tree, NULL, 4);
+}
+
+static TestQueryParser*
+logical_test_three_way_union(uint32_t boolop) {
+    Query *a_query = make_leaf_query(NULL, "a");
+    Query *b_query = make_leaf_query(NULL, "b");
+    Query *c_query = make_leaf_query(NULL, "c");
+    Query *tree = make_poly_query(BOOLOP_OR, a_query, b_query, c_query, NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("a OR b OR c", tree, NULL, 4);
+}
+
+static TestQueryParser*
+logical_test_a_or_plus_b(uint32_t boolop) {
+    Query   *a_query = make_leaf_query(NULL, "a");
+    Query   *b_query = make_leaf_query(NULL, "b");
+    Query   *tree    = make_poly_query(BOOLOP_OR, a_query, b_query, NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("a OR +b", tree, NULL, 4);
+}
+
+static TestQueryParser*
+logical_test_and_not(uint32_t boolop) {
+    Query   *a_query = make_leaf_query(NULL, "a");
+    Query   *b_query = make_leaf_query(NULL, "b");
+    Query   *not_b   = make_not_query(b_query);
+    Query   *tree    = make_poly_query(BOOLOP_AND, a_query, not_b, NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("a AND NOT b", tree, NULL, 1);
+}
+
+static TestQueryParser*
+logical_test_nested_or(uint32_t boolop) {
+    Query   *a_query = make_leaf_query(NULL, "a");
+    Query   *b_query = make_leaf_query(NULL, "b");
+    Query   *c_query = make_leaf_query(NULL, "c");
+    Query   *nested  = make_poly_query(BOOLOP_OR, b_query, c_query, NULL);
+    Query   *tree    = make_poly_query(boolop, a_query, nested, NULL);
+    return TestQP_new("a (b OR c)", tree, NULL, boolop == BOOLOP_OR ? 4 : 3);
+}
+
+static TestQueryParser*
+logical_test_and_nested_or(uint32_t boolop) {
+    Query   *a_query = make_leaf_query(NULL, "a");
+    Query   *b_query = make_leaf_query(NULL, "b");
+    Query   *c_query = make_leaf_query(NULL, "c");
+    Query   *nested  = make_poly_query(BOOLOP_OR, b_query, c_query, NULL);
+    Query   *tree    = make_poly_query(BOOLOP_AND, a_query, nested, NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("a AND (b OR c)", tree, NULL, 3);
+}
+
+static TestQueryParser*
+logical_test_or_nested_or(uint32_t boolop) {
+    Query   *a_query = make_leaf_query(NULL, "a");
+    Query   *b_query = make_leaf_query(NULL, "b");
+    Query   *c_query = make_leaf_query(NULL, "c");
+    Query   *nested  = make_poly_query(BOOLOP_OR, b_query, c_query, NULL);
+    Query   *tree    = make_poly_query(BOOLOP_OR, a_query, nested, NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("a OR (b OR c)", tree, NULL, 4);
+}
+
+static TestQueryParser*
+logical_test_and_not_nested_or(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *nested     = make_poly_query(BOOLOP_OR, b_query, c_query, NULL);
+    Query *not_nested = make_not_query(nested);
+    Query *tree       = make_poly_query(BOOLOP_AND, a_query,
+                                        not_nested, NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("a AND NOT (b OR c)", tree, NULL, 1);
+}
+
+static TestQueryParser*
+logical_test_a_AND_b_AND_c_AND_d(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *tree       = make_poly_query(BOOLOP_AND, a_query, b_query, c_query,
+                                        d_query, NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("a AND b AND c AND d", tree, NULL, 1);
+}
+
+static TestQueryParser*
+logical_test_a_AND_b_AND_c_OR_d(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *nested     = make_poly_query(BOOLOP_AND, a_query, b_query, c_query,
+                                        NULL);
+    Query *tree       = make_poly_query(BOOLOP_OR, nested,
+                                        d_query, NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("a AND b AND c OR d", tree, NULL, 2);
+}
+
+static TestQueryParser*
+logical_test_a_AND_b_OR_c_AND_d(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *left       = make_poly_query(BOOLOP_AND, a_query, b_query, NULL);
+    Query *right      = make_poly_query(BOOLOP_AND, c_query, d_query, NULL);
+    Query *tree       = make_poly_query(BOOLOP_OR, left, right, NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("a AND b OR c AND d", tree, NULL, 3);
+}
+
+static TestQueryParser*
+logical_test_a_AND_b_OR_c_OR_d(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *left       = make_poly_query(BOOLOP_AND, a_query, b_query, NULL);
+    Query *tree       = make_poly_query(BOOLOP_OR, left, c_query, d_query,
+                                        NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("a AND b OR c OR d", tree, NULL, 3);
+}
+
+static TestQueryParser*
+logical_test_a_OR_b_AND_c_AND_d(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *right      = make_poly_query(BOOLOP_AND, b_query, c_query, d_query,
+                                        NULL);
+    Query *tree       = make_poly_query(BOOLOP_OR, a_query, right, NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("a OR b AND c AND d", tree, NULL, 4);
+}
+
+static TestQueryParser*
+logical_test_a_OR_b_AND_c_OR_d(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *middle     = make_poly_query(BOOLOP_AND, b_query, c_query, NULL);
+    Query *tree       = make_poly_query(BOOLOP_OR, a_query, middle, d_query,
+                                        NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("a OR b AND c OR d", tree, NULL, 4);
+}
+
+static TestQueryParser*
+logical_test_a_OR_b_OR_c_AND_d(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *right      = make_poly_query(BOOLOP_AND, c_query, d_query, NULL);
+    Query *tree       = make_poly_query(BOOLOP_OR, a_query, b_query, right,
+                                        NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("a OR b OR c AND d", tree, NULL, 4);
+}
+
+static TestQueryParser*
+logical_test_a_OR_b_OR_c_OR_d(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *tree       = make_poly_query(BOOLOP_OR, a_query, b_query, c_query,
+                                        d_query, NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("a OR b OR c OR d", tree, NULL, 4);
+}
+
+static TestQueryParser*
+logical_test_a_AND_b_AND_c_d(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *left       = make_poly_query(BOOLOP_AND, a_query, b_query, c_query,
+                                        NULL);
+    Query *tree       = make_poly_query(boolop, left, d_query, NULL);
+    uint32_t num_hits = boolop == BOOLOP_AND ? 1 : 2;
+    return TestQP_new("a AND b AND c d", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_a_AND_b_OR_c_d(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *inner      = make_poly_query(BOOLOP_AND, a_query, b_query, NULL);
+    Query *left       = make_poly_query(BOOLOP_OR, inner, c_query, NULL);
+    Query *tree       = make_poly_query(boolop, left, d_query, NULL);
+    uint32_t num_hits = boolop == BOOLOP_AND ? 1 : 3;
+    return TestQP_new("a AND b OR c d", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_a_OR_b_AND_c_d(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *inner      = make_poly_query(BOOLOP_AND, b_query, c_query, NULL);
+    Query *left       = make_poly_query(BOOLOP_OR, a_query, inner, NULL);
+    Query *tree       = make_poly_query(boolop, left, d_query, NULL);
+    uint32_t num_hits = boolop == BOOLOP_AND ? 1 : 4;
+    return TestQP_new("a OR b AND c d", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_a_OR_b_OR_c_d(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *left       = make_poly_query(BOOLOP_OR, a_query, b_query, c_query,
+                                        NULL);
+    Query *tree       = make_poly_query(boolop, left, d_query, NULL);
+    uint32_t num_hits = boolop == BOOLOP_AND ? 1 : 4;
+    return TestQP_new("a OR b OR c d", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_a_AND_b_c_AND_d(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *left       = make_poly_query(BOOLOP_AND, a_query, b_query, NULL);
+    Query *right      = make_poly_query(BOOLOP_AND, c_query, d_query, NULL);
+    Query *tree       = make_poly_query(boolop, left, right, NULL);
+    uint32_t num_hits = boolop == BOOLOP_AND ? 1 : 3;
+    return TestQP_new("a AND b c AND d", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_a_AND_b_c_OR_d(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *left       = make_poly_query(BOOLOP_AND, a_query, b_query, NULL);
+    Query *right      = make_poly_query(BOOLOP_OR, c_query, d_query, NULL);
+    Query *tree       = make_poly_query(boolop, left, right, NULL);
+    uint32_t num_hits = boolop == BOOLOP_AND ? 2 : 3;
+    return TestQP_new("a AND b c OR d", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_a_OR_b_c_AND_d(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *left       = make_poly_query(BOOLOP_OR, a_query, b_query, NULL);
+    Query *right      = make_poly_query(BOOLOP_AND, c_query, d_query, NULL);
+    Query *tree       = make_poly_query(boolop, left, right, NULL);
+    uint32_t num_hits = boolop == BOOLOP_AND ? 1 : 4;
+    return TestQP_new("a OR b c AND d", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_a_OR_b_c_OR_d(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *left       = make_poly_query(BOOLOP_OR, a_query, b_query, NULL);
+    Query *right      = make_poly_query(BOOLOP_OR, c_query, d_query, NULL);
+    Query *tree       = make_poly_query(boolop, left, right, NULL);
+    uint32_t num_hits = boolop == BOOLOP_AND ? 2 : 4;
+    return TestQP_new("a OR b c OR d", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_a_b_AND_c_AND_d(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *right      = make_poly_query(BOOLOP_AND, b_query, c_query, d_query,
+                                        NULL);
+    Query *tree       = make_poly_query(boolop, a_query, right, NULL);
+    uint32_t num_hits = boolop == BOOLOP_AND ? 1 : 4;
+    return TestQP_new("a b AND c AND d", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_a_b_AND_c_OR_d(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *inner      = make_poly_query(BOOLOP_AND, b_query, c_query, NULL);
+    Query *right      = make_poly_query(BOOLOP_OR, inner, d_query, NULL);
+    Query *tree       = make_poly_query(boolop, a_query, right, NULL);
+    uint32_t num_hits = boolop == BOOLOP_AND ? 2 : 4;
+    return TestQP_new("a b AND c OR d", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_a_b_OR_c_AND_d(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *inner      = make_poly_query(BOOLOP_AND, c_query, d_query, NULL);
+    Query *right      = make_poly_query(BOOLOP_OR, b_query, inner, NULL);
+    Query *tree       = make_poly_query(boolop, a_query, right, NULL);
+    uint32_t num_hits = boolop == BOOLOP_AND ? 3 : 4;
+    return TestQP_new("a b OR c AND d", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_a_b_OR_c_OR_d(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *right      = make_poly_query(BOOLOP_OR, b_query, c_query, d_query,
+                                        NULL);
+    Query *tree       = make_poly_query(boolop, a_query, right, NULL);
+    uint32_t num_hits = boolop == BOOLOP_AND ? 3 : 4;
+    return TestQP_new("a b OR c OR d", tree, NULL, num_hits);
+}
+
+static TestQueryParser*
+logical_test_required_phrase_negated_term(uint32_t boolop) {
+    Query *bc_query   = make_leaf_query(NULL, "\"b c\"");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *not_d      = make_not_query(d_query);
+    Query *tree       = make_poly_query(BOOLOP_AND, bc_query, not_d, NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("+\"b c\" -d", tree, NULL, 1);
+}
+
+static TestQueryParser*
+logical_test_required_term_optional_phrase(uint32_t boolop) {
+    Query *ab_query   = make_leaf_query(NULL, "\"a b\"");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *tree;
+    if (boolop == BOOLOP_AND) {
+        tree = make_poly_query(BOOLOP_AND, ab_query, d_query, NULL);
+    }
+    else {
+        tree = (Query*)ReqOptQuery_new(d_query, ab_query);
+        DECREF(d_query);
+        DECREF(ab_query);
+    }
+    UNUSED_VAR(boolop);
+    return TestQP_new("\"a b\" +d", tree, NULL, 1);
+}
+
+static TestQueryParser*
+logical_test_nested_nest(uint32_t boolop) {
+    Query *a_query    = make_leaf_query(NULL, "a");
+    Query *b_query    = make_leaf_query(NULL, "b");
+    Query *c_query    = make_leaf_query(NULL, "c");
+    Query *d_query    = make_leaf_query(NULL, "d");
+    Query *innermost  = make_poly_query(BOOLOP_AND, c_query, d_query, NULL);
+    Query *inner      = make_poly_query(BOOLOP_OR, b_query, innermost, NULL);
+    Query *not_inner  = make_not_query(inner);
+    Query *tree       = make_poly_query(BOOLOP_AND, a_query, not_inner, NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("a AND NOT (b OR (c AND d))", tree, NULL, 1);
+}
+
+static TestQueryParser*
+logical_test_field_bool_group(uint32_t boolop) {
+    Query   *b_query = make_leaf_query("content", "b");
+    Query   *c_query = make_leaf_query("content", "c");
+    Query   *tree    = make_poly_query(boolop, b_query, c_query, NULL);
+    return TestQP_new("content:(b c)", tree, NULL,
+                      boolop == BOOLOP_OR ? 3 : 2);
+}
+
+static TestQueryParser*
+logical_test_field_multi_OR(uint32_t boolop) {
+    Query *a_query = make_leaf_query("content", "a");
+    Query *b_query = make_leaf_query("content", "b");
+    Query *c_query = make_leaf_query("content", "c");
+    Query *tree = make_poly_query(BOOLOP_OR, a_query, b_query, c_query, NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("content:(a OR b OR c)", tree, NULL, 4);
+}
+
+static TestQueryParser*
+logical_test_field_multi_AND(uint32_t boolop) {
+    Query *a_query = make_leaf_query("content", "a");
+    Query *b_query = make_leaf_query("content", "b");
+    Query *c_query = make_leaf_query("content", "c");
+    Query *tree    = make_poly_query(BOOLOP_AND, a_query, b_query,
+                                     c_query, NULL);
+    UNUSED_VAR(boolop);
+    return TestQP_new("content:(a AND b AND c)", tree, NULL, 2);
+}
+
+static TestQueryParser*
+logical_test_field_phrase(uint32_t boolop) {
+    Query   *tree = make_leaf_query("content", "\"b c\"");
+    UNUSED_VAR(boolop);
+    return TestQP_new("content:\"b c\"", tree, NULL, 2);
+}
+
+static TestQueryParser*
+prune_test_null_querystring() {
+    Query   *pruned = (Query*)NoMatchQuery_new();
+    return TestQP_new(NULL, NULL, pruned, 0);
+}
+
+static TestQueryParser*
+prune_test_matchall() {
+    Query   *tree   = (Query*)MatchAllQuery_new();
+    Query   *pruned = (Query*)NoMatchQuery_new();
+    return TestQP_new(NULL, tree, pruned, 0);
+}
+
+static TestQueryParser*
+prune_test_nomatch() {
+    Query   *tree   = (Query*)NoMatchQuery_new();
+    Query   *pruned = (Query*)NoMatchQuery_new();
+    return TestQP_new(NULL, tree, pruned, 0);
+}
+
+static TestQueryParser*
+prune_test_optional_not() {
+    Query   *a_leaf  = make_leaf_query(NULL, "a");
+    Query   *b_leaf  = make_leaf_query(NULL, "b");
+    Query   *not_b   = make_not_query(b_leaf);
+    Query   *tree    = make_poly_query(BOOLOP_OR, (Query*)INCREF(a_leaf),
+                                       not_b, NULL);
+    Query   *nomatch = (Query*)NoMatchQuery_new();
+    Query   *pruned  = make_poly_query(BOOLOP_OR, a_leaf, nomatch, NULL);
+    return TestQP_new(NULL, tree, pruned, 4);
+}
+
+static TestQueryParser*
+prune_test_reqopt_optional_not() {
+    Query   *a_leaf  = make_leaf_query(NULL, "a");
+    Query   *b_leaf  = make_leaf_query(NULL, "b");
+    Query   *not_b   = make_not_query(b_leaf);
+    Query   *tree    = (Query*)ReqOptQuery_new(a_leaf, not_b);
+    Query   *nomatch = (Query*)NoMatchQuery_new();
+    Query   *pruned  = (Query*)ReqOptQuery_new(a_leaf, nomatch);
+    DECREF(nomatch);
+    DECREF(not_b);
+    DECREF(a_leaf);
+    return TestQP_new(NULL, tree, pruned, 4);
+}
+
+static TestQueryParser*
+prune_test_reqopt_required_not() {
+    Query   *a_leaf  = make_leaf_query(NULL, "a");
+    Query   *b_leaf  = make_leaf_query(NULL, "b");
+    Query   *not_a   = make_not_query(a_leaf);
+    Query   *tree    = (Query*)ReqOptQuery_new(not_a, b_leaf);
+    Query   *nomatch = (Query*)NoMatchQuery_new();
+    Query   *pruned  = (Query*)ReqOptQuery_new(nomatch, b_leaf);
+    DECREF(nomatch);
+    DECREF(not_a);
+    DECREF(b_leaf);
+    return TestQP_new(NULL, tree, pruned, 0);
+}
+
+static TestQueryParser*
+prune_test_not_and_not() {
+    Query   *a_leaf  = make_leaf_query(NULL, "a");
+    Query   *b_leaf  = make_leaf_query(NULL, "b");
+    Query   *not_a   = make_not_query(a_leaf);
+    Query   *not_b   = make_not_query(b_leaf);
+    Query   *tree    = make_poly_query(BOOLOP_AND, not_a, not_b, NULL);
+    Query   *pruned  = make_poly_query(BOOLOP_AND, NULL);
+    return TestQP_new(NULL, tree, pruned, 0);
+}
+
+/***************************************************************************/
+
+typedef TestQueryParser*
+(*LUCY_TestQPLogic_Logical_Test_t)(uint32_t boolop_sym);
+
+static LUCY_TestQPLogic_Logical_Test_t logical_test_funcs[] = {
+    logical_test_empty_phrase,
+    logical_test_empty_parens,
+    logical_test_nested_empty_parens,
+    logical_test_nested_empty_phrase,
+    logical_test_simple_term,
+    logical_test_one_nested_term,
+    logical_test_one_term_phrase,
+    logical_test_two_terms,
+    logical_test_two_terms_nested,
+    logical_test_one_term_one_single_term_phrase,
+    logical_test_two_terms_one_nested,
+    logical_test_one_term_one_nested_phrase,
+    logical_test_phrase,
+    logical_test_nested_phrase,
+    logical_test_three_terms,
+    logical_test_three_terms_two_nested,
+    logical_test_one_term_one_phrase,
+    logical_test_one_term_one_nested_single_term_phrase,
+    logical_test_long_phrase,
+    logical_test_pure_negation,
+    logical_test_double_negative,
+    logical_test_triple_negative,
+    logical_test_nested_negations,
+    logical_test_two_terms_one_required,
+    logical_test_intersection,
+    logical_test_three_way_intersection,
+    logical_test_union,
+    logical_test_three_way_union,
+    logical_test_a_or_plus_b,
+    logical_test_and_not,
+    logical_test_nested_or,
+    logical_test_and_nested_or,
+    logical_test_or_nested_or,
+    logical_test_and_not_nested_or,
+    logical_test_a_AND_b_AND_c_AND_d,
+    logical_test_a_AND_b_AND_c_OR_d,
+    logical_test_a_AND_b_OR_c_AND_d,
+    logical_test_a_AND_b_OR_c_OR_d,
+    logical_test_a_OR_b_AND_c_AND_d,
+    logical_test_a_OR_b_AND_c_OR_d,
+    logical_test_a_OR_b_OR_c_AND_d,
+    logical_test_a_OR_b_OR_c_OR_d,
+    logical_test_a_AND_b_AND_c_d,
+    logical_test_a_AND_b_OR_c_d,
+    logical_test_a_OR_b_AND_c_d,
+    logical_test_a_OR_b_OR_c_d,
+    logical_test_a_AND_b_c_AND_d,
+    logical_test_a_AND_b_c_OR_d,
+    logical_test_a_OR_b_c_AND_d,
+    logical_test_a_OR_b_c_OR_d,
+    logical_test_a_b_AND_c_AND_d,
+    logical_test_a_b_AND_c_OR_d,
+    logical_test_a_b_OR_c_AND_d,
+    logical_test_a_b_OR_c_OR_d,
+    logical_test_required_phrase_negated_term,
+    logical_test_required_term_optional_phrase,
+    logical_test_nested_nest,
+    logical_test_field_phrase,
+    logical_test_field_bool_group,
+    logical_test_field_multi_OR,
+    logical_test_field_multi_AND,
+    NULL
+};
+
+typedef TestQueryParser*
+(*LUCY_TestQPLogic_Prune_Test_t)();
+
+static LUCY_TestQPLogic_Prune_Test_t prune_test_funcs[] = {
+    prune_test_null_querystring,
+    prune_test_matchall,
+    prune_test_nomatch,
+    prune_test_optional_not,
+    prune_test_reqopt_optional_not,
+    prune_test_reqopt_required_not,
+    prune_test_not_and_not,
+    NULL
+};
+
+static Folder*
+S_create_index() {
+    Schema     *schema  = (Schema*)TestSchema_new(false);
+    RAMFolder  *folder  = RAMFolder_new(NULL);
+    Vector     *doc_set = TestUtils_doc_set();
+    Indexer    *indexer = Indexer_new(schema, (Obj*)folder, NULL, 0);
+
+    String *field = SSTR_WRAP_C("content");
+    for (size_t i = 0, max = Vec_Get_Size(doc_set); i < max; i++) {
+        Doc *doc = Doc_new(NULL, 0);
+        Doc_Store(doc, field, Vec_Fetch(doc_set, i));
+        Indexer_Add_Doc(indexer, doc, 1.0f);
+        DECREF(doc);
+    }
+
+    Indexer_Commit(indexer);
+
+    DECREF(doc_set);
+    DECREF(indexer);
+    DECREF(schema);
+
+    return (Folder*)folder;
+}
+
+void
+TestQPLogic_Run_IMP(TestQueryParserLogic *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 258);
+
+    uint32_t i;
+    Folder        *folder     = S_create_index();
+    IndexSearcher *searcher   = IxSearcher_new((Obj*)folder);
+    QueryParser   *or_parser  = QParser_new(IxSearcher_Get_Schema(searcher),
+                                            NULL, NULL, NULL);
+    String        *AND        = SSTR_WRAP_C("AND");
+    QueryParser   *and_parser = QParser_new(IxSearcher_Get_Schema(searcher),
+                                            NULL, AND, NULL);
+    QParser_Set_Heed_Colons(or_parser, true);
+    QParser_Set_Heed_Colons(and_parser, true);
+
+    // Run logical tests with default boolop of OR.
+    for (i = 0; logical_test_funcs[i] != NULL; i++) {
+        LUCY_TestQPLogic_Logical_Test_t test_func = logical_test_funcs[i];
+        TestQueryParser *test_case_obj = test_func(BOOLOP_OR);
+        TestQueryParserIVARS *test_case = TestQP_IVARS(test_case_obj);
+        Query *tree     = QParser_Tree(or_parser, test_case->query_string);
+        Query *parsed   = QParser_Parse(or_parser, test_case->query_string);
+        Hits  *hits     = IxSearcher_Hits(searcher, (Obj*)parsed, 0, 10, NULL);
+        char  *qstr     = Str_To_Utf8(test_case->query_string);
+
+        TEST_TRUE(runner, Query_Equals(tree, (Obj*)test_case->tree),
+                  "tree() OR   %s", qstr);
+        TEST_INT_EQ(runner, Hits_Total_Hits(hits), test_case->num_hits,
+                    "hits: OR   %s", qstr);
+        free(qstr);
+        DECREF(hits);
+        DECREF(parsed);
+        DECREF(tree);
+        DECREF(test_case_obj);
+    }
+
+    // Run logical tests with default boolop of AND.
+    for (i = 0; logical_test_funcs[i] != NULL; i++) {
+        LUCY_TestQPLogic_Logical_Test_t test_func = logical_test_funcs[i];
+        TestQueryParser *test_case_obj = test_func(BOOLOP_AND);
+        TestQueryParserIVARS *test_case = TestQP_IVARS(test_case_obj);
+        Query *tree     = QParser_Tree(and_parser, test_case->query_string);
+        Query *parsed   = QParser_Parse(and_parser, test_case->query_string);
+        Hits  *hits     = IxSearcher_Hits(searcher, (Obj*)parsed, 0, 10, NULL);
+        char  *qstr     = Str_To_Utf8(test_case->query_string);
+
+        TEST_TRUE(runner, Query_Equals(tree, (Obj*)test_case->tree),
+                  "tree() AND   %s", qstr);
+        TEST_INT_EQ(runner, Hits_Total_Hits(hits), test_case->num_hits,
+                    "hits: AND   %s", qstr);
+        free(qstr);
+        DECREF(hits);
+        DECREF(parsed);
+        DECREF(tree);
+        DECREF(test_case_obj);
+    }
+
+    // Run tests for QParser_Prune().
+    for (i = 0; prune_test_funcs[i] != NULL; i++) {
+        LUCY_TestQPLogic_Prune_Test_t test_func = prune_test_funcs[i];
+        TestQueryParser *test_case_obj = test_func();
+        TestQueryParserIVARS *test_case = TestQP_IVARS(test_case_obj);
+        String *qstring = test_case->tree
+                          ? Query_To_String(test_case->tree)
+                          : Str_new_from_trusted_utf8("(NULL)", 6);
+        char  *qstr = Str_To_Utf8(qstring);
+        Query *tree = test_case->tree;
+        Query *wanted = test_case->expanded;
+        Query *pruned   = QParser_Prune(or_parser, tree);
+        Query *expanded;
+        Hits  *hits;
+
+        TEST_TRUE(runner, Query_Equals(pruned, (Obj*)wanted),
+                  "prune()   %s", qstr);
+        expanded = QParser_Expand(or_parser, pruned);
+        hits = IxSearcher_Hits(searcher, (Obj*)expanded, 0, 10, NULL);
+        TEST_INT_EQ(runner, Hits_Total_Hits(hits), test_case->num_hits,
+                    "hits:    %s", qstr);
+
+        free(qstr);
+        DECREF(hits);
+        DECREF(expanded);
+        DECREF(pruned);
+        DECREF(qstring);
+        DECREF(test_case_obj);
+    }
+
+    DECREF(and_parser);
+    DECREF(or_parser);
+    DECREF(searcher);
+    DECREF(folder);
+}
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Search/TestQueryParserLogic.cfh
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Search/TestQueryParserLogic.cfh b/test/Lucy/Test/Search/TestQueryParserLogic.cfh
new file mode 100644
index 0000000..a0b1a75
--- /dev/null
+++ b/test/Lucy/Test/Search/TestQueryParserLogic.cfh
@@ -0,0 +1,32 @@
+/* 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 logical structure of Query objects output by QueryParser.
+ */
+
+class Lucy::Test::Search::TestQueryParserLogic nickname TestQPLogic
+    inherits Clownfish::TestHarness::TestBatch {
+
+    inert incremented TestQueryParserLogic*
+    new();
+
+    void
+    Run(TestQueryParserLogic *self, TestBatchRunner *runner);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Search/TestQueryParserSyntax.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Search/TestQueryParserSyntax.c b/test/Lucy/Test/Search/TestQueryParserSyntax.c
new file mode 100644
index 0000000..1cc4680
--- /dev/null
+++ b/test/Lucy/Test/Search/TestQueryParserSyntax.c
@@ -0,0 +1,452 @@
+/* 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_TESTQUERYPARSERSYNTAX
+#define C_TESTLUCY_TESTQUERYPARSER
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+#include <string.h>
+#include <stdlib.h>
+
+#include "Clownfish/Boolean.h"
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/Search/TestQueryParserSyntax.h"
+#include "Lucy/Test/Search/TestQueryParser.h"
+#include "Lucy/Test/TestUtils.h"
+#include "Lucy/Analysis/PolyAnalyzer.h"
+#include "Lucy/Analysis/RegexTokenizer.h"
+#include "Lucy/Analysis/SnowballStopFilter.h"
+#include "Lucy/Document/Doc.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/QueryParser.h"
+#include "Lucy/Search/TermQuery.h"
+#include "Lucy/Search/PhraseQuery.h"
+#include "Lucy/Search/LeafQuery.h"
+#include "Lucy/Search/ANDQuery.h"
+#include "Lucy/Search/NOTQuery.h"
+#include "Lucy/Search/ORQuery.h"
+#include "Lucy/Store/Folder.h"
+#include "Lucy/Store/RAMFolder.h"
+
+#define make_term_query   (Query*)TestUtils_make_term_query
+#define make_phrase_query (Query*)TestUtils_make_phrase_query
+#define make_leaf_query   (Query*)TestUtils_make_leaf_query
+#define make_not_query    (Query*)TestUtils_make_not_query
+#define make_poly_query   (Query*)TestUtils_make_poly_query
+
+TestQueryParserSyntax*
+TestQPSyntax_new() {
+    return (TestQueryParserSyntax*)Class_Make_Obj(TESTQUERYPARSERSYNTAX);
+}
+
+static Folder*
+build_index() {
+    // Plain type.
+    String         *pattern   = Str_newf("\\S+");
+    RegexTokenizer *tokenizer = RegexTokenizer_new(pattern);
+    FullTextType   *plain     = FullTextType_new((Analyzer*)tokenizer);
+
+    // Fancy type.
+
+    String         *word_pattern   = Str_newf("\\w+");
+    RegexTokenizer *word_tokenizer = RegexTokenizer_new(word_pattern);
+
+    Hash *stop_list = Hash_new(0);
+    Hash_Store_Utf8(stop_list, "x", 1, (Obj*)CFISH_TRUE);
+    SnowballStopFilter *stop_filter = SnowStop_new(NULL, stop_list);
+
+    Vector *analyzers = Vec_new(0);
+    Vec_Push(analyzers, (Obj*)word_tokenizer);
+    Vec_Push(analyzers, (Obj*)stop_filter);
+    PolyAnalyzer *fancy_analyzer = PolyAnalyzer_new(NULL, analyzers);
+
+    FullTextType *fancy = FullTextType_new((Analyzer*)fancy_analyzer);
+
+    // Schema.
+    Schema *schema   = Schema_new();
+    String *plain_str = Str_newf("plain");
+    String *fancy_str = Str_newf("fancy");
+    Schema_Spec_Field(schema, plain_str, (FieldType*)plain);
+    Schema_Spec_Field(schema, fancy_str, (FieldType*)fancy);
+
+    // Indexer.
+    RAMFolder *folder  = RAMFolder_new(NULL);
+    Indexer   *indexer = Indexer_new(schema, (Obj*)folder, NULL, 0);
+
+    // Index documents.
+    Vector *doc_set = TestUtils_doc_set();
+    for (size_t i = 0; i < Vec_Get_Size(doc_set); ++i) {
+        String *content_string = (String*)Vec_Fetch(doc_set, i);
+        Doc *doc = Doc_new(NULL, 0);
+        Doc_Store(doc, plain_str, (Obj*)content_string);
+        Doc_Store(doc, fancy_str, (Obj*)content_string);
+        Indexer_Add_Doc(indexer, doc, 1.0);
+        DECREF(doc);
+    }
+    Indexer_Commit(indexer);
+
+    // Clean up.
+    DECREF(doc_set);
+    DECREF(indexer);
+    DECREF(fancy_str);
+    DECREF(plain_str);
+    DECREF(schema);
+    DECREF(fancy);
+    DECREF(fancy_analyzer);
+    DECREF(analyzers);
+    DECREF(stop_list);
+    DECREF(word_pattern);
+    DECREF(plain);
+    DECREF(tokenizer);
+    DECREF(pattern);
+
+    return (Folder*)folder;
+}
+
+static TestQueryParser*
+leaf_test_simple_term() {
+    Query   *tree     = make_leaf_query(NULL, "a");
+    Query   *plain_q  = make_term_query("plain", "a");
+    Query   *fancy_q  = make_term_query("fancy", "a");
+    Query   *expanded = make_poly_query(BOOLOP_OR, fancy_q, plain_q, NULL);
+    return TestQP_new("a", tree, expanded, 4);
+}
+
+static TestQueryParser*
+leaf_test_simple_phrase() {
+    Query   *tree     = make_leaf_query(NULL, "\"a b\"");
+    Query   *plain_q  = make_phrase_query("plain", "a", "b", NULL);
+    Query   *fancy_q  = make_phrase_query("fancy", "a", "b", NULL);
+    Query   *expanded = make_poly_query(BOOLOP_OR, fancy_q, plain_q, NULL);
+    return TestQP_new("\"a b\"", tree, expanded, 3);
+}
+
+static TestQueryParser*
+leaf_test_unclosed_quote() {
+    Query   *tree     = make_leaf_query(NULL, "\"a b");
+    Query   *plain_q  = make_phrase_query("plain", "a", "b", NULL);
+    Query   *fancy_q  = make_phrase_query("fancy", "a", "b", NULL);
+    Query   *expanded = make_poly_query(BOOLOP_OR, fancy_q, plain_q, NULL);
+    return TestQP_new("\"a b", tree, expanded, 3);
+}
+
+static TestQueryParser*
+leaf_test_escaped_quotes_inside() {
+    Query   *tree     = make_leaf_query(NULL, "\"\\\"a b\\\"\"");
+    Query   *plain_q  = make_phrase_query("plain", "\"a", "b\"", NULL);
+    Query   *fancy_q  = make_phrase_query("fancy", "a", "b", NULL);
+    Query   *expanded = make_poly_query(BOOLOP_OR, fancy_q, plain_q, NULL);
+    return TestQP_new("\"\\\"a b\\\"\"", tree, expanded, 3);
+}
+
+static TestQueryParser*
+leaf_test_escaped_quotes_outside() {
+    Query   *tree = make_leaf_query(NULL, "\\\"a");
+    Query   *plain_q  = make_term_query("plain", "\"a");
+    Query   *fancy_q  = make_term_query("fancy", "a");
+    Query   *expanded = make_poly_query(BOOLOP_OR, fancy_q, plain_q, NULL);
+    return TestQP_new("\\\"a", tree, expanded, 4);
+}
+
+static TestQueryParser*
+leaf_test_single_term_phrase() {
+    Query   *tree     = make_leaf_query(NULL, "\"a\"");
+    Query   *plain_q  = make_phrase_query("plain", "a", NULL);
+    Query   *fancy_q  = make_phrase_query("fancy", "a", NULL);
+    Query   *expanded = make_poly_query(BOOLOP_OR, fancy_q, plain_q, NULL);
+    return TestQP_new("\"a\"", tree, expanded, 4);
+}
+
+static TestQueryParser*
+leaf_test_longer_phrase() {
+    Query   *tree     = make_leaf_query(NULL, "\"a b c\"");
+    Query   *plain_q  = make_phrase_query("plain", "a", "b", "c", NULL);
+    Query   *fancy_q  = make_phrase_query("fancy", "a", "b", "c", NULL);
+    Query   *expanded = make_poly_query(BOOLOP_OR, fancy_q, plain_q, NULL);
+    return TestQP_new("\"a b c\"", tree, expanded, 2);
+}
+
+static TestQueryParser*
+leaf_test_empty_phrase() {
+    Query   *tree     = make_leaf_query(NULL, "\"\"");
+    Query   *plain_q  = make_phrase_query("plain", NULL);
+    Query   *fancy_q  = make_phrase_query("fancy", NULL);
+    Query   *expanded = make_poly_query(BOOLOP_OR, fancy_q, plain_q, NULL);
+    return TestQP_new("\"\"", tree, expanded, 0);
+}
+
+static TestQueryParser*
+leaf_test_phrase_with_stopwords() {
+    Query   *tree     = make_leaf_query(NULL, "\"x a\"");
+    Query   *plain_q  = make_phrase_query("plain", "x", "a", NULL);
+    Query   *fancy_q  = make_phrase_query("fancy", "a", NULL);
+    Query   *expanded = make_poly_query(BOOLOP_OR, fancy_q, plain_q, NULL);
+    return TestQP_new("\"x a\"", tree, expanded, 4);
+}
+
+static TestQueryParser*
+leaf_test_different_tokenization() {
+    Query   *tree     = make_leaf_query(NULL, "a.b");
+    Query   *plain_q  = make_term_query("plain", "a.b");
+    Query   *fancy_q  = make_phrase_query("fancy", "a", "b", NULL);
+    Query   *expanded = make_poly_query(BOOLOP_OR, fancy_q, plain_q, NULL);
+    return TestQP_new("a.b", tree, expanded, 3);
+}
+
+static TestQueryParser*
+leaf_test_http() {
+    char address[] = "http://www.foo.com/bar.html";
+    Query *tree = make_leaf_query(NULL, address);
+    Query *plain_q = make_term_query("plain", address);
+    Query *fancy_q = make_phrase_query("fancy", "http", "www", "foo",
+                                       "com", "bar", "html", NULL);
+    Query   *expanded = make_poly_query(BOOLOP_OR, fancy_q, plain_q, NULL);
+    return TestQP_new(address, tree, expanded, 0);
+}
+
+static TestQueryParser*
+leaf_test_field() {
+    Query *tree     = make_leaf_query("plain", "b");
+    Query *expanded = make_term_query("plain", "b");
+    return TestQP_new("plain:b", tree, expanded, 3);
+}
+
+static TestQueryParser*
+leaf_test_unrecognized_field() {
+    Query *tree     = make_leaf_query("bogusfield", "b");
+    Query *expanded = make_term_query("bogusfield", "b");
+    return TestQP_new("bogusfield:b", tree, expanded, 0);
+}
+
+static TestQueryParser*
+leaf_test_unescape_colons() {
+    Query *tree     = make_leaf_query("plain", "a\\:b");
+    Query *expanded = make_term_query("plain", "a:b");
+    return TestQP_new("plain:a\\:b", tree, expanded, 0);
+}
+
+static TestQueryParser*
+syntax_test_minus_plus() {
+    Query *leaf = make_leaf_query(NULL, "a");
+    Query *tree = make_not_query(leaf);
+    return TestQP_new("-+a", tree, NULL, 0);
+}
+
+static TestQueryParser*
+syntax_test_plus_minus() {
+    // Not a perfect result, but then it's not a good query string.
+    Query *leaf = make_leaf_query(NULL, "a");
+    Query *tree = make_not_query(leaf);
+    return TestQP_new("+-a", tree, NULL, 0);
+}
+
+static TestQueryParser*
+syntax_test_minus_minus() {
+    // Not a perfect result, but then it's not a good query string.
+    Query *tree = make_leaf_query(NULL, "a");
+    return TestQP_new("--a", tree, NULL, 4);
+}
+
+static TestQueryParser*
+syntax_test_not_minus() {
+    Query *tree = make_leaf_query(NULL, "a");
+    return TestQP_new("NOT -a", tree, NULL, 4);
+}
+
+static TestQueryParser*
+syntax_test_not_plus() {
+    // Not a perfect result, but then it's not a good query string.
+    Query *leaf = make_leaf_query(NULL, "a");
+    Query *tree = make_not_query(leaf);
+    return TestQP_new("NOT +a", tree, NULL, 0);
+}
+
+static TestQueryParser*
+syntax_test_padded_plus() {
+    Query *plus = make_leaf_query(NULL, "+");
+    Query *a = make_leaf_query(NULL, "a");
+    Query *tree = make_poly_query(BOOLOP_OR, plus, a, NULL);
+    return TestQP_new("+ a", tree, NULL, 4);
+}
+
+static TestQueryParser*
+syntax_test_padded_minus() {
+    Query *minus = make_leaf_query(NULL, "-");
+    Query *a = make_leaf_query(NULL, "a");
+    Query *tree = make_poly_query(BOOLOP_OR, minus, a, NULL);
+    return TestQP_new("- a", tree, NULL, 4);
+}
+
+static TestQueryParser*
+syntax_test_unclosed_parens() {
+    // Not a perfect result, but then it's not a good query string.
+    Query *inner = make_poly_query(BOOLOP_OR, NULL);
+    Query *tree = make_poly_query(BOOLOP_OR, inner, NULL);
+    return TestQP_new("((", tree, NULL, 0);
+}
+
+static TestQueryParser*
+syntax_test_unmatched_parens() {
+    Query *tree = make_leaf_query(NULL, "a");
+    return TestQP_new(")a)", tree, NULL, 4);
+}
+
+static TestQueryParser*
+syntax_test_escaped_quotes_outside() {
+    Query *tree = make_leaf_query(NULL, "\\\"a\\\"");
+    return TestQP_new("\\\"a\\\"", tree, NULL, 4);
+}
+
+static TestQueryParser*
+syntax_test_escaped_quotes_inside() {
+    Query *tree = make_leaf_query(NULL, "\"\\\"a\\\"\"");
+    return TestQP_new("\"\\\"a\\\"\"", tree, NULL, 4);
+}
+
+static TestQueryParser*
+syntax_test_identifier_field_name() {
+    // Field names must be identifiers, i.e. they cannot start with a number.
+    Query *tree = make_leaf_query(NULL, "10:30");
+    return TestQP_new("10:30", tree, NULL, 0);
+}
+
+static TestQueryParser*
+syntax_test_double_colon() {
+    Query *tree = make_leaf_query(NULL, "PHP::Interpreter");
+    return TestQP_new("PHP::Interpreter", tree, NULL, 0);
+}
+
+/***************************************************************************/
+
+typedef TestQueryParser*
+(*LUCY_TestQPSyntax_Test_t)();
+
+static LUCY_TestQPSyntax_Test_t leaf_test_funcs[] = {
+    leaf_test_simple_term,
+    leaf_test_simple_phrase,
+    leaf_test_unclosed_quote,
+    leaf_test_escaped_quotes_inside,
+    leaf_test_escaped_quotes_outside,
+    leaf_test_single_term_phrase,
+    leaf_test_longer_phrase,
+    leaf_test_empty_phrase,
+    leaf_test_different_tokenization,
+    leaf_test_phrase_with_stopwords,
+    leaf_test_http,
+    leaf_test_field,
+    leaf_test_unrecognized_field,
+    leaf_test_unescape_colons,
+    NULL
+};
+
+static LUCY_TestQPSyntax_Test_t syntax_test_funcs[] = {
+    syntax_test_minus_plus,
+    syntax_test_plus_minus,
+    syntax_test_minus_minus,
+    syntax_test_not_minus,
+    syntax_test_not_plus,
+    syntax_test_padded_plus,
+    syntax_test_padded_minus,
+    syntax_test_unclosed_parens,
+    syntax_test_unmatched_parens,
+    syntax_test_escaped_quotes_outside,
+    syntax_test_escaped_quotes_inside,
+    syntax_test_identifier_field_name,
+    syntax_test_double_colon,
+    NULL
+};
+
+static void
+test_query_parser_syntax(TestBatchRunner *runner) {
+    if (!RegexTokenizer_is_available()) {
+        for (uint32_t i = 0; leaf_test_funcs[i] != NULL; i++) {
+            SKIP(runner, 3, "RegexTokenizer not available");
+        }
+
+        for (uint32_t i = 0; syntax_test_funcs[i] != NULL; i++) {
+            SKIP(runner, 2, "RegexTokenizer not available");
+        }
+
+        return;
+    }
+
+    Folder        *index    = build_index();
+    IndexSearcher *searcher = IxSearcher_new((Obj*)index);
+    QueryParser   *qparser  = QParser_new(IxSearcher_Get_Schema(searcher),
+                                          NULL, NULL, NULL);
+    QParser_Set_Heed_Colons(qparser, true);
+
+    for (uint32_t i = 0; leaf_test_funcs[i] != NULL; i++) {
+        LUCY_TestQPSyntax_Test_t test_func = leaf_test_funcs[i];
+        TestQueryParser *test_case = test_func();
+        TestQueryParserIVARS *ivars = TestQP_IVARS(test_case);
+        Query *tree     = QParser_Tree(qparser, ivars->query_string);
+        Query *expanded = QParser_Expand_Leaf(qparser, ivars->tree);
+        Query *parsed   = QParser_Parse(qparser, ivars->query_string);
+        Hits  *hits     = IxSearcher_Hits(searcher, (Obj*)parsed, 0, 10, NULL);
+        char  *qstr     = Str_To_Utf8(ivars->query_string);
+
+        TEST_TRUE(runner, Query_Equals(tree, (Obj*)ivars->tree),
+                  "tree()    %s", qstr);
+        TEST_TRUE(runner, Query_Equals(expanded, (Obj*)ivars->expanded),
+                  "expand_leaf()    %s", qstr);
+        TEST_INT_EQ(runner, Hits_Total_Hits(hits), ivars->num_hits,
+                    "hits:    %s", qstr);
+        free(qstr);
+        DECREF(hits);
+        DECREF(parsed);
+        DECREF(expanded);
+        DECREF(tree);
+        DECREF(test_case);
+    }
+
+    for (uint32_t i = 0; syntax_test_funcs[i] != NULL; i++) {
+        LUCY_TestQPSyntax_Test_t test_func = syntax_test_funcs[i];
+        TestQueryParser *test_case = test_func();
+        TestQueryParserIVARS *ivars = TestQP_IVARS(test_case);
+        Query *tree   = QParser_Tree(qparser, ivars->query_string);
+        Query *parsed = QParser_Parse(qparser, ivars->query_string);
+        Hits  *hits   = IxSearcher_Hits(searcher, (Obj*)parsed, 0, 10, NULL);
+        char  *qstr   = Str_To_Utf8(ivars->query_string);
+
+        TEST_TRUE(runner, Query_Equals(tree, (Obj*)ivars->tree),
+                  "tree()    %s", qstr);
+        TEST_INT_EQ(runner, Hits_Total_Hits(hits), ivars->num_hits,
+                    "hits:    %s", qstr);
+        free(qstr);
+        DECREF(hits);
+        DECREF(parsed);
+        DECREF(tree);
+        DECREF(test_case);
+    }
+
+    DECREF(searcher);
+    DECREF(qparser);
+    DECREF(index);
+}
+
+void
+TestQPSyntax_Run_IMP(TestQueryParserSyntax *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 68);
+    test_query_parser_syntax(runner);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Search/TestQueryParserSyntax.cfh
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Search/TestQueryParserSyntax.cfh b/test/Lucy/Test/Search/TestQueryParserSyntax.cfh
new file mode 100644
index 0000000..dee071c
--- /dev/null
+++ b/test/Lucy/Test/Search/TestQueryParserSyntax.cfh
@@ -0,0 +1,32 @@
+/* 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 logical structure of Query objects output by QueryParser.
+ */
+
+class Lucy::Test::Search::TestQueryParserSyntax nickname TestQPSyntax
+    inherits Clownfish::TestHarness::TestBatch {
+
+    inert incremented TestQueryParserSyntax*
+    new();
+
+    void
+    Run(TestQueryParserSyntax *self, TestBatchRunner *runner);
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Search/TestRangeQuery.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Search/TestRangeQuery.c b/test/Lucy/Test/Search/TestRangeQuery.c
new file mode 100644
index 0000000..ab8a9ca
--- /dev/null
+++ b/test/Lucy/Test/Search/TestRangeQuery.c
@@ -0,0 +1,75 @@
+/* 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_TESTRANGEQUERY
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+#include <math.h>
+
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/TestUtils.h"
+#include "Lucy/Test/Search/TestRangeQuery.h"
+#include "Lucy/Search/RangeQuery.h"
+
+TestRangeQuery*
+TestRangeQuery_new() {
+    return (TestRangeQuery*)Class_Make_Obj(TESTRANGEQUERY);
+}
+
+static void
+test_Dump_Load_and_Equals(TestBatchRunner *runner) {
+    RangeQuery *query 
+        = TestUtils_make_range_query("content", "foo", "phooey", true, true);
+    RangeQuery *lo_term_differs 
+        = TestUtils_make_range_query("content", "goo", "phooey", true, true);
+    RangeQuery *hi_term_differs 
+        = TestUtils_make_range_query("content", "foo", "gooey", true, true);
+    RangeQuery *include_lower_differs 
+        = TestUtils_make_range_query("content", "foo", "phooey", false, true);
+    RangeQuery *include_upper_differs 
+        = TestUtils_make_range_query("content", "foo", "phooey", true, false);
+    Obj        *dump  = (Obj*)RangeQuery_Dump(query);
+    RangeQuery *clone = (RangeQuery*)RangeQuery_Load(lo_term_differs, dump);
+
+    TEST_FALSE(runner, RangeQuery_Equals(query, (Obj*)lo_term_differs),
+               "Equals() false with different lower term");
+    TEST_FALSE(runner, RangeQuery_Equals(query, (Obj*)hi_term_differs),
+               "Equals() false with different upper term");
+    TEST_FALSE(runner, RangeQuery_Equals(query, (Obj*)include_lower_differs),
+               "Equals() false with different include_lower");
+    TEST_FALSE(runner, RangeQuery_Equals(query, (Obj*)include_upper_differs),
+               "Equals() false with different include_upper");
+    TEST_TRUE(runner, RangeQuery_Equals(query, (Obj*)clone),
+              "Dump => Load round trip");
+
+    DECREF(query);
+    DECREF(lo_term_differs);
+    DECREF(hi_term_differs);
+    DECREF(include_lower_differs);
+    DECREF(include_upper_differs);
+    DECREF(dump);
+    DECREF(clone);
+}
+
+
+void
+TestRangeQuery_Run_IMP(TestRangeQuery *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 5);
+    test_Dump_Load_and_Equals(runner);
+}
+
+

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

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Search/TestReqOptQuery.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Search/TestReqOptQuery.c b/test/Lucy/Test/Search/TestReqOptQuery.c
new file mode 100644
index 0000000..404e987
--- /dev/null
+++ b/test/Lucy/Test/Search/TestReqOptQuery.c
@@ -0,0 +1,73 @@
+/* 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_TESTREQOPTQUERY
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+#include <math.h>
+
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/TestUtils.h"
+#include "Lucy/Test/Search/TestReqOptQuery.h"
+#include "Lucy/Search/RequiredOptionalQuery.h"
+#include "Lucy/Search/LeafQuery.h"
+#include "Lucy/Util/Freezer.h"
+
+TestReqOptQuery*
+TestReqOptQuery_new() {
+    return (TestReqOptQuery*)Class_Make_Obj(TESTREQOPTQUERY);
+}
+
+static void
+test_Dump_Load_and_Equals(TestBatchRunner *runner) {
+    Query *a_leaf  = (Query*)TestUtils_make_leaf_query(NULL, "a");
+    Query *b_leaf  = (Query*)TestUtils_make_leaf_query(NULL, "b");
+    Query *c_leaf  = (Query*)TestUtils_make_leaf_query(NULL, "c");
+    RequiredOptionalQuery *query = ReqOptQuery_new(a_leaf, b_leaf);
+    RequiredOptionalQuery *kids_differ = ReqOptQuery_new(a_leaf, c_leaf);
+    RequiredOptionalQuery *boost_differs = ReqOptQuery_new(a_leaf, b_leaf);
+    Obj *dump = (Obj*)ReqOptQuery_Dump(query);
+    RequiredOptionalQuery *clone
+        = (RequiredOptionalQuery*)Freezer_load(dump);
+
+    TEST_FALSE(runner, ReqOptQuery_Equals(query, (Obj*)kids_differ),
+               "Different kids spoil Equals");
+    TEST_TRUE(runner, ReqOptQuery_Equals(query, (Obj*)boost_differs),
+              "Equals with identical boosts");
+    ReqOptQuery_Set_Boost(boost_differs, 1.5);
+    TEST_FALSE(runner, ReqOptQuery_Equals(query, (Obj*)boost_differs),
+               "Different boost spoils Equals");
+    TEST_TRUE(runner, ReqOptQuery_Equals(query, (Obj*)clone),
+              "Dump => Load round trip");
+
+    DECREF(a_leaf);
+    DECREF(b_leaf);
+    DECREF(c_leaf);
+    DECREF(query);
+    DECREF(kids_differ);
+    DECREF(boost_differs);
+    DECREF(dump);
+    DECREF(clone);
+}
+
+void
+TestReqOptQuery_Run_IMP(TestReqOptQuery *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 4);
+    test_Dump_Load_and_Equals(runner);
+}
+
+

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

http://git-wip-us.apache.org/repos/asf/lucy/blob/572d3564/test/Lucy/Test/Search/TestSeriesMatcher.c
----------------------------------------------------------------------
diff --git a/test/Lucy/Test/Search/TestSeriesMatcher.c b/test/Lucy/Test/Search/TestSeriesMatcher.c
new file mode 100644
index 0000000..bdd2e78
--- /dev/null
+++ b/test/Lucy/Test/Search/TestSeriesMatcher.c
@@ -0,0 +1,136 @@
+/* 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_TESTSERIESMATCHER
+#define TESTLUCY_USE_SHORT_NAMES
+#include "Lucy/Util/ToolSet.h"
+
+#include "Clownfish/TestHarness/TestBatchRunner.h"
+#include "Lucy/Test.h"
+#include "Lucy/Test/Search/TestSeriesMatcher.h"
+#include "Lucy/Search/BitVecMatcher.h"
+#include "Lucy/Search/SeriesMatcher.h"
+
+TestSeriesMatcher*
+TestSeriesMatcher_new() {
+    return (TestSeriesMatcher*)Class_Make_Obj(TESTSERIESMATCHER);
+}
+
+static SeriesMatcher*
+S_make_series_matcher(I32Array *doc_ids, I32Array *offsets, int32_t doc_max) {
+    size_t   num_doc_ids  = I32Arr_Get_Size(doc_ids);
+    size_t   num_matchers = I32Arr_Get_Size(offsets);
+    Vector  *matchers     = Vec_new(num_matchers);
+    size_t   tick         = 0;
+
+    // Divvy up doc_ids by segment into BitVectors.
+    for (size_t i = 0; i < num_matchers; i++) {
+        int32_t offset = I32Arr_Get(offsets, i);
+        int32_t max    = i == num_matchers - 1
+                         ? doc_max + 1
+                         : I32Arr_Get(offsets, i + 1);
+        BitVector *bit_vec = BitVec_new((size_t)(max - offset));
+        while (tick < num_doc_ids) {
+            int32_t doc_id = I32Arr_Get(doc_ids, tick);
+            if (doc_id > max) { break; }
+            else               { tick++; }
+            BitVec_Set(bit_vec, (size_t)(doc_id - offset));
+        }
+        Vec_Push(matchers, (Obj*)BitVecMatcher_new(bit_vec));
+        DECREF(bit_vec);
+    }
+
+    SeriesMatcher *series_matcher = SeriesMatcher_new(matchers, offsets);
+    DECREF(matchers);
+    return series_matcher;
+}
+
+static I32Array*
+S_generate_match_list(int32_t first, int32_t max, int32_t doc_inc) {
+    int32_t  count     = (max - first + doc_inc - 1) / doc_inc;
+    int32_t *doc_ids   = (int32_t*)MALLOCATE((size_t)count * sizeof(int32_t));
+    int32_t  doc_id    = first;
+    int32_t  i         = 0;
+
+    for (; doc_id < max; doc_id += doc_inc, i++) {
+        doc_ids[i] = doc_id;
+    }
+    if (i != count) { THROW(ERR, "Screwed up somehow: %i32 %i32", i, count); }
+
+    return I32Arr_new_steal(doc_ids, (size_t)count);
+}
+
+static void
+S_do_test_matrix(TestBatchRunner *runner, int32_t doc_max, int32_t first_doc_id,
+                 int32_t doc_inc, int32_t offset_inc) {
+    I32Array *doc_ids
+        = S_generate_match_list(first_doc_id, doc_max, doc_inc);
+    I32Array *offsets
+        = S_generate_match_list(0, doc_max, offset_inc);
+    SeriesMatcher *series_matcher
+        = S_make_series_matcher(doc_ids, offsets, doc_max);
+    size_t num_in_agreement = 0;
+    int32_t got;
+
+    while (0 != (got = SeriesMatcher_Next(series_matcher))) {
+        if (got != I32Arr_Get(doc_ids, num_in_agreement)) { break; }
+        num_in_agreement++;
+    }
+    TEST_UINT_EQ(runner, num_in_agreement, I32Arr_Get_Size(doc_ids),
+                 "doc_max=%d first_doc_id=%d doc_inc=%d offset_inc=%d",
+                 doc_max, first_doc_id, doc_inc, offset_inc);
+
+    DECREF(doc_ids);
+    DECREF(offsets);
+    DECREF(series_matcher);
+}
+
+static void
+test_matrix(TestBatchRunner *runner) {
+    int32_t doc_max_nums[]     = { 10, 100, 1000, 0 };
+    int32_t first_doc_ids[]    = { 1, 2, 10, 0 };
+    int32_t doc_inc_nums[]     = { 20, 13, 9, 4, 2, 0 };
+    int32_t offset_inc_nums[]  = { 7, 29, 71, 0 };
+    int32_t a, b, c, d;
+
+    for (a = 0; doc_max_nums[a] != 0; a++) {
+        for (b = 0; first_doc_ids[b] != 0; b++) {
+            for (c = 0; doc_inc_nums[c] != 0; c++) {
+                for (d = 0; offset_inc_nums[d] != 0; d++) {
+                    int32_t doc_max        = doc_max_nums[a];
+                    int32_t first_doc_id   = first_doc_ids[b];
+                    int32_t doc_inc        = doc_inc_nums[c];
+                    int32_t offset_inc     = offset_inc_nums[d];
+                    if (first_doc_id > doc_max) {
+                        continue;
+                    }
+                    else {
+                        S_do_test_matrix(runner, doc_max, first_doc_id,
+                                         doc_inc, offset_inc);
+                    }
+                }
+            }
+        }
+    }
+}
+
+void
+TestSeriesMatcher_Run_IMP(TestSeriesMatcher *self, TestBatchRunner *runner) {
+    TestBatchRunner_Plan(runner, (TestBatch*)self, 135);
+    test_matrix(runner);
+}
+
+

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