| 
									
										
										
										
											2022-02-02 21:50:44 +08:00
										 |  |  | package expr | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2022-05-23 22:08:14 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2022-02-02 21:50:44 +08:00
										 |  |  | 	"encoding/json" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2023-09-15 00:26:00 +08:00
										 |  |  | 	"math" | 
					
						
							| 
									
										
										
										
											2022-05-23 22:08:14 +08:00
										 |  |  | 	"math/rand" | 
					
						
							| 
									
										
										
										
											2022-02-02 21:50:44 +08:00
										 |  |  | 	"testing" | 
					
						
							| 
									
										
										
										
											2022-10-27 04:13:58 +08:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2022-02-02 21:50:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-23 22:08:14 +08:00
										 |  |  | 	"github.com/grafana/grafana-plugin-sdk-go/data" | 
					
						
							| 
									
										
										
										
											2022-09-21 17:01:51 +08:00
										 |  |  | 	"github.com/stretchr/testify/assert" | 
					
						
							| 
									
										
										
										
											2022-02-02 21:50:44 +08:00
										 |  |  | 	"github.com/stretchr/testify/require" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/expr/mathexp" | 
					
						
							| 
									
										
										
										
											2023-01-19 01:39:38 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/expr/mathexp/parse" | 
					
						
							| 
									
										
										
										
											2023-04-18 20:04:51 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/infra/tracing" | 
					
						
							| 
									
										
										
										
											2022-05-23 22:08:14 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/util" | 
					
						
							| 
									
										
										
										
											2022-02-02 21:50:44 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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) | 
					
						
							| 
									
										
										
										
											2023-08-30 23:46:47 +08:00
										 |  |  | 			var qmap = make(map[string]any) | 
					
						
							| 
									
										
										
										
											2022-02-02 21:50:44 +08:00
										 |  |  | 			require.NoError(t, json.Unmarshal([]byte(q), &qmap)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			cmd, err := UnmarshalReduceCommand(&rawNode{ | 
					
						
							|  |  |  | 				RefID:      "A", | 
					
						
							|  |  |  | 				Query:      qmap, | 
					
						
							|  |  |  | 				QueryType:  "", | 
					
						
							| 
									
										
										
										
											2022-10-27 04:13:58 +08:00
										 |  |  | 				TimeRange:  RelativeTimeRange{}, | 
					
						
							| 
									
										
										
										
											2022-02-02 21:50:44 +08:00
										 |  |  | 				DataSource: nil, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if test.isError { | 
					
						
							|  |  |  | 				require.Error(t, err) | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			require.NotNil(t, cmd) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			require.Equal(t, test.expectedMapper, cmd.seriesMapper) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-05-23 22:08:14 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | func TestReduceExecute(t *testing.T) { | 
					
						
							|  |  |  | 	varToReduce := util.GenerateShortUID() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-15 00:26:00 +08:00
										 |  |  | 	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, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-27 15:51:57 +08:00
										 |  |  | 			execute, err := cmd.Execute(context.Background(), time.Now(), vars, tracing.InitializeTracerForTest()) | 
					
						
							| 
									
										
										
										
											2023-09-15 00:26:00 +08:00
										 |  |  | 			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) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-15 23:07:17 +08:00
										 |  |  | 			t.Run("should add warn notices to the first frame", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2023-09-15 00:26:00 +08:00
										 |  |  | 				frames := execute.Values.AsDataFrames("test") | 
					
						
							| 
									
										
										
										
											2023-09-15 23:07:17 +08:00
										 |  |  | 				notice := frames[0].Meta.Notices[0] | 
					
						
							|  |  |  | 				require.Equal(t, data.NoticeSeverityWarning, notice.Severity) | 
					
						
							|  |  |  | 				for _, frame := range frames[1:] { | 
					
						
							|  |  |  | 					require.Empty(t, frame.Meta.Notices) | 
					
						
							| 
									
										
										
										
											2023-09-15 00:26:00 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("when mapper is not nil", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-05-23 22:08:14 +08:00
										 |  |  | 		var numbers mathexp.Values = []mathexp.Value{ | 
					
						
							| 
									
										
										
										
											2023-03-06 18:23:15 +08:00
										 |  |  | 			mathexp.GenerateNumber(util.Pointer(rand.Float64())), | 
					
						
							| 
									
										
										
										
											2023-09-15 00:26:00 +08:00
										 |  |  | 			mathexp.GenerateNumber(nil), | 
					
						
							|  |  |  | 			mathexp.GenerateNumber(util.Pointer(math.NaN())), | 
					
						
							|  |  |  | 			mathexp.GenerateNumber(util.Pointer(math.Inf(-1))), | 
					
						
							|  |  |  | 			mathexp.GenerateNumber(util.Pointer(math.Inf(1))), | 
					
						
							| 
									
										
										
										
											2023-03-06 18:23:15 +08:00
										 |  |  | 			mathexp.GenerateNumber(util.Pointer(rand.Float64())), | 
					
						
							| 
									
										
										
										
											2022-05-23 22:08:14 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-09-15 00:26:00 +08:00
										 |  |  | 		varToReduce := util.GenerateShortUID() | 
					
						
							| 
									
										
										
										
											2022-05-23 22:08:14 +08:00
										 |  |  | 		vars := map[string]mathexp.Results{ | 
					
						
							|  |  |  | 			varToReduce: { | 
					
						
							|  |  |  | 				Values: numbers, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-15 00:26:00 +08:00
										 |  |  | 		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) | 
					
						
							| 
									
										
										
										
											2023-09-27 15:51:57 +08:00
										 |  |  | 			execute, err := cmd.Execute(context.Background(), time.Now(), vars, tracing.InitializeTracerForTest()) | 
					
						
							| 
									
										
										
										
											2023-09-15 00:26:00 +08:00
										 |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 			require.Len(t, execute.Values, 2) | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2022-05-23 22:08:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-15 00:26:00 +08:00
										 |  |  | 		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) | 
					
						
							| 
									
										
										
										
											2023-09-27 15:51:57 +08:00
										 |  |  | 			execute, err := cmd.Execute(context.Background(), time.Now(), vars, tracing.InitializeTracerForTest()) | 
					
						
							| 
									
										
										
										
											2023-09-15 00:26:00 +08:00
										 |  |  | 			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()) | 
					
						
							| 
									
										
										
										
											2022-05-23 22:08:14 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2022-09-21 17:01:51 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	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, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-09-15 00:26:00 +08:00
										 |  |  | 		cmd, err := NewReduceCommand(util.GenerateShortUID(), randomReduceFunc(), varToReduce, nil) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2023-09-27 15:51:57 +08:00
										 |  |  | 		results, err := cmd.Execute(context.Background(), time.Now(), vars, tracing.InitializeTracerForTest()) | 
					
						
							| 
									
										
										
										
											2022-09-21 17:01:51 +08:00
										 |  |  | 		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) | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2022-05-23 22:08:14 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-17 08:59:11 +08:00
										 |  |  | func randomReduceFunc() mathexp.ReducerID { | 
					
						
							| 
									
										
										
										
											2022-05-23 22:08:14 +08:00
										 |  |  | 	res := mathexp.GetSupportedReduceFuncs() | 
					
						
							| 
									
										
										
										
											2023-01-25 02:07:37 +08:00
										 |  |  | 	return res[rand.Intn(len(res))] | 
					
						
							| 
									
										
										
										
											2022-05-23 22:08:14 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2023-01-19 01:39:38 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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", | 
					
						
							| 
									
										
										
										
											2023-03-06 18:23:15 +08:00
										 |  |  | 			vals:    mathexp.NewScalar("test", util.Pointer(rand.Float64())), | 
					
						
							| 
									
										
										
										
											2023-01-19 01:39:38 +08:00
										 |  |  | 			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}}, | 
					
						
							| 
									
										
										
										
											2023-09-27 15:51:57 +08:00
										 |  |  | 			}, tracing.InitializeTracerForTest()) | 
					
						
							| 
									
										
										
										
											2023-01-19 01:39:38 +08:00
										 |  |  | 			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}}, | 
					
						
							| 
									
										
										
										
											2023-09-27 15:51:57 +08:00
										 |  |  | 		}, tracing.InitializeTracerForTest()) | 
					
						
							| 
									
										
										
										
											2023-01-19 01:39:38 +08:00
										 |  |  | 		require.Empty(t, result.Values) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } |