The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
grafana/pkg/expr/classic/classic_test.go

406 lines
9.2 KiB

package classic
import (
"context"
"encoding/json"
"testing"
"time"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/stretchr/testify/require"
ptr "github.com/xorcare/pointer"
"github.com/grafana/grafana/pkg/expr/mathexp"
)
func TestConditionsCmd(t *testing.T) {
tests := []struct {
name string
cmd *ConditionsCmd
vars mathexp.Vars
expected func() mathexp.Results
}{{
name: "single query and single condition",
vars: mathexp.Vars{
"A": mathexp.Results{
Values: []mathexp.Value{
valBasedSeries(ptr.Float64(30), ptr.Float64(40)),
},
},
},
cmd: &ConditionsCmd{
Conditions: []condition{
{
InputRefID: "A",
Reducer: reducer("avg"),
Operator: "and",
Evaluator: &thresholdEvaluator{Type: "gt", Threshold: 34},
},
}},
expected: func() mathexp.Results {
v := valBasedNumber(ptr.Float64(1))
v.SetMeta([]EvalMatch{{Value: ptr.Float64(35)}})
return mathexp.NewResults(v)
},
}, {
name: "single query and single condition - empty series",
vars: mathexp.Vars{
"A": mathexp.Results{
Values: []mathexp.Value{
valBasedSeries(),
},
},
},
cmd: &ConditionsCmd{
Conditions: []condition{
{
InputRefID: "A",
Reducer: reducer("avg"),
Operator: "and",
Evaluator: &thresholdEvaluator{Type: "gt", Threshold: 34},
},
}},
expected: func() mathexp.Results {
v := valBasedNumber(nil)
v.SetMeta([]EvalMatch{{Metric: "NoData"}})
return mathexp.NewResults(v)
},
}, {
name: "single query and single condition - empty series and not empty series",
vars: mathexp.Vars{
"A": mathexp.Results{
Values: []mathexp.Value{
valBasedSeries(),
valBasedSeries(ptr.Float64(3)),
},
},
},
cmd: &ConditionsCmd{
Conditions: []condition{
{
InputRefID: "A",
Reducer: reducer("avg"),
Operator: "and",
Evaluator: &thresholdEvaluator{Type: "gt", Threshold: .5},
},
}},
expected: func() mathexp.Results {
v := valBasedNumber(ptr.Float64(1))
v.SetMeta([]EvalMatch{{Value: ptr.Float64(3)}})
return mathexp.NewResults(v)
},
}, {
name: "single query and two conditions",
vars: mathexp.Vars{
"A": mathexp.Results{
Values: []mathexp.Value{
valBasedSeries(ptr.Float64(30), ptr.Float64(40)),
},
},
},
cmd: &ConditionsCmd{
Conditions: []condition{
{
InputRefID: "A",
Reducer: reducer("max"),
Evaluator: &thresholdEvaluator{Type: "gt", Threshold: 34},
},
{
InputRefID: "A",
Reducer: reducer("min"),
Operator: "or",
Evaluator: &thresholdEvaluator{Type: "gt", Threshold: 12},
},
}},
expected: func() mathexp.Results {
v := valBasedNumber(ptr.Float64(1))
v.SetMeta([]EvalMatch{{Value: ptr.Float64(40)}, {Value: ptr.Float64(30)}})
return mathexp.NewResults(v)
},
}, {
name: "single query and single condition - multiple series (one true, one not == true)",
vars: mathexp.Vars{
"A": mathexp.Results{
Values: []mathexp.Value{
valBasedSeriesWithLabels(data.Labels{"h": "1"}, ptr.Float64(30), ptr.Float64(40)),
valBasedSeries(ptr.Float64(0), ptr.Float64(10)),
},
},
},
cmd: &ConditionsCmd{
Conditions: []condition{
{
InputRefID: "A",
Reducer: reducer("avg"),
Operator: "and",
Evaluator: &thresholdEvaluator{Type: "gt", Threshold: 34},
},
}},
expected: func() mathexp.Results {
v := valBasedNumber(ptr.Float64(1))
v.SetMeta([]EvalMatch{{Value: ptr.Float64(35), Labels: data.Labels{"h": "1"}}})
return mathexp.NewResults(v)
},
}, {
name: "single query and single condition - multiple series (one not true, one true == true)",
vars: mathexp.Vars{
"A": mathexp.Results{
Values: []mathexp.Value{
valBasedSeries(ptr.Float64(0), ptr.Float64(10)),
valBasedSeries(ptr.Float64(30), ptr.Float64(40)),
},
},
},
cmd: &ConditionsCmd{
Conditions: []condition{
{
InputRefID: "A",
Reducer: reducer("avg"),
Operator: "and",
Evaluator: &thresholdEvaluator{Type: "gt", Threshold: 34},
},
}},
expected: func() mathexp.Results {
v := valBasedNumber(ptr.Float64(1))
v.SetMeta([]EvalMatch{{Value: ptr.Float64(35)}})
return mathexp.NewResults(v)
},
}, {
name: "single query and single condition - multiple series (2 not true == false)",
vars: mathexp.Vars{
"A": mathexp.Results{
Values: []mathexp.Value{
valBasedSeries(ptr.Float64(0), ptr.Float64(10)),
valBasedSeries(ptr.Float64(20), ptr.Float64(30)),
},
},
},
cmd: &ConditionsCmd{
Conditions: []condition{
{
InputRefID: "A",
Reducer: reducer("avg"),
Operator: "and",
Evaluator: &thresholdEvaluator{Type: "gt", Threshold: 34},
},
}},
expected: func() mathexp.Results {
v := valBasedNumber(ptr.Float64(0))
v.SetMeta([]EvalMatch{})
return mathexp.Results{Values: mathexp.Values{v}}
},
}, {
name: "single query and single ranged condition",
vars: mathexp.Vars{
"A": mathexp.Results{
Values: []mathexp.Value{
valBasedSeries(ptr.Float64(30), ptr.Float64(40)),
},
},
},
cmd: &ConditionsCmd{
Conditions: []condition{
{
InputRefID: "A",
Reducer: reducer("diff"),
Operator: "and",
Evaluator: &rangedEvaluator{Type: "within_range", Lower: 2, Upper: 3},
},
},
},
expected: func() mathexp.Results {
v := valBasedNumber(ptr.Float64(0))
v.SetMeta([]EvalMatch{})
return mathexp.NewResults(v)
},
}, {
name: "single query with no data",
vars: mathexp.Vars{
"A": mathexp.Results{
Values: []mathexp.Value{mathexp.NoData{}.New()},
},
},
cmd: &ConditionsCmd{
Conditions: []condition{
{
InputRefID: "A",
Reducer: reducer("avg"),
Operator: "and",
Evaluator: &thresholdEvaluator{"gt", 1},
},
},
},
expected: func() mathexp.Results {
v := valBasedNumber(nil)
v.SetMeta([]EvalMatch{{Metric: "NoData"}})
return mathexp.NewResults(v)
},
}, {
name: "single query with no values",
vars: mathexp.Vars{
"A": mathexp.Results{
Values: []mathexp.Value{},
},
},
cmd: &ConditionsCmd{
Conditions: []condition{
{
InputRefID: "A",
Reducer: reducer("avg"),
Operator: "and",
Evaluator: &thresholdEvaluator{"gt", 1},
},
},
},
expected: func() mathexp.Results {
v := valBasedNumber(nil)
v.SetMeta([]EvalMatch{{Metric: "NoData"}})
return mathexp.NewResults(v)
},
}, {
name: "should accept numbers",
vars: mathexp.Vars{
"A": mathexp.Results{
Values: []mathexp.Value{
valBasedNumber(ptr.Float64(5)),
valBasedNumber(ptr.Float64(10)),
valBasedNumber(ptr.Float64(15)),
},
},
},
cmd: &ConditionsCmd{
Conditions: []condition{
{
InputRefID: "A",
Reducer: reducer("avg"),
Operator: "and",
Evaluator: &thresholdEvaluator{"gt", 1},
},
},
},
expected: func() mathexp.Results {
v := valBasedNumber(ptr.Float64(1))
v.SetMeta([]EvalMatch{
{Value: ptr.Float64(5)},
{Value: ptr.Float64(10)},
{Value: ptr.Float64(15)},
})
return mathexp.NewResults(v)
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res, err := tt.cmd.Execute(context.Background(), time.Now(), tt.vars)
require.NoError(t, err)
require.Equal(t, 1, len(res.Values))
require.Equal(t, tt.expected(), res)
})
}
}
func TestUnmarshalConditionsCmd(t *testing.T) {
var tests = []struct {
name string
rawJSON string
expectedCommand *ConditionsCmd
needsVars []string
}{
{
name: "basic threshold condition",
rawJSON: `{
"conditions": [
{
"evaluator": {
"params": [
2
],
"type": "gt"
},
"operator": {
"type": "and"
},
"query": {
"params": [
"A"
]
},
"reducer": {
"params": [],
"type": "avg"
},
"type": "query"
}
]
}`,
expectedCommand: &ConditionsCmd{
Conditions: []condition{
{
InputRefID: "A",
Reducer: reducer("avg"),
Operator: "and",
Evaluator: &thresholdEvaluator{Type: "gt", Threshold: 2},
},
},
},
needsVars: []string{"A"},
},
{
name: "ranged condition",
rawJSON: `{
"conditions": [
{
"evaluator": {
"params": [
2,
3
],
"type": "within_range"
},
"operator": {
"type": "or"
},
"query": {
"params": [
"A"
]
},
"reducer": {
"params": [],
"type": "diff"
},
"type": "query"
}
]
}`,
expectedCommand: &ConditionsCmd{
Conditions: []condition{
{
InputRefID: "A",
Reducer: reducer("diff"),
Operator: "or",
Evaluator: &rangedEvaluator{Type: "within_range", Lower: 2, Upper: 3},
},
},
},
needsVars: []string{"A"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var rq map[string]interface{}
err := json.Unmarshal([]byte(tt.rawJSON), &rq)
require.NoError(t, err)
cmd, err := UnmarshalConditionsCmd(rq, "")
require.NoError(t, err)
require.Equal(t, tt.expectedCommand, cmd)
require.Equal(t, tt.needsVars, cmd.NeedsVars())
})
}
}