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/mathexp/exp_series_test.go

383 lines
9.5 KiB

package mathexp
import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/stretchr/testify/assert"
)
func TestSeriesExpr(t *testing.T) {
var tests = []struct {
name string
expr string
vars Vars
newErrIs assert.ErrorAssertionFunc
execErrIs assert.ErrorAssertionFunc
results Results
}{
{
name: "unary series",
expr: "! ! $A",
vars: aSeriesNullableTime,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{ // Not sure about preservering names...
unixTimePointer(5, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(1),
}),
},
},
},
{
name: "binary scalar Op series",
expr: "98 + $A",
vars: aSeriesNullableTime,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{ // Not sure about preservering names...
unixTimePointer(5, 0), float64Pointer(100),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(99),
}),
},
},
},
{
name: "binary series Op scalar",
expr: "$A + 98",
vars: aSeriesNullableTime,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{ // Not sure about preservering names...
unixTimePointer(5, 0), float64Pointer(100),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(99),
}),
},
},
},
{
name: "series Op series",
expr: "$A + $A",
vars: aSeriesNullableTime,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{ // Not sure about preservering names...
unixTimePointer(5, 0), float64Pointer(4),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(2),
}),
},
},
},
{
name: "series Op number",
expr: "$A + $B",
vars: aSeriesbNumber,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("id=1", data.Labels{"id": "1"}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(9),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(8),
}),
},
},
},
{
name: "number Op series",
expr: "$B + $A",
vars: aSeriesbNumber,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("id=1", data.Labels{"id": "1"}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(9),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(8),
}),
},
},
},
{
name: "series Op series with label union",
expr: "$A * $B",
vars: twoSeriesSets,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("sensor=a, turbine=1", data.Labels{"sensor": "a", "turbine": "1"}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(6 * .5),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(8 * .2),
}),
makeSeriesNullableTime("sensor=b, turbine=1", data.Labels{"sensor": "b", "turbine": "1"}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(10 * .5),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(16 * .2),
}),
},
},
},
// Length of resulting series is A when A + B. However, only points where the time matches
// for A and B are added to the result
{
name: "series Op series with sparse time join",
expr: "$A + $B",
vars: Vars{
"A": Results{
[]Value{
makeSeriesNullableTime("temp", data.Labels{}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(1),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(2),
}),
},
},
"B": Results{
[]Value{
makeSeriesNullableTime("efficiency", data.Labels{}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(3),
}, nullTimeTP{
unixTimePointer(9, 0), float64Pointer(4),
}),
},
},
},
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{ // Not sure about preserving names...
unixTimePointer(5, 0), float64Pointer(4),
}),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e, err := New(tt.expr)
tt.newErrIs(t, err)
if e != nil {
res, err := e.Execute(tt.vars)
tt.execErrIs(t, err)
if diff := cmp.Diff(tt.results, res, data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
}
})
}
}
func TestSeriesAlternateFormsExpr(t *testing.T) {
var tests = []struct {
name string
expr string
vars Vars
newErrIs assert.ErrorAssertionFunc
execErrIs assert.ErrorAssertionFunc
results Results
}{
{
name: "unary series: non-nullable time",
expr: "! ! $A",
vars: aSeries,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeries("", nil, tp{ // Not sure about preservering names...
time.Unix(5, 0), float64Pointer(1),
}, tp{
time.Unix(10, 0), float64Pointer(1),
}),
},
},
},
{
name: "unary series: non-nullable time, time second",
expr: "! ! $A",
vars: aSeriesTimeSecond,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesTimeSecond("", nil, timeSecondTP{ // Not sure about preservering names...
float64Pointer(1), time.Unix(5, 0),
}, timeSecondTP{
float64Pointer(1), time.Unix(10, 0),
}),
},
},
},
{
name: "unary series: non-nullable value",
expr: "! ! $A",
vars: aSeriesNoNull,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeNoNullSeries("", nil, noNullTP{ // Not sure about preservering names...
time.Unix(5, 0), 1,
}, noNullTP{
time.Unix(10, 0), 1,
}),
},
},
},
{
name: "series Op series: nullable and non-nullable time",
expr: "$A + $B",
vars: Vars{
"A": Results{
[]Value{
makeSeries("temp", data.Labels{}, tp{
time.Unix(5, 0), float64Pointer(1),
}, tp{
time.Unix(10, 0), float64Pointer(2),
}),
},
},
"B": Results{
[]Value{
makeSeriesNullableTime("efficiency", data.Labels{}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(3),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(4),
}),
},
},
},
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(4),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(6),
}),
},
},
},
{
name: "series Op series: nullable (time second) and non-nullable time (time first)",
expr: "$B + $A", // takes order from first operator
vars: Vars{
"A": Results{
[]Value{
makeSeriesTimeSecond("temp", data.Labels{}, timeSecondTP{
float64Pointer(1), time.Unix(5, 0),
}, timeSecondTP{
float64Pointer(2), time.Unix(10, 0),
}),
},
},
"B": Results{
[]Value{
makeSeriesNullableTime("efficiency", data.Labels{}, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(3),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(4),
}),
},
},
},
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesNullableTime("", nil, nullTimeTP{
unixTimePointer(5, 0), float64Pointer(4),
}, nullTimeTP{
unixTimePointer(10, 0), float64Pointer(6),
}),
},
},
},
{
name: "series Op series: nullable and non-nullable values",
expr: "$A + $B",
vars: Vars{
"A": Results{
[]Value{
makeSeries("temp", data.Labels{}, tp{
time.Unix(5, 0), float64Pointer(1),
}, tp{
time.Unix(10, 0), float64Pointer(2),
}),
},
},
"B": Results{
[]Value{
makeNoNullSeries("efficiency", data.Labels{}, noNullTP{
time.Unix(5, 0), 3,
}, noNullTP{
time.Unix(10, 0), 4,
}),
},
},
},
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeries("", nil, tp{
time.Unix(5, 0), float64Pointer(4),
}, tp{
time.Unix(10, 0), float64Pointer(6),
}),
},
},
},
{
name: "binary scalar Op series: non-nullable time second",
expr: "98 + $A",
vars: aSeriesTimeSecond,
newErrIs: assert.NoError,
execErrIs: assert.NoError,
results: Results{
[]Value{
makeSeriesTimeSecond("", nil, timeSecondTP{ // Not sure about preservering names...
float64Pointer(100), time.Unix(5, 0),
}, timeSecondTP{
float64Pointer(99), time.Unix(10, 0),
}),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e, err := New(tt.expr)
tt.newErrIs(t, err)
if e != nil {
res, err := e.Execute(tt.vars)
tt.execErrIs(t, err)
if diff := cmp.Diff(tt.results, res, data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
}
})
}
}