2525import org .elasticsearch .search .aggregations .BucketOrder ;
2626import org .elasticsearch .search .aggregations .InternalAggregation ;
2727import org .elasticsearch .search .aggregations .InternalAggregations ;
28- import org .elasticsearch .test .InternalMultiBucketAggregationTestCase ;
2928import org .elasticsearch .search .aggregations .ParsedMultiBucketAggregation ;
3029import org .elasticsearch .search .aggregations .pipeline .PipelineAggregator ;
30+ import org .elasticsearch .test .InternalMultiBucketAggregationTestCase ;
3131
3232import java .util .ArrayList ;
3333import 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