stillbox/internal/timeseries/timeseries_test.go
Daniel Ponte cdc2034af6 Initial alerting
wip

kind of working

wip
2024-10-31 13:15:48 -04:00

334 lines
7.7 KiB
Go

package timeseries
import (
"testing"
"time"
)
// TODO: do table based testing
func setup() (*TimeSeries, *clock.Mock) {
clock := clock.NewMock()
ts, _ := NewTimeSeries(
WithClock(clock),
WithGranularities(
[]Granularity{
{time.Second, 60},
{time.Minute, 60},
},
),
)
return ts, clock
}
func TestClock(t *testing.T) {
clock := &defaultClock{}
// there is a small chance this won't pass
if clock.Now().Truncate(time.Second) != time.Now().Truncate(time.Second) {
t.Errorf("default clock does not track time.Now")
}
}
func TestNewTimeSeries(t *testing.T) {
ts, err := NewTimeSeries()
if ts == nil {
t.Errorf("constructor returned nil")
}
if err != nil {
t.Errorf("should not return error")
}
}
func TestNewTimeSeriesWithGranularities(t *testing.T) {
granularities := []Granularity{
{time.Second, 60},
{time.Minute, 60},
{time.Hour, 24},
}
ts, err := NewTimeSeries(WithGranularities(granularities))
if ts == nil || err != nil {
t.Error("could not create time series")
}
badGranularities := []Granularity{
{time.Minute, 60},
{time.Second, 60},
{time.Hour, 24},
}
_, err = NewTimeSeries(WithGranularities(badGranularities))
if err != ErrBadGranularities {
t.Error("should not accept decreasing granularities")
}
badGranularities = []Granularity{
{time.Minute, 60},
{time.Second, 0},
{time.Hour, 24},
}
_, err = NewTimeSeries(WithGranularities(badGranularities))
if err != ErrBadGranularities {
t.Error("should not accept granularities with zero count")
}
_, err = NewTimeSeries(WithGranularities([]Granularity{}))
if err != ErrBadGranularities {
t.Error("should not accept empty granularities")
}
}
func TestNewTimeSeriesWithClock(t *testing.T) {
clock := clock.NewMock()
ts, _ := NewTimeSeries(WithClock(clock))
ts.Increase(2)
clock.Add(time.Second * 1)
ts.Increase(1)
res, _ := ts.Range(time.Unix(0, 0), time.Unix(1, 0))
if res != 2 {
t.Errorf("expected %d got %f", 2, res)
}
}
func TestRecentSeconds(t *testing.T) {
ts, clock := setup()
clock.Add(time.Minute * 5)
ts.Increase(1)
clock.Add(time.Second * 1)
ts.Increase(2)
clock.Add(time.Second * 1)
ts.Increase(3)
res, _ := ts.Recent(time.Second)
if res != 2 {
t.Errorf("expected %d got %f", 2, res)
}
res, _ = ts.Recent(2 * time.Second)
if res != 3 {
t.Errorf("expected %d got %f", 3, res)
}
// test earliest second
clock.Add(57 * time.Second) // time: 09:05:59
res, _ = ts.Recent(59 * time.Second)
if res != 6 {
t.Errorf("expected %d got %f", 6, res)
}
// test future time
clock.Add(1 * time.Second)
clock.Add(57 * time.Second) // time: 09:06:00
res, _ = ts.Recent(59 * time.Second)
if res != 0 {
t.Errorf("expected %d got %f", 0, res)
}
}
func TestRecentMinutes(t *testing.T) {
ts, clock := setup()
clock.Add(time.Minute * 1) // 09:01:00
ts.Increase(60)
clock.Add(time.Minute * 1) // 09:02:00
ts.Increase(1)
clock.Add(time.Minute * 1) // 09:03:00
ts.Increase(60)
clock.Add(time.Second * 1) // 09:03:01
ts.Increase(3)
// test interpolation at beginning
// 59/60 * 60 + 1 + 60 = 120
res, _ := ts.Recent(2 * time.Minute)
if res != 120 {
t.Errorf("expected %d got %f", 120, res)
}
// test interpolation at end
// 60/2 = 30
res, _ = ts.Range(
clock.Now().Add(-2*time.Minute+-1*time.Second), // 09:01:00
clock.Now().Add(-1*time.Minute+-31*time.Second), // 09:01:30
)
if res != 30 {
t.Errorf("expected %d got %f", 30, res)
}
// get from earliest data point
clock.Add(time.Second*59 + time.Minute*56)
ts.Increase(60)
clock.Add(time.Minute * 1)
ts.Increase(70)
clock.Add(time.Minute * 59)
res, _ = ts.Recent(time.Minute * 60)
if res != 70 {
t.Errorf("expected %d got %f", 70, res)
}
}
func TestRecentWholeRange(t *testing.T) {
ts, clock := setup()
clock.Add(time.Minute * 1) // 09:01:00
ts.Increase(60)
clock.Add(time.Minute * 1) // 09:02:00
ts.Increase(1)
clock.Add(time.Minute * 1) // 09:03:00
ts.Increase(60)
clock.Add(time.Second * 1) // 09:03:01
ts.Increase(3)
// 60 + 1 + 60 = 121
res, _ := ts.Recent(60 * time.Minute)
if res != 121 {
t.Errorf("expected %d got %f", 62, res)
}
}
func TestRecentWholeRangeBig(t *testing.T) {
ts, clock := setup()
clock.Add(time.Minute * 1) // 09:01:00
ts.Increase(60)
clock.Add(time.Minute * 1) // 09:02:00
ts.Increase(1)
clock.Add(time.Minute * 1) // 09:03:00
ts.Increase(60)
clock.Add(time.Second * 1) // 09:03:01
ts.Increase(3)
// 60 + 1 + 60 = 121
res, _ := ts.Recent(120 * time.Minute)
if res != 121 {
t.Errorf("expected %d got %f", 121, res)
}
}
func TestRangeEndInFuture(t *testing.T) {
ts, clock := setup()
clock.Add(time.Minute * 1) // 09:01:00
ts.Increase(1)
res, _ := ts.Range(clock.Now().Add(-1*time.Minute), clock.Now().Add(5*time.Minute))
if res != 0 {
t.Errorf("expected %d got %f", 0, res)
}
}
func TestRangeBadRange(t *testing.T) {
ts, clock := setup()
clock.Add(time.Minute * 1) // 09:01:00
ts.Increase(60)
clock.Add(time.Minute * 1) // 09:02:00
ts.Increase(1)
clock.Add(time.Minute * 1) // 09:03:00
ts.Increase(60)
clock.Add(time.Second * 1) // 09:03:01
ts.Increase(3)
// start is after end
_, err := ts.Range(clock.Now().Add(time.Minute), clock.Now())
if err != ErrBadRange {
t.Errorf("should return ErrBadRange")
}
// range is after end
_, err = ts.Range(clock.Now().Add(time.Minute), clock.Now().Add(5*time.Minute))
if err != ErrRangeNotCovered {
t.Errorf("should return ErrRangeNotCovered")
}
// range is before start
_, err = ts.Range(clock.Now().Add(-5*time.Hour), clock.Now().Add(-4*time.Hour))
if err != ErrRangeNotCovered {
t.Errorf("should return ErrRangeNotCovered")
}
}
func TestIncrease(t *testing.T) {
ts, clock := setup()
// time 12:00
ts.Increase(2)
clock.Add(time.Minute * 1) // time: 12:01:00
ts.Increase(4)
clock.Add(time.Minute * 1) // time: 12:02:00
ts.Increase(6)
clock.Add(time.Second * 10) // time: 12:02:10
ts.Increase(2)
clock.Add(time.Second * 10) // time: 12:02:20
ts.Increase(2)
clock.Add(time.Second * 10) // time: 12:02:30
ts.Increase(2)
clock.Add(time.Second * 10) // time: 12:02:40
ts.Increase(2)
clock.Add(time.Second * 10) // time: 12:02:50
ts.Increase(2)
clock.Add(time.Second * 10) // time: 12:03:00
ts.Increase(2)
// get range from 12:00:30 - 12:02:30
// 0.5 * 2 + 4 + 0.5 * 16 = 13
res, _ := ts.Range(clock.Now().Add(-time.Second*150), clock.Now().Add(-time.Second*30))
if res != 13 {
t.Errorf("expected %d got %f", 13, res)
}
// get range from 12:01:00 - 12:02:00
// = 4
res, _ = ts.Range(clock.Now().Add(-time.Second*120), clock.Now().Add(-time.Second*60))
if res != 4 {
t.Errorf("expected %d got %f", 4, res)
}
}
func TestIncreasePending(t *testing.T) {
ts, clock := setup()
ts.Increase(1) // this should advance and reset pending
ts.Increase(1) // this should increase pending
clock.Add(time.Second)
ts.Increase(1)
res, _ := ts.Recent(59 * time.Second)
if res != 2 {
t.Errorf("expected %d got %f", 2, res)
}
clock.Add(time.Second) // the latest data gets merged in because time advanced
res, _ = ts.Recent(59 * time.Second)
if res != 3 {
t.Errorf("expected %d got %f", 3, res)
}
}
func TestIncreaseAtTime(t *testing.T) {
ts, clock := setup()
ts.Increase(60) // time: 09:00:00
clock.Add(time.Second) // time: 09:00:01
ts.IncreaseAtTime(60, clock.Now().Add(-1*time.Minute)) // time: 08:59:01
ts.Increase(1) // time: 09:00:01
// from: 08:59:01 - 09:00:01
// (59/60 * 60) + 60 = 119
res, _ := ts.Recent(time.Minute)
if res != 119 {
t.Errorf("expected %d got %f", 119, res)
}
// from: 08:59:00 - 09:00:00
// 60
res, _ = ts.Range(
clock.Now().Add(-1*time.Minute+-1*time.Second),
clock.Now().Add(-1*time.Second),
)
if res != 60 {
t.Errorf("expected %d got %f", 60, res)
}
}