Skip to content

Commit 63fef5a

Browse files
committed
Add scripting support to AggregatorTestCase (#43494)
This refactors AggregatorTestCase to allow testing mock scripts. The main change is to QueryShardContext. This was previously mocked, but to get the ScriptService you have to invoke a final method which can't be mocked. Instead, we just create a mostly-empty QueryShardContext and populate the fields that are needed for testing. It also introduces a few new helper methods that can be overridden to change the default behavior a bit. Most tests should be able to override getMockScriptService() to supply a ScriptService to the context, which is later used by the aggs. More complicated tests can override queryShardContextMock() as before. Adds a test to MaxAggregatorTests to test out the new functionality.
1 parent 2beb193 commit 63fef5a

File tree

3 files changed

+75
-19
lines changed

3 files changed

+75
-19
lines changed

server/src/test/java/org/elasticsearch/search/aggregations/metrics/MaxAggregatorTests.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,15 @@
4646
import org.apache.lucene.util.FutureArrays;
4747
import org.elasticsearch.common.CheckedConsumer;
4848
import org.elasticsearch.common.collect.Tuple;
49+
import org.elasticsearch.common.settings.Settings;
4950
import org.elasticsearch.index.mapper.MappedFieldType;
5051
import org.elasticsearch.index.mapper.NumberFieldMapper;
52+
import org.elasticsearch.script.MockScriptEngine;
53+
import org.elasticsearch.script.Script;
54+
import org.elasticsearch.script.ScriptEngine;
55+
import org.elasticsearch.script.ScriptModule;
56+
import org.elasticsearch.script.ScriptService;
57+
import org.elasticsearch.script.ScriptType;
5158
import org.elasticsearch.search.aggregations.AggregatorTestCase;
5259
import org.elasticsearch.search.aggregations.support.AggregationInspectionHelper;
5360

@@ -57,6 +64,7 @@
5764
import java.util.Collections;
5865
import java.util.Comparator;
5966
import java.util.List;
67+
import java.util.Map;
6068
import java.util.function.Consumer;
6169
import java.util.function.Function;
6270
import java.util.function.Supplier;
@@ -65,6 +73,19 @@
6573
import static org.hamcrest.Matchers.equalTo;
6674

6775
public class MaxAggregatorTests extends AggregatorTestCase {
76+
private final String SCRIPT_NAME = "script_name";
77+
private final long SCRIPT_VALUE = 19L;
78+
79+
@Override
80+
protected ScriptService getMockScriptService() {
81+
MockScriptEngine scriptEngine = new MockScriptEngine(MockScriptEngine.NAME,
82+
Collections.singletonMap(SCRIPT_NAME, script -> SCRIPT_VALUE), // return 19 from script
83+
Collections.emptyMap());
84+
Map<String, ScriptEngine> engines = Collections.singletonMap(scriptEngine.getType(), scriptEngine);
85+
86+
return new ScriptService(Settings.EMPTY, engines, ScriptModule.CORE_CONTEXTS);
87+
}
88+
6889
public void testNoDocs() throws IOException {
6990
testCase(new MatchAllDocsQuery(), iw -> {
7091
// Intentionally not writing any docs
@@ -147,6 +168,23 @@ public void testUnmappedWithMissingField() throws IOException {
147168
}, null);
148169
}
149170

171+
public void testScript() throws IOException {
172+
MappedFieldType fieldType = new NumberFieldMapper.NumberFieldType(NumberFieldMapper.NumberType.INTEGER);
173+
fieldType.setName("number");
174+
175+
MaxAggregationBuilder aggregationBuilder = new MaxAggregationBuilder("_name")
176+
.field("number")
177+
.script(new Script(ScriptType.INLINE, MockScriptEngine.NAME, SCRIPT_NAME, Collections.emptyMap()));
178+
179+
testCase(aggregationBuilder, new DocValuesFieldExistsQuery("number"), iw -> {
180+
iw.addDocument(singleton(new NumericDocValuesField("number", 7)));
181+
iw.addDocument(singleton(new NumericDocValuesField("number", 1)));
182+
}, max -> {
183+
assertEquals(max.getValue(), SCRIPT_VALUE, 0); // Note this is the script value (19L), not the doc values above
184+
assertTrue(AggregationInspectionHelper.hasValue(max));
185+
}, fieldType);
186+
}
187+
150188
private void testCase(Query query,
151189
CheckedConsumer<RandomIndexWriter, IOException> buildIndex,
152190
Consumer<InternalMax> verify) throws IOException {
@@ -282,4 +320,5 @@ public PointValues.Relation compare(byte[] minPackedValue, byte[] maxPackedValue
282320
});
283321
assertTrue(seen[0]);
284322
}
323+
285324
}

server/src/test/java/org/elasticsearch/search/aggregations/metrics/ScriptedMetricAggregatorTests.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626
import org.apache.lucene.search.MatchAllDocsQuery;
2727
import org.apache.lucene.store.Directory;
2828
import org.elasticsearch.common.settings.Settings;
29+
import org.elasticsearch.index.IndexSettings;
2930
import org.elasticsearch.index.mapper.MapperService;
3031
import org.elasticsearch.index.query.QueryShardContext;
32+
import org.elasticsearch.indices.breaker.CircuitBreakerService;
3133
import org.elasticsearch.script.MockScriptEngine;
3234
import org.elasticsearch.script.Script;
3335
import org.elasticsearch.script.ScriptEngine;
@@ -403,11 +405,12 @@ public void testSelfReferencingAggStateAfterCombine() throws IOException {
403405
* is final and cannot be mocked
404406
*/
405407
@Override
406-
protected QueryShardContext queryShardContextMock(MapperService mapperService) {
408+
protected QueryShardContext queryShardContextMock(MapperService mapperService, IndexSettings indexSettings,
409+
CircuitBreakerService circuitBreakerService) {
407410
MockScriptEngine scriptEngine = new MockScriptEngine(MockScriptEngine.NAME, SCRIPTS, Collections.emptyMap());
408411
Map<String, ScriptEngine> engines = Collections.singletonMap(scriptEngine.getType(), scriptEngine);
409412
ScriptService scriptService = new ScriptService(Settings.EMPTY, engines, ScriptModule.CORE_CONTEXTS);
410-
return new QueryShardContext(0, mapperService.getIndexSettings(), null, null, null, mapperService, null, scriptService,
413+
return new QueryShardContext(0, indexSettings, null, null, null, mapperService, null, scriptService,
411414
xContentRegistry(), writableRegistry(), null, null, System::currentTimeMillis, null);
412415
}
413416
}

test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.elasticsearch.index.cache.bitset.BitsetFilterCache.Listener;
4949
import org.elasticsearch.index.cache.query.DisabledQueryCache;
5050
import org.elasticsearch.index.engine.Engine;
51+
import org.elasticsearch.index.fielddata.IndexFieldData;
5152
import org.elasticsearch.index.fielddata.IndexFieldDataCache;
5253
import org.elasticsearch.index.fielddata.IndexFieldDataService;
5354
import org.elasticsearch.index.mapper.ContentPath;
@@ -58,7 +59,6 @@
5859
import org.elasticsearch.index.mapper.ObjectMapper;
5960
import org.elasticsearch.index.mapper.ObjectMapper.Nested;
6061
import org.elasticsearch.index.query.QueryShardContext;
61-
import org.elasticsearch.index.query.support.NestedScope;
6262
import org.elasticsearch.index.shard.IndexShard;
6363
import org.elasticsearch.index.shard.ShardId;
6464
import org.elasticsearch.indices.breaker.CircuitBreakerService;
@@ -83,10 +83,10 @@
8383
import java.util.Arrays;
8484
import java.util.Collections;
8585
import java.util.HashMap;
86-
import java.util.HashSet;
8786
import java.util.List;
8887
import java.util.Map;
8988
import java.util.Objects;
89+
import java.util.function.BiFunction;
9090
import java.util.function.Function;
9191
import java.util.stream.Collectors;
9292

@@ -157,8 +157,8 @@ protected AggregatorFactory<?> createAggregatorFactory(Query query,
157157
SearchLookup searchLookup = new SearchLookup(mapperService, ifds::getForField, new String[]{TYPE_NAME});
158158
when(searchContext.lookup()).thenReturn(searchLookup);
159159

160-
QueryShardContext queryShardContext = queryShardContextMock(mapperService);
161-
when(queryShardContext.getIndexSettings()).thenReturn(indexSettings);
160+
QueryShardContext queryShardContext = queryShardContextMock(mapperService, indexSettings, circuitBreakerService);
161+
162162
when(searchContext.getQueryShardContext()).thenReturn(queryShardContext);
163163
Map<String, MappedFieldType> fieldNameToType = new HashMap<>();
164164
fieldNameToType.putAll(Arrays.stream(fieldTypes)
@@ -189,16 +189,11 @@ private void registerFieldTypes(QueryShardContext queryShardContext,
189189
String fieldName = entry.getKey();
190190
MappedFieldType fieldType = entry.getValue();
191191

192-
when(queryShardContext.fieldMapper(fieldName)).thenReturn(fieldType);
192+
when(mapperService.fullName(fieldName)).thenReturn(fieldType);
193193
when(searchContext.smartNameFieldType(fieldName)).thenReturn(fieldType);
194194
}
195195

196-
for (MappedFieldType fieldType : new HashSet<>(fieldNameToType.values())) {
197-
when(queryShardContext.getForField(fieldType)).then(invocation ->
198-
fieldType.fielddataBuilder(mapperService.getIndexSettings().getIndex().getName())
199-
.build(mapperService.getIndexSettings(), fieldType,
200-
new IndexFieldDataCache.None(), circuitBreakerService, mapperService));
201-
}
196+
202197
}
203198

204199
protected <A extends Aggregator> A createAggregator(AggregationBuilder aggregationBuilder,
@@ -304,12 +299,31 @@ protected MapperService mapperServiceMock() {
304299
/**
305300
* sub-tests that need a more complex mock can overwrite this
306301
*/
307-
protected QueryShardContext queryShardContextMock(MapperService mapperService) {
308-
QueryShardContext queryShardContext = mock(QueryShardContext.class);
309-
when(queryShardContext.getMapperService()).thenReturn(mapperService);
310-
NestedScope nestedScope = new NestedScope();
311-
when(queryShardContext.nestedScope()).thenReturn(nestedScope);
312-
return queryShardContext;
302+
protected QueryShardContext queryShardContextMock(MapperService mapperService, IndexSettings indexSettings,
303+
CircuitBreakerService circuitBreakerService) {
304+
305+
return new QueryShardContext(0, indexSettings, null, null,
306+
getIndexFieldDataLookup(mapperService, circuitBreakerService),
307+
mapperService, null, getMockScriptService(), xContentRegistry(),
308+
writableRegistry(), null, null, System::currentTimeMillis, null);
309+
}
310+
311+
/**
312+
* Sub-tests that need a more complex index field data provider can override this
313+
*/
314+
protected BiFunction<MappedFieldType, String, IndexFieldData<?>> getIndexFieldDataLookup(MapperService mapperService,
315+
CircuitBreakerService circuitBreakerService) {
316+
return (fieldType, s) -> fieldType.fielddataBuilder(mapperService.getIndexSettings().getIndex().getName())
317+
.build(mapperService.getIndexSettings(), fieldType,
318+
new IndexFieldDataCache.None(), circuitBreakerService, mapperService);
319+
320+
}
321+
322+
/**
323+
* Sub-tests that need scripting can override this method to provide a script service and pre-baked scripts
324+
*/
325+
protected ScriptService getMockScriptService() {
326+
return null;
313327
}
314328

315329
protected <A extends InternalAggregation, C extends Aggregator> A search(IndexSearcher searcher,

0 commit comments

Comments
 (0)