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;
+ }
+
+}