Index: src/test/org/apache/lucene/search/TestExplanations.java
===================================================================
--- src/test/org/apache/lucene/search/TestExplanations.java	(revision 415854)
+++ src/test/org/apache/lucene/search/TestExplanations.java	(working copy)
@@ -68,8 +68,7 @@
       writer.addDocument(doc);
     }
     writer.close();
-    searcher = new CheckHits.ExplanationAssertingSearcher(directory);
-    //searcher = new IndexSearcher(directory);
+    searcher = new IndexSearcher(directory);
   }
 
   protected String[] docFields = {
@@ -87,7 +86,9 @@
     qtest(makeQuery(queryText), expDocNrs);
   }
   public void qtest(Query q, int[] expDocNrs) throws Exception {
-    CheckHits.checkHits(q, FIELD, searcher, expDocNrs);
+    // check that the expDocNrs first, then check the explanations
+    CheckHits.checkHitCollector(q, FIELD, searcher, expDocNrs);
+    CheckHits.checkExplanations(q, FIELD, searcher);
   }
 
   /**
@@ -191,38 +192,37 @@
 
   /**
    * MACRO: Wraps a Query in a BooleanQuery so that it is optional, along
-   * with a second clause which will never match anything
+   * with a second prohibited clause which will never match anything
    */
   public Query optB(String q) throws Exception {
     return optB(makeQuery(q));
   }
   /**
    * MACRO: Wraps a Query in a BooleanQuery so that it is optional, along
-   * with a second clause which will never match anything
+   * with a second prohibited clause which will never match anything
    */
   public Query optB(Query q) throws Exception {
-    return buildWrappingB(q, BooleanClause.Occur.SHOULD);
+    BooleanQuery bq = new BooleanQuery(true);
+    bq.add(q, BooleanClause.Occur.SHOULD);
+    bq.add(new TermQuery(new Term("NEVER","MATCH")), BooleanClause.Occur.MUST_NOT);
+    return bq;
   }
   
   /**
    * MACRO: Wraps a Query in a BooleanQuery so that it is required, along
-   * with a second clause which will never match anything
+   * with a second optional clause which will match everything
    */
   public Query reqB(String q) throws Exception {
     return reqB(makeQuery(q));
   }
   /**
    * MACRO: Wraps a Query in a BooleanQuery so that it is required, along
-   * with a second clause which will never match anything
+   * with a second optional clause which will match everything
    */
   public Query reqB(Query q) throws Exception {
-    return buildWrappingB(q, BooleanClause.Occur.MUST);
-  }
-
-  private Query buildWrappingB(Query q, BooleanClause.Occur o) {
     BooleanQuery bq = new BooleanQuery(true);
-    bq.add(q, o);
-    bq.add(new TermQuery(new Term("NEVER","MATCH")), BooleanClause.Occur.MUST_NOT);
+    bq.add(q, BooleanClause.Occur.MUST);
+    bq.add(new TermQuery(new Term(FIELD,"w1")), BooleanClause.Occur.SHOULD);
     return bq;
   }
   
Index: src/test/org/apache/lucene/search/TestComplexExplanations.java
===================================================================
--- src/test/org/apache/lucene/search/TestComplexExplanations.java	(revision 415854)
+++ src/test/org/apache/lucene/search/TestComplexExplanations.java	(working copy)
@@ -17,6 +17,7 @@
  */
 
 import org.apache.lucene.search.BooleanClause.Occur;
+import org.apache.lucene.search.spans.*;
 import org.apache.lucene.store.RAMDirectory;
 
 import org.apache.lucene.index.IndexWriter;
@@ -43,6 +44,20 @@
  */
 public class TestComplexExplanations extends TestExplanations {
 
+  /**
+   * Override the Similarity used in our searcher with one that plays
+   * nice with boosts of 0.0
+   */
+  public void setUp() throws Exception {
+    super.setUp();
+    searcher.setSimilarity(new DefaultSimilarity() {
+        public float queryNorm(float sumOfSquaredWeights) {
+          return 1.0f; // / (float) Math.sqrt(1.0f + sumOfSquaredWeights);
+        }
+      });
+  }
+
+  
   public void test1() throws Exception {
     
     BooleanQuery q = new BooleanQuery();
@@ -92,6 +107,174 @@
     
   }
 
+  /** may also require a fix for LUCENE-569 */
+  public void test2() throws Exception {
+    
+    BooleanQuery q = new BooleanQuery();
+    
+    q.add(qp.parse("\"w1 w2\"~1"), Occur.MUST);
+    q.add(snear(st("w2"),
+                sor("w5","zz"),
+                4, true),
+          Occur.SHOULD);
+    q.add(snear(sf("w3",2), st("w2"), st("w3"), 5, true),
+          Occur.SHOULD);
+    
+    Query t = new FilteredQuery(qp.parse("xx"),
+                                new ItemizedFilter(new int[] {1,3}));
+    t.setBoost(1000);
+    q.add(t, Occur.SHOULD);
+    
+    t = new ConstantScoreQuery(new ItemizedFilter(new int[] {0,2}));
+    t.setBoost(-20.0f);
+    q.add(t, Occur.SHOULD);
+    
+    DisjunctionMaxQuery dm = new DisjunctionMaxQuery(0.2f);
+    dm.add(snear(st("w2"),
+                 sor("w5","zz"),
+                 4, true));
+    dm.add(qp.parse("QQ"));
+    dm.add(qp.parse("xx yy -zz"));
+    dm.add(qp.parse("-xx -w1"));
+
+    DisjunctionMaxQuery dm2 = new DisjunctionMaxQuery(0.5f);
+    dm2.add(qp.parse("w1"));
+    dm2.add(qp.parse("w2"));
+    dm2.add(qp.parse("w3"));
+    dm.add(dm2);
+
+    q.add(dm, Occur.SHOULD);
+
+    BooleanQuery b = new BooleanQuery();
+    b.setMinimumNumberShouldMatch(2);
+    b.add(snear("w1","w2",1,true), Occur.SHOULD);
+    b.add(snear("w2","w3",1,true), Occur.SHOULD);
+    b.add(snear("w1","w3",3,true), Occur.SHOULD);
+    b.setBoost(0.0f);
+    
+    q.add(b, Occur.SHOULD);
+    
+    qtest(q, new int[] { 1 });
+    
+  }
+  
   // :TODO: we really need more crazy complex cases.
 
+
+  // //////////////////////////////////////////////////////////////////
+
+  // The rest of these aren't that complex, but they are <i>somewhat</i>
+  // complex, and they expose weakness in dealing with queries that match
+  // with scores of 0 wrapped in other queries
+
+  public void testT3() throws Exception {
+    bqtest("w1^0.0", new int[] { 0,1,2,3 });
+  }
+
+  public void testMA3() throws Exception {
+    Query q=new MatchAllDocsQuery();
+    q.setBoost(0);
+    bqtest(q, new int[] { 0,1,2,3 });
+  }
+  
+  public void testFQ5() throws Exception {
+    bqtest(new FilteredQuery(qp.parse("xx^0"),
+                             new ItemizedFilter(new int[] {1,3})),
+           new int[] {3});
+  }
+  
+  public void testCSQ4() throws Exception {
+    Query q = new ConstantScoreQuery(new ItemizedFilter(new int[] {3}));
+    q.setBoost(0);
+    bqtest(q, new int[] {3});
+  }
+  
+  public void testDMQ10() throws Exception {
+    DisjunctionMaxQuery q = new DisjunctionMaxQuery(0.5f);
+    q.add(qp.parse("yy w5^100"));
+    q.add(qp.parse("xx^0"));
+    q.setBoost(0.0f);
+    bqtest(q, new int[] { 0,2,3 });
+  }
+  
+  public void testMPQ7() throws Exception {
+    MultiPhraseQuery q = new MultiPhraseQuery();
+    q.add(ta(new String[] {"w1"}));
+    q.add(ta(new String[] {"w2"}));
+    q.setSlop(1);
+    q.setBoost(0.0f);
+    bqtest(q, new int[] { 0,1,2 });
+  }
+  
+  public void testBQ12() throws Exception {
+    // NOTE: using qtest not bqtest
+    qtest("w1 w2^0.0", new int[] { 0,1,2,3 });
+  }
+  public void testBQ13() throws Exception {
+    // NOTE: using qtest not bqtest
+    qtest("w1 -w5^0.0", new int[] { 1,2,3 });
+  }
+  public void testBQ18() throws Exception {
+    // NOTE: using qtest not bqtest
+    qtest("+w1^0.0 w2", new int[] { 0,1,2,3 });
+  }
+  public void testBQ21() throws Exception {
+    bqtest("(+w1 w2)^0.0", new int[] { 0,1,2,3 });
+  }
+  public void testBQ22() throws Exception {
+    bqtest("(+w1^0.0 w2)^0.0", new int[] { 0,1,2,3 });
+  }
+
+  public void testST3() throws Exception {
+    SpanQuery q = st("w1");
+    q.setBoost(0);
+    bqtest(q, new int[] {0,1,2,3});
+  }
+  public void testST6() throws Exception {
+    SpanQuery q = st("xx");
+    q.setBoost(0);
+    qtest(q, new int[] {2,3});
+  }
+
+  public void testSF3() throws Exception {
+    SpanQuery q = sf(("w1"),1);
+    q.setBoost(0);
+    bqtest(q, new int[] {0,1,2,3});
+  }
+  public void testSF7() throws Exception {
+    SpanQuery q = sf(("xx"),3);
+    q.setBoost(0);
+    bqtest(q, new int[] {2,3});
+  }
+  
+  public void testSNot3() throws Exception {
+    SpanQuery q = snot(sf("w1",10),st("QQ"));
+    q.setBoost(0);
+    bqtest(q, new int[] {0,1,2,3});
+  }
+  public void testSNot6() throws Exception {
+    SpanQuery q = snot(sf("w1",10),st("xx"));
+    q.setBoost(0);
+    bqtest(q, new int[] {0,1,2,3});
+  }
+
+  public void testSNot8() throws Exception {
+    // NOTE: using qtest not bqtest
+    SpanQuery f = snear("w1","w3",10,true);
+    f.setBoost(0);
+    SpanQuery q = snot(f, st("xx"));
+    qtest(q, new int[] {0,1,3});
+  }
+  public void testSNot9() throws Exception {
+    // NOTE: using qtest not bqtest
+    SpanQuery t = st("xx");
+    t.setBoost(0);
+    SpanQuery q = snot(snear("w1","w3",10,true), t);
+    qtest(q, new int[] {0,1,3});
+  }
+
+
+  
+
+  
 }
Index: src/test/org/apache/lucene/search/CheckHits.java
===================================================================
--- src/test/org/apache/lucene/search/CheckHits.java	(revision 415854)
+++ src/test/org/apache/lucene/search/CheckHits.java	(working copy)
@@ -55,14 +55,53 @@
     }
     
   }
+  
+  /**
+   * Tests that a query matches the an expected set of documents using a
+   * HitCollector.
+   *
+   * <p>
+   * Note that when using the HitCollector API, documents will be collected
+   * if they "match" regardless of what their score is.
+   * </p>
+   * @param query the query to test
+   * @param searcher the searcher to test the query against
+   * @param defaultFieldName used for displaing the query in assertion messages
+   * @param results a list of documentIds that must match the query
+   * @see Searcher#search(Query,HitCollector)
+   * @see #checkHits
+   */
+  public static void checkHitCollector(Query query, String defaultFieldName,
+                                       Searcher searcher, int[] results)
+    throws IOException {
     
+    Set correct = new TreeSet();
+    for (int i = 0; i < results.length; i++) {
+      correct.add(new Integer(results[i]));
+    }
+    
+    final Set actual = new TreeSet();
+    searcher.search(query, new HitCollector() {
+        public void collect(int doc, float score) {
+          actual.add(new Integer(doc));
+        }
+      });
+    TestCase.assertEquals(query.toString(defaultFieldName), correct, actual);
+  }
+  
   /**
-   * Tests that a query matches the an expected set of documents
+   * Tests that a query matches the an expected set of documents using Hits.
    *
+   * <p>
+   * Note that when using the Hits API, documents will only be returned
+   * if they have a positive normalized score.
+   * </p>
    * @param query the query to test
    * @param searcher the searcher to test the query against
    * @param defaultFieldName used for displaing the query in assertion messages
    * @param results a list of documentIds that must match the query
+   * @see Searcher#search(Query)
+   * @see #checkHitCollector
    */
   public static void checkHits(
         Query query,
