From f2b7e719543408650fef7e3290f77962654453a9 Mon Sep 17 00:00:00 2001 From: Will Hawkins Date: Fri, 14 Jul 2023 10:59:05 -0400 Subject: [Bugfix]: Out of range percentile calculations It was possible for a user of percentile-calculation functions to request a percentile that caused the underlying array of values to be accessed out of bounds. This patch fixes that error. Signed-off-by: Will Hawkins --- series/series.go | 3 +++ series/series_test.go | 37 +++++++++++++++++++++++++++++++++++-- utilities/math.go | 9 ++++++--- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/series/series.go b/series/series.go index 43d9809..6b9af1f 100644 --- a/series/series.go +++ b/series/series.go @@ -127,6 +127,9 @@ func (wsi *windowSeriesWindowOnlyImpl[Data, Bucket]) previousIndex(currentIndex func (wsi *windowSeriesWindowOnlyImpl[Data, Bucket]) toArray() []utilities.Optional[Data] { result := make([]utilities.Optional[Data], wsi.windowSize) + if wsi.empty { + return result + } iterator := wsi.latestIndex parallelIterator := 0 for { diff --git a/series/series_test.go b/series/series_test.go index 5d2ab57..3ed752d 100644 --- a/series/series_test.go +++ b/series/series_test.go @@ -95,6 +95,25 @@ func Test_ForeverValues(test *testing.T) { } } +func Test_WindowOnly_no_values_getvalues(test *testing.T) { + expectedLen := 5 + series := newWindowSeriesWindowOnlyImpl[float64, int](5) + result := series.GetValues() + allZeros := true + for _, v := range result { + if utilities.IsSome(v) { + allZeros = false + break + } + } + if len(result) != expectedLen { + test.Fatalf("GetValues of empty window-only series returned list with incorrect size.") + } + if !allZeros { + test.Fatalf("GetValues of empty window-only series returned list with some values.") + } +} + func Test_WindowOnlySequentialIncreasesAlwaysLessThan(test *testing.T) { series := newWindowSeriesWindowOnlyImpl[float64, int](10) previous := float64(1.0) @@ -154,11 +173,18 @@ func Test_Forever_degenerate_percentile_too_high(test *testing.T) { func Test_Forever_degenerate_percentile_too_low(test *testing.T) { series := newWindowSeriesForeverImpl[int, int]() - if complete, result := Percentile[int, int](series, -1); !complete || result != 0.0 { + if complete, result := Percentile[int, int](series, 0); !complete || result != 0.0 { test.Fatalf("(infinite) Series percentile of -1 failed.") } } +func Test_Forever_degenerate_percentile_no_values(test *testing.T) { + series := newWindowSeriesForeverImpl[int, int]() + if complete, p := Percentile[int, int](series, 50); !complete || p != 0 { + test.Fatalf("empty series percentile of 50 failed.") + } +} + /////////// func Test_Forever90_percentile(test *testing.T) { @@ -557,11 +583,18 @@ func Test_WindowOnly_degenerate_percentile_too_high(test *testing.T) { func Test_WindowOnly_degenerate_percentile_too_low(test *testing.T) { series := newWindowSeriesWindowOnlyImpl[int, int](21) - if complete, p := Percentile[int, int](series, -1); complete != false || p != 0 { + if complete, p := Percentile[int, int](series, 0); complete != false || p != 0 { test.Fatalf("Series percentile of -1 failed.") } } +func Test_WindowOnly_degenerate_percentile_no_values(test *testing.T) { + series := newWindowSeriesWindowOnlyImpl[int, int](0) + if complete, p := Percentile[int, int](series, 50); !complete || p != 0 { + test.Fatalf("empty series percentile of 50 failed.") + } +} + func Test_WindowOnly90_percentile(test *testing.T) { var expected int = 10 series := newWindowSeriesWindowOnlyImpl[int, int](10) diff --git a/utilities/math.go b/utilities/math.go index 1ecca61..96b9f10 100644 --- a/utilities/math.go +++ b/utilities/math.go @@ -36,15 +36,18 @@ func CalculateAverage[T Number](elements []T) float64 { func CalculatePercentile[T Number]( elements []T, - p int, + p uint, ) (result T) { result = T(0) - if p < 0 || p > 100 { + if p < 1 || p > 100 { return } sort.Slice(elements, func(l int, r int) bool { return elements[l] < elements[r] }) - pindex := int64((float64(p) / float64(100)) * float64(len(elements))) + pindex := int((float64(p) / float64(100)) * float64(len(elements))) + if pindex >= len(elements) { + return + } result = elements[pindex] return } -- cgit v1.2.3