@@ -7,18 +7,297 @@ SPDX-License-Identifier: Apache-2.0
77package utils
88
99import (
10+ "fmt"
11+ "math"
1012 "testing"
1113 "time"
12-
13- "github.com/stretchr/testify/assert"
1414)
1515
16- func TestLinearBucketRange (t * testing.T ) {
17- buckets := LinearBucketTimeRange (0 , 5 * time .Second , 10 )
18- assert .Equal (t , []float64 {0 , 0.5 , 1 , 1.5 , 2 , 2.5 , 3 , 3.5 , 4 , 4.5 , 5 }, buckets )
16+ // Test Fix #1: Safer calculation method (Pow vs Exp/Log) - maintains exponential spacing
17+ func TestFix1_SaferCalculation_MaintainsExponentialSpacing (t * testing.T ) {
18+ t .Run ("Verify exponential spacing with constant ratio" , func (t * testing.T ) {
19+ buckets := ExponentialBucketTimeRange (0 , 1 * time .Second , 10 )
20+
21+ // Calculate ratios between consecutive buckets
22+ var ratios []float64
23+ for i := 2 ; i < len (buckets ); i ++ {
24+ if buckets [i - 1 ] > 0 {
25+ ratio := buckets [i ] / buckets [i - 1 ]
26+ ratios = append (ratios , ratio )
27+ }
28+ }
29+
30+ // All ratios should be approximately equal (exponential spacing)
31+ if len (ratios ) < 2 {
32+ t .Fatal ("Not enough ratios to verify exponential spacing" )
33+ }
34+
35+ expectedRatio := ratios [0 ]
36+ for i , ratio := range ratios {
37+ diff := math .Abs (ratio - expectedRatio )
38+ if diff > 0.01 { // Allow 1% tolerance
39+ t .Errorf ("Ratio %d: %.6f differs from expected %.6f by %.6f" , i , ratio , expectedRatio , diff )
40+ }
41+ }
42+
43+ t .Logf ("✓ Exponential spacing confirmed: constant ratio = %.6f" , expectedRatio )
44+ t .Logf (" Buckets: %v" , buckets )
45+ })
46+ }
47+
48+ // Test Fix #2: Guaranteed exact bucket count
49+ func TestFix2_GuaranteedExactBucketCount (t * testing.T ) {
50+ testCases := []struct {
51+ name string
52+ start time.Duration
53+ end time.Duration
54+ buckets int
55+ }{
56+ {"10 buckets" , 0 , 1 * time .Second , 10 },
57+ {"15 buckets" , 0 , 5 * time .Second , 15 },
58+ {"7 buckets" , 0 , 1 * time .Second , 7 },
59+ {"2 buckets" , 0 , 1 * time .Second , 2 },
60+ {"20 buckets" , 0 , 10 * time .Second , 20 },
61+ }
62+
63+ for _ , tc := range testCases {
64+ t .Run (tc .name , func (t * testing.T ) {
65+ result := ExponentialBucketTimeRange (tc .start , tc .end , tc .buckets )
66+
67+ if len (result ) != tc .buckets {
68+ t .Errorf ("Expected exactly %d buckets, got %d" , tc .buckets , len (result ))
69+ t .Errorf ("Buckets: %v" , result )
70+ } else {
71+ t .Logf ("✓ Got exactly %d buckets as requested" , tc .buckets )
72+ }
73+ })
74+ }
1975}
2076
21- func TestExponentialBucketRange (t * testing.T ) {
22- buckets := ExponentialBucketTimeRange (0 , 1 * time .Second , 10 )
23- assert .Equal (t , []float64 {0 , 0.002154434 , 0.004641588 , 0.01 , 0.021544346 , 0.046415888 , 0.1 , 0.215443469 , 0.464158883 , 1 }, buckets )
77+ // Test Fix #3: Independent calculation (no error accumulation)
78+ func TestFix3_IndependentCalculation_NoErrorAccumulation (t * testing.T ) {
79+ t .Run ("Verify monotonically increasing values" , func (t * testing.T ) {
80+ buckets := ExponentialBucketTimeRange (0 , 5 * time .Second , 15 )
81+
82+ for i := 1 ; i < len (buckets ); i ++ {
83+ if buckets [i ] <= buckets [i - 1 ] {
84+ t .Errorf ("Bucket %d (%.10f) is not greater than bucket %d (%.10f)" ,
85+ i , buckets [i ], i - 1 , buckets [i - 1 ])
86+ }
87+ }
88+
89+ t .Logf ("✓ All buckets monotonically increasing" )
90+ })
91+
92+ t .Run ("Verify first and last buckets match start and end" , func (t * testing.T ) {
93+ start := 0 * time .Second
94+ end := 1 * time .Second
95+ buckets := ExponentialBucketTimeRange (start , end , 10 )
96+
97+ if buckets [0 ] != start .Seconds () {
98+ t .Errorf ("First bucket %.10f != start %.10f" , buckets [0 ], start .Seconds ())
99+ }
100+
101+ // Last bucket should be close to end (within rounding)
102+ diff := math .Abs (buckets [len (buckets )- 1 ] - end .Seconds ())
103+ if diff > 0.01 {
104+ t .Errorf ("Last bucket %.10f differs from end %.10f by %.10f" ,
105+ buckets [len (buckets )- 1 ], end .Seconds (), diff )
106+ }
107+
108+ t .Logf ("✓ First bucket = %.10f (start)" , buckets [0 ])
109+ t .Logf ("✓ Last bucket = %.10f (end = %.10f)" , buckets [len (buckets )- 1 ], end .Seconds ())
110+ })
111+ }
112+
113+ // Test Fix #4: Rounding to significant digits produces clean values
114+ func TestFix4_RoundingProducesCleanValues (t * testing.T ) {
115+ t .Run ("Verify Prometheus output format is clean" , func (t * testing.T ) {
116+ buckets := ExponentialBucketTimeRange (0 , 1 * time .Second , 10 )
117+
118+ for i , v := range buckets {
119+ // Format as Prometheus would (%g format)
120+ prometheusStr := fmt .Sprintf ("%g" , v )
121+
122+ // Check for floating point issue patterns
123+ if len (prometheusStr ) > 12 && v > 0 && v < 10 {
124+ t .Errorf ("Bucket %d has potentially floating point issue in Prometheus output: le=\" %s\" " , i , prometheusStr )
125+ }
126+
127+ t .Logf ("Bucket %d: le=\" %s\" ✓" , i , prometheusStr )
128+ }
129+ })
130+
131+ t .Run ("Compare internal vs Prometheus representation" , func (t * testing.T ) {
132+ buckets := ExponentialBucketTimeRange (0 , 100 * time .Millisecond , 10 )
133+
134+ t .Logf ("\n Internal vs Prometheus representation:" )
135+ for i , v := range buckets {
136+ internal := fmt .Sprintf ("%.17g" , v )
137+ prometheus := fmt .Sprintf ("%g" , v )
138+ t .Logf (" Bucket %d: internal=%.17g, prometheus=le=\" %s\" " , i , v , prometheus )
139+
140+ // Prometheus format should be shorter/cleaner
141+ if len (prometheus ) > len (internal ) {
142+ t .Errorf ("Prometheus format longer than internal for bucket %d" , i )
143+ }
144+ }
145+ })
146+ }
147+
148+ // Test Fix #5: Edge case handling
149+ func TestFix5_EdgeCaseHandling (t * testing.T ) {
150+ testCases := []struct {
151+ name string
152+ start time.Duration
153+ end time.Duration
154+ buckets int
155+ expectedLength int
156+ shouldPanic bool
157+ }{
158+ {"Zero buckets" , 0 , 1 * time .Second , 0 , 1 , false },
159+ {"Negative buckets" , 0 , 1 * time .Second , - 5 , 1 , false },
160+ {"Single bucket" , 0 , 1 * time .Second , 1 , 1 , false },
161+ {"Start equals end" , 1 * time .Second , 1 * time .Second , 10 , 1 , false },
162+ {"Start greater than end" , 5 * time .Second , 1 * time .Second , 10 , 1 , false },
163+ {"Normal case" , 0 , 1 * time .Second , 10 , 10 , false },
164+ }
165+
166+ for _ , tc := range testCases {
167+ t .Run (tc .name , func (t * testing.T ) {
168+ defer func () {
169+ if r := recover (); r != nil {
170+ if ! tc .shouldPanic {
171+ t .Errorf ("Unexpected panic: %v" , r )
172+ }
173+ }
174+ }()
175+
176+ result := ExponentialBucketTimeRange (tc .start , tc .end , tc .buckets )
177+
178+ if len (result ) != tc .expectedLength {
179+ t .Errorf ("Expected %d buckets, got %d" , tc .expectedLength , len (result ))
180+ } else {
181+ t .Logf ("✓ Handled edge case correctly: got %d bucket(s)" , len (result ))
182+ }
183+
184+ t .Logf (" Result: %v" , result )
185+ })
186+ }
187+ }
188+
189+ // Comprehensive test combining all fixes
190+ func TestAllFixes_Comprehensive (t * testing.T ) {
191+ t .Run ("10 buckets from 0 to 1 second" , func (t * testing.T ) {
192+ buckets := ExponentialBucketTimeRange (0 , 1 * time .Second , 10 )
193+
194+ // Fix #2: Exact count
195+ if len (buckets ) != 10 {
196+ t .Errorf ("Expected 10 buckets, got %d" , len (buckets ))
197+ }
198+
199+ // Fix #3: Monotonic
200+ for i := 1 ; i < len (buckets ); i ++ {
201+ if buckets [i ] <= buckets [i - 1 ] {
202+ t .Errorf ("Not monotonic at index %d" , i )
203+ }
204+ }
205+
206+ // Fix #1: Exponential spacing
207+ var ratios []float64
208+ for i := 2 ; i < len (buckets ); i ++ {
209+ if buckets [i - 1 ] > 0 {
210+ ratios = append (ratios , buckets [i ]/ buckets [i - 1 ])
211+ }
212+ }
213+
214+ if len (ratios ) > 1 {
215+ avgRatio := ratios [0 ]
216+ for _ , r := range ratios {
217+ if math .Abs (r - avgRatio ) > 0.01 {
218+ t .Errorf ("Ratio variance too high: %.6f vs %.6f" , r , avgRatio )
219+ }
220+ }
221+ }
222+
223+ // Fix #4: Clean Prometheus output
224+ for i , v := range buckets {
225+ prometheusStr := fmt .Sprintf ("%g" , v )
226+ if len (prometheusStr ) > 12 && v > 0 && v < 10 {
227+ t .Errorf ("Bucket %d has potentially floating point issue in the output: %s" , i , prometheusStr )
228+ }
229+ }
230+
231+ t .Logf ("✓ All fixes verified" )
232+ t .Logf (" Buckets: %v" , buckets )
233+ t .Logf (" Exponential ratio: %.6f" , ratios [0 ])
234+ })
235+ }
236+
237+ // Test for LinearBucketTimeRange (unchanged, for completeness)
238+ func TestLinearBucketTimeRange (t * testing.T ) {
239+ tests := []struct {
240+ name string
241+ start time.Duration
242+ end time.Duration
243+ buckets int
244+ }{
245+ {"10 buckets from 0 to 1 second" , 0 , 1 * time .Second , 10 },
246+ {"5 buckets from 0 to 5 seconds" , 0 , 5 * time .Second , 5 },
247+ }
248+
249+ for _ , tt := range tests {
250+ t .Run (tt .name , func (t * testing.T ) {
251+ buckets := LinearBucketTimeRange (tt .start , tt .end , tt .buckets )
252+
253+ expectedLen := tt .buckets + 1
254+ if len (buckets ) != expectedLen {
255+ t .Errorf ("Expected %d buckets, got %d" , expectedLen , len (buckets ))
256+ }
257+
258+ if buckets [0 ] != tt .start .Seconds () {
259+ t .Errorf ("First bucket = %v, want %v" , buckets [0 ], tt .start .Seconds ())
260+ }
261+
262+ if math .Abs (buckets [len (buckets )- 1 ]- tt .end .Seconds ()) > 0.0001 {
263+ t .Errorf ("Last bucket = %v, want %v" , buckets [len (buckets )- 1 ], tt .end .Seconds ())
264+ }
265+
266+ t .Logf ("Linear buckets: %v" , buckets )
267+ })
268+ }
269+ }
270+
271+ // Test for LinearBucketRange (unchanged, for completeness)
272+ func TestLinearBucketRange (t * testing.T ) {
273+ tests := []struct {
274+ name string
275+ start int64
276+ end int64
277+ buckets int
278+ }{
279+ {"10 buckets from 0 to 100" , 0 , 100 , 10 },
280+ {"5 buckets from 10 to 50" , 10 , 50 , 5 },
281+ }
282+
283+ for _ , tt := range tests {
284+ t .Run (tt .name , func (t * testing.T ) {
285+ buckets := LinearBucketRange (tt .start , tt .end , tt .buckets )
286+
287+ expectedLen := tt .buckets + 1
288+ if len (buckets ) != expectedLen {
289+ t .Errorf ("Expected %d buckets, got %d" , expectedLen , len (buckets ))
290+ }
291+
292+ if buckets [0 ] != float64 (tt .start ) {
293+ t .Errorf ("First bucket = %v, want %v" , buckets [0 ], float64 (tt .start ))
294+ }
295+
296+ if math .Abs (buckets [len (buckets )- 1 ]- float64 (tt .end )) > 0.0001 {
297+ t .Errorf ("Last bucket = %v, want %v" , buckets [len (buckets )- 1 ], float64 (tt .end ))
298+ }
299+
300+ t .Logf ("Linear range buckets: %v" , buckets )
301+ })
302+ }
24303}
0 commit comments