Skip to content

Commit 8eac0a7

Browse files
committed
[TEST] Increase InternalHistogramTests coverage
In this test 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. 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 elastic#35921 and its need to test adding empty buckets as part of the reduce phase. Also relates to elastic#26856 as one more key comparison needed to use `Double.compare` to properly handle `NaN` values, this was triggered by the increased test coverage.
1 parent 31fdb76 commit 8eac0a7

File tree

2 files changed

+60
-12
lines changed

2 files changed

+60
-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: 57 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,52 @@ 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+
interval = randomIntBetween(1, 3);
54+
offset = randomIntBetween(0, 3);
55+
if (randomBoolean()) {
56+
minDocCount = randomIntBetween(1, 10);
57+
emptyBucketInfo = null;
58+
} else {
59+
minDocCount = 0;
60+
//it's ok if minBound and maxBound are outside the range of the generated buckets, that will just mean that
61+
//empty buckets won't be added before the first bucket and/or after the last one
62+
int minBound = randomInt(50) - 30;
63+
int maxBound = randomNumberOfBuckets() * interval + randomIntBetween(0, 10);
64+
emptyBucketInfo = new InternalHistogram.EmptyBucketInfo(interval, offset, minBound, maxBound, InternalAggregations.EMPTY);
65+
}
66+
}
67+
68+
private double round(double key) {
69+
return Math.floor((key - offset) / interval) * interval + offset;
4970
}
5071

5172
@Override
5273
protected InternalHistogram createTestInstance(String name,
5374
List<PipelineAggregator> pipelineAggregators,
5475
Map<String, Object> metaData,
5576
InternalAggregations aggregations) {
56-
final int base = randomInt(50) - 30;
77+
final double base = round(randomInt(50) - 30);
5778
final int numBuckets = randomNumberOfBuckets();
58-
final int interval = randomIntBetween(1, 3);
5979
List<InternalHistogram.Bucket> buckets = new ArrayList<>();
6080
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));
81+
//rarely leave some holes to be filled up with empty buckets in case minDocCount is set to 0
82+
if (frequently()) {
83+
final int docCount = TestUtil.nextInt(random(), 1, 50);
84+
buckets.add(new InternalHistogram.Bucket(base + i * interval, docCount, keyed, format, aggregations));
85+
}
6386
}
6487
BucketOrder order = BucketOrder.key(randomBoolean());
65-
return new InternalHistogram(name, buckets, order, 1, null, format, keyed, pipelineAggregators, metaData);
88+
return new InternalHistogram(name, buckets, order, minDocCount, emptyBucketInfo, format, keyed, pipelineAggregators, metaData);
6689
}
6790

6891
// issue 26787
@@ -88,13 +111,36 @@ public void testHandlesNaN() {
88111

89112
@Override
90113
protected void assertReduced(InternalHistogram reduced, List<InternalHistogram> inputs) {
91-
Map<Double, Long> expectedCounts = new TreeMap<>();
114+
TreeMap<Double, Long> expectedCounts = new TreeMap<>();
92115
for (Histogram histogram : inputs) {
93116
for (Histogram.Bucket bucket : histogram.getBuckets()) {
94117
expectedCounts.compute((Double) bucket.getKey(),
95118
(key, oldValue) -> (oldValue == null ? 0 : oldValue) + bucket.getDocCount());
96119
}
97120
}
121+
if (minDocCount == 0) {
122+
double minBound = round(emptyBucketInfo.minBound);
123+
if (expectedCounts.isEmpty() && emptyBucketInfo.minBound < emptyBucketInfo.maxBound) {
124+
expectedCounts.put(minBound, 0L);
125+
}
126+
if (expectedCounts.isEmpty() == false) {
127+
Double nextKey = expectedCounts.firstKey();
128+
while (nextKey < expectedCounts.lastKey()) {
129+
expectedCounts.putIfAbsent(nextKey, 0L);
130+
nextKey += interval;
131+
}
132+
while (minBound < expectedCounts.firstKey()) {
133+
expectedCounts.put(expectedCounts.firstKey() - interval, 0L);
134+
}
135+
double maxBound = round(emptyBucketInfo.maxBound);
136+
while (expectedCounts.lastKey() < maxBound) {
137+
expectedCounts.put(expectedCounts.lastKey() + interval, 0L);
138+
}
139+
}
140+
}
141+
142+
expectedCounts.entrySet().removeIf(doubleLongEntry -> doubleLongEntry.getValue() < minDocCount);
143+
98144
Map<Double, Long> actualCounts = new TreeMap<>();
99145
for (Histogram.Bucket bucket : reduced.getBuckets()) {
100146
actualCounts.compute((Double) bucket.getKey(),
@@ -121,6 +167,7 @@ protected InternalHistogram mutateInstance(InternalHistogram instance) {
121167
long minDocCount = instance.getMinDocCount();
122168
List<PipelineAggregator> pipelineAggregators = instance.pipelineAggregators();
123169
Map<String, Object> metaData = instance.getMetaData();
170+
InternalHistogram.EmptyBucketInfo emptyBucketInfo = instance.emptyBucketInfo;
124171
switch (between(0, 4)) {
125172
case 0:
126173
name += randomAlphaOfLength(5);
@@ -135,6 +182,7 @@ protected InternalHistogram mutateInstance(InternalHistogram instance) {
135182
break;
136183
case 3:
137184
minDocCount += between(1, 10);
185+
emptyBucketInfo = null;
138186
break;
139187
case 4:
140188
if (metaData == null) {
@@ -147,6 +195,6 @@ protected InternalHistogram mutateInstance(InternalHistogram instance) {
147195
default:
148196
throw new AssertionError("Illegal randomisation branch");
149197
}
150-
return new InternalHistogram(name, buckets, order, minDocCount, null, format, keyed, pipelineAggregators, metaData);
198+
return new InternalHistogram(name, buckets, order, minDocCount, emptyBucketInfo, format, keyed, pipelineAggregators, metaData);
151199
}
152200
}

0 commit comments

Comments
 (0)