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:
Christoph Büscher 2017-02-24 10:29:31 +01:00 committed by GitHub
parent 211d50f7b8
commit b9eb1bba65
2 changed files with 204 additions and 2 deletions

View File

@ -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,

View File

@ -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);
}
}