Skip to content

Commit 4b85769

Browse files
authored
Increase InternalHistogramTests coverage (#36004)
In `InternalHistogramTests` we were randomizing different values but `minDocCount` was hardcoded to `1`. It's important to test other values, especially `0` as it's the default. To make this possible, the test needed some adapting in the way buckets are randomly generated: all aggs need to share the same `interval`, `minDocCount` and `emptyBucketInfo`. Also assertions need to take into account that more (or less) buckets are expected depending on `minDocCount`. This was originated by #35921 and its need to test adding empty buckets as part of the reduce phase. Also relates to #26856 as one more key comparison needed to use `Double.compare` to properly handle `NaN` values, which was triggered by the increased test coverage.
1 parent 513e1ed commit 4b85769

File tree

2 files changed

+63
-12
lines changed

2 files changed

+63
-12
lines changed

server/src/main/java/org/elasticsearch/search/aggregations/bucket/histogram/InternalHistogram.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ public int hashCode() {
213213
private final DocValueFormat format;
214214
private final boolean keyed;
215215
private final long minDocCount;
216-
private final EmptyBucketInfo emptyBucketInfo;
216+
final EmptyBucketInfo emptyBucketInfo;
217217

218218
InternalHistogram(String name, List<Bucket> buckets, BucketOrder order, long minDocCount, EmptyBucketInfo emptyBucketInfo,
219219
DocValueFormat formatter, boolean keyed, List<PipelineAggregator> pipelineAggregators,
@@ -302,7 +302,7 @@ private List<Bucket> reduceBuckets(List<InternalAggregation> aggregations, Reduc
302302
final PriorityQueue<IteratorAndCurrent> pq = new PriorityQueue<IteratorAndCurrent>(aggregations.size()) {
303303
@Override
304304
protected boolean lessThan(IteratorAndCurrent a, IteratorAndCurrent b) {
305-
return a.current.key < b.current.key;
305+
return Double.compare(a.current.key, b.current.key) < 0;
306306
}
307307
};
308308
for (InternalAggregation aggregation : aggregations) {
@@ -405,7 +405,7 @@ private void addEmptyBuckets(List<Bucket> list, ReduceContext reduceContext) {
405405
iter.add(new Bucket(key, 0, keyed, format, reducedEmptySubAggs));
406406
key = nextKey(key);
407407
}
408-
assert key == nextBucket.key;
408+
assert key == nextBucket.key || Double.isNaN(nextBucket.key) : "key: " + key + ", nextBucket.key: " + nextBucket.key;
409409
}
410410
lastBucket = iter.next();
411411
} while (iter.hasNext());

server/src/test/java/org/elasticsearch/search/aggregations/bucket/histogram/InternalHistogramTests.java

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
import org.elasticsearch.search.aggregations.BucketOrder;
2626
import org.elasticsearch.search.aggregations.InternalAggregation;
2727
import org.elasticsearch.search.aggregations.InternalAggregations;
28-
import org.elasticsearch.test.InternalMultiBucketAggregationTestCase;
2928
import org.elasticsearch.search.aggregations.ParsedMultiBucketAggregation;
3029
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
30+
import org.elasticsearch.test.InternalMultiBucketAggregationTestCase;
3131

3232
import java.util.ArrayList;
3333
import java.util.Arrays;
@@ -40,29 +40,55 @@ public class InternalHistogramTests extends InternalMultiBucketAggregationTestCa
4040

4141
private boolean keyed;
4242
private DocValueFormat format;
43+
private int interval;
44+
private int minDocCount;
45+
private InternalHistogram.EmptyBucketInfo emptyBucketInfo;
46+
private int offset;
4347

4448
@Override
45-
public void setUp() throws Exception{
49+
public void setUp() throws Exception {
4650
super.setUp();
4751
keyed = randomBoolean();
4852
format = randomNumericDocValueFormat();
53+
//in order for reduction to work properly (and be realistic) we need to use the same interval, minDocCount, emptyBucketInfo
54+
//and offset in all randomly created aggs as part of the same test run. This is particularly important when minDocCount is
55+
//set to 0 as empty buckets need to be added to fill the holes.
56+
interval = randomIntBetween(1, 3);
57+
offset = randomIntBetween(0, 3);
58+
if (randomBoolean()) {
59+
minDocCount = randomIntBetween(1, 10);
60+
emptyBucketInfo = null;
61+
} else {
62+
minDocCount = 0;
63+
//it's ok if minBound and maxBound are outside the range of the generated buckets, that will just mean that
64+
//empty buckets won't be added before the first bucket and/or after the last one
65+
int minBound = randomInt(50) - 30;
66+
int maxBound = randomNumberOfBuckets() * interval + randomIntBetween(0, 10);
67+
emptyBucketInfo = new InternalHistogram.EmptyBucketInfo(interval, offset, minBound, maxBound, InternalAggregations.EMPTY);
68+
}
69+
}
70+
71+
private double round(double key) {
72+
return Math.floor((key - offset) / interval) * interval + offset;
4973
}
5074

5175
@Override
5276
protected InternalHistogram createTestInstance(String name,
5377
List<PipelineAggregator> pipelineAggregators,
5478
Map<String, Object> metaData,
5579
InternalAggregations aggregations) {
56-
final int base = randomInt(50) - 30;
80+
final double base = round(randomInt(50) - 30);
5781
final int numBuckets = randomNumberOfBuckets();
58-
final int interval = randomIntBetween(1, 3);
5982
List<InternalHistogram.Bucket> buckets = new ArrayList<>();
6083
for (int i = 0; i < numBuckets; ++i) {
61-
final int docCount = TestUtil.nextInt(random(), 1, 50);
62-
buckets.add(new InternalHistogram.Bucket(base + i * interval, docCount, keyed, format, aggregations));
84+
//rarely leave some holes to be filled up with empty buckets in case minDocCount is set to 0
85+
if (frequently()) {
86+
final int docCount = TestUtil.nextInt(random(), 1, 50);
87+
buckets.add(new InternalHistogram.Bucket(base + i * interval, docCount, keyed, format, aggregations));
88+
}
6389
}
6490
BucketOrder order = BucketOrder.key(randomBoolean());
65-
return new InternalHistogram(name, buckets, order, 1, null, format, keyed, pipelineAggregators, metaData);
91+
return new InternalHistogram(name, buckets, order, minDocCount, emptyBucketInfo, format, keyed, pipelineAggregators, metaData);
6692
}
6793

6894
// issue 26787
@@ -88,13 +114,36 @@ public void testHandlesNaN() {
88114

89115
@Override
90116
protected void assertReduced(InternalHistogram reduced, List<InternalHistogram> inputs) {
91-
Map<Double, Long> expectedCounts = new TreeMap<>();
117+
TreeMap<Double, Long> expectedCounts = new TreeMap<>();
92118
for (Histogram histogram : inputs) {
93119
for (Histogram.Bucket bucket : histogram.getBuckets()) {
94120
expectedCounts.compute((Double) bucket.getKey(),
95121
(key, oldValue) -> (oldValue == null ? 0 : oldValue) + bucket.getDocCount());
96122
}
97123
}
124+
if (minDocCount == 0) {
125+
double minBound = round(emptyBucketInfo.minBound);
126+
if (expectedCounts.isEmpty() && emptyBucketInfo.minBound < emptyBucketInfo.maxBound) {
127+
expectedCounts.put(minBound, 0L);
128+
}
129+
if (expectedCounts.isEmpty() == false) {
130+
Double nextKey = expectedCounts.firstKey();
131+
while (nextKey < expectedCounts.lastKey()) {
132+
expectedCounts.putIfAbsent(nextKey, 0L);
133+
nextKey += interval;
134+
}
135+
while (minBound < expectedCounts.firstKey()) {
136+
expectedCounts.put(expectedCounts.firstKey() - interval, 0L);
137+
}
138+
double maxBound = round(emptyBucketInfo.maxBound);
139+
while (expectedCounts.lastKey() < maxBound) {
140+
expectedCounts.put(expectedCounts.lastKey() + interval, 0L);
141+
}
142+
}
143+
} else {
144+
expectedCounts.entrySet().removeIf(doubleLongEntry -> doubleLongEntry.getValue() < minDocCount);
145+
}
146+
98147
Map<Double, Long> actualCounts = new TreeMap<>();
99148
for (Histogram.Bucket bucket : reduced.getBuckets()) {
100149
actualCounts.compute((Double) bucket.getKey(),
@@ -121,6 +170,7 @@ protected InternalHistogram mutateInstance(InternalHistogram instance) {
121170
long minDocCount = instance.getMinDocCount();
122171
List<PipelineAggregator> pipelineAggregators = instance.pipelineAggregators();
123172
Map<String, Object> metaData = instance.getMetaData();
173+
InternalHistogram.EmptyBucketInfo emptyBucketInfo = instance.emptyBucketInfo;
124174
switch (between(0, 4)) {
125175
case 0:
126176
name += randomAlphaOfLength(5);
@@ -135,6 +185,7 @@ protected InternalHistogram mutateInstance(InternalHistogram instance) {
135185
break;
136186
case 3:
137187
minDocCount += between(1, 10);
188+
emptyBucketInfo = null;
138189
break;
139190
case 4:
140191
if (metaData == null) {
@@ -147,6 +198,6 @@ protected InternalHistogram mutateInstance(InternalHistogram instance) {
147198
default:
148199
throw new AssertionError("Illegal randomisation branch");
149200
}
150-
return new InternalHistogram(name, buckets, order, minDocCount, null, format, keyed, pipelineAggregators, metaData);
201+
return new InternalHistogram(name, buckets, order, minDocCount, emptyBucketInfo, format, keyed, pipelineAggregators, metaData);
151202
}
152203
}

0 commit comments

Comments
 (0)