From 14e69b9eb88dab40da959f9d79209352a670500b Mon Sep 17 00:00:00 2001 From: novahe Date: Tue, 25 Feb 2025 20:08:04 +0800 Subject: [PATCH] fix array index out of bounds --- summary/percentiles.go | 8 +++++ summary/percentiles_test.go | 58 +++++++++++++++++++++++++++---------- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/summary/percentiles.go b/summary/percentiles.go index 8ce706ad9e..4d21d38c54 100644 --- a/summary/percentiles.go +++ b/summary/percentiles.go @@ -44,9 +44,17 @@ func (s Uint64Slice) GetPercentile(d float64) uint64 { return 0 } sort.Sort(s) + if d == 1.0 { + return s[count-1] + } n := float64(d * (float64(count) + 1)) idx, frac := math.Modf(n) index := int(idx) + + if index < 1 { + index = 1 + } + percentile := float64(s[index-1]) if index > 1 && index < count { percentile += frac * float64(s[index]-s[index-1]) diff --git a/summary/percentiles_test.go b/summary/percentiles_test.go index 4dbe3665d3..c649969f9b 100644 --- a/summary/percentiles_test.go +++ b/summary/percentiles_test.go @@ -30,22 +30,48 @@ func assertPercentile(t *testing.T, s Uint64Slice, f float64, want uint64) { } func TestPercentile(t *testing.T) { - N := 100 - s := make(Uint64Slice, 0, N) - for i := N; i > 0; i-- { - s = append(s, uint64(i)) - } - assertPercentile(t, s, 0.2, 20) - assertPercentile(t, s, 0.7, 70) - assertPercentile(t, s, 0.9, 90) - N = 105 - for i := 101; i <= N; i++ { - s = append(s, uint64(i)) - } - // 90p should be between 94 and 95. Promoted to 95. - assertPercentile(t, s, 0.2, 21) - assertPercentile(t, s, 0.7, 74) - assertPercentile(t, s, 0.9, 95) + tests := []struct { + name string + data Uint64Slice + p float64 + want uint64 + }{ + + {"20p of 100 elements", generateSlice(100, true), 0.2, 20}, + {"70p of 100 elements", generateSlice(100, true), 0.7, 70}, + {"90p of 100 elements", generateSlice(100, true), 0.9, 90}, + + // 90p should be between 94 and 95. Promoted to 95. + {"20p of 105 elements", generateSlice(105, true), 0.2, 21}, + {"70p of 105 elements", generateSlice(105, true), 0.7, 74}, + {"90p of 105 elements", generateSlice(105, true), 0.9, 95}, + + // boundary value + {"90p of 5 elements", Uint64Slice{1, 2, 3, 4, 5}, 0.9, 5}, + {"Out of range p > 1", Uint64Slice{1, 2, 3, 4, 5}, 1.1, 0}, + {"Empty slice", Uint64Slice{}, 0, 0}, + {"Zero percentile", generateSlice(100, true), 0, 1}, + {"100p of 11 elements", Uint64Slice{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, 1.0, 11}, + {"100p of 105 elements", generateSlice(105, true), 1.0, 105}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertPercentile(t, tt.data, tt.p, tt.want) + }) + } +} + +func generateSlice(n int, descending bool) Uint64Slice { + s := make(Uint64Slice, 0, n) + for i := 1; i <= n; i++ { + if descending { + s = append(s, uint64(n-i+1)) + } else { + s = append(s, uint64(i)) + } + } + return s } func TestMean(t *testing.T) {