Add unit tests for ParentToChildAggregator (#23305)
Adds unit tests for the `children` aggregation. This change also add the ability to mock Mapperservice in subtests of AggregatorTestCase.
This commit is contained in:
parent
211d50f7b8
commit
b9eb1bba65
|
@ -43,6 +43,7 @@ import org.elasticsearch.index.mapper.MapperService;
|
|||
import org.elasticsearch.index.query.QueryShardContext;
|
||||
import org.elasticsearch.indices.breaker.CircuitBreakerService;
|
||||
import org.elasticsearch.indices.breaker.NoneCircuitBreakerService;
|
||||
import org.elasticsearch.indices.fielddata.cache.IndicesFieldDataCache;
|
||||
import org.elasticsearch.search.fetch.FetchPhase;
|
||||
import org.elasticsearch.search.fetch.subphase.DocValueFieldsFetchSubPhase;
|
||||
import org.elasticsearch.search.fetch.subphase.FetchSourceSubPhase;
|
||||
|
@ -50,6 +51,7 @@ import org.elasticsearch.search.internal.ContextIndexSearcher;
|
|||
import org.elasticsearch.search.internal.SearchContext;
|
||||
import org.elasticsearch.search.lookup.SearchLookup;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.test.IndexSettingsModule;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
@ -112,11 +114,15 @@ public abstract class AggregatorTestCase extends ESTestCase {
|
|||
}).when(searchContext).addReleasable(anyObject(), anyObject());
|
||||
|
||||
// TODO: now just needed for top_hits, this will need to be revised for other agg unit tests:
|
||||
MapperService mapperService = mock(MapperService.class);
|
||||
MapperService mapperService = mapperServiceMock();
|
||||
when(mapperService.hasNested()).thenReturn(false);
|
||||
when(searchContext.mapperService()).thenReturn(mapperService);
|
||||
IndexFieldDataService ifds = new IndexFieldDataService(IndexSettingsModule.newIndexSettings("test", Settings.EMPTY),
|
||||
new IndicesFieldDataCache(Settings.EMPTY, new IndexFieldDataCache.Listener() {
|
||||
}), circuitBreakerService, mapperService);
|
||||
when(searchContext.fieldData()).thenReturn(ifds);
|
||||
|
||||
SearchLookup searchLookup = new SearchLookup(mapperService, mock(IndexFieldDataService.class), new String[]{"type"});
|
||||
SearchLookup searchLookup = new SearchLookup(mapperService, ifds, new String[]{"type"});
|
||||
when(searchContext.lookup()).thenReturn(searchLookup);
|
||||
|
||||
QueryShardContext queryShardContext = mock(QueryShardContext.class);
|
||||
|
@ -132,6 +138,13 @@ public abstract class AggregatorTestCase extends ESTestCase {
|
|||
return aggregator;
|
||||
}
|
||||
|
||||
/**
|
||||
* sub-tests that need a more complex mock can overwrite this
|
||||
*/
|
||||
protected MapperService mapperServiceMock() {
|
||||
return mock(MapperService.class);
|
||||
}
|
||||
|
||||
protected <A extends InternalAggregation, C extends Aggregator> A search(IndexSearcher searcher,
|
||||
Query query,
|
||||
AggregationBuilder builder,
|
||||
|
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch 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.elasticsearch.search.aggregations.bucket.children;
|
||||
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.SortedDocValuesField;
|
||||
import org.apache.lucene.document.SortedNumericDocValuesField;
|
||||
import org.apache.lucene.document.StringField;
|
||||
import org.apache.lucene.index.DirectoryReader;
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.RandomIndexWriter;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.MatchAllDocsQuery;
|
||||
import org.apache.lucene.search.Query;
|
||||
import org.apache.lucene.search.TermInSetQuery;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.BytesRef;
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
||||
import org.elasticsearch.common.collect.Tuple;
|
||||
import org.elasticsearch.common.lucene.index.ElasticsearchDirectoryReader;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.index.Index;
|
||||
import org.elasticsearch.index.mapper.ContentPath;
|
||||
import org.elasticsearch.index.mapper.DocumentMapper;
|
||||
import org.elasticsearch.index.mapper.MappedFieldType;
|
||||
import org.elasticsearch.index.mapper.Mapper;
|
||||
import org.elasticsearch.index.mapper.MapperService;
|
||||
import org.elasticsearch.index.mapper.NumberFieldMapper;
|
||||
import org.elasticsearch.index.mapper.ParentFieldMapper;
|
||||
import org.elasticsearch.index.mapper.TypeFieldMapper;
|
||||
import org.elasticsearch.index.mapper.Uid;
|
||||
import org.elasticsearch.index.mapper.UidFieldMapper;
|
||||
import org.elasticsearch.index.shard.ShardId;
|
||||
import org.elasticsearch.search.aggregations.AggregatorTestCase;
|
||||
import org.elasticsearch.search.aggregations.metrics.min.InternalMin;
|
||||
import org.elasticsearch.search.aggregations.metrics.min.MinAggregationBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ParentToChildrenAggregatorTests extends AggregatorTestCase {
|
||||
|
||||
private static final String CHILD_TYPE = "child_type";
|
||||
private static final String PARENT_TYPE = "parent_type";
|
||||
|
||||
public void testNoDocs() throws IOException {
|
||||
Directory directory = newDirectory();
|
||||
|
||||
RandomIndexWriter indexWriter = new RandomIndexWriter(random(), directory);
|
||||
// intentionally not writing any docs
|
||||
indexWriter.close();
|
||||
IndexReader indexReader = DirectoryReader.open(directory);
|
||||
|
||||
testCase(new MatchAllDocsQuery(), newSearcher(indexReader, false, true), parentToChild -> {
|
||||
assertEquals(0, parentToChild.getDocCount());
|
||||
assertEquals(Double.POSITIVE_INFINITY, ((InternalMin) parentToChild.getAggregations().get("in_child")).getValue(),
|
||||
Double.MIN_VALUE);
|
||||
});
|
||||
indexReader.close();
|
||||
directory.close();
|
||||
}
|
||||
|
||||
public void testParentChild() throws IOException {
|
||||
Directory directory = newDirectory();
|
||||
RandomIndexWriter indexWriter = new RandomIndexWriter(random(), directory);
|
||||
|
||||
final Map<String, Tuple<Integer, Integer>> expectedParentChildRelations = setupIndex(indexWriter);
|
||||
indexWriter.close();
|
||||
|
||||
IndexReader indexReader = ElasticsearchDirectoryReader.wrap(DirectoryReader.open(directory),
|
||||
new ShardId(new Index("foo", "_na_"), 1));
|
||||
// TODO set "maybeWrap" to true for IndexSearcher once #23338 is resolved
|
||||
IndexSearcher indexSearcher = newSearcher(indexReader, false, true);
|
||||
|
||||
testCase(new MatchAllDocsQuery(), indexSearcher, child -> {
|
||||
int expectedTotalChildren = 0;
|
||||
int expectedMinValue = Integer.MAX_VALUE;
|
||||
for (Tuple<Integer, Integer> expectedValues : expectedParentChildRelations.values()) {
|
||||
expectedTotalChildren += expectedValues.v1();
|
||||
expectedMinValue = Math.min(expectedMinValue, expectedValues.v2());
|
||||
}
|
||||
assertEquals(expectedTotalChildren, child.getDocCount());
|
||||
assertEquals(expectedMinValue, ((InternalMin) child.getAggregations().get("in_child")).getValue(), Double.MIN_VALUE);
|
||||
});
|
||||
|
||||
for (String parent : expectedParentChildRelations.keySet()) {
|
||||
testCase(new TermInSetQuery(UidFieldMapper.NAME, new BytesRef(Uid.createUid(PARENT_TYPE, parent))), indexSearcher, child -> {
|
||||
assertEquals((long) expectedParentChildRelations.get(parent).v1(), child.getDocCount());
|
||||
assertEquals(expectedParentChildRelations.get(parent).v2(),
|
||||
((InternalMin) child.getAggregations().get("in_child")).getValue(), Double.MIN_VALUE);
|
||||
});
|
||||
}
|
||||
indexReader.close();
|
||||
directory.close();
|
||||
}
|
||||
|
||||
private static Map<String, Tuple<Integer, Integer>> setupIndex(RandomIndexWriter iw) throws IOException {
|
||||
Map<String, Tuple<Integer, Integer>> expectedValues = new HashMap<>();
|
||||
int numParents = randomIntBetween(1, 10);
|
||||
for (int i = 0; i < numParents; i++) {
|
||||
String parent = "parent" + i;
|
||||
iw.addDocument(createParentDocument(parent));
|
||||
int numChildren = randomIntBetween(1, 10);
|
||||
int minValue = Integer.MAX_VALUE;
|
||||
for (int c = 0; c < numChildren; c++) {
|
||||
int randomValue = randomIntBetween(0, 100);
|
||||
minValue = Math.min(minValue, randomValue);
|
||||
iw.addDocument(createChildDocument("child" + c + "_" + parent, parent, randomValue));
|
||||
}
|
||||
expectedValues.put(parent, new Tuple<>(numChildren, minValue));
|
||||
}
|
||||
return expectedValues;
|
||||
}
|
||||
|
||||
private static List<Field> createParentDocument(String id) {
|
||||
return Arrays.asList(new StringField(TypeFieldMapper.NAME, PARENT_TYPE, Field.Store.NO),
|
||||
new StringField(UidFieldMapper.NAME, Uid.createUid(PARENT_TYPE, id), Field.Store.NO),
|
||||
createJoinField(PARENT_TYPE, id));
|
||||
}
|
||||
|
||||
private static List<Field> createChildDocument(String childId, String parentId, int value) {
|
||||
return Arrays.asList(new StringField(TypeFieldMapper.NAME, CHILD_TYPE, Field.Store.NO),
|
||||
new StringField(UidFieldMapper.NAME, Uid.createUid(CHILD_TYPE, childId), Field.Store.NO),
|
||||
new SortedNumericDocValuesField("number", value),
|
||||
createJoinField(PARENT_TYPE, parentId));
|
||||
}
|
||||
|
||||
private static SortedDocValuesField createJoinField(String parentType, String id) {
|
||||
return new SortedDocValuesField(ParentFieldMapper.joinField(parentType), new BytesRef(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MapperService mapperServiceMock() {
|
||||
MapperService mapperService = mock(MapperService.class);
|
||||
DocumentMapper childDocMapper = mock(DocumentMapper.class);
|
||||
DocumentMapper parentDocMapper = mock(DocumentMapper.class);
|
||||
ParentFieldMapper parentFieldMapper = createParentFieldMapper();
|
||||
when(childDocMapper.parentFieldMapper()).thenReturn(parentFieldMapper);
|
||||
when(parentDocMapper.parentFieldMapper()).thenReturn(parentFieldMapper);
|
||||
when(mapperService.documentMapper(CHILD_TYPE)).thenReturn(childDocMapper);
|
||||
when(mapperService.documentMapper(PARENT_TYPE)).thenReturn(parentDocMapper);
|
||||
when(mapperService.docMappers(false)).thenReturn(Arrays.asList(new DocumentMapper[] { childDocMapper, parentDocMapper }));
|
||||
when(parentDocMapper.typeFilter()).thenReturn(new TypeFieldMapper.TypesQuery(new BytesRef(PARENT_TYPE)));
|
||||
when(childDocMapper.typeFilter()).thenReturn(new TypeFieldMapper.TypesQuery(new BytesRef(CHILD_TYPE)));
|
||||
return mapperService;
|
||||
}
|
||||
|
||||
private static ParentFieldMapper createParentFieldMapper() {
|
||||
Settings settings = Settings.builder().put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build();
|
||||
return new ParentFieldMapper.Builder("parent").type(PARENT_TYPE).build(new Mapper.BuilderContext(settings, new ContentPath(0)));
|
||||
}
|
||||
|
||||
private void testCase(Query query, IndexSearcher indexSearcher, Consumer<InternalChildren> verify)
|
||||
throws IOException {
|
||||
|
||||
ChildrenAggregationBuilder aggregationBuilder = new ChildrenAggregationBuilder("_name", CHILD_TYPE);
|
||||
aggregationBuilder.subAggregation(new MinAggregationBuilder("in_child").field("number"));
|
||||
|
||||
MappedFieldType fieldType = new NumberFieldMapper.NumberFieldType(NumberFieldMapper.NumberType.LONG);
|
||||
fieldType.setName("number");
|
||||
InternalChildren result = search(indexSearcher, query, aggregationBuilder, fieldType);
|
||||
verify.accept(result);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue