|
|
|
@ -1,15 +1,22 @@ |
|
|
|
|
package expr |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"context" |
|
|
|
|
"encoding/json" |
|
|
|
|
"fmt" |
|
|
|
|
"math" |
|
|
|
|
"slices" |
|
|
|
|
"sort" |
|
|
|
|
"testing" |
|
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/data" |
|
|
|
|
"github.com/stretchr/testify/require" |
|
|
|
|
"golang.org/x/exp/maps" |
|
|
|
|
|
|
|
|
|
"github.com/grafana/grafana/pkg/expr/mathexp" |
|
|
|
|
"github.com/grafana/grafana/pkg/infra/tracing" |
|
|
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt" |
|
|
|
|
"github.com/grafana/grafana/pkg/util" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
func TestNewThresholdCommand(t *testing.T) { |
|
|
|
@ -108,7 +115,7 @@ func TestUnmarshalThresholdCommand(t *testing.T) { |
|
|
|
|
cmd := command.(*ThresholdCommand) |
|
|
|
|
require.Equal(t, []string{"A"}, cmd.NeedsVars()) |
|
|
|
|
require.Equal(t, ThresholdIsAbove, cmd.ThresholdFunc) |
|
|
|
|
require.Equal(t, []float64{20.0, 80.0}, cmd.Conditions) |
|
|
|
|
require.Equal(t, greaterThanPredicate{20.0}, cmd.predicate) |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
@ -173,10 +180,10 @@ func TestUnmarshalThresholdCommand(t *testing.T) { |
|
|
|
|
require.Equal(t, []string{"B"}, cmd.NeedsVars()) |
|
|
|
|
require.Equal(t, []string{"B"}, cmd.LoadingThresholdFunc.NeedsVars()) |
|
|
|
|
require.Equal(t, ThresholdIsAbove, cmd.LoadingThresholdFunc.ThresholdFunc) |
|
|
|
|
require.Equal(t, []float64{100.0}, cmd.LoadingThresholdFunc.Conditions) |
|
|
|
|
require.Equal(t, greaterThanPredicate{100.0}, cmd.LoadingThresholdFunc.predicate) |
|
|
|
|
require.Equal(t, []string{"B"}, cmd.UnloadingThresholdFunc.NeedsVars()) |
|
|
|
|
require.Equal(t, ThresholdIsBelow, cmd.UnloadingThresholdFunc.ThresholdFunc) |
|
|
|
|
require.Equal(t, []float64{31.0}, cmd.UnloadingThresholdFunc.Conditions) |
|
|
|
|
require.Equal(t, lessThanPredicate{31.0}, cmd.UnloadingThresholdFunc.predicate) |
|
|
|
|
require.True(t, cmd.UnloadingThresholdFunc.Invert) |
|
|
|
|
require.NotNil(t, cmd.LoadedDimensions) |
|
|
|
|
actual := make([]uint64, 0, len(cmd.LoadedDimensions)) |
|
|
|
@ -227,74 +234,6 @@ func TestThresholdCommandVars(t *testing.T) { |
|
|
|
|
require.Equal(t, cmd.NeedsVars(), []string{"A"}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestCreateMathExpression(t *testing.T) { |
|
|
|
|
type testCase struct { |
|
|
|
|
description string |
|
|
|
|
expected string |
|
|
|
|
|
|
|
|
|
ref string |
|
|
|
|
function ThresholdType |
|
|
|
|
params []float64 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cases := []testCase{ |
|
|
|
|
{ |
|
|
|
|
description: "is above", |
|
|
|
|
ref: "My Ref", |
|
|
|
|
function: "gt", |
|
|
|
|
params: []float64{0}, |
|
|
|
|
expected: "${My Ref} > 0.000000", |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
description: "is below", |
|
|
|
|
ref: "A", |
|
|
|
|
function: "lt", |
|
|
|
|
params: []float64{0}, |
|
|
|
|
expected: "${A} < 0.000000", |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
description: "is within", |
|
|
|
|
ref: "B", |
|
|
|
|
function: "within_range", |
|
|
|
|
params: []float64{20, 80}, |
|
|
|
|
expected: "${B} > 20.000000 && ${B} < 80.000000", |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
description: "is outside", |
|
|
|
|
ref: "B", |
|
|
|
|
function: "outside_range", |
|
|
|
|
params: []float64{20, 80}, |
|
|
|
|
expected: "${B} < 20.000000 || ${B} > 80.000000", |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for _, tc := range cases { |
|
|
|
|
t.Run(tc.description, func(t *testing.T) { |
|
|
|
|
expr, err := createMathExpression(tc.ref, tc.function, tc.params, false) |
|
|
|
|
|
|
|
|
|
require.Nil(t, err) |
|
|
|
|
require.NotNil(t, expr) |
|
|
|
|
|
|
|
|
|
require.Equal(t, tc.expected, expr) |
|
|
|
|
|
|
|
|
|
t.Run("inverted", func(t *testing.T) { |
|
|
|
|
expr, err := createMathExpression(tc.ref, tc.function, tc.params, true) |
|
|
|
|
require.Nil(t, err) |
|
|
|
|
require.NotNil(t, expr) |
|
|
|
|
|
|
|
|
|
require.Equal(t, fmt.Sprintf("!(%s)", tc.expected), expr) |
|
|
|
|
}) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
t.Run("should error if function is unsupported", func(t *testing.T) { |
|
|
|
|
expr, err := createMathExpression("A", "foo", []float64{0}, false) |
|
|
|
|
require.Equal(t, expr, "") |
|
|
|
|
require.NotNil(t, err) |
|
|
|
|
require.Contains(t, err.Error(), "no such threshold function") |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestIsSupportedThresholdFunc(t *testing.T) { |
|
|
|
|
type testCase struct { |
|
|
|
|
function ThresholdType |
|
|
|
@ -443,3 +382,178 @@ func TestSetLoadedDimensionsToHysteresisCommand(t *testing.T) { |
|
|
|
|
require.Equal(t, fingerprints, cmd.(*HysteresisCommand).LoadedDimensions) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestThresholdExecute(t *testing.T) { |
|
|
|
|
input := map[string]mathexp.Value{ |
|
|
|
|
//
|
|
|
|
|
"no-data": mathexp.NewNoData(), |
|
|
|
|
//
|
|
|
|
|
"series - numbers": newSeries(8, 9, 10, 11, 12), |
|
|
|
|
"series - empty": newSeriesPointer(), |
|
|
|
|
"series - all nils": newSeriesPointer(nil, nil, nil), |
|
|
|
|
"series - with labels": newSeriesWithLabels(data.Labels{"test": "test"}, nil, util.Pointer(float64(9)), nil, util.Pointer(float64(11)), nil), |
|
|
|
|
"series - with NaNs": newSeries(math.NaN(), math.NaN(), math.NaN()), |
|
|
|
|
//
|
|
|
|
|
"scalar - nil": newScalar(nil), |
|
|
|
|
"scalar - NaN": newScalar(util.Pointer(math.NaN())), |
|
|
|
|
"scalar - 8": newScalar(util.Pointer(float64(8))), |
|
|
|
|
"scalar - 9": newScalar(util.Pointer(float64(9))), |
|
|
|
|
"scalar - 10": newScalar(util.Pointer(float64(10))), |
|
|
|
|
"scalar - 11": newScalar(util.Pointer(float64(11))), |
|
|
|
|
"scalar - 12": newScalar(util.Pointer(float64(12))), |
|
|
|
|
//
|
|
|
|
|
"number - nil": newNumber(data.Labels{"number": "test"}, nil), |
|
|
|
|
"number - NaN": newNumber(data.Labels{"number": "test"}, util.Pointer(math.NaN())), |
|
|
|
|
"number - 8": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(8))), |
|
|
|
|
"number - 9": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(9))), |
|
|
|
|
"number - 10": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(10))), |
|
|
|
|
"number - 11": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(11))), |
|
|
|
|
"number - 12": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(12))), |
|
|
|
|
} |
|
|
|
|
keys := maps.Keys(input) |
|
|
|
|
slices.Sort(keys) |
|
|
|
|
testCases := []struct { |
|
|
|
|
name string |
|
|
|
|
pred predicate |
|
|
|
|
expected map[string]mathexp.Value |
|
|
|
|
errorMsg string |
|
|
|
|
}{ |
|
|
|
|
{ |
|
|
|
|
name: "greater than 10", |
|
|
|
|
pred: greaterThanPredicate{10.0}, |
|
|
|
|
expected: map[string]mathexp.Value{ |
|
|
|
|
//
|
|
|
|
|
"no-data": mathexp.NewNoData(), |
|
|
|
|
//
|
|
|
|
|
"series - numbers": newSeries(0, 0, 0, 1, 1), |
|
|
|
|
"series - empty": newSeriesPointer(), |
|
|
|
|
"series - all nils": newSeriesPointer(nil, nil, nil), |
|
|
|
|
"series - with labels": newSeriesWithLabels(data.Labels{"test": "test"}, nil, util.Pointer(float64(0)), nil, util.Pointer(float64(1)), nil), |
|
|
|
|
"series - with NaNs": newSeries(0, 0, 0), |
|
|
|
|
//
|
|
|
|
|
"scalar - nil": newScalar(nil), |
|
|
|
|
"scalar - NaN": newScalar(util.Pointer(float64(0))), |
|
|
|
|
"scalar - 8": newScalar(util.Pointer(float64(0))), |
|
|
|
|
"scalar - 9": newScalar(util.Pointer(float64(0))), |
|
|
|
|
"scalar - 10": newScalar(util.Pointer(float64(0))), |
|
|
|
|
"scalar - 11": newScalar(util.Pointer(float64(1))), |
|
|
|
|
"scalar - 12": newScalar(util.Pointer(float64(1))), |
|
|
|
|
//
|
|
|
|
|
"number - nil": newNumber(data.Labels{"number": "test"}, nil), |
|
|
|
|
"number - NaN": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), |
|
|
|
|
"number - 8": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), |
|
|
|
|
"number - 9": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), |
|
|
|
|
"number - 10": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), |
|
|
|
|
"number - 11": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))), |
|
|
|
|
"number - 12": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))), |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: "less than 10", |
|
|
|
|
pred: lessThanPredicate{10.0}, |
|
|
|
|
expected: map[string]mathexp.Value{ |
|
|
|
|
//
|
|
|
|
|
"no-data": mathexp.NewNoData(), |
|
|
|
|
//
|
|
|
|
|
"series - numbers": newSeries(1, 1, 0, 0, 0), |
|
|
|
|
"series - empty": newSeriesPointer(), |
|
|
|
|
"series - all nils": newSeriesPointer(nil, nil, nil), |
|
|
|
|
"series - with labels": newSeriesWithLabels(data.Labels{"test": "test"}, nil, util.Pointer(float64(1)), nil, util.Pointer(float64(0)), nil), |
|
|
|
|
"series - with NaNs": newSeries(0, 0, 0), |
|
|
|
|
//
|
|
|
|
|
"scalar - nil": newScalar(nil), |
|
|
|
|
"scalar - NaN": newScalar(util.Pointer(float64(0))), |
|
|
|
|
"scalar - 8": newScalar(util.Pointer(float64(1))), |
|
|
|
|
"scalar - 9": newScalar(util.Pointer(float64(1))), |
|
|
|
|
"scalar - 10": newScalar(util.Pointer(float64(0))), |
|
|
|
|
"scalar - 11": newScalar(util.Pointer(float64(0))), |
|
|
|
|
"scalar - 12": newScalar(util.Pointer(float64(0))), |
|
|
|
|
//
|
|
|
|
|
"number - nil": newNumber(data.Labels{"number": "test"}, nil), |
|
|
|
|
"number - NaN": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), |
|
|
|
|
"number - 8": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))), |
|
|
|
|
"number - 9": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))), |
|
|
|
|
"number - 10": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), |
|
|
|
|
"number - 11": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), |
|
|
|
|
"number - 12": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: "within range (8,11)", |
|
|
|
|
pred: withinRangePredicate{8, 11}, |
|
|
|
|
expected: map[string]mathexp.Value{ |
|
|
|
|
//
|
|
|
|
|
"no-data": mathexp.NewNoData(), |
|
|
|
|
//
|
|
|
|
|
"series - numbers": newSeries(0, 1, 1, 0, 0), |
|
|
|
|
"series - empty": newSeriesPointer(), |
|
|
|
|
"series - all nils": newSeriesPointer(nil, nil, nil), |
|
|
|
|
"series - with labels": newSeriesWithLabels(data.Labels{"test": "test"}, nil, util.Pointer(float64(1)), nil, util.Pointer(float64(0)), nil), |
|
|
|
|
"series - with NaNs": newSeries(0, 0, 0), |
|
|
|
|
//
|
|
|
|
|
"scalar - nil": newScalar(nil), |
|
|
|
|
"scalar - NaN": newScalar(util.Pointer(float64(0))), |
|
|
|
|
"scalar - 8": newScalar(util.Pointer(float64(0))), |
|
|
|
|
"scalar - 9": newScalar(util.Pointer(float64(1))), |
|
|
|
|
"scalar - 10": newScalar(util.Pointer(float64(1))), |
|
|
|
|
"scalar - 11": newScalar(util.Pointer(float64(0))), |
|
|
|
|
"scalar - 12": newScalar(util.Pointer(float64(0))), |
|
|
|
|
//
|
|
|
|
|
"number - nil": newNumber(data.Labels{"number": "test"}, nil), |
|
|
|
|
"number - NaN": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), |
|
|
|
|
"number - 8": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), |
|
|
|
|
"number - 9": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))), |
|
|
|
|
"number - 10": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))), |
|
|
|
|
"number - 11": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), |
|
|
|
|
"number - 12": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: "outside range (8, 11)", |
|
|
|
|
pred: outsideRangePredicate{8, 11}, |
|
|
|
|
expected: map[string]mathexp.Value{ |
|
|
|
|
//
|
|
|
|
|
"no-data": mathexp.NewNoData(), |
|
|
|
|
//
|
|
|
|
|
"series - numbers": newSeries(0, 0, 0, 0, 1), |
|
|
|
|
"series - empty": newSeriesPointer(), |
|
|
|
|
"series - all nils": newSeriesPointer(nil, nil, nil), |
|
|
|
|
"series - with labels": newSeriesWithLabels(data.Labels{"test": "test"}, nil, util.Pointer(float64(0)), nil, util.Pointer(float64(0)), nil), |
|
|
|
|
"series - with NaNs": newSeries(0, 0, 0), |
|
|
|
|
//
|
|
|
|
|
"scalar - nil": newScalar(nil), |
|
|
|
|
"scalar - NaN": newScalar(util.Pointer(float64(0))), |
|
|
|
|
"scalar - 8": newScalar(util.Pointer(float64(0))), |
|
|
|
|
"scalar - 9": newScalar(util.Pointer(float64(0))), |
|
|
|
|
"scalar - 10": newScalar(util.Pointer(float64(0))), |
|
|
|
|
"scalar - 11": newScalar(util.Pointer(float64(0))), |
|
|
|
|
"scalar - 12": newScalar(util.Pointer(float64(1))), |
|
|
|
|
//
|
|
|
|
|
"number - nil": newNumber(data.Labels{"number": "test"}, nil), |
|
|
|
|
"number - NaN": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), |
|
|
|
|
"number - 8": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), |
|
|
|
|
"number - 9": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), |
|
|
|
|
"number - 10": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), |
|
|
|
|
"number - 11": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(0))), |
|
|
|
|
"number - 12": newNumber(data.Labels{"number": "test"}, util.Pointer(float64(1))), |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
for _, tc := range testCases { |
|
|
|
|
t.Run(tc.name, func(t *testing.T) { |
|
|
|
|
cmd := ThresholdCommand{ |
|
|
|
|
predicate: tc.pred, |
|
|
|
|
ReferenceVar: "A", |
|
|
|
|
} |
|
|
|
|
for _, name := range keys { |
|
|
|
|
t.Run(name, func(t *testing.T) { |
|
|
|
|
result, err := cmd.Execute(context.Background(), time.Now(), mathexp.Vars{ |
|
|
|
|
"A": newResults(input[name]), |
|
|
|
|
}, tracing.InitializeTracerForTest()) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
require.Equal(t, newResults(tc.expected[name]), result) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|