* add function to convert StateTransition to LokiEntry * add QueryResultBuilder * update backtesting to produce result similar to historian * make shouldRecord public * filter out noop transitions * add experimental front-end * add new fields * move conversion of api model to AlertRule to validation * add extra labels * calculate tick timestamp using the same logic as in scheduler * implement correct logic of calculating first evaluation timestamp * add uid, group and folder uid they are needed for jitter strategy * add JitterOffsetInDuration and JitterStrategy.String() * add config `backtesting_max_evaluations` to [unified_alerting] (not documented for now) * remove obsolete tests * elevate permisisons for backtesting endpoint * move backtesting to separate dir
112 lines
3.3 KiB
Go
112 lines
3.3 KiB
Go
package backtesting
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"math/rand"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/eval/eval_mocks"
|
|
)
|
|
|
|
func TestQueryEvaluator_Eval(t *testing.T) {
|
|
ctx := context.Background()
|
|
interval := time.Duration(rand.Int63n(9)+1) * time.Second
|
|
times := rand.Intn(11) + 5
|
|
from := time.Now().Add(-time.Duration(times) * interval)
|
|
|
|
t.Run("should evaluate query", func(t *testing.T) {
|
|
m := &eval_mocks.ConditionEvaluatorMock{}
|
|
expectedResults := eval.Results{}
|
|
m.EXPECT().Evaluate(mock.Anything, mock.Anything).Return(expectedResults, nil)
|
|
evaluator := queryEvaluator{
|
|
eval: m,
|
|
}
|
|
|
|
intervals := make([]time.Time, times)
|
|
|
|
err := evaluator.Eval(ctx, from, interval, times, func(idx int, now time.Time, results eval.Results) (bool, error) {
|
|
intervals[idx] = now
|
|
return true, nil
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, intervals, times)
|
|
|
|
expected := from
|
|
for idx, actual := range intervals {
|
|
assert.Equalf(t, expected, actual, "item at index %d is not times of interval %v", idx, interval)
|
|
expected = expected.Add(interval)
|
|
}
|
|
m.AssertNumberOfCalls(t, "Evaluate", times)
|
|
for _, now := range intervals {
|
|
m.AssertCalled(t, "Evaluate", ctx, now)
|
|
}
|
|
})
|
|
|
|
t.Run("should stop evaluation", func(t *testing.T) {
|
|
t.Run("when evaluation fails", func(t *testing.T) {
|
|
m := &eval_mocks.ConditionEvaluatorMock{}
|
|
expectedResults := eval.Results{}
|
|
expectedError := errors.New("test")
|
|
m.EXPECT().Evaluate(mock.Anything, mock.Anything).Return(expectedResults, nil).Times(3)
|
|
m.EXPECT().Evaluate(mock.Anything, mock.Anything).Return(nil, expectedError).Once()
|
|
evaluator := queryEvaluator{
|
|
eval: m,
|
|
}
|
|
|
|
intervals := make([]time.Time, 0, times)
|
|
|
|
err := evaluator.Eval(ctx, from, interval, times, func(idx int, now time.Time, results eval.Results) (bool, error) {
|
|
intervals = append(intervals, now)
|
|
return true, nil
|
|
})
|
|
require.ErrorIs(t, err, expectedError)
|
|
require.Len(t, intervals, 3)
|
|
})
|
|
|
|
t.Run("when callback fails", func(t *testing.T) {
|
|
m := &eval_mocks.ConditionEvaluatorMock{}
|
|
expectedResults := eval.Results{}
|
|
expectedError := errors.New("test")
|
|
m.EXPECT().Evaluate(mock.Anything, mock.Anything).Return(expectedResults, nil)
|
|
evaluator := queryEvaluator{
|
|
eval: m,
|
|
}
|
|
|
|
intervals := make([]time.Time, 0, times)
|
|
|
|
err := evaluator.Eval(ctx, from, interval, times, func(idx int, now time.Time, results eval.Results) (bool, error) {
|
|
if len(intervals) > 3 {
|
|
return false, expectedError
|
|
}
|
|
intervals = append(intervals, now)
|
|
return true, nil
|
|
})
|
|
require.ErrorIs(t, err, expectedError)
|
|
})
|
|
|
|
t.Run("when callback does not want to continue", func(t *testing.T) {
|
|
m := &eval_mocks.ConditionEvaluatorMock{}
|
|
expectedResults := eval.Results{}
|
|
m.EXPECT().Evaluate(mock.Anything, mock.Anything).Return(expectedResults, nil)
|
|
evaluator := queryEvaluator{
|
|
eval: m,
|
|
}
|
|
|
|
evaluated := 0
|
|
err := evaluator.Eval(ctx, from, interval, times, func(idx int, now time.Time, results eval.Results) (bool, error) {
|
|
evaluated++
|
|
return evaluated <= 2, nil
|
|
})
|
|
require.NoError(t, err, nil)
|
|
require.Equal(t, 3, evaluated)
|
|
})
|
|
})
|
|
}
|