From 4941650c3ac98f9104b125320f537fe80192d8bb Mon Sep 17 00:00:00 2001 From: Martin Gaievski Date: Wed, 28 Jun 2023 10:05:12 -0700 Subject: [PATCH] Address review comments, mostly refactorings Signed-off-by: Martin Gaievski --- .../query/HybridQueryBuilder.java | 7 +- .../search/HybridTopScoreDocCollector.java | 11 +- .../query/HybridQueryPhaseSearcher.java | 116 +++++++++--------- .../plugin/NeuralSearchTests.java | 4 +- .../query/HybridQueryBuilderTests.java | 23 +++- .../neuralsearch/query/HybridQueryIT.java | 13 +- .../query/OpenSearchQueryTestCase.java | 43 ------- .../query/HybridQueryPhaseSearcherTests.java | 12 +- 8 files changed, 103 insertions(+), 126 deletions(-) diff --git a/src/main/java/org/opensearch/neuralsearch/query/HybridQueryBuilder.java b/src/main/java/org/opensearch/neuralsearch/query/HybridQueryBuilder.java index 4bf51af48..fc2f97984 100644 --- a/src/main/java/org/opensearch/neuralsearch/query/HybridQueryBuilder.java +++ b/src/main/java/org/opensearch/neuralsearch/query/HybridQueryBuilder.java @@ -191,11 +191,10 @@ public static HybridQueryBuilder fromXContent(XContentParser parser) throws IOEx boost = parser.floatValue(); // regular boost functionality is not supported, user should use score normalization methods to manipulate with scores if (boost != DEFAULT_BOOST) { - log.error(String.format(Locale.ROOT, "[%s] query does not support [%s]", NAME, BOOST_FIELD)); - throw new ParsingException( - parser.getTokenLocation(), - String.format(Locale.ROOT, "[%s] query does not support [%s]", NAME, BOOST_FIELD) + log.error( + String.format(Locale.ROOT, "[%s] query does not support provided value %.4f for [%s]", NAME, boost, BOOST_FIELD) ); + throw new ParsingException(parser.getTokenLocation(), "[{}] query does not support [{}]", NAME, BOOST_FIELD); } } else if (AbstractQueryBuilder.NAME_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { queryName = parser.text(); diff --git a/src/main/java/org/opensearch/neuralsearch/search/HybridTopScoreDocCollector.java b/src/main/java/org/opensearch/neuralsearch/search/HybridTopScoreDocCollector.java index 3ca829901..3fa413826 100644 --- a/src/main/java/org/opensearch/neuralsearch/search/HybridTopScoreDocCollector.java +++ b/src/main/java/org/opensearch/neuralsearch/search/HybridTopScoreDocCollector.java @@ -9,6 +9,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import lombok.Getter; import lombok.extern.log4j.Log4j2; @@ -96,15 +98,12 @@ public ScoreMode scoreMode() { * @return */ public List topDocs() { - List topDocs; if (compoundScores == null) { return new ArrayList<>(); } - topDocs = new ArrayList(compoundScores.length); - for (int i = 0; i < compoundScores.length; i++) { - int qTopSize = totalHits[i]; - topDocs.add(topDocsPerQuery(0, Math.min(qTopSize, compoundScores[i].size()), compoundScores[i], qTopSize)); - } + final List topDocs = IntStream.range(0, compoundScores.length) + .mapToObj(i -> topDocsPerQuery(0, Math.min(totalHits[i], compoundScores[i].size()), compoundScores[i], totalHits[i])) + .collect(Collectors.toList()); return topDocs; } diff --git a/src/main/java/org/opensearch/neuralsearch/search/query/HybridQueryPhaseSearcher.java b/src/main/java/org/opensearch/neuralsearch/search/query/HybridQueryPhaseSearcher.java index d6dee77b2..1385b110a 100644 --- a/src/main/java/org/opensearch/neuralsearch/search/query/HybridQueryPhaseSearcher.java +++ b/src/main/java/org/opensearch/neuralsearch/search/query/HybridQueryPhaseSearcher.java @@ -10,8 +10,6 @@ import java.io.IOException; import java.util.LinkedList; import java.util.List; -import java.util.Locale; -import java.util.function.Function; import lombok.extern.log4j.Log4j2; @@ -36,6 +34,8 @@ import org.opensearch.search.rescore.RescoreContext; import org.opensearch.search.sort.SortAndFormats; +import com.google.common.annotations.VisibleForTesting; + /** * Custom search implementation to be used at {@link QueryPhase} for Hybrid Query search. For queries other than Hybrid the * upstream standard implementation of searcher is called. @@ -43,17 +43,13 @@ @Log4j2 public class HybridQueryPhaseSearcher extends QueryPhase.DefaultQueryPhaseSearcher { - private Function, TotalHits> totalHitsSupplier; - private Function, Float> maxScoreSupplier; - protected SortAndFormats sortAndFormats; - public boolean searchWith( - SearchContext searchContext, - ContextIndexSearcher searcher, - Query query, - LinkedList collectors, - boolean hasFilterCollector, - boolean hasTimeout + final SearchContext searchContext, + final ContextIndexSearcher searcher, + final Query query, + final LinkedList collectors, + final boolean hasFilterCollector, + final boolean hasTimeout ) throws IOException { if (query instanceof HybridQuery) { return searchWithCollector(searchContext, searcher, query, collectors, hasFilterCollector, hasTimeout); @@ -61,30 +57,29 @@ public boolean searchWith( return super.searchWithCollector(searchContext, searcher, query, collectors, hasFilterCollector, hasTimeout); } + @VisibleForTesting protected boolean searchWithCollector( - SearchContext searchContext, - ContextIndexSearcher searcher, - Query query, - LinkedList collectors, - boolean hasFilterCollector, - boolean hasTimeout + final SearchContext searchContext, + final ContextIndexSearcher searcher, + final Query query, + final LinkedList collectors, + final boolean hasFilterCollector, + final boolean hasTimeout ) throws IOException { - log.debug(String.format(Locale.ROOT, "searching with custom doc collector, shard %s", searchContext.shardTarget().getShardId())); + log.debug("searching with custom doc collector, shard {}", searchContext.shardTarget().getShardId()); final TopDocsCollectorContext topDocsFactory = createTopDocsCollectorContext(searchContext, hasFilterCollector); collectors.addFirst(topDocsFactory); - - final IndexReader reader = searchContext.searcher().getIndexReader(); - int totalNumDocs = Math.max(0, reader.numDocs()); if (searchContext.size() == 0) { final TotalHitCountCollector collector = new TotalHitCountCollector(); searcher.search(query, collector); return false; } + final IndexReader reader = searchContext.searcher().getIndexReader(); + int totalNumDocs = Math.max(0, reader.numDocs()); int numDocs = Math.min(searchContext.from() + searchContext.size(), totalNumDocs); - final boolean rescore = !searchContext.rescore().isEmpty(); - if (rescore) { - assert searchContext.sort() == null; + final boolean shouldRescore = !searchContext.rescore().isEmpty(); + if (shouldRescore) { for (RescoreContext rescoreContext : searchContext.rescore()) { numDocs = Math.max(numDocs, rescoreContext.getWindowSize()); } @@ -96,32 +91,6 @@ protected boolean searchWithCollector( numDocs, new HitsThresholdChecker(Math.max(numDocs, searchContext.trackTotalHitsUpTo())) ); - totalHitsSupplier = topDocs -> { - int trackTotalHitsUpTo = searchContext.trackTotalHitsUpTo(); - final TotalHits.Relation relation = trackTotalHitsUpTo == SearchContext.TRACK_TOTAL_HITS_DISABLED - ? TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO - : TotalHits.Relation.EQUAL_TO; - if (topDocs == null || topDocs.size() == 0) { - return new TotalHits(0, relation); - } - long maxTotalHits = topDocs.get(0).totalHits.value; - for (TopDocs topDoc : topDocs) { - maxTotalHits = Math.max(maxTotalHits, topDoc.totalHits.value); - } - return new TotalHits(maxTotalHits, relation); - }; - maxScoreSupplier = topDocs -> { - if (topDocs.size() == 0) { - return Float.NaN; - } else { - return topDocs.stream() - .map(docs -> docs.scoreDocs.length == 0 ? new ScoreDoc(-1, 0.0f) : docs.scoreDocs[0]) - .map(scoreDoc -> scoreDoc.score) - .max(Float::compare) - .get(); - } - }; - sortAndFormats = searchContext.sort(); searcher.search(query, collector); @@ -129,20 +98,51 @@ protected boolean searchWithCollector( queryResult.terminatedEarly(false); } - setTopDocsInQueryResult(queryResult, collector); + setTopDocsInQueryResult(queryResult, collector, searchContext); - return rescore; + return shouldRescore; } - void setTopDocsInQueryResult(final QuerySearchResult queryResult, final HybridTopScoreDocCollector collector) { + private void setTopDocsInQueryResult( + final QuerySearchResult queryResult, + final HybridTopScoreDocCollector collector, + final SearchContext searchContext + ) { final List topDocs = collector.topDocs(); - float maxScore = maxScoreSupplier.apply(topDocs); - final TopDocs newTopDocs = new CompoundTopDocs(totalHitsSupplier.apply(topDocs), topDocs); + final float maxScore = getMaxScore(topDocs); + final TopDocs newTopDocs = new CompoundTopDocs(getTotalHits(searchContext, topDocs), topDocs); final TopDocsAndMaxScore topDocsAndMaxScore = new TopDocsAndMaxScore(newTopDocs, maxScore); - queryResult.topDocs(topDocsAndMaxScore, getSortValueFormats()); + queryResult.topDocs(topDocsAndMaxScore, getSortValueFormats(searchContext.sort())); + } + + private TotalHits getTotalHits(final SearchContext searchContext, final List topDocs) { + int trackTotalHitsUpTo = searchContext.trackTotalHitsUpTo(); + final TotalHits.Relation relation = trackTotalHitsUpTo == SearchContext.TRACK_TOTAL_HITS_DISABLED + ? TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO + : TotalHits.Relation.EQUAL_TO; + if (topDocs == null || topDocs.size() == 0) { + return new TotalHits(0, relation); + } + long maxTotalHits = topDocs.get(0).totalHits.value; + for (TopDocs topDoc : topDocs) { + maxTotalHits = Math.max(maxTotalHits, topDoc.totalHits.value); + } + return new TotalHits(maxTotalHits, relation); + } + + private float getMaxScore(List topDocs) { + if (topDocs.size() == 0) { + return Float.NaN; + } else { + return topDocs.stream() + .map(docs -> docs.scoreDocs.length == 0 ? new ScoreDoc(-1, 0.0f) : docs.scoreDocs[0]) + .map(scoreDoc -> scoreDoc.score) + .max(Float::compare) + .get(); + } } - private DocValueFormat[] getSortValueFormats() { + private DocValueFormat[] getSortValueFormats(final SortAndFormats sortAndFormats) { return sortAndFormats == null ? null : sortAndFormats.formats; } } diff --git a/src/test/java/org/opensearch/neuralsearch/plugin/NeuralSearchTests.java b/src/test/java/org/opensearch/neuralsearch/plugin/NeuralSearchTests.java index b9551be32..c2925bf43 100644 --- a/src/test/java/org/opensearch/neuralsearch/plugin/NeuralSearchTests.java +++ b/src/test/java/org/opensearch/neuralsearch/plugin/NeuralSearchTests.java @@ -5,6 +5,8 @@ package org.opensearch.neuralsearch.plugin; +import static org.mockito.Mockito.mock; + import java.util.List; import java.util.Map; import java.util.Optional; @@ -18,8 +20,6 @@ import org.opensearch.search.query.QueryPhaseSearcher; import org.opensearch.test.OpenSearchTestCase; -import static org.mockito.Mockito.mock; - public class NeuralSearchTests extends OpenSearchTestCase { public void testQuerySpecs() { diff --git a/src/test/java/org/opensearch/neuralsearch/query/HybridQueryBuilderTests.java b/src/test/java/org/opensearch/neuralsearch/query/HybridQueryBuilderTests.java index 9b379fb13..06b07fed5 100644 --- a/src/test/java/org/opensearch/neuralsearch/query/HybridQueryBuilderTests.java +++ b/src/test/java/org/opensearch/neuralsearch/query/HybridQueryBuilderTests.java @@ -597,9 +597,30 @@ public void testRewrite_whenMultipleSubQueries_thenReturnBuilderForEachSubQuery( assertEquals(termSubQuery.value(), termQueryBuilder.value()); } + /** + * Tests query with boost: + * { + * "query": { + * "hybrid": { + * "queries": [ + * { + * "term": { + * "text": "keyword" + * } + * }, + * { + * "term": { + * "text": "keyword" + * } + * } + * ], + * "boost" : 2.0 + * } + * } + * } + */ @SneakyThrows public void testBoost_whenNonDefaultBoostSet_thenFail() { - // create query with 6 sub-queries, which is more than current max allowed XContentBuilder xContentBuilderWithNonDefaultBoost = XContentFactory.jsonBuilder() .startObject() .startArray("queries") diff --git a/src/test/java/org/opensearch/neuralsearch/query/HybridQueryIT.java b/src/test/java/org/opensearch/neuralsearch/query/HybridQueryIT.java index 5673a7e98..bd5502988 100644 --- a/src/test/java/org/opensearch/neuralsearch/query/HybridQueryIT.java +++ b/src/test/java/org/opensearch/neuralsearch/query/HybridQueryIT.java @@ -33,6 +33,7 @@ public class HybridQueryIT extends BaseNeuralSearchIT { private static final String TEST_BASIC_INDEX_NAME = "test-neural-basic-index"; private static final String TEST_BASIC_VECTOR_DOC_FIELD_INDEX_NAME = "test-neural-vector-doc-field-index"; private static final String TEST_MULTI_DOC_INDEX_NAME = "test-neural-multi-doc-index"; + private static final int MAX_NUMBER_OF_DOCS_IN_MULTI_DOC_INDEX = 3; private static final String TEST_QUERY_TEXT = "greetings"; private static final String TEST_QUERY_TEXT2 = "salute"; private static final String TEST_QUERY_TEXT3 = "hello"; @@ -105,7 +106,7 @@ public void testBasicQuery_whenOneSubQuery_thenSuccessful() { Map searchResponseAsMap1 = search(TEST_MULTI_DOC_INDEX_NAME, hybridQueryBuilder, 10); - assertEquals(3, getHitCount(searchResponseAsMap1)); + assertEquals(MAX_NUMBER_OF_DOCS_IN_MULTI_DOC_INDEX, getHitCount(searchResponseAsMap1)); List> hits1NestedList = getNestedHits(searchResponseAsMap1); List ids = new ArrayList<>(); @@ -122,7 +123,7 @@ public void testBasicQuery_whenOneSubQuery_thenSuccessful() { Map total = getTotalHits(searchResponseAsMap1); assertNotNull(total.get("value")); - assertEquals(3, total.get("value")); + assertEquals(MAX_NUMBER_OF_DOCS_IN_MULTI_DOC_INDEX, total.get("value")); assertNotNull(total.get("relation")); assertEquals(RELATION_EQUAL_TO, total.get("relation")); assertTrue(getMaxScore(searchResponseAsMap1).isPresent()); @@ -271,7 +272,7 @@ public void testSubQuery_whenSubqueriesInDifferentOrder_thenResultIsSame() { Map searchResponseAsMap1 = search(TEST_MULTI_DOC_INDEX_NAME, hybridQueryBuilderNeuralThenTerm, 10); - assertEquals(3, getHitCount(searchResponseAsMap1)); + assertEquals(MAX_NUMBER_OF_DOCS_IN_MULTI_DOC_INDEX, getHitCount(searchResponseAsMap1)); List> hits1NestedList = getNestedHits(searchResponseAsMap1); List ids1 = new ArrayList<>(); @@ -283,7 +284,7 @@ public void testSubQuery_whenSubqueriesInDifferentOrder_thenResultIsSame() { Map total = getTotalHits(searchResponseAsMap1); assertNotNull(total.get("value")); - assertEquals(3, total.get("value")); + assertEquals(MAX_NUMBER_OF_DOCS_IN_MULTI_DOC_INDEX, total.get("value")); assertNotNull(total.get("relation")); assertEquals(RELATION_EQUAL_TO, total.get("relation")); @@ -299,7 +300,7 @@ public void testSubQuery_whenSubqueriesInDifferentOrder_thenResultIsSame() { Map searchResponseAsMap2 = search(TEST_MULTI_DOC_INDEX_NAME, hybridQueryBuilderNeuralThenTerm, 10); - assertEquals(3, getHitCount(searchResponseAsMap2)); + assertEquals(MAX_NUMBER_OF_DOCS_IN_MULTI_DOC_INDEX, getHitCount(searchResponseAsMap2)); List ids2 = new ArrayList<>(); List scores2 = new ArrayList<>(); @@ -310,7 +311,7 @@ public void testSubQuery_whenSubqueriesInDifferentOrder_thenResultIsSame() { Map total2 = getTotalHits(searchResponseAsMap2); assertNotNull(total.get("value")); - assertEquals(3, total2.get("value")); + assertEquals(MAX_NUMBER_OF_DOCS_IN_MULTI_DOC_INDEX, total2.get("value")); assertNotNull(total2.get("relation")); assertEquals(RELATION_EQUAL_TO, total2.get("relation")); // doc ids must match same from the previous query, order of sub-queries doesn't change the result diff --git a/src/test/java/org/opensearch/neuralsearch/query/OpenSearchQueryTestCase.java b/src/test/java/org/opensearch/neuralsearch/query/OpenSearchQueryTestCase.java index 7ad91a612..e954004c7 100644 --- a/src/test/java/org/opensearch/neuralsearch/query/OpenSearchQueryTestCase.java +++ b/src/test/java/org/opensearch/neuralsearch/query/OpenSearchQueryTestCase.java @@ -11,15 +11,10 @@ import java.io.IOException; import java.util.Arrays; -import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.stream.Stream; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; import org.apache.lucene.analysis.standard.StandardAnalyzer; -import org.apache.lucene.document.BinaryDocValuesField; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.FieldType; @@ -46,15 +41,12 @@ import org.opensearch.index.similarity.SimilarityService; import org.opensearch.indices.IndicesModule; import org.opensearch.indices.mapper.MapperRegistry; -import org.opensearch.knn.index.VectorField; import org.opensearch.plugins.MapperPlugin; import org.opensearch.plugins.ScriptPlugin; import org.opensearch.script.ScriptModule; import org.opensearch.script.ScriptService; import org.opensearch.test.OpenSearchTestCase; -import com.carrotsearch.randomizedtesting.RandomizedTest; - public abstract class OpenSearchQueryTestCase extends OpenSearchTestCase { protected final MapperService createMapperService(Version version, XContentBuilder mapping) throws IOException { @@ -141,41 +133,6 @@ protected static Document getDocument(String fieldName, int docId, String fieldV return doc; } - protected static Document getTextAndVectorDocument( - String fieldName, - int docId, - String fieldValue, - FieldType ft, - String vectorName, - float[] vector, - FieldType vt - ) { - Document doc = new Document(); - doc.add(new TextField("id", Integer.toString(docId), Field.Store.YES)); - doc.add(new Field(fieldName, fieldValue, ft)); - doc.add(new BinaryDocValuesField(vectorName, new VectorField(vectorName, vector, new FieldType()).binaryValue())); - return doc; - } - - private Pair generateDocuments(int maxDocId) { - final int numDocs = RandomizedTest.randomIntBetween(1, maxDocId / 2); - final int[] docs = new int[numDocs]; - final Set uniqueDocs = new HashSet<>(); - while (uniqueDocs.size() < numDocs) { - uniqueDocs.add(random().nextInt(maxDocId)); - } - int i = 0; - for (int doc : uniqueDocs) { - docs[i++] = doc; - } - Arrays.sort(docs); - final float[] scores = new float[numDocs]; - for (int j = 0; j < numDocs; ++j) { - scores[j] = random().nextFloat(); - } - return new ImmutablePair<>(docs, scores); - } - protected static Weight fakeWeight(Query query) { return new Weight(query) { diff --git a/src/test/java/org/opensearch/neuralsearch/search/query/HybridQueryPhaseSearcherTests.java b/src/test/java/org/opensearch/neuralsearch/search/query/HybridQueryPhaseSearcherTests.java index 9875d8c14..f08dc2332 100644 --- a/src/test/java/org/opensearch/neuralsearch/search/query/HybridQueryPhaseSearcherTests.java +++ b/src/test/java/org/opensearch/neuralsearch/search/query/HybridQueryPhaseSearcherTests.java @@ -83,7 +83,7 @@ public void testQueryType_whenQueryIsHybrid_thenCallHybridDocCollector() { when(mockQueryShardContext.fieldMapper(eq(TEXT_FIELD_NAME))).thenReturn(fieldType); Directory directory = newDirectory(); - final IndexWriter w = new IndexWriter(directory, newIndexWriterConfig(new MockAnalyzer(random()))); + IndexWriter w = new IndexWriter(directory, newIndexWriterConfig(new MockAnalyzer(random()))); FieldType ft = new FieldType(TextField.TYPE_NOT_STORED); ft.setIndexOptions(random().nextBoolean() ? IndexOptions.DOCS : IndexOptions.DOCS_AND_FREQS); ft.setOmitNorms(random().nextBoolean()); @@ -143,7 +143,7 @@ public void testQueryType_whenQueryIsNotHybrid_thenDoNotCallHybridDocCollector() when(mockQueryShardContext.fieldMapper(eq(TEXT_FIELD_NAME))).thenReturn(fieldType); Directory directory = newDirectory(); - final IndexWriter w = new IndexWriter(directory, newIndexWriterConfig(new MockAnalyzer(random()))); + IndexWriter w = new IndexWriter(directory, newIndexWriterConfig(new MockAnalyzer(random()))); FieldType ft = new FieldType(TextField.TYPE_NOT_STORED); ft.setIndexOptions(random().nextBoolean() ? IndexOptions.DOCS : IndexOptions.DOCS_AND_FREQS); ft.setOmitNorms(random().nextBoolean()); @@ -202,7 +202,7 @@ public void testQueryResult_whenOneSubQueryWithHits_thenHybridResultsAreSet() { when(mockQueryShardContext.fieldMapper(eq(TEXT_FIELD_NAME))).thenReturn(fieldType); Directory directory = newDirectory(); - final IndexWriter w = new IndexWriter(directory, newIndexWriterConfig(new MockAnalyzer(random()))); + IndexWriter w = new IndexWriter(directory, newIndexWriterConfig(new MockAnalyzer(random()))); FieldType ft = new FieldType(TextField.TYPE_NOT_STORED); ft.setIndexOptions(random().nextBoolean() ? IndexOptions.DOCS : IndexOptions.DOCS_AND_FREQS); ft.setOmitNorms(random().nextBoolean()); @@ -237,7 +237,7 @@ public void testQueryResult_whenOneSubQueryWithHits_thenHybridResultsAreSet() { when(searchContext.shardTarget()).thenReturn(shardTarget); when(searchContext.searcher()).thenReturn(contextIndexSearcher); when(searchContext.size()).thenReturn(3); - final QuerySearchResult querySearchResult = new QuerySearchResult(); + QuerySearchResult querySearchResult = new QuerySearchResult(); when(searchContext.queryResult()).thenReturn(querySearchResult); LinkedList collectors = new LinkedList<>(); @@ -284,7 +284,7 @@ public void testQueryResult_whenMultipleTextSubQueriesWithSomeHits_thenHybridRes when(mockQueryShardContext.fieldMapper(eq(TEXT_FIELD_NAME))).thenReturn(fieldType); Directory directory = newDirectory(); - final IndexWriter w = new IndexWriter(directory, newIndexWriterConfig(new MockAnalyzer(random()))); + IndexWriter w = new IndexWriter(directory, newIndexWriterConfig(new MockAnalyzer(random()))); FieldType ft = new FieldType(TextField.TYPE_NOT_STORED); ft.setIndexOptions(random().nextBoolean() ? IndexOptions.DOCS : IndexOptions.DOCS_AND_FREQS); ft.setOmitNorms(random().nextBoolean()); @@ -321,7 +321,7 @@ public void testQueryResult_whenMultipleTextSubQueriesWithSomeHits_thenHybridRes when(searchContext.shardTarget()).thenReturn(shardTarget); when(searchContext.searcher()).thenReturn(contextIndexSearcher); when(searchContext.size()).thenReturn(4); - final QuerySearchResult querySearchResult = new QuerySearchResult(); + QuerySearchResult querySearchResult = new QuerySearchResult(); when(searchContext.queryResult()).thenReturn(querySearchResult); LinkedList collectors = new LinkedList<>();