mirror of
https://github.com/grafana/grafana.git
synced 2024-12-30 10:47:30 -06:00
276 lines
8.4 KiB
Go
276 lines
8.4 KiB
Go
package expr
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"math/rand"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/grafana/pkg/expr/mathexp"
|
|
"github.com/grafana/grafana/pkg/expr/mathexp/parse"
|
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
)
|
|
|
|
func Test_UnmarshalReduceCommand_Settings(t *testing.T) {
|
|
var tests = []struct {
|
|
name string
|
|
querySettings string
|
|
isError bool
|
|
expectedMapper mathexp.ReduceMapper
|
|
}{
|
|
{
|
|
name: "no mapper function when settings is not specified",
|
|
querySettings: ``,
|
|
expectedMapper: nil,
|
|
},
|
|
{
|
|
name: "no mapper function when mode is not specified",
|
|
querySettings: `, "settings" : { }`,
|
|
expectedMapper: nil,
|
|
},
|
|
{
|
|
name: "error when settings is not object",
|
|
querySettings: `, "settings" : "drop-nan"`,
|
|
isError: true,
|
|
},
|
|
{
|
|
name: "no mapper function when mode is empty",
|
|
querySettings: `, "settings" : { "mode": "" }`,
|
|
expectedMapper: nil,
|
|
},
|
|
{
|
|
name: "error when mode is not known",
|
|
querySettings: `, "settings" : { "mode": "test" }`,
|
|
isError: true,
|
|
},
|
|
{
|
|
name: "filterNonNumber function when mode is 'dropNN'",
|
|
querySettings: `, "settings" : { "mode": "dropNN" }`,
|
|
expectedMapper: mathexp.DropNonNumber{},
|
|
},
|
|
{
|
|
name: "replaceNanWithValue function when mode is 'dropNN'",
|
|
querySettings: `, "settings" : { "mode": "replaceNN" , "replaceWithValue": -12 }`,
|
|
expectedMapper: mathexp.ReplaceNonNumberWithValue{Value: -12},
|
|
},
|
|
{
|
|
name: "error if mode is 'replaceNN' but field replaceWithValue is not specified",
|
|
querySettings: `, "settings" : { "mode": "replaceNN" }`,
|
|
isError: true,
|
|
},
|
|
{
|
|
name: "error if mode is 'replaceNN' but field replaceWithValue is not a number",
|
|
querySettings: `, "settings" : { "mode": "replaceNN", "replaceWithValue" : "-12" }`,
|
|
isError: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
q := fmt.Sprintf(`{ "expression" : "$A", "reducer": "sum"%s }`, test.querySettings)
|
|
var qmap = make(map[string]any)
|
|
require.NoError(t, json.Unmarshal([]byte(q), &qmap))
|
|
|
|
cmd, err := UnmarshalReduceCommand(&rawNode{
|
|
RefID: "A",
|
|
Query: qmap,
|
|
QueryType: "",
|
|
TimeRange: RelativeTimeRange{},
|
|
DataSource: nil,
|
|
})
|
|
|
|
if test.isError {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NotNil(t, cmd)
|
|
|
|
require.Equal(t, test.expectedMapper, cmd.seriesMapper)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReduceExecute(t *testing.T) {
|
|
varToReduce := util.GenerateShortUID()
|
|
|
|
t.Run("when mapper is nil", func(t *testing.T) {
|
|
cmd, err := NewReduceCommand(util.GenerateShortUID(), randomReduceFunc(), varToReduce, nil)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("should noop if Number", func(t *testing.T) {
|
|
var numbers mathexp.Values = []mathexp.Value{
|
|
mathexp.GenerateNumber(util.Pointer(rand.Float64())),
|
|
mathexp.GenerateNumber(util.Pointer(rand.Float64())),
|
|
mathexp.GenerateNumber(util.Pointer(rand.Float64())),
|
|
}
|
|
|
|
vars := map[string]mathexp.Results{
|
|
varToReduce: {
|
|
Values: numbers,
|
|
},
|
|
}
|
|
|
|
execute, err := cmd.Execute(context.Background(), time.Now(), vars, tracing.InitializeTracerForTest())
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, execute.Values, len(numbers))
|
|
for i, value := range execute.Values {
|
|
expected := numbers[i]
|
|
require.Equal(t, expected.Type(), value.Type())
|
|
require.Equal(t, expected.GetLabels(), value.GetLabels())
|
|
|
|
expectedValue := expected.Value().(*mathexp.Number).GetFloat64Value()
|
|
actualValue := value.Value().(*mathexp.Number).GetFloat64Value()
|
|
require.Equal(t, expectedValue, actualValue)
|
|
}
|
|
|
|
t.Run("should add warn notices to the first frame", func(t *testing.T) {
|
|
frames := execute.Values.AsDataFrames("test")
|
|
notice := frames[0].Meta.Notices[0]
|
|
require.Equal(t, data.NoticeSeverityWarning, notice.Severity)
|
|
for _, frame := range frames[1:] {
|
|
require.Empty(t, frame.Meta.Notices)
|
|
}
|
|
})
|
|
})
|
|
})
|
|
|
|
t.Run("when mapper is not nil", func(t *testing.T) {
|
|
var numbers mathexp.Values = []mathexp.Value{
|
|
mathexp.GenerateNumber(util.Pointer(rand.Float64())),
|
|
mathexp.GenerateNumber(nil),
|
|
mathexp.GenerateNumber(util.Pointer(math.NaN())),
|
|
mathexp.GenerateNumber(util.Pointer(math.Inf(-1))),
|
|
mathexp.GenerateNumber(util.Pointer(math.Inf(1))),
|
|
mathexp.GenerateNumber(util.Pointer(rand.Float64())),
|
|
}
|
|
varToReduce := util.GenerateShortUID()
|
|
vars := map[string]mathexp.Results{
|
|
varToReduce: {
|
|
Values: numbers,
|
|
},
|
|
}
|
|
|
|
t.Run("drop all non numbers if mapper is DropNonNumber", func(t *testing.T) {
|
|
cmd, err := NewReduceCommand(util.GenerateShortUID(), randomReduceFunc(), varToReduce, &mathexp.DropNonNumber{})
|
|
require.NoError(t, err)
|
|
execute, err := cmd.Execute(context.Background(), time.Now(), vars, tracing.InitializeTracerForTest())
|
|
require.NoError(t, err)
|
|
require.Len(t, execute.Values, 2)
|
|
})
|
|
|
|
t.Run("replace all non numbers if mapper is ReplaceNonNumberWithValue", func(t *testing.T) {
|
|
cmd, err := NewReduceCommand(util.GenerateShortUID(), randomReduceFunc(), varToReduce, &mathexp.ReplaceNonNumberWithValue{Value: 1})
|
|
require.NoError(t, err)
|
|
execute, err := cmd.Execute(context.Background(), time.Now(), vars, tracing.InitializeTracerForTest())
|
|
require.NoError(t, err)
|
|
require.Len(t, execute.Values, len(numbers))
|
|
for _, value := range execute.Values[1 : len(numbers)-1] {
|
|
require.IsType(t, &mathexp.Number{}, value.Value())
|
|
f := value.Value().(*mathexp.Number)
|
|
require.Equal(t, float64(1), *f.GetFloat64Value())
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("should return new NoData", func(t *testing.T) {
|
|
var noData mathexp.Values = []mathexp.Value{
|
|
mathexp.NoData{Frame: data.NewFrame("no data")},
|
|
}
|
|
|
|
vars := map[string]mathexp.Results{
|
|
varToReduce: {
|
|
Values: noData,
|
|
},
|
|
}
|
|
cmd, err := NewReduceCommand(util.GenerateShortUID(), randomReduceFunc(), varToReduce, nil)
|
|
require.NoError(t, err)
|
|
results, err := cmd.Execute(context.Background(), time.Now(), vars, tracing.InitializeTracerForTest())
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, results.Values, 1)
|
|
|
|
v := results.Values[0]
|
|
assert.Equal(t, v, mathexp.NoData{}.New())
|
|
|
|
// should not be able to change the original frame
|
|
v.AsDataFrame().Name = "there is still no data"
|
|
assert.NotEqual(t, v, mathexp.NoData{}.New())
|
|
assert.NotEqual(t, v, noData[0])
|
|
assert.Equal(t, "no data", noData[0].AsDataFrame().Name)
|
|
})
|
|
}
|
|
|
|
func randomReduceFunc() mathexp.ReducerID {
|
|
res := mathexp.GetSupportedReduceFuncs()
|
|
return res[rand.Intn(len(res))]
|
|
}
|
|
|
|
func TestResampleCommand_Execute(t *testing.T) {
|
|
varToReduce := util.GenerateShortUID()
|
|
tr := RelativeTimeRange{
|
|
From: -10 * time.Second,
|
|
To: 0,
|
|
}
|
|
cmd, err := NewResampleCommand(util.GenerateShortUID(), "1s", varToReduce, "sum", "pad", tr)
|
|
require.NoError(t, err)
|
|
|
|
var tests = []struct {
|
|
name string
|
|
vals mathexp.Value
|
|
isError bool
|
|
expectedType parse.ReturnType
|
|
}{
|
|
{
|
|
name: "should resample when input Series",
|
|
vals: mathexp.NewSeries(varToReduce, nil, 100),
|
|
expectedType: parse.TypeSeriesSet,
|
|
},
|
|
{
|
|
name: "should return NoData when input NoData",
|
|
vals: mathexp.NoData{},
|
|
expectedType: parse.TypeNoData,
|
|
}, {
|
|
name: "should return error when input Number",
|
|
vals: mathexp.NewNumber("test", nil),
|
|
isError: true,
|
|
}, {
|
|
name: "should return error when input Scalar",
|
|
vals: mathexp.NewScalar("test", util.Pointer(rand.Float64())),
|
|
isError: true,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
result, err := cmd.Execute(context.Background(), time.Now(), mathexp.Vars{
|
|
varToReduce: mathexp.Results{Values: mathexp.Values{test.vals}},
|
|
}, tracing.InitializeTracerForTest())
|
|
if test.isError {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Len(t, result.Values, 1)
|
|
res := result.Values[0]
|
|
require.Equal(t, test.expectedType, res.Type())
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("should return empty result if input is nil Value", func(t *testing.T) {
|
|
result, err := cmd.Execute(context.Background(), time.Now(), mathexp.Vars{
|
|
varToReduce: mathexp.Results{Values: mathexp.Values{nil}},
|
|
}, tracing.InitializeTracerForTest())
|
|
require.Empty(t, result.Values)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|