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 2019/10/22 15:37:11 UTC

[lucene-solr] branch master updated: LUCENE-9010: extend TopGroups.merge test coverage

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

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


The following commit(s) were added to refs/heads/master by this push:
     new f8292f5  LUCENE-9010: extend TopGroups.merge test coverage
f8292f5 is described below

commit f8292f5372502598dc8cabc50642c4f783e1c811
Author: Christine Poerschke <cp...@apache.org>
AuthorDate: Tue Oct 22 15:50:48 2019 +0100

    LUCENE-9010: extend TopGroups.merge test coverage
---
 .../lucene/search/grouping/TopGroupsTest.java      | 235 +++++++++++++++++++++
 1 file changed, 235 insertions(+)

diff --git a/lucene/grouping/src/test/org/apache/lucene/search/grouping/TopGroupsTest.java b/lucene/grouping/src/test/org/apache/lucene/search/grouping/TopGroupsTest.java
new file mode 100644
index 0000000..8fb661d
--- /dev/null
+++ b/lucene/grouping/src/test/org/apache/lucene/search/grouping/TopGroupsTest.java
@@ -0,0 +1,235 @@
+/*
+ * 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.grouping;
+
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.TotalHits;
+import org.apache.lucene.util.LuceneTestCase;
+
+import org.junit.Ignore;
+
+public class TopGroupsTest extends LuceneTestCase {
+
+  @Ignore // https://issues.apache.org/jira/browse/LUCENE-8996
+  public void testAllGroupsEmptyInSecondPass() {
+    narrativeMergeTestImplementation(false, false, false, false);
+  }
+
+  @Ignore // https://issues.apache.org/jira/browse/LUCENE-8996
+  public void testSomeGroupsEmptyInSecondPass() {
+    narrativeMergeTestImplementation(false, false, false, true);
+    narrativeMergeTestImplementation(false, false, true, false);
+    narrativeMergeTestImplementation(false, false, true, true);
+
+    narrativeMergeTestImplementation(false, true, false, false);
+    narrativeMergeTestImplementation(false, true, false, true);
+    narrativeMergeTestImplementation(false, true, true, false);
+    narrativeMergeTestImplementation(false, true, true, true);
+
+    narrativeMergeTestImplementation(true, false, false, false);
+    narrativeMergeTestImplementation(true, false, false, true);
+    narrativeMergeTestImplementation(true, false, true, false);
+    narrativeMergeTestImplementation(true, false, true, true);
+
+    narrativeMergeTestImplementation(true, true, false, false);
+    narrativeMergeTestImplementation(true, true, false, true);
+    narrativeMergeTestImplementation(true, true, true, false);
+  }
+
+  public void testNoGroupsEmptyInSecondPass() {
+    narrativeMergeTestImplementation(true, true, true, true);
+  }
+
+  /*
+   * This method implements tests for the <code>TopGroup.merge</code> method
+   * using a narrative approach. Use of a creative narrative may seem unusual
+   * or even silly but the idea behind it is to make it hopefully easier to
+   * reason about the documents and groups and scores in the test whilst testing
+   * several scenario permutations.
+   *
+   * Imagine:
+   *
+   * Each document represents (say) a picture book of an animal.
+   * We are searching for two books and wish to draw a picture of our own, inspired by the books.
+   * We think that large animals are easier to draw and therefore order the books by the featured animal's size.
+   * We think that different colors would make for a good drawing and therefore group the books by the featured animal's color.
+   *
+   * Index content:
+   *
+   * The documents are in 2 groups ("blue" and "red") and there are 4 documents across 2 shards:
+   * shard 1 (blue whale, red ant) and shard 2 (blue dragonfly, red squirrel).
+   *
+   * If all documents are present the "blue whale" and the "red squirrel" documents would be returned
+   * for our drawing since they are the largest animals in their respective groups.
+   *
+   * Test permutations (haveBlueWhale, haveRedAnt, haveBlueDragonfly, haveRedSquirrel) arise because
+   * in the first pass of the search all documents can be present, but
+   * in the second pass of the search some documents could be missing
+   * if they have been deleted 'just so' between the two phases.
+   *
+   * Additionally a <code>haveAnimal == false</code> condition also represents scenarios where a given
+   * group has documents on some but not all shards in the collection.
+   */
+  private void narrativeMergeTestImplementation(
+      boolean haveBlueWhale,
+      boolean haveRedAnt,
+      boolean haveBlueDragonfly,
+      boolean haveRedSquirrel) {
+
+    final String blueGroupValue = "blue";
+    final String redGroupValue = "red";
+
+    final Integer redAntSize = 1;
+    final Integer blueDragonflySize = 10;
+    final Integer redSquirrelSize = 100;
+    final Integer blueWhaleSize = 1000;
+
+    final float redAntScore = redAntSize;
+    final float blueDragonflyScore = blueDragonflySize;
+    final float redSquirrelScore = redSquirrelSize;
+    final float blueWhaleScore = blueWhaleSize;
+
+    final Sort sort = Sort.RELEVANCE;
+
+    final TopGroups<String> shard1TopGroups;
+    {
+      final GroupDocs<String> group1 = haveBlueWhale
+          ? createSingletonGroupDocs(blueGroupValue, new Object[] { blueWhaleSize }, 1 /* docId */, blueWhaleScore, 0 /* shardIndex */)
+              : createEmptyGroupDocs(blueGroupValue, new Object[] { blueWhaleSize });
+
+      final GroupDocs<String> group2 = haveRedAnt
+          ? createSingletonGroupDocs(redGroupValue, new Object[] { redAntSize }, 2 /* docId */, redAntScore, 0 /* shardIndex */)
+              : createEmptyGroupDocs(redGroupValue, new Object[] { redAntSize });
+
+      shard1TopGroups = new TopGroups<String>(
+          sort.getSort() /* groupSort */,
+          sort.getSort() /* withinGroupSort */,
+          group1.scoreDocs.length + group2.scoreDocs.length /* totalHitCount */,
+          group1.scoreDocs.length + group2.scoreDocs.length /* totalGroupedHitCount */,
+          combineGroupDocs(group1, group2) /* groups */,
+          (haveBlueWhale ? blueWhaleScore : (haveRedAnt ? redAntScore : Float.NaN)) /* maxScore */);
+    }
+
+    final TopGroups<String> shard2TopGroups;
+    {
+      final GroupDocs<String> group1 = haveBlueDragonfly
+          ? createSingletonGroupDocs(blueGroupValue, new Object[] { blueDragonflySize }, 3 /* docId */, blueDragonflyScore, 1 /* shardIndex */)
+              : createEmptyGroupDocs(blueGroupValue, new Object[] { blueDragonflySize });
+
+      final GroupDocs<String> group2 = haveRedSquirrel
+      ? createSingletonGroupDocs(redGroupValue, new Object[] { redSquirrelSize }, 4 /* docId */, redSquirrelScore, 1 /* shardIndex */)
+          : createEmptyGroupDocs(redGroupValue, new Object[] { redSquirrelSize });
+
+      shard2TopGroups = new TopGroups<String>(
+          sort.getSort() /* groupSort */,
+          sort.getSort() /* withinGroupSort */,
+          group1.scoreDocs.length + group2.scoreDocs.length /* totalHitCount */,
+          group1.scoreDocs.length + group2.scoreDocs.length /* totalGroupedHitCount */,
+          combineGroupDocs(group1, group2) /* groups */,
+          (haveRedSquirrel ? redSquirrelScore : (haveBlueDragonfly ? blueDragonflyScore : Float.NaN)) /* maxScore */);
+    }
+
+    final TopGroups<String> mergedTopGroups = TopGroups.<String>merge(
+        combineTopGroups(shard1TopGroups, shard2TopGroups),
+        sort /* groupSort */,
+        sort /* docSort */,
+        0 /* docOffset */,
+        2 /* docTopN */,
+        TopGroups.ScoreMergeMode.None);
+    assertNotNull(mergedTopGroups);
+
+    final int expectedCount =
+        (haveBlueWhale     ? 1 : 0) +
+        (haveRedAnt        ? 1 : 0) +
+        (haveBlueDragonfly ? 1 : 0) +
+        (haveRedSquirrel   ? 1 : 0);
+
+    assertEquals(expectedCount, mergedTopGroups.totalHitCount);
+    assertEquals(expectedCount, mergedTopGroups.totalGroupedHitCount);
+
+    assertEquals(2, mergedTopGroups.groups.length);
+    {
+      assertEquals(blueGroupValue, mergedTopGroups.groups[0].groupValue);
+      final float expectedBlueMaxScore =
+          (haveBlueWhale ? blueWhaleScore : (haveBlueDragonfly ? blueDragonflyScore : Float.MIN_VALUE));
+      checkMaxScore(expectedBlueMaxScore, mergedTopGroups.groups[0].maxScore);
+    }
+    {
+      assertEquals(redGroupValue, mergedTopGroups.groups[1].groupValue);
+      final float expectedRedMaxScore =
+          (haveRedSquirrel ? redSquirrelScore : (haveRedAnt ? redAntScore : Float.MIN_VALUE));
+      checkMaxScore(expectedRedMaxScore, mergedTopGroups.groups[1].maxScore);
+    }
+
+    final float expectedMaxScore =
+        (haveBlueWhale ? blueWhaleScore
+            : (haveRedSquirrel ? redSquirrelScore
+                : (haveBlueDragonfly ? blueDragonflyScore
+                    : (haveRedAnt ? redAntScore
+                        : Float.MIN_VALUE))));
+    checkMaxScore(expectedMaxScore, mergedTopGroups.maxScore);
+  }
+
+  private static void checkMaxScore(float expected, float actual) {
+    if (Float.isNaN(expected)) {
+      assertTrue(Float.isNaN(actual));
+    } else {
+      assertEquals(expected, actual, 0.0);
+    }
+  }
+
+  // helper methods
+
+  private static GroupDocs<String> createEmptyGroupDocs(String groupValue, Object[] groupSortValues) {
+    return new  GroupDocs<String>(
+        Float.NaN /* score */,
+        Float.NaN /* maxScore */,
+        new TotalHits(0, TotalHits.Relation.EQUAL_TO),
+        new ScoreDoc[0],
+        groupValue,
+        groupSortValues);
+    }
+
+  private static GroupDocs<String> createSingletonGroupDocs(String groupValue, Object[] groupSortValues,
+      int docId, float docScore, int shardIndex) {
+    return new  GroupDocs<String>(
+        Float.NaN /* score */,
+        docScore /* maxScore */,
+        new TotalHits(1, TotalHits.Relation.EQUAL_TO),
+        new ScoreDoc[] { new ScoreDoc(docId, docScore, shardIndex) },
+        groupValue,
+        groupSortValues);
+    }
+
+  private static GroupDocs<String>[] combineGroupDocs(GroupDocs<String> group0, GroupDocs<String> group1) {
+    @SuppressWarnings({"unchecked","rawtypes"})
+    final GroupDocs<String>[] groups = new GroupDocs[2];
+    groups[0] = group0;
+    groups[1] = group1;
+    return groups;
+  }
+
+  private static TopGroups<String>[] combineTopGroups(TopGroups<String> group0, TopGroups<String> group1) {
+    @SuppressWarnings({"unchecked","rawtypes"})
+    final TopGroups<String>[] groups = new TopGroups[2];
+    groups[0] = group0;
+    groups[1] = group1;
+    return groups;
+  }
+
+}