You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by cp...@apache.org on 2020/08/24 12:24:21 UTC

[lucene-solr] 01/02: Append MultiCollectorTest to TestMultiCollector. (#1745)

This is an automated email from the ASF dual-hosted git repository.

cpoerschke pushed a commit to branch branch_8x
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git

commit d30d9d7004c8c0160d6aaa0442a77de405c4f28b
Author: Christine Poerschke <cp...@apache.org>
AuthorDate: Mon Aug 24 12:27:56 2020 +0100

    Append MultiCollectorTest to TestMultiCollector. (#1745)
---
 .../apache/lucene/search/MultiCollectorTest.java   | 338 ---------------------
 .../apache/lucene/search/TestMultiCollector.java   | 309 +++++++++++++++++++
 2 files changed, 309 insertions(+), 338 deletions(-)

diff --git a/lucene/core/src/test/org/apache/lucene/search/MultiCollectorTest.java b/lucene/core/src/test/org/apache/lucene/search/MultiCollectorTest.java
deleted file mode 100644
index 80a5a9a..0000000
--- a/lucene/core/src/test/org/apache/lucene/search/MultiCollectorTest.java
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.lucene.search;
-
-
-import java.io.IOException;
-
-import org.apache.lucene.document.Document;
-import org.apache.lucene.index.DirectoryReader;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.RandomIndexWriter;
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.util.LuceneTestCase;
-import org.junit.Test;
-
-public class MultiCollectorTest extends LuceneTestCase {
-
-  private static class DummyCollector extends SimpleCollector {
-
-    boolean collectCalled = false;
-    boolean setNextReaderCalled = false;
-    boolean setScorerCalled = false;
-
-    @Override
-    public void collect(int doc) throws IOException {
-      collectCalled = true;
-    }
-
-    @Override
-    protected void doSetNextReader(LeafReaderContext context) throws IOException {
-      setNextReaderCalled = true;
-    }
-
-    @Override
-    public void setScorer(Scorable scorer) throws IOException {
-      setScorerCalled = true;
-    }
-
-    @Override
-    public ScoreMode scoreMode() {
-      return ScoreMode.COMPLETE;
-    }
-  }
-
-  @Test
-  public void testNullCollectors() throws Exception {
-    // Tests that the collector rejects all null collectors.
-    expectThrows(IllegalArgumentException.class, () -> {
-      MultiCollector.wrap(null, null);
-    });
-
-    // Tests that the collector handles some null collectors well. If it
-    // doesn't, an NPE would be thrown.
-    Collector c = MultiCollector.wrap(new DummyCollector(), null, new DummyCollector());
-    assertTrue(c instanceof MultiCollector);
-    final LeafCollector ac = c.getLeafCollector(null);
-    ac.collect(1);
-    c.getLeafCollector(null);
-    c.getLeafCollector(null).setScorer(new ScoreAndDoc());
-  }
-
-  @Test
-  public void testSingleCollector() throws Exception {
-    // Tests that if a single Collector is input, it is returned (and not MultiCollector).
-    DummyCollector dc = new DummyCollector();
-    assertSame(dc, MultiCollector.wrap(dc));
-    assertSame(dc, MultiCollector.wrap(dc, null));
-  }
-  
-  @Test
-  public void testCollector() throws Exception {
-    // Tests that the collector delegates calls to input collectors properly.
-
-    // Tests that the collector handles some null collectors well. If it
-    // doesn't, an NPE would be thrown.
-    DummyCollector[] dcs = new DummyCollector[] { new DummyCollector(), new DummyCollector() };
-    Collector c = MultiCollector.wrap(dcs);
-    LeafCollector ac = c.getLeafCollector(null);
-    ac.collect(1);
-    ac = c.getLeafCollector(null);
-    ac.setScorer(new ScoreAndDoc());
-
-    for (DummyCollector dc : dcs) {
-      assertTrue(dc.collectCalled);
-      assertTrue(dc.setNextReaderCalled);
-      assertTrue(dc.setScorerCalled);
-    }
-
-  }
-
-  private static Collector collector(ScoreMode scoreMode, Class<?> expectedScorer) {
-    return new Collector() {
-
-      @Override
-      public LeafCollector getLeafCollector(LeafReaderContext context) throws IOException {
-        return new LeafCollector() {
-
-          @Override
-          public void setScorer(Scorable scorer) throws IOException {
-            while (expectedScorer.equals(scorer.getClass()) == false && scorer instanceof FilterScorable) {
-              scorer = ((FilterScorable) scorer).in;
-            }
-            assertEquals(expectedScorer, scorer.getClass());
-          }
-
-          @Override
-          public void collect(int doc) throws IOException {}
-          
-        };
-      }
-
-      @Override
-      public ScoreMode scoreMode() {
-        return scoreMode;
-      }
-      
-    };
-  }
-
-  public void testCacheScoresIfNecessary() throws IOException {
-    Directory dir = newDirectory();
-    RandomIndexWriter iw = new RandomIndexWriter(random(), dir);
-    iw.addDocument(new Document());
-    iw.commit();
-    DirectoryReader reader = iw.getReader();
-    iw.close();
-    
-    final LeafReaderContext ctx = reader.leaves().get(0);
-
-    expectThrows(AssertionError.class, () -> {
-      collector(ScoreMode.COMPLETE_NO_SCORES, ScoreCachingWrappingScorer.class).getLeafCollector(ctx).setScorer(new ScoreAndDoc());
-    });
-
-    // no collector needs scores => no caching
-    Collector c1 = collector(ScoreMode.COMPLETE_NO_SCORES, ScoreAndDoc.class);
-    Collector c2 = collector(ScoreMode.COMPLETE_NO_SCORES, ScoreAndDoc.class);
-    MultiCollector.wrap(c1, c2).getLeafCollector(ctx).setScorer(new ScoreAndDoc());
-
-    // only one collector needs scores => no caching
-    c1 = collector(ScoreMode.COMPLETE, ScoreAndDoc.class);
-    c2 = collector(ScoreMode.COMPLETE_NO_SCORES, ScoreAndDoc.class);
-    MultiCollector.wrap(c1, c2).getLeafCollector(ctx).setScorer(new ScoreAndDoc());
-
-    // several collectors need scores => caching
-    c1 = collector(ScoreMode.COMPLETE, ScoreCachingWrappingScorer.class);
-    c2 = collector(ScoreMode.COMPLETE, ScoreCachingWrappingScorer.class);
-    MultiCollector.wrap(c1, c2).getLeafCollector(ctx).setScorer(new ScoreAndDoc());
-
-    reader.close();
-    dir.close();
-  }
-  
-  public void testScorerWrappingForTopScores() throws IOException {
-    Directory dir = newDirectory();
-    RandomIndexWriter iw = new RandomIndexWriter(random(), dir);
-    iw.addDocument(new Document());
-    DirectoryReader reader = iw.getReader();
-    iw.close();
-    final LeafReaderContext ctx = reader.leaves().get(0);
-    Collector c1 = collector(ScoreMode.TOP_SCORES, MultiCollector.MinCompetitiveScoreAwareScorable.class);
-    Collector c2 = collector(ScoreMode.TOP_SCORES, MultiCollector.MinCompetitiveScoreAwareScorable.class);
-    MultiCollector.wrap(c1, c2).getLeafCollector(ctx).setScorer(new ScoreAndDoc());
-    
-    c1 = collector(ScoreMode.TOP_SCORES, ScoreCachingWrappingScorer.class);
-    c2 = collector(ScoreMode.COMPLETE, ScoreCachingWrappingScorer.class);
-    MultiCollector.wrap(c1, c2).getLeafCollector(ctx).setScorer(new ScoreAndDoc());
-    
-    reader.close();
-    dir.close();
-  }
-  
-  public void testMinCompetitiveScore() throws IOException {
-    float[] currentMinScores = new float[3];
-    float[] minCompetitiveScore = new float[1];
-    Scorable scorer = new Scorable() {
-      
-      @Override
-      public float score() throws IOException {
-        return 0;
-      }
-      
-      @Override
-      public int docID() {
-        return 0;
-      }
-      
-      @Override
-      public void setMinCompetitiveScore(float minScore) throws IOException {
-        minCompetitiveScore[0] = minScore;
-      }
-    };
-    Scorable s0 = new MultiCollector.MinCompetitiveScoreAwareScorable(scorer, 0, currentMinScores);
-    Scorable s1 = new MultiCollector.MinCompetitiveScoreAwareScorable(scorer, 1, currentMinScores);
-    Scorable s2 = new MultiCollector.MinCompetitiveScoreAwareScorable(scorer, 2, currentMinScores);
-    assertEquals(0f, minCompetitiveScore[0], 0);
-    s0.setMinCompetitiveScore(0.5f);
-    assertEquals(0f, minCompetitiveScore[0], 0);
-    s1.setMinCompetitiveScore(0.8f);
-    assertEquals(0f, minCompetitiveScore[0], 0);
-    s2.setMinCompetitiveScore(0.3f);
-    assertEquals(0.3f, minCompetitiveScore[0], 0);
-    s2.setMinCompetitiveScore(0.1f);
-    assertEquals(0.3f, minCompetitiveScore[0], 0);
-    s1.setMinCompetitiveScore(Float.MAX_VALUE);
-    assertEquals(0.3f, minCompetitiveScore[0], 0);
-    s2.setMinCompetitiveScore(Float.MAX_VALUE);
-    assertEquals(0.5f, minCompetitiveScore[0], 0);
-    s0.setMinCompetitiveScore(Float.MAX_VALUE);
-    assertEquals(Float.MAX_VALUE, minCompetitiveScore[0], 0);
-  }
-  
-  public void testCollectionTermination() throws IOException {
-    Directory dir = newDirectory();
-    RandomIndexWriter iw = new RandomIndexWriter(random(), dir);
-    iw.addDocument(new Document());
-    DirectoryReader reader = iw.getReader();
-    iw.close();
-    final LeafReaderContext ctx = reader.leaves().get(0);
-    DummyCollector c1 = new TerminatingDummyCollector(1, ScoreMode.COMPLETE);
-    DummyCollector c2 = new TerminatingDummyCollector(2, ScoreMode.COMPLETE);
-
-    Collector mc = MultiCollector.wrap(c1, c2);
-    LeafCollector lc = mc.getLeafCollector(ctx);
-    lc.setScorer(new ScoreAndDoc());
-    lc.collect(0); // OK
-    assertTrue("c1's collect should be called", c1.collectCalled);
-    assertTrue("c2's collect should be called", c2.collectCalled);
-    c1.collectCalled = false;
-    c2.collectCalled = false;
-    lc.collect(1); // OK, but c1 should terminate
-    assertFalse("c1 should be removed already", c1.collectCalled);
-    assertTrue("c2's collect should be called", c2.collectCalled);
-    c2.collectCalled = false;
-    
-    expectThrows(CollectionTerminatedException.class, () -> {
-      lc.collect(2);
-    });
-    assertFalse("c1 should be removed already", c1.collectCalled);
-    assertFalse("c2 should be removed already", c2.collectCalled);
-    
-    reader.close();
-    dir.close();
-  }
-  
-  public void testSetScorerOnCollectionTerminationSkipNonCompetitive() throws IOException {
-    doTestSetScorerOnCollectionTermination(true);
-  }
-  
-  public void testSetScorerOnCollectionTerminationSkipNoSkips() throws IOException {
-    doTestSetScorerOnCollectionTermination(false);
-  }
-  
-  private void doTestSetScorerOnCollectionTermination(boolean allowSkipNonCompetitive) throws IOException {
-    Directory dir = newDirectory();
-    RandomIndexWriter iw = new RandomIndexWriter(random(), dir);
-    iw.addDocument(new Document());
-    DirectoryReader reader = iw.getReader();
-    iw.close();
-    final LeafReaderContext ctx = reader.leaves().get(0);
-    
-    DummyCollector c1 = new TerminatingDummyCollector(1, allowSkipNonCompetitive? ScoreMode.TOP_SCORES : ScoreMode.COMPLETE);
-    DummyCollector c2 = new TerminatingDummyCollector(2, allowSkipNonCompetitive? ScoreMode.TOP_SCORES : ScoreMode.COMPLETE);
-    
-    Collector mc = MultiCollector.wrap(c1, c2);
-    LeafCollector lc = mc.getLeafCollector(ctx);
-    assertFalse(c1.setScorerCalled);
-    assertFalse(c2.setScorerCalled);
-    lc.setScorer(new ScoreAndDoc());
-    assertTrue(c1.setScorerCalled);
-    assertTrue(c2.setScorerCalled);
-    c1.setScorerCalled = false;
-    c2.setScorerCalled = false;
-    lc.collect(0); // OK
-    
-    lc.setScorer(new ScoreAndDoc());
-    assertTrue(c1.setScorerCalled);
-    assertTrue(c2.setScorerCalled);
-    c1.setScorerCalled = false;
-    c2.setScorerCalled = false;
-    
-    lc.collect(1); // OK, but c1 should terminate
-    lc.setScorer(new ScoreAndDoc());
-    assertFalse(c1.setScorerCalled);
-    assertTrue(c2.setScorerCalled);
-    c2.setScorerCalled = false;
-    
-    expectThrows(CollectionTerminatedException.class, () -> {
-      lc.collect(2);
-    });
-    lc.setScorer(new ScoreAndDoc());
-    assertFalse(c1.setScorerCalled);
-    assertFalse(c2.setScorerCalled);
-    
-    reader.close();
-    dir.close();
-  }
-  
-  private static class TerminatingDummyCollector extends DummyCollector {
-    
-    private final int terminateOnDoc;
-    private final ScoreMode scoreMode;
-    
-    public TerminatingDummyCollector(int terminateOnDoc, ScoreMode scoreMode) {
-      super();
-      this.terminateOnDoc = terminateOnDoc;
-      this.scoreMode = scoreMode;
-    }
-    
-    @Override
-    public void collect(int doc) throws IOException {
-      if (doc == terminateOnDoc) {
-        throw new CollectionTerminatedException();
-      }
-      super.collect(doc);
-    }
-    
-    @Override
-    public ScoreMode scoreMode() {
-      return scoreMode;
-    }
-    
-  }
-
-}
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestMultiCollector.java b/lucene/core/src/test/org/apache/lucene/search/TestMultiCollector.java
index dda314b..f1adc1b 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestMultiCollector.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestMultiCollector.java
@@ -36,6 +36,8 @@ import org.apache.lucene.store.Directory;
 import org.apache.lucene.util.LuceneTestCase;
 import org.apache.lucene.util.TestUtil;
 
+import org.junit.Test;
+
 public class TestMultiCollector extends LuceneTestCase {
 
   private static class TerminateAfterCollector extends FilterCollector {
@@ -217,4 +219,311 @@ public class TestMultiCollector extends LuceneTestCase {
     reader.close();
     dir.close();
   }
+
+  private static class DummyCollector extends SimpleCollector {
+
+    boolean collectCalled = false;
+    boolean setNextReaderCalled = false;
+    boolean setScorerCalled = false;
+
+    @Override
+    public void collect(int doc) throws IOException {
+      collectCalled = true;
+    }
+
+    @Override
+    protected void doSetNextReader(LeafReaderContext context) throws IOException {
+      setNextReaderCalled = true;
+    }
+
+    @Override
+    public void setScorer(Scorable scorer) throws IOException {
+      setScorerCalled = true;
+    }
+
+    @Override
+    public ScoreMode scoreMode() {
+      return ScoreMode.COMPLETE;
+    }
+  }
+
+  @Test
+  public void testNullCollectors() throws Exception {
+    // Tests that the collector rejects all null collectors.
+    expectThrows(IllegalArgumentException.class, () -> {
+      MultiCollector.wrap(null, null);
+    });
+
+    // Tests that the collector handles some null collectors well. If it
+    // doesn't, an NPE would be thrown.
+    Collector c = MultiCollector.wrap(new DummyCollector(), null, new DummyCollector());
+    assertTrue(c instanceof MultiCollector);
+    final LeafCollector ac = c.getLeafCollector(null);
+    ac.collect(1);
+    c.getLeafCollector(null);
+    c.getLeafCollector(null).setScorer(new ScoreAndDoc());
+  }
+
+  @Test
+  public void testSingleCollector() throws Exception {
+    // Tests that if a single Collector is input, it is returned (and not MultiCollector).
+    DummyCollector dc = new DummyCollector();
+    assertSame(dc, MultiCollector.wrap(dc));
+    assertSame(dc, MultiCollector.wrap(dc, null));
+  }
+  
+  @Test
+  public void testCollector() throws Exception {
+    // Tests that the collector delegates calls to input collectors properly.
+
+    // Tests that the collector handles some null collectors well. If it
+    // doesn't, an NPE would be thrown.
+    DummyCollector[] dcs = new DummyCollector[] { new DummyCollector(), new DummyCollector() };
+    Collector c = MultiCollector.wrap(dcs);
+    LeafCollector ac = c.getLeafCollector(null);
+    ac.collect(1);
+    ac = c.getLeafCollector(null);
+    ac.setScorer(new ScoreAndDoc());
+
+    for (DummyCollector dc : dcs) {
+      assertTrue(dc.collectCalled);
+      assertTrue(dc.setNextReaderCalled);
+      assertTrue(dc.setScorerCalled);
+    }
+
+  }
+
+  private static Collector collector(ScoreMode scoreMode, Class<?> expectedScorer) {
+    return new Collector() {
+
+      @Override
+      public LeafCollector getLeafCollector(LeafReaderContext context) throws IOException {
+        return new LeafCollector() {
+
+          @Override
+          public void setScorer(Scorable scorer) throws IOException {
+            while (expectedScorer.equals(scorer.getClass()) == false && scorer instanceof FilterScorable) {
+              scorer = ((FilterScorable) scorer).in;
+            }
+            assertEquals(expectedScorer, scorer.getClass());
+          }
+
+          @Override
+          public void collect(int doc) throws IOException {}
+          
+        };
+      }
+
+      @Override
+      public ScoreMode scoreMode() {
+        return scoreMode;
+      }
+      
+    };
+  }
+
+  public void testCacheScoresIfNecessary() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter iw = new RandomIndexWriter(random(), dir);
+    iw.addDocument(new Document());
+    iw.commit();
+    DirectoryReader reader = iw.getReader();
+    iw.close();
+    
+    final LeafReaderContext ctx = reader.leaves().get(0);
+
+    expectThrows(AssertionError.class, () -> {
+      collector(ScoreMode.COMPLETE_NO_SCORES, ScoreCachingWrappingScorer.class).getLeafCollector(ctx).setScorer(new ScoreAndDoc());
+    });
+
+    // no collector needs scores => no caching
+    Collector c1 = collector(ScoreMode.COMPLETE_NO_SCORES, ScoreAndDoc.class);
+    Collector c2 = collector(ScoreMode.COMPLETE_NO_SCORES, ScoreAndDoc.class);
+    MultiCollector.wrap(c1, c2).getLeafCollector(ctx).setScorer(new ScoreAndDoc());
+
+    // only one collector needs scores => no caching
+    c1 = collector(ScoreMode.COMPLETE, ScoreAndDoc.class);
+    c2 = collector(ScoreMode.COMPLETE_NO_SCORES, ScoreAndDoc.class);
+    MultiCollector.wrap(c1, c2).getLeafCollector(ctx).setScorer(new ScoreAndDoc());
+
+    // several collectors need scores => caching
+    c1 = collector(ScoreMode.COMPLETE, ScoreCachingWrappingScorer.class);
+    c2 = collector(ScoreMode.COMPLETE, ScoreCachingWrappingScorer.class);
+    MultiCollector.wrap(c1, c2).getLeafCollector(ctx).setScorer(new ScoreAndDoc());
+
+    reader.close();
+    dir.close();
+  }
+  
+  public void testScorerWrappingForTopScores() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter iw = new RandomIndexWriter(random(), dir);
+    iw.addDocument(new Document());
+    DirectoryReader reader = iw.getReader();
+    iw.close();
+    final LeafReaderContext ctx = reader.leaves().get(0);
+    Collector c1 = collector(ScoreMode.TOP_SCORES, MultiCollector.MinCompetitiveScoreAwareScorable.class);
+    Collector c2 = collector(ScoreMode.TOP_SCORES, MultiCollector.MinCompetitiveScoreAwareScorable.class);
+    MultiCollector.wrap(c1, c2).getLeafCollector(ctx).setScorer(new ScoreAndDoc());
+    
+    c1 = collector(ScoreMode.TOP_SCORES, ScoreCachingWrappingScorer.class);
+    c2 = collector(ScoreMode.COMPLETE, ScoreCachingWrappingScorer.class);
+    MultiCollector.wrap(c1, c2).getLeafCollector(ctx).setScorer(new ScoreAndDoc());
+    
+    reader.close();
+    dir.close();
+  }
+  
+  public void testMinCompetitiveScore() throws IOException {
+    float[] currentMinScores = new float[3];
+    float[] minCompetitiveScore = new float[1];
+    Scorable scorer = new Scorable() {
+      
+      @Override
+      public float score() throws IOException {
+        return 0;
+      }
+      
+      @Override
+      public int docID() {
+        return 0;
+      }
+      
+      @Override
+      public void setMinCompetitiveScore(float minScore) throws IOException {
+        minCompetitiveScore[0] = minScore;
+      }
+    };
+    Scorable s0 = new MultiCollector.MinCompetitiveScoreAwareScorable(scorer, 0, currentMinScores);
+    Scorable s1 = new MultiCollector.MinCompetitiveScoreAwareScorable(scorer, 1, currentMinScores);
+    Scorable s2 = new MultiCollector.MinCompetitiveScoreAwareScorable(scorer, 2, currentMinScores);
+    assertEquals(0f, minCompetitiveScore[0], 0);
+    s0.setMinCompetitiveScore(0.5f);
+    assertEquals(0f, minCompetitiveScore[0], 0);
+    s1.setMinCompetitiveScore(0.8f);
+    assertEquals(0f, minCompetitiveScore[0], 0);
+    s2.setMinCompetitiveScore(0.3f);
+    assertEquals(0.3f, minCompetitiveScore[0], 0);
+    s2.setMinCompetitiveScore(0.1f);
+    assertEquals(0.3f, minCompetitiveScore[0], 0);
+    s1.setMinCompetitiveScore(Float.MAX_VALUE);
+    assertEquals(0.3f, minCompetitiveScore[0], 0);
+    s2.setMinCompetitiveScore(Float.MAX_VALUE);
+    assertEquals(0.5f, minCompetitiveScore[0], 0);
+    s0.setMinCompetitiveScore(Float.MAX_VALUE);
+    assertEquals(Float.MAX_VALUE, minCompetitiveScore[0], 0);
+  }
+  
+  public void testCollectionTermination() throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter iw = new RandomIndexWriter(random(), dir);
+    iw.addDocument(new Document());
+    DirectoryReader reader = iw.getReader();
+    iw.close();
+    final LeafReaderContext ctx = reader.leaves().get(0);
+    DummyCollector c1 = new TerminatingDummyCollector(1, ScoreMode.COMPLETE);
+    DummyCollector c2 = new TerminatingDummyCollector(2, ScoreMode.COMPLETE);
+
+    Collector mc = MultiCollector.wrap(c1, c2);
+    LeafCollector lc = mc.getLeafCollector(ctx);
+    lc.setScorer(new ScoreAndDoc());
+    lc.collect(0); // OK
+    assertTrue("c1's collect should be called", c1.collectCalled);
+    assertTrue("c2's collect should be called", c2.collectCalled);
+    c1.collectCalled = false;
+    c2.collectCalled = false;
+    lc.collect(1); // OK, but c1 should terminate
+    assertFalse("c1 should be removed already", c1.collectCalled);
+    assertTrue("c2's collect should be called", c2.collectCalled);
+    c2.collectCalled = false;
+    
+    expectThrows(CollectionTerminatedException.class, () -> {
+      lc.collect(2);
+    });
+    assertFalse("c1 should be removed already", c1.collectCalled);
+    assertFalse("c2 should be removed already", c2.collectCalled);
+    
+    reader.close();
+    dir.close();
+  }
+  
+  public void testSetScorerOnCollectionTerminationSkipNonCompetitive() throws IOException {
+    doTestSetScorerOnCollectionTermination(true);
+  }
+  
+  public void testSetScorerOnCollectionTerminationSkipNoSkips() throws IOException {
+    doTestSetScorerOnCollectionTermination(false);
+  }
+  
+  private void doTestSetScorerOnCollectionTermination(boolean allowSkipNonCompetitive) throws IOException {
+    Directory dir = newDirectory();
+    RandomIndexWriter iw = new RandomIndexWriter(random(), dir);
+    iw.addDocument(new Document());
+    DirectoryReader reader = iw.getReader();
+    iw.close();
+    final LeafReaderContext ctx = reader.leaves().get(0);
+    
+    DummyCollector c1 = new TerminatingDummyCollector(1, allowSkipNonCompetitive? ScoreMode.TOP_SCORES : ScoreMode.COMPLETE);
+    DummyCollector c2 = new TerminatingDummyCollector(2, allowSkipNonCompetitive? ScoreMode.TOP_SCORES : ScoreMode.COMPLETE);
+    
+    Collector mc = MultiCollector.wrap(c1, c2);
+    LeafCollector lc = mc.getLeafCollector(ctx);
+    assertFalse(c1.setScorerCalled);
+    assertFalse(c2.setScorerCalled);
+    lc.setScorer(new ScoreAndDoc());
+    assertTrue(c1.setScorerCalled);
+    assertTrue(c2.setScorerCalled);
+    c1.setScorerCalled = false;
+    c2.setScorerCalled = false;
+    lc.collect(0); // OK
+    
+    lc.setScorer(new ScoreAndDoc());
+    assertTrue(c1.setScorerCalled);
+    assertTrue(c2.setScorerCalled);
+    c1.setScorerCalled = false;
+    c2.setScorerCalled = false;
+    
+    lc.collect(1); // OK, but c1 should terminate
+    lc.setScorer(new ScoreAndDoc());
+    assertFalse(c1.setScorerCalled);
+    assertTrue(c2.setScorerCalled);
+    c2.setScorerCalled = false;
+    
+    expectThrows(CollectionTerminatedException.class, () -> {
+      lc.collect(2);
+    });
+    lc.setScorer(new ScoreAndDoc());
+    assertFalse(c1.setScorerCalled);
+    assertFalse(c2.setScorerCalled);
+    
+    reader.close();
+    dir.close();
+  }
+  
+  private static class TerminatingDummyCollector extends DummyCollector {
+    
+    private final int terminateOnDoc;
+    private final ScoreMode scoreMode;
+    
+    public TerminatingDummyCollector(int terminateOnDoc, ScoreMode scoreMode) {
+      super();
+      this.terminateOnDoc = terminateOnDoc;
+      this.scoreMode = scoreMode;
+    }
+    
+    @Override
+    public void collect(int doc) throws IOException {
+      if (doc == terminateOnDoc) {
+        throw new CollectionTerminatedException();
+      }
+      super.collect(doc);
+    }
+    
+    @Override
+    public ScoreMode scoreMode() {
+      return scoreMode;
+    }
+    
+  }
+
 }