github.com/m3db/m3@v1.5.0/src/query/graphite/native/builtin_functions_test.go (about)

     1  // Copyright (c) 2019 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package native
    22  
    23  import (
    24  	"context"
    25  	"fmt"
    26  	"math"
    27  	"math/rand"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/m3db/m3/src/query/block"
    32  	"github.com/m3db/m3/src/query/graphite/common"
    33  	xctx "github.com/m3db/m3/src/query/graphite/context"
    34  	"github.com/m3db/m3/src/query/graphite/storage"
    35  	xtest "github.com/m3db/m3/src/query/graphite/testing"
    36  	"github.com/m3db/m3/src/query/graphite/ts"
    37  	querystorage "github.com/m3db/m3/src/query/storage"
    38  	"github.com/m3db/m3/src/query/storage/m3/consolidators"
    39  	xgomock "github.com/m3db/m3/src/x/test"
    40  
    41  	"github.com/golang/mock/gomock"
    42  	"github.com/stretchr/testify/assert"
    43  	"github.com/stretchr/testify/require"
    44  )
    45  
    46  var (
    47  	// testInput defines the input for various tests
    48  	testInput = []common.TestSeries{
    49  		{"foo", []float64{0, 601, 3, 4}},
    50  		{"nan", []float64{math.NaN(), math.NaN(), math.NaN()}},
    51  		{"bar", []float64{500, -8}},
    52  		{"baz", []float64{600, -600, 3}},
    53  		{"quux", []float64{100, 50000, 888, -1, -2, math.NaN()}},
    54  	}
    55  
    56  	// testSmallInput defines a small input for various tests
    57  	testSmallInput = []common.TestSeries{
    58  		testInput[0],
    59  		testInput[2],
    60  	}
    61  
    62  	// testInputWithNaNSeries defines another input set with all-nan series
    63  	testInputWithNaNSeries = []common.TestSeries{
    64  		testInput[0],
    65  		testInput[2],
    66  		testInput[4],
    67  		{"allNaN", []float64{math.NaN(), math.NaN()}},
    68  	}
    69  )
    70  
    71  func TestExclude(t *testing.T) {
    72  	ctx := common.NewTestContext()
    73  	defer func() { _ = ctx.Close() }()
    74  
    75  	now := time.Now()
    76  	values := ts.NewConstantValues(ctx, 10.0, 1000, 10)
    77  
    78  	g01 := ts.NewSeries(ctx, "servers.graphite01-foo.disk.bar.available_bytes", now, values)
    79  	g02 := ts.NewSeries(ctx, "servers.graphite02-foo.disk.bar.available_bytes", now, values)
    80  	g03 := ts.NewSeries(ctx, "servers.graphite03-foo.disk.bar.available_bytes", now, values)
    81  
    82  	sampleInput := []*ts.Series{g01, g02, g03}
    83  	sampleOutput := []*ts.Series{g01, g03}
    84  	tests := []struct {
    85  		inputs  []*ts.Series
    86  		r       string
    87  		n       int
    88  		outputs []*ts.Series
    89  	}{
    90  		{
    91  			sampleInput,
    92  			"graphite02-foo",
    93  			2,
    94  			sampleOutput,
    95  		},
    96  		{
    97  			sampleInput,
    98  			"graphite",
    99  			0,
   100  			[]*ts.Series{},
   101  		},
   102  		{
   103  			sampleInput,
   104  			"graphite.*-foo",
   105  			0,
   106  			[]*ts.Series{},
   107  		},
   108  	}
   109  
   110  	for _, test := range tests {
   111  		results, err := exclude(nil, singlePathSpec{
   112  			Values: test.inputs,
   113  		}, test.r)
   114  		require.Nil(t, err)
   115  		require.NotNil(t, results)
   116  		require.Equal(t, test.n, results.Len())
   117  		for i := range results.Values {
   118  			assert.Equal(t, sampleOutput[i].Name(), results.Values[i].Name())
   119  		}
   120  	}
   121  }
   122  
   123  func TestExcludeErr(t *testing.T) {
   124  	ctx := common.NewTestContext()
   125  	defer func() { _ = ctx.Close() }()
   126  
   127  	now := time.Now()
   128  	values := ts.NewConstantValues(ctx, 10.0, 1000, 10)
   129  
   130  	series := []*ts.Series{
   131  		ts.NewSeries(ctx, "anything", now, values),
   132  	}
   133  	results, err := exclude(ctx, singlePathSpec{
   134  		Values: series,
   135  	}, "(")
   136  	require.Error(t, err, "Failure is expected")
   137  	require.Nil(t, results.Values)
   138  }
   139  
   140  func TestGrep(t *testing.T) {
   141  	ctx := common.NewTestContext()
   142  	defer func() { _ = ctx.Close() }()
   143  
   144  	now := time.Now()
   145  	values := ts.NewConstantValues(ctx, 10.0, 5, 10)
   146  
   147  	series1 := ts.NewSeries(ctx, "collectd.test-db1.load.value", now, values)
   148  	series2 := ts.NewSeries(ctx, "collectd.test-db2.load.value", now, values)
   149  	series3 := ts.NewSeries(ctx, "collectd.test-db3.load.value", now, values)
   150  	series4 := ts.NewSeries(ctx, "collectd.test-db4.load.value", now, values)
   151  
   152  	testInputs := []*ts.Series{series1, series2, series3, series4}
   153  	expectedOutput := []common.TestSeries{
   154  		{
   155  			Name: "collectd.test-db1.load.value",
   156  			Data: []float64{10.0, 10.0, 10.0, 10.0, 10.0},
   157  		},
   158  		{
   159  			Name: "collectd.test-db2.load.value",
   160  			Data: []float64{10.0, 10.0, 10.0, 10.0, 10.0},
   161  		},
   162  	}
   163  
   164  	results, err := grep(nil, singlePathSpec{
   165  		Values: testInputs,
   166  	}, ".*db[12]")
   167  	require.Nil(t, err)
   168  	require.NotNil(t, results)
   169  	common.CompareOutputsAndExpected(t, 10, now, expectedOutput, results.Values)
   170  
   171  	// error case
   172  	_, err = grep(nil, singlePathSpec{
   173  		Values: testInputs,
   174  	}, "+++++")
   175  	require.NotNil(t, err)
   176  }
   177  
   178  func TestSortByName(t *testing.T) {
   179  	ctx := common.NewTestContext()
   180  	defer func() { _ = ctx.Close() }()
   181  
   182  	now := time.Now()
   183  	values := ts.NewConstantValues(ctx, 10.0, 1000, 10)
   184  
   185  	series := []*ts.Series{
   186  		ts.NewSeries(ctx, "b.d.a", now, values),
   187  		ts.NewSeries(ctx, "zee", now, values),
   188  		ts.NewSeries(ctx, "a.c.d", now, values),
   189  	}
   190  
   191  	// Normal.
   192  	results, err := sortByName(ctx, singlePathSpec{
   193  		Values: series,
   194  	}, false, false)
   195  	require.Nil(t, err)
   196  	require.Equal(t, len(series), results.Len())
   197  	assert.Equal(t, "a.c.d", results.Values[0].Name())
   198  	assert.Equal(t, "b.d.a", results.Values[1].Name())
   199  	assert.Equal(t, "zee", results.Values[2].Name())
   200  
   201  	// Reverse.
   202  	results, err = sortByName(ctx, singlePathSpec{
   203  		Values: series,
   204  	}, false, true)
   205  	require.Nil(t, err)
   206  	require.Equal(t, len(series), results.Len())
   207  	assert.Equal(t, "zee", results.Values[0].Name())
   208  	assert.Equal(t, "b.d.a", results.Values[1].Name())
   209  	assert.Equal(t, "a.c.d", results.Values[2].Name())
   210  }
   211  
   212  func TestSortByNameNatural(t *testing.T) {
   213  	ctx := common.NewTestContext()
   214  	defer func() { _ = ctx.Close() }()
   215  
   216  	now := time.Now()
   217  	values := ts.NewConstantValues(ctx, 10.0, 1000, 10)
   218  
   219  	series := []*ts.Series{
   220  		ts.NewSeries(ctx, "server1", now, values),
   221  		ts.NewSeries(ctx, "server11", now, values),
   222  		ts.NewSeries(ctx, "server12", now, values),
   223  		ts.NewSeries(ctx, "server2", now, values),
   224  	}
   225  
   226  	results, err := sortByName(ctx, singlePathSpec{
   227  		Values: series,
   228  	}, true, false)
   229  	require.Nil(t, err)
   230  	require.Equal(t, len(series), results.Len())
   231  	assert.Equal(t, "server1", results.Values[0].Name())
   232  	assert.Equal(t, "server2", results.Values[1].Name())
   233  	assert.Equal(t, "server11", results.Values[2].Name())
   234  	assert.Equal(t, "server12", results.Values[3].Name())
   235  }
   236  
   237  func getTestInput(ctx *common.Context) []*ts.Series {
   238  	series := make([]*ts.Series, len(testInput))
   239  	now := time.Now()
   240  	for idx, s := range testInput {
   241  		series[idx] = ts.NewSeries(ctx, s.Name, now, common.NewTestSeriesValues(ctx, 100, s.Data))
   242  	}
   243  	return series
   244  }
   245  
   246  func testSortingFuncs(
   247  	t *testing.T,
   248  	f func(ctx *common.Context, series singlePathSpec) (ts.SeriesList, error),
   249  	resultIndexes []int,
   250  ) {
   251  	ctx := common.NewTestContext()
   252  	defer func() { _ = ctx.Close() }()
   253  
   254  	input := getTestInput(ctx)
   255  	results, err := f(ctx, singlePathSpec{Values: input})
   256  	require.Nil(t, err)
   257  	require.Equal(t, len(resultIndexes), results.Len())
   258  
   259  	expected := make([]string, 0, len(input))
   260  	for _, idx := range resultIndexes {
   261  		expected = append(expected, input[idx].Name())
   262  	}
   263  
   264  	actual := make([]string, 0, len(input))
   265  	for _, s := range results.Values {
   266  		actual = append(actual, s.Name())
   267  	}
   268  	require.Equal(t, expected, actual)
   269  
   270  	for i, idx := range resultIndexes {
   271  		require.Equal(t, results.Values[i], input[idx])
   272  	}
   273  }
   274  
   275  func TestSortBy(t *testing.T) {
   276  	for _, test := range []struct {
   277  		fn      string
   278  		indices []int
   279  	}{
   280  		{fn: "average", indices: []int{4, 2, 0, 3, 1}},
   281  		{fn: "sum", indices: []int{4, 0, 2, 3, 1}},
   282  		{fn: "max", indices: []int{4, 0, 3, 2, 1}},
   283  		{fn: "min", indices: []int{1, 3, 2, 4, 0}},
   284  	} {
   285  		t.Run(test.fn, func(t *testing.T) {
   286  			// Regular.
   287  			expected := test.indices
   288  			testSortingFuncs(t,
   289  				func(ctx *common.Context, series singlePathSpec) (ts.SeriesList, error) {
   290  					return sortBy(ctx, series, test.fn, false)
   291  				},
   292  				expected)
   293  
   294  			// Reversed.
   295  			for i, j := 0, len(expected)-1; i < j; i, j = i+1, j-1 {
   296  				expected[i], expected[j] = expected[j], expected[i]
   297  			}
   298  			testSortingFuncs(t,
   299  				func(ctx *common.Context, series singlePathSpec) (ts.SeriesList, error) {
   300  					return sortBy(ctx, series, test.fn, true)
   301  				},
   302  				expected)
   303  		})
   304  	}
   305  }
   306  
   307  func TestSortByTotal(t *testing.T) {
   308  	testSortingFuncs(t, sortByTotal, []int{4, 0, 2, 3, 1})
   309  }
   310  
   311  func TestSortByMaxima(t *testing.T) {
   312  	testSortingFuncs(t, sortByMaxima, []int{4, 0, 3, 2, 1})
   313  }
   314  
   315  func TestSortByMinima(t *testing.T) {
   316  	testSortingFuncs(t, sortByMinima, []int{1, 3, 2, 4, 0})
   317  }
   318  
   319  func TestAbsolute(t *testing.T) {
   320  	ctx := common.NewTestContext()
   321  	defer func() { _ = ctx.Close() }()
   322  
   323  	inputVals := []float64{-2, 0, 42, math.NaN()}
   324  	outputVals := []float64{2, 0, 42, math.NaN()}
   325  	start := time.Now()
   326  
   327  	input := ts.NewSeries(ctx, "foo", start, common.NewTestSeriesValues(ctx, 100, inputVals))
   328  	r, err := absolute(ctx, singlePathSpec{
   329  		Values: []*ts.Series{input},
   330  	})
   331  	require.NoError(t, err)
   332  
   333  	outputs := r.Values
   334  	require.Equal(t, 1, len(outputs))
   335  	require.Equal(t, 100, outputs[0].MillisPerStep())
   336  	require.Equal(t, len(outputVals), outputs[0].Len())
   337  	require.Equal(t, start, outputs[0].StartTime())
   338  	assert.Equal(t, "absolute(foo)", outputs[0].Name())
   339  
   340  	for step := 0; step < outputs[0].Len(); step++ {
   341  		v := outputs[0].ValueAt(step)
   342  		xtest.Equalish(t, outputVals[step], v, "invalid value for %d", step)
   343  	}
   344  }
   345  
   346  func TestScale(t *testing.T) {
   347  	ctx := common.NewTestContext()
   348  	defer func() { _ = ctx.Close() }()
   349  
   350  	tests := []struct {
   351  		inputs  []float64
   352  		scale   float64
   353  		outputs []float64
   354  	}{
   355  		{
   356  			[]float64{0, 1.0, 2.0, math.NaN(), 3.0},
   357  			2.5,
   358  			[]float64{0, 2.5, 5.0, math.NaN(), 7.5},
   359  		},
   360  		{
   361  			[]float64{0, 1.0, 2.0, math.NaN(), 3.0},
   362  			0.5,
   363  			[]float64{0, 0.5, 1.0, math.NaN(), 1.5},
   364  		},
   365  	}
   366  
   367  	start := time.Now()
   368  	for _, test := range tests {
   369  		input := ts.NewSeries(ctx, "foo", start, common.NewTestSeriesValues(ctx, 100, test.inputs))
   370  		r, err := scale(ctx, singlePathSpec{
   371  			Values: []*ts.Series{input},
   372  		}, test.scale)
   373  		require.NoError(t, err)
   374  
   375  		outputs := r.Values
   376  		require.Equal(t, 1, len(outputs))
   377  		require.Equal(t, 100, outputs[0].MillisPerStep())
   378  		require.Equal(t, len(test.inputs), outputs[0].Len())
   379  		require.Equal(t, start, outputs[0].StartTime())
   380  		assert.Equal(t, fmt.Sprintf("scale(foo,"+common.FloatingPointFormat+")", test.scale), outputs[0].Name())
   381  
   382  		for step := 0; step < outputs[0].Len(); step++ {
   383  			v := outputs[0].ValueAt(step)
   384  			xtest.Equalish(t, test.outputs[step], v, "invalid value for %d", step)
   385  		}
   386  	}
   387  }
   388  
   389  func TestUseSeriesAbove(t *testing.T) {
   390  	var (
   391  		ctrl      = xgomock.NewController(t)
   392  		store     = storage.NewMockStorage(ctrl)
   393  		now       = time.Now().Truncate(time.Hour)
   394  		engine    = NewEngine(store, CompileOptions{})
   395  		startTime = now.Add(-3 * time.Minute)
   396  		endTime   = now.Add(-time.Minute)
   397  		ctx       = common.NewContext(common.ContextOptions{Start: startTime, End: endTime, Engine: engine})
   398  		stepSize  = 60000
   399  	)
   400  
   401  	defer ctrl.Finish()
   402  	defer func() { _ = ctx.Close() }()
   403  
   404  	store.EXPECT().FetchByQuery(gomock.Any(), "foo.bar.q.zed", gomock.Any()).DoAndReturn(
   405  		buildTestSeriesFn(stepSize, "foo.bar.q.zed"))
   406  	store.EXPECT().FetchByQuery(gomock.Any(), "foo.bar.g.zed", gomock.Any()).DoAndReturn(
   407  		buildTestSeriesFn(stepSize, "foo.bar.g.zed"))
   408  	store.EXPECT().FetchByQuery(gomock.Any(), "foo.bar.x.zed", gomock.Any()).DoAndReturn(
   409  		buildTestSeriesFn(stepSize, "foo.bar.x.zed")).Times(2)
   410  	store.EXPECT().FetchByQuery(gomock.Any(), "foo.bar.g.zed.g", gomock.Any()).Return(
   411  		&storage.FetchResult{SeriesList: []*ts.Series{ts.NewSeries(ctx, "foo.bar.g.zed.g", startTime,
   412  			common.NewTestSeriesValues(ctx, 60000, []float64{10, 20, 30}))}}, nil)
   413  	store.EXPECT().FetchByQuery(gomock.Any(), "foo.bar.q.zed.q", gomock.Any()).Return(
   414  		&storage.FetchResult{SeriesList: []*ts.Series{ts.NewSeries(ctx, "foo.bar.q.zed.q", startTime,
   415  			common.NewTestSeriesValues(ctx, 60000, []float64{1, 2, 3}))}}, nil)
   416  
   417  	tests := []struct {
   418  		target   string
   419  		expected common.TestSeries
   420  	}{
   421  		{
   422  			"useSeriesAbove(foo.bar.q.zed, -1, 'q', 'g')",
   423  			common.TestSeries{
   424  				Name: "foo.bar.g.zed",
   425  				Data: []float64{1.0, 1.0},
   426  			},
   427  		},
   428  		// two replacements
   429  		{
   430  			"useSeriesAbove(foo.bar.g.zed.g, 15, 'g', 'q')",
   431  			common.TestSeries{
   432  				Name: "foo.bar.q.zed.q",
   433  				Data: []float64{1.0, 2.0, 3.0},
   434  			},
   435  		},
   436  		// no replacments
   437  		{
   438  			"useSeriesAbove(foo.bar.x.zed, 1, 'p', 'g')",
   439  			common.TestSeries{
   440  				Name: "foo.bar.x.zed",
   441  				Data: []float64{2.0, 2.0},
   442  			},
   443  		},
   444  	}
   445  
   446  	for _, test := range tests {
   447  		expr, err := engine.Compile(test.target)
   448  		require.NoError(t, err)
   449  		res, err := expr.Execute(ctx)
   450  		require.NoError(t, err)
   451  		common.CompareOutputsAndExpected(t, stepSize, startTime,
   452  			[]common.TestSeries{test.expected}, res.Values)
   453  	}
   454  }
   455  
   456  func TestPercentileOfSeriesErrors(t *testing.T) {
   457  	ctx := common.NewTestContext()
   458  
   459  	tests := []struct {
   460  		stepPerMillis         []int
   461  		percentile            float64
   462  		values                [][]float64
   463  		expectedValues        []float64
   464  		expectedStepPerMillis float64
   465  		interpolate           genericInterface
   466  	}{
   467  		{ // percentile is over 100%.
   468  			[]int{120, 120},
   469  			101.0,
   470  			[][]float64{
   471  				{60.0, 50.0, 40.0, 30.0, 20.0, 10.0},
   472  				{6, 5, 4, 3, 2, 1},
   473  			},
   474  			[]float64{5.5, 11.0, 16.5, 22.0, 27.5, 33.0},
   475  			120,
   476  			"true",
   477  		},
   478  		{ // percentile is less than zero.
   479  			[]int{120, 120},
   480  			-10.0,
   481  			[][]float64{
   482  				{60.0, 50.0, 40.0, 30.0, 20.0, 10.0},
   483  				{6, 5, 4, 3, 2, 1},
   484  			},
   485  			[]float64{5.5, 11.0, 16.5, 22.0, 27.5, 33.0},
   486  			120,
   487  			"true",
   488  		},
   489  		{ // percentile input is empty.
   490  			[]int{120, 120},
   491  			10.0,
   492  			[][]float64{},
   493  			[]float64{},
   494  			120,
   495  			"true",
   496  		},
   497  		{ // percentile series have different size millisPerStep.
   498  			[]int{120, 320},
   499  			33.0,
   500  			[][]float64{
   501  				{60.0, 50.0, 40.0, 30.0, 20.0, 10.0},
   502  				{6, 5, 4, 3, 2, 1},
   503  			},
   504  			[]float64{5.5, 11.0, 16.5, 22.0, 27.5, 33.0},
   505  			960,
   506  			"true",
   507  		},
   508  		{ // interpolateStr is neither "true" nor "false".
   509  			[]int{120, 320},
   510  			33.0,
   511  			[][]float64{
   512  				{60.0, 50.0, 40.0, 30.0, 20.0, 10.0},
   513  				{6, 5, 4, 3, 2, 1},
   514  			},
   515  			[]float64{5.5, 11.0, 16.5, 22.0, 27.5, 33.0},
   516  			960,
   517  			"random",
   518  		},
   519  		{ // types other than boolean and string are not allowed
   520  			[]int{120, 120, 120, 120, 120},
   521  			33.0,
   522  			[][]float64{
   523  				{math.NaN(), 16, 23, math.NaN(), 75, 48, 42, 41},
   524  				{math.NaN(), 36, 74, 43, 73},
   525  				{math.NaN(), 61, 24, 29, math.NaN(), 62, 65, 72},
   526  				{math.NaN(), 48, 94, math.NaN(), 32, 39, math.NaN(), 84},
   527  				{math.NaN(), 16, math.NaN(), 85, 34, 27, 74, math.NaN(), 72},
   528  			},
   529  			[]float64{math.NaN(), 16, 24, 43, 34},
   530  			120,
   531  			[]*ts.Series(nil),
   532  		},
   533  	}
   534  
   535  	for _, test := range tests {
   536  		seriesList := make([]*ts.Series, len(test.values))
   537  		for i := 0; i < len(seriesList); i++ {
   538  			seriesList[i] = ts.NewSeries(ctx, "<values>", time.Now(), common.NewTestSeriesValues(ctx, test.stepPerMillis[i], test.values[i]))
   539  		}
   540  
   541  		_, err := percentileOfSeries(ctx, singlePathSpec{
   542  			Values: seriesList,
   543  		}, test.percentile, test.interpolate)
   544  		assert.NotNil(t, err)
   545  	}
   546  }
   547  
   548  func TestPercentileOfSeries(t *testing.T) {
   549  	ctx := common.NewTestContext()
   550  
   551  	tests := []struct {
   552  		stepPerMillis         []int
   553  		percentile            float64
   554  		values                [][]float64
   555  		expectedValues        []float64
   556  		expectedStepPerMillis float64
   557  		interpolate           genericInterface
   558  	}{
   559  		{ // Test arrays with NaNs, multiple series, and same time step.
   560  			[]int{120, 120, 120, 120, 120},
   561  			33,
   562  			[][]float64{
   563  				{math.NaN(), 16, 23, math.NaN(), 75, 48, 42, 41},
   564  				{math.NaN(), 36, 74, 43, 73},
   565  				{math.NaN(), 61, 24, 29, math.NaN(), 62, 65, 72},
   566  				{math.NaN(), 48, 94, math.NaN(), 32, 39, math.NaN(), 84},
   567  				{math.NaN(), 16, math.NaN(), 85, 34, 27, 74, math.NaN(), 72},
   568  			},
   569  			[]float64{math.NaN(), 16, 24, 43, 34},
   570  			120,
   571  			"false",
   572  		},
   573  		{ // Test arrays with NaNs, multiple series, and same time step.
   574  			[]int{120, 120, 120, 120, 120},
   575  			33,
   576  			[][]float64{
   577  				{math.NaN(), 16, 23, math.NaN(), 75, 48, 42, 41},
   578  				{math.NaN(), 36, 74, 43, 73},
   579  				{math.NaN(), 61, 24, 29, math.NaN(), 62, 65, 72},
   580  				{math.NaN(), 48, 94, math.NaN(), 32, 39, math.NaN(), 84},
   581  				{math.NaN(), 16, math.NaN(), 85, 34, 27, 74, math.NaN(), 72},
   582  			},
   583  			[]float64{math.NaN(), 16.0, 23.65, 33.480000000000004, 33.3},
   584  			120,
   585  			"true",
   586  		},
   587  		{ // Test arrays with NaNs remove them and get correct percentile value
   588  			[]int{120, 120, 120},
   589  			5,
   590  			[][]float64{
   591  				{math.NaN(), 60, 50, 40, math.NaN(), 30, 20, 10},
   592  				{math.NaN(), 15, 12, 9, 6, 3, math.NaN()},
   593  				{math.NaN(), 6, 5, 4, 3, 2, 1},
   594  			},
   595  			[]float64{math.NaN(), 6, 5, 4, 3, 2, 1},
   596  			120,
   597  			"false",
   598  		},
   599  		{ // Test non-interpolated percentile
   600  			[]int{120, 120},
   601  			42,
   602  			[][]float64{
   603  				{60, 5, 40, 30, 20, 10},
   604  				{3, 40, 4, 1, 2, 6},
   605  			},
   606  			[]float64{60, 40, 40, 30, 20, 10},
   607  			120,
   608  			"false",
   609  		},
   610  		{ // Test non-interpolated percentile for 100th percentile
   611  			[]int{120, 120, 120},
   612  			100,
   613  			[][]float64{
   614  				{60, 50, 40, 30, 20, 10},
   615  				{18, 15, 12, 9, 6, 3},
   616  				{6, 5, 4, 3, 2, 1},
   617  			},
   618  			[]float64{60, 50, 40, 30, 20, 10},
   619  			120,
   620  			"false",
   621  		},
   622  		{ // Test non-interpolated percentile for 1st percentile
   623  			[]int{120, 120},
   624  			1,
   625  			[][]float64{
   626  				{60, 50, 40, 30, 20, 10},
   627  				{6, 5, 4, 3, 2, 1},
   628  			},
   629  			[]float64{6, 5, 4, 3, 2, 1},
   630  			120,
   631  			"false",
   632  		},
   633  		{ // Test interpolation for a percentile series
   634  			[]int{120, 120},
   635  			75,
   636  			[][]float64{
   637  				{60, 50, 40, 30, 20, 10},
   638  				{6, 5, 4, 3, 2, 1},
   639  			},
   640  			[]float64{60, 50, 40, 30, 20, 10},
   641  			120,
   642  			"true",
   643  		},
   644  	}
   645  
   646  	for _, test := range tests {
   647  		seriesList := make([]*ts.Series, len(test.values))
   648  		for i := 0; i < len(seriesList); i++ {
   649  			seriesList[i] = ts.NewSeries(ctx, "<values>", time.Now(), common.NewTestSeriesValues(ctx, test.stepPerMillis[i], test.values[i]))
   650  		}
   651  
   652  		r, err := percentileOfSeries(ctx, singlePathSpec{
   653  			Values: seriesList,
   654  		}, test.percentile, test.interpolate)
   655  		require.NoError(t, err)
   656  
   657  		output := r.Values
   658  		name := fmt.Sprintf("percentileOfSeries(<values>,"+common.FloatingPointFormat+")",
   659  			test.percentile)
   660  		assert.Equal(t, name, output[0].Name())
   661  		for step := 0; step < output[0].Len(); step++ {
   662  			v := output[0].ValueAt(step)
   663  			require.NoError(t, err)
   664  
   665  			xtest.Equalish(t, test.expectedValues[step], v)
   666  		}
   667  		xtest.Equalish(t, test.expectedStepPerMillis, output[0].MillisPerStep())
   668  	}
   669  }
   670  
   671  func TestOffset(t *testing.T) {
   672  	ctx := common.NewTestContext()
   673  	defer func() { _ = ctx.Close() }()
   674  
   675  	tests := []struct {
   676  		inputs  []float64
   677  		factor  float64
   678  		outputs []float64
   679  	}{
   680  		{
   681  			[]float64{0, 1.0, 2.0, math.NaN(), 3.0},
   682  			2.5,
   683  			[]float64{2.5, 3.5, 4.5, math.NaN(), 5.5},
   684  		},
   685  		{
   686  			[]float64{0, 1.0, 2.0, math.NaN(), 3.0},
   687  			-0.5,
   688  			[]float64{-0.5, 0.5, 1.5, math.NaN(), 2.5},
   689  		},
   690  	}
   691  
   692  	start := time.Now()
   693  	for _, test := range tests {
   694  		input := ts.NewSeries(ctx, "foo", start, common.NewTestSeriesValues(ctx, 100, test.inputs))
   695  		r, err := offset(ctx, singlePathSpec{
   696  			Values: []*ts.Series{input},
   697  		}, test.factor)
   698  		require.NoError(t, err)
   699  
   700  		outputs := r.Values
   701  		require.Equal(t, 1, len(outputs))
   702  		require.Equal(t, 100, outputs[0].MillisPerStep())
   703  		require.Equal(t, len(test.inputs), outputs[0].Len())
   704  		require.Equal(t, start, outputs[0].StartTime())
   705  		assert.Equal(t, fmt.Sprintf("offset(foo,"+common.FloatingPointFormat+")", test.factor), outputs[0].Name())
   706  
   707  		for step := 0; step < outputs[0].Len(); step++ {
   708  			v := outputs[0].ValueAt(step)
   709  			xtest.Equalish(t, test.outputs[step], v, "invalid value for %d", step)
   710  		}
   711  	}
   712  }
   713  
   714  func TestPerSecond(t *testing.T) {
   715  	ctx := common.NewTestContext()
   716  	defer func() { _ = ctx.Close() }()
   717  
   718  	tests := []struct {
   719  		millisPerStep int
   720  		input         []float64
   721  		output        []float64
   722  	}{
   723  		// increase by 1 per 100ms == 10 per sec
   724  		{100, []float64{1, 2, 3, 4, 5}, []float64{math.NaN(), 10, 10, 10, 10}},
   725  
   726  		// increase by 1 per 10s == .1 per sec
   727  		{10000, []float64{1, 2, 3, 4, 5}, []float64{math.NaN(), 0.1, 0.1, 0.1, 0.1}},
   728  
   729  		// decreasing value - rate of change not applicable
   730  		{
   731  			1000,
   732  			[]float64{5, 4, 3, 2, 1},
   733  			[]float64{math.NaN(), math.NaN(), math.NaN(), math.NaN(), math.NaN()},
   734  		},
   735  
   736  		// skip over missing values
   737  		{1000, []float64{1, 2, math.NaN(), 4, 5}, []float64{math.NaN(), 1, math.NaN(), 1, 1}},
   738  	}
   739  
   740  	for _, test := range tests {
   741  		values := common.NewTestSeriesValues(ctx, test.millisPerStep, test.input)
   742  		series := ts.NewSeries(ctx, "foo", time.Now(), values)
   743  		r, err := perSecond(ctx, singlePathSpec{
   744  			Values: []*ts.Series{series},
   745  		}, math.NaN())
   746  		require.NoError(t, err)
   747  
   748  		perSec := r.Values
   749  		require.Equal(t, 1, len(perSec))
   750  		require.Equal(t, len(test.output), perSec[0].Len())
   751  		assert.Equal(t, series.StartTime(), perSec[0].StartTime())
   752  		assert.Equal(t, "perSecond(foo)", perSec[0].Name())
   753  		for i := 0; i < perSec[0].Len(); i++ {
   754  			val := perSec[0].ValueAt(i)
   755  			xtest.Equalish(t, test.output[i], val, "invalid value for %d", i)
   756  		}
   757  	}
   758  }
   759  
   760  func TestTransformNull(t *testing.T) {
   761  	var (
   762  		start         = time.Now()
   763  		ctx           = common.NewTestContext()
   764  		millisPerStep = 100
   765  	)
   766  	defer func() { _ = ctx.Close() }()
   767  
   768  	tests := []struct {
   769  		inputs       []*ts.Series
   770  		defaultValue float64
   771  		outputs      []common.TestSeries
   772  	}{
   773  		{
   774  			[]*ts.Series{
   775  				ts.NewSeries(ctx, "foo1", start,
   776  					common.NewTestSeriesValues(ctx, millisPerStep, []float64{0, math.NaN(), 2.0, math.NaN(), 3.0})),
   777  				ts.NewSeries(ctx, "foo2", start,
   778  					common.NewTestSeriesValues(ctx, millisPerStep, []float64{math.NaN(), 7, 2.0, 6.5, math.NaN()})),
   779  			},
   780  			42.5,
   781  			[]common.TestSeries{
   782  				{
   783  					Name: "transformNull(foo1,42.500)",
   784  					Data: []float64{0, 42.5, 2.0, 42.5, 3.0},
   785  				},
   786  				{
   787  					Name: "transformNull(foo2,42.500)",
   788  					Data: []float64{42.5, 7, 2.0, 6.5, 42.5},
   789  				},
   790  			},
   791  		},
   792  		{
   793  			[]*ts.Series{
   794  				ts.NewSeries(ctx, "foo1", start,
   795  					common.NewTestSeriesValues(ctx, millisPerStep, []float64{0, 1.0, 2.0, math.NaN(), 3.0})),
   796  				ts.NewSeries(ctx, "foo2", start,
   797  					common.NewTestSeriesValues(ctx, millisPerStep, []float64{math.NaN(), 7, math.NaN(), 6.5, math.NaN()})),
   798  			},
   799  			-0.5,
   800  			[]common.TestSeries{
   801  				{
   802  					Name: "transformNull(foo1,-0.500)",
   803  					Data: []float64{0, 1.0, 2.0, -0.5, 3.0},
   804  				},
   805  				{
   806  					Name: "transformNull(foo2,-0.500)",
   807  					Data: []float64{-0.5, 7, -0.5, 6.5, -0.5},
   808  				},
   809  			},
   810  		},
   811  	}
   812  
   813  	for _, test := range tests {
   814  		r, err := transformNull(ctx, singlePathSpec{
   815  			Values: test.inputs,
   816  		}, test.defaultValue)
   817  		require.NoError(t, err)
   818  
   819  		common.CompareOutputsAndExpected(t, 100, start,
   820  			test.outputs, r.Values)
   821  	}
   822  }
   823  
   824  var (
   825  	testMovingFunctionBootstrap = testMovingFunctionStart.Add(-30 * time.Second)
   826  	testMovingFunctionStart     = time.Now().Truncate(time.Minute)
   827  	testMovingFunctionEnd       = testMovingFunctionStart.Add(time.Minute).Add(-time.Second)
   828  )
   829  
   830  func testMovingFunction(t *testing.T, target, expectedName string, values, bootstrap, output []float64) {
   831  	ctx := common.NewTestContext()
   832  	defer func() { _ = ctx.Close() }()
   833  
   834  	engine := NewEngine(&common.MovingFunctionStorage{
   835  		StepMillis:     10000,
   836  		Bootstrap:      bootstrap,
   837  		BootstrapStart: testMovingFunctionBootstrap,
   838  		Values:         values,
   839  	}, CompileOptions{})
   840  	phonyContext := common.NewContext(common.ContextOptions{
   841  		Start:  testMovingFunctionStart,
   842  		End:    testMovingFunctionEnd,
   843  		Engine: engine,
   844  	})
   845  
   846  	expr, err := phonyContext.Engine.(*Engine).Compile(target)
   847  	require.NoError(t, err)
   848  	res, err := expr.Execute(phonyContext)
   849  	require.NoError(t, err)
   850  	var expected []common.TestSeries
   851  	if output != nil {
   852  		expectedSeries := common.TestSeries{
   853  			Name: expectedName,
   854  			Data: output,
   855  		}
   856  		expected = append(expected, expectedSeries)
   857  	}
   858  	common.CompareOutputsAndExpected(t, 10000, testMovingFunctionStart,
   859  		expected, res.Values)
   860  }
   861  
   862  var (
   863  	testGeneralFunctionStart = time.Now().Add(time.Minute * -11).Truncate(time.Minute)
   864  	testGeneralFunctionEnd   = time.Now().Add(time.Minute * -3).Truncate(time.Minute)
   865  )
   866  
   867  // testGeneralFunction is a copy of testMovingFunction but without any logic for bootstrapping values
   868  func testGeneralFunction(t *testing.T, target, expectedName string, values, output []float64) {
   869  	ctx := common.NewTestContext()
   870  	defer func() { _ = ctx.Close() }()
   871  
   872  	engine := NewEngine(&common.MovingFunctionStorage{
   873  		StepMillis: 60000,
   874  		Values:     values,
   875  	}, CompileOptions{})
   876  	phonyContext := common.NewContext(common.ContextOptions{
   877  		Start:  testGeneralFunctionStart,
   878  		End:    testGeneralFunctionEnd,
   879  		Engine: engine,
   880  	})
   881  
   882  	expr, err := phonyContext.Engine.(*Engine).Compile(target)
   883  	require.NoError(t, err)
   884  	res, err := expr.Execute(phonyContext)
   885  	require.NoError(t, err)
   886  	var expected []common.TestSeries
   887  	if output != nil {
   888  		expectedSeries := common.TestSeries{
   889  			Name: expectedName,
   890  			Data: output,
   891  		}
   892  		expected = append(expected, expectedSeries)
   893  	}
   894  	common.CompareOutputsAndExpected(t, 60000, testGeneralFunctionStart, expected, res.Values)
   895  }
   896  
   897  func TestCombineBootstrapWithOriginal(t *testing.T) {
   898  	var (
   899  		contextStart = time.Date(2020, time.October, 5, 1, 15, 37, 884207922, time.UTC)
   900  		contextEnd   = time.Date(2020, time.October, 5, 1, 18, 37, 884207922, time.UTC)
   901  		ctx          = common.NewContext(common.ContextOptions{
   902  			Start:  contextStart,
   903  			End:    contextEnd,
   904  			Engine: NewEngine(&common.MovingFunctionStorage{}, CompileOptions{}),
   905  		})
   906  
   907  		originalStart            = time.Date(2020, time.October, 5, 1, 16, 0o0, 0, time.UTC)
   908  		originalValues           = []float64{14, 15, 16, 17, 18}
   909  		originalSeriesListValues = []*ts.Series{ts.NewSeries(ctx, "original", originalStart, common.NewTestSeriesValues(ctx, 30000, originalValues))}
   910  		originalSeriesList       = singlePathSpec{Values: originalSeriesListValues}
   911  
   912  		bootstrappedStart            = time.Date(2020, time.October, 5, 1, 15, 0o0, 0, time.UTC)
   913  		bootstrappedValues           = []float64{12, 13, 14, 15, 16, 17, 18}
   914  		bootstrappedSeriesListValues = []*ts.Series{ts.NewSeries(ctx, "original", bootstrappedStart, common.NewTestSeriesValues(ctx, 30000, bootstrappedValues))}
   915  		bootstrappedSeriesList       = ts.NewSeriesList()
   916  
   917  		bootstrapStartTime = time.Date(2020, time.October, 5, 1, 14, 37, 884207922, time.UTC)
   918  		bootstrapEndTime   = time.Date(2020, time.October, 5, 1, 15, 37, 884207922, time.UTC)
   919  
   920  		expectedValues = []float64{12, 13, 14, 15, 16, 17, 18}
   921  		expectedSeries = ts.NewSeries(ctx, "original", bootstrapStartTime, common.NewTestSeriesValues(ctx, 30000, expectedValues))
   922  	)
   923  	bootstrappedSeriesList.Values = bootstrappedSeriesListValues
   924  
   925  	defer func() { _ = ctx.Close() }()
   926  
   927  	output, err := combineBootstrapWithOriginal(ctx, bootstrapStartTime, bootstrapEndTime,
   928  		contextStart, contextEnd, bootstrappedSeriesList, originalSeriesList)
   929  	assert.Equal(t, output.Values[0], expectedSeries)
   930  	assert.Nil(t, err)
   931  }
   932  
   933  func TestMovingAverageSuccess(t *testing.T) {
   934  	values := []float64{12.0, 19.0, -10.0, math.NaN(), 10.0}
   935  	bootstrap := []float64{3.0, 4.0, 5.0}
   936  	expected := []float64{4.0, 7.0, 12.0, 7.0, 4.5}
   937  	expectedWithXFiles := []float64{4.0, 7.0, 12.0, 7.0, math.NaN()}
   938  
   939  	testMovingFunction(t, "movingAverage(foo.bar.baz, '30s', 0.5)", "movingAverage(foo.bar.baz,\"30s\")", values, bootstrap, expected)
   940  	testMovingFunction(t, "movingAverage(foo.bar.baz, '30s', 0.8)", "movingAverage(foo.bar.baz,\"30s\")", values, bootstrap, expectedWithXFiles)
   941  	testMovingFunction(t, "movingAverage(foo.bar.baz, 3, 0.6)", "movingAverage(foo.bar.baz,3)", values, bootstrap, expected)
   942  }
   943  
   944  func TestExponentialMovingAverageSuccess(t *testing.T) {
   945  	tests := []struct {
   946  		target       string
   947  		expectedName string
   948  		bootstrap    []float64
   949  		inputs       []float64
   950  		expected     []float64
   951  	}{
   952  		{
   953  			"exponentialMovingAverage(foo.bar.baz, '30s')",
   954  			"exponentialMovingAverage(foo.bar.baz,\"30s\")",
   955  			[]float64{0.0, 1.0, 2.0},
   956  			[]float64{3.0, 4.0, 5.0, 6.0, 7.0},
   957  			[]float64{1, 1.193548, 1.439126, 1.733376, 2.073158},
   958  		},
   959  		{
   960  			"exponentialMovingAverage(foo.bar.baz, 3)",
   961  			"exponentialMovingAverage(foo.bar.baz,3)",
   962  			[]float64{0.0, 1.0, 2.0},
   963  			[]float64{3.0, 4.0, 5.0, 6.0, 7.0},
   964  			[]float64{1, 2.5, 3.75, 4.875, 5.9375},
   965  		},
   966  		{
   967  			"exponentialMovingAverage(foo.bar.baz, 3)",
   968  			"exponentialMovingAverage(foo.bar.baz,3)",
   969  			[]float64{0.0, 1.0, 2.0},
   970  			[]float64{3.0, 4.0, 5.0, math.NaN(), 7.0},
   971  			[]float64{1, 2.5, 3.75, math.NaN(), 5.375},
   972  		},
   973  	}
   974  
   975  	for _, test := range tests {
   976  		t.Run(test.target, func(t *testing.T) {
   977  			testMovingFunction(t, test.target, test.expectedName, test.inputs, test.bootstrap, test.expected)
   978  		})
   979  	}
   980  }
   981  
   982  func testMovingFunctionError(t *testing.T, target string) {
   983  	ctx := common.NewTestContext()
   984  	defer func() { _ = ctx.Close() }()
   985  
   986  	engine := NewEngine(&common.MovingFunctionStorage{
   987  		StepMillis:     10000,
   988  		Bootstrap:      []float64{1.0},
   989  		BootstrapStart: testMovingFunctionBootstrap,
   990  		Values:         []float64{1.0},
   991  	}, CompileOptions{})
   992  	phonyContext := common.NewContext(common.ContextOptions{
   993  		Start:  testMovingFunctionStart,
   994  		End:    testMovingFunctionEnd,
   995  		Engine: engine,
   996  	})
   997  
   998  	expr, err := phonyContext.Engine.(*Engine).Compile(target)
   999  	require.NoError(t, err)
  1000  	res, err := expr.Execute(phonyContext)
  1001  	require.Error(t, err)
  1002  	require.Nil(t, res.Values)
  1003  }
  1004  
  1005  func testMovingFunctionErrorWithInput(t *testing.T, target string, values, bootstrap []float64) {
  1006  	ctx := common.NewTestContext()
  1007  	defer func() { _ = ctx.Close() }()
  1008  
  1009  	engine := NewEngine(&common.MovingFunctionStorage{
  1010  		StepMillis:     10000,
  1011  		Bootstrap:      bootstrap,
  1012  		BootstrapStart: testMovingFunctionBootstrap,
  1013  		Values:         values,
  1014  	}, CompileOptions{})
  1015  	phonyContext := common.NewContext(common.ContextOptions{
  1016  		Start:  testMovingFunctionStart,
  1017  		End:    testMovingFunctionEnd,
  1018  		Engine: engine,
  1019  	})
  1020  
  1021  	expr, err := phonyContext.Engine.(*Engine).Compile(target)
  1022  	require.NoError(t, err)
  1023  	res, err := expr.Execute(phonyContext)
  1024  	require.Error(t, err)
  1025  	require.Nil(t, res.Values)
  1026  }
  1027  
  1028  func TestMovingAverageError(t *testing.T) {
  1029  	testMovingFunctionError(t, "movingAverage(foo.bar.baz, '-30s')")
  1030  	testMovingFunctionError(t, "movingAverage(foo.bar.baz, 0)")
  1031  	testMovingFunctionErrorWithInput(t, "movingAverage(foo.bar.baz, 3, 0.1)", nil, nil)
  1032  }
  1033  
  1034  func TestMovingSumSuccess(t *testing.T) {
  1035  	values := []float64{12.0, 19.0, -10.0, math.NaN(), 10.0}
  1036  	bootstrap := []float64{3.0, 4.0, 5.0}
  1037  	expected := []float64{12.0, 21.0, 36.0, 21.0, 9.0} // (3+4+5), (4+5+12), (5+12+19), (12+19-10), (19-10+Nan)
  1038  	expectedXFF := []float64{12.0, 21.0, 36.0, 21.0, math.NaN()}
  1039  
  1040  	testMovingFunction(t, "movingSum(foo.bar.baz, '30s', 0.1)", "movingSum(foo.bar.baz,\"30s\")", values, bootstrap, expected)
  1041  	testMovingFunction(t, "movingSum(foo.bar.baz, '30s')", "movingSum(foo.bar.baz,\"30s\")", values, bootstrap, expected)
  1042  	testMovingFunction(t, "movingSum(foo.bar.baz, '30s', 1.0)", "movingSum(foo.bar.baz,\"30s\")", values, bootstrap, expectedXFF)
  1043  	testMovingFunction(t, "movingSum(foo.bar.baz, '30s')", "movingSum(foo.bar.baz,\"30s\")", values, bootstrap, expected)
  1044  
  1045  	testMovingFunction(t, "movingSum(foo.bar.baz, '30s')", "movingSum(foo.bar.baz,3)", nil, nil, nil)
  1046  }
  1047  
  1048  // TestMovingSumOfMovingSum tests that expansion of the time window
  1049  // fetched is stacked when child contexts are created, otherwise the child
  1050  // functions do not have enough data to work with to satisfy the parent call.
  1051  func TestMovingSumOfMovingSum(t *testing.T) {
  1052  	var (
  1053  		ctrl   = xgomock.NewController(t)
  1054  		store  = storage.NewMockStorage(ctrl)
  1055  		engine = NewEngine(store, CompileOptions{})
  1056  		end    = time.Now().Truncate(time.Minute)
  1057  		start  = end.Add(-5 * time.Minute)
  1058  		ctx    = common.NewContext(common.ContextOptions{
  1059  			Start:  start,
  1060  			End:    end,
  1061  			Engine: engine,
  1062  		})
  1063  		millisPerStep = 60000
  1064  		data          = []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
  1065  	)
  1066  
  1067  	defer ctrl.Finish()
  1068  	defer func() { _ = ctx.Close() }()
  1069  
  1070  	store.EXPECT().
  1071  		FetchByQuery(gomock.Any(), "foo.bar", gomock.Any()).
  1072  		DoAndReturn(func(
  1073  			_ *common.Context,
  1074  			query string,
  1075  			opts storage.FetchOptions,
  1076  		) (*storage.FetchResult, error) {
  1077  			if !opts.EndTime.Equal(end) {
  1078  				return nil, fmt.Errorf("unexpected end")
  1079  			}
  1080  			length := int(opts.EndTime.Sub(opts.StartTime) / time.Minute)
  1081  			values := data[len(data)-length:]
  1082  			return &storage.FetchResult{
  1083  				SeriesList: []*ts.Series{
  1084  					ts.NewSeries(ctx, query, opts.StartTime,
  1085  						common.NewTestSeriesValues(ctx, millisPerStep, values)),
  1086  				},
  1087  			}, nil
  1088  		}).
  1089  		AnyTimes()
  1090  
  1091  	target := `movingSum(movingSum(foo.bar,"2min"),"5min")`
  1092  
  1093  	phonyContext := common.NewContext(common.ContextOptions{
  1094  		Start:  start,
  1095  		End:    end,
  1096  		Engine: engine,
  1097  	})
  1098  
  1099  	expr, err := phonyContext.Engine.(*Engine).Compile(target)
  1100  	require.NoError(t, err)
  1101  	res, err := expr.Execute(phonyContext)
  1102  	require.NoError(t, err)
  1103  
  1104  	expected := []common.TestSeries{
  1105  		{
  1106  			Name: target,
  1107  			Data: []float64{65, 75, 85, 95, 105},
  1108  		},
  1109  	}
  1110  
  1111  	common.CompareOutputsAndExpected(t, millisPerStep, start,
  1112  		expected, res.Values)
  1113  }
  1114  
  1115  func TestMovingSumError(t *testing.T) {
  1116  	testMovingFunctionError(t, "movingSum(foo.bar.baz, '-30s')")
  1117  	testMovingFunctionError(t, "movingSum(foo.bar.baz, 0)")
  1118  }
  1119  
  1120  // TestMovingSumOriginalIDsMissingFromBootstrapIDs tests the case for the
  1121  // "moving" function families where the bootstrap of the time range that
  1122  // expands back returns timeseries not present from the original series list
  1123  // which can happen when using a temporal index (i.e. latest time window
  1124  // does not include the same timeseries as the time window from the bootstrap
  1125  // list, say using movingSum 1h but the query is only for the last few minutes
  1126  // and in the last few minutes data for a series from an hour ago exists
  1127  // but is not present in the last few minutes; for this case the results
  1128  // from the preceding hour should still be evaluated as part of the movingSum
  1129  // calculation).
  1130  func TestMovingSumOriginalIDsMissingFromBootstrapIDs(t *testing.T) {
  1131  	ctx := common.NewTestContext()
  1132  	defer func() { _ = ctx.Close() }()
  1133  
  1134  	end := time.Now().Truncate(time.Minute)
  1135  	start := end.Add(-3 * time.Minute)
  1136  	end = end.Add(time.Second) // Extend so three values are calculated.
  1137  	bootstrapStart := start.Add(-10 * time.Minute)
  1138  
  1139  	engine := NewEngine(&common.MovingFunctionStorage{
  1140  		StepMillis: 60000,
  1141  		OriginalValues: []common.SeriesNameAndValues{
  1142  			{Name: "foo.bar", Values: []float64{3, 3, 3}},
  1143  		},
  1144  		ExplicitBootstraps: []common.ExplicitBootstrap{
  1145  			{
  1146  				Start: bootstrapStart,
  1147  				Values: []common.SeriesNameAndValues{
  1148  					{Name: "foo.bar", Values: []float64{1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3}},
  1149  					{Name: "foo.baz", Values: []float64{1, 1, 1, 1, 1, 2, 2, 2, 2, 2, math.NaN(), math.NaN(), math.NaN()}},
  1150  				},
  1151  			},
  1152  		},
  1153  	}, CompileOptions{})
  1154  	phonyContext := common.NewContext(common.ContextOptions{
  1155  		Start:  start,
  1156  		End:    end,
  1157  		Engine: engine,
  1158  	})
  1159  
  1160  	target := "movingSum(foo.*, '10min')"
  1161  	expr, err := phonyContext.Engine.(*Engine).Compile(target)
  1162  	require.NoError(t, err)
  1163  	res, err := expr.Execute(phonyContext)
  1164  	require.NoError(t, err)
  1165  	expected := []common.TestSeries{
  1166  		{
  1167  			Name: "movingSum(foo.bar,\"10min\")",
  1168  			Data: []float64{15, 17, 19},
  1169  		},
  1170  		{
  1171  			Name: "movingSum(foo.baz,\"10min\")",
  1172  			Data: []float64{15, 14, 13},
  1173  		},
  1174  	}
  1175  	common.CompareOutputsAndExpected(t, 60000, start,
  1176  		expected, res.Values)
  1177  }
  1178  
  1179  // TestMovingSumAllOriginalIDsMissingFromBootstrapIDs tests the case for the
  1180  // "moving" function families where the bootstrap of the time range that
  1181  // expands back returns timeseries and the original series list is empty
  1182  // which can also happen when using a temporal index.
  1183  func TestMovingSumAllOriginalIDsMissingFromBootstrapIDs(t *testing.T) {
  1184  	ctx := common.NewTestContext()
  1185  	defer func() { _ = ctx.Close() }()
  1186  
  1187  	end := time.Now().Truncate(time.Minute)
  1188  	start := end.Add(-3 * time.Minute)
  1189  	end = end.Add(time.Second) // Extend so three values are calculated.
  1190  	bootstrapStart := start.Add(-10 * time.Minute)
  1191  
  1192  	engine := NewEngine(&common.MovingFunctionStorage{
  1193  		StepMillis: 60000,
  1194  		ExplicitBootstraps: []common.ExplicitBootstrap{
  1195  			{
  1196  				Start: bootstrapStart,
  1197  				Values: []common.SeriesNameAndValues{
  1198  					{Name: "foo.bar", Values: []float64{1, 1, 1, 1, 1, 2, 2, 2, 2, 2, math.NaN(), math.NaN(), math.NaN()}},
  1199  					{Name: "foo.baz", Values: []float64{1, 1, 1, 1, 1, 2, 2, 2, 2, 2, math.NaN(), math.NaN(), math.NaN()}},
  1200  				},
  1201  			},
  1202  		},
  1203  	}, CompileOptions{})
  1204  	phonyContext := common.NewContext(common.ContextOptions{
  1205  		Start:  start,
  1206  		End:    end,
  1207  		Engine: engine,
  1208  	})
  1209  
  1210  	target := "movingSum(foo.*, '10min')"
  1211  	expr, err := phonyContext.Engine.(*Engine).Compile(target)
  1212  	require.NoError(t, err)
  1213  	res, err := expr.Execute(phonyContext)
  1214  	require.NoError(t, err)
  1215  	expected := []common.TestSeries{
  1216  		{
  1217  			Name: "movingSum(foo.bar,\"10min\")",
  1218  			Data: []float64{15, 14, 13},
  1219  		},
  1220  		{
  1221  			Name: "movingSum(foo.baz,\"10min\")",
  1222  			Data: []float64{15, 14, 13},
  1223  		},
  1224  	}
  1225  	common.CompareOutputsAndExpected(t, 60000, start,
  1226  		expected, res.Values)
  1227  }
  1228  
  1229  // TestMovingSumOriginalIDsDifferentResolutionFromBootstrapIDs tests the case
  1230  // where when a "moving" function fetches the bootstrapped time range but that
  1231  // ends up returning a different resolution of data from a different namespace
  1232  // and as such requires an adjusted context shift.
  1233  func TestMovingSumOriginalIDsDifferentResolutionFromBootstrapIDs(t *testing.T) {
  1234  	ctx := common.NewTestContext()
  1235  	defer func() { _ = ctx.Close() }()
  1236  
  1237  	end := time.Now().Truncate(time.Minute)
  1238  	start := end.Add(-3 * time.Minute)
  1239  	end = end.Add(time.Second) // Extend so three values are calculated.
  1240  
  1241  	engine := NewEngine(&common.MovingFunctionStorage{
  1242  		StepMillis: int(time.Minute / time.Millisecond),
  1243  		OriginalValues: []common.SeriesNameAndValues{
  1244  			{Name: "foo.bar", Values: []float64{1, 1, 1}},
  1245  		},
  1246  		ExplicitBootstraps: []common.ExplicitBootstrap{
  1247  			{
  1248  				Start: start.Add(-3 * time.Minute),
  1249  				Values: []common.SeriesNameAndValues{
  1250  					// Two values for a 3min query + 3min bootstrap (6min window) at 5min resolution.
  1251  					{Name: "foo.bar", Values: []float64{2, 2}},
  1252  					{Name: "foo.baz", Values: []float64{4, 4}},
  1253  				},
  1254  				StepMillis: int((5 * time.Minute) / time.Millisecond),
  1255  			},
  1256  			{
  1257  				Start: start.Add(-15 * time.Minute),
  1258  				Values: []common.SeriesNameAndValues{
  1259  					// Four values for a 3min query + 15 bootstrap (18min window) at 5min resolution.
  1260  					{Name: "foo.bar", Values: []float64{3, 3, 3, 3}},
  1261  					{Name: "foo.baz", Values: []float64{6, 6, 6, 6}},
  1262  				},
  1263  				StepMillis: int((5 * time.Minute) / time.Millisecond),
  1264  			},
  1265  		},
  1266  	}, CompileOptions{})
  1267  	phonyContext := common.NewContext(common.ContextOptions{
  1268  		Start:  start,
  1269  		End:    end,
  1270  		Engine: engine,
  1271  	})
  1272  
  1273  	target := "movingSum(foo.*, 3)"
  1274  	expr, err := phonyContext.Engine.(*Engine).Compile(target)
  1275  	require.NoError(t, err)
  1276  	res, err := expr.Execute(phonyContext)
  1277  	require.NoError(t, err)
  1278  	expected := []common.TestSeries{
  1279  		{
  1280  			Name: "movingSum(foo.bar,3)",
  1281  			Data: []float64{9},
  1282  		},
  1283  		{
  1284  			Name: "movingSum(foo.baz,3)",
  1285  			Data: []float64{18},
  1286  		},
  1287  	}
  1288  	common.CompareOutputsAndExpected(t, int((5*time.Minute)/time.Millisecond),
  1289  		start, expected, res.Values)
  1290  }
  1291  
  1292  func TestMovingMaxSuccess(t *testing.T) {
  1293  	values := []float64{12.0, 19.0, -10.0, math.NaN(), 10.0}
  1294  	bootstrap := []float64{3.0, 4.0, 5.0}
  1295  	expected := []float64{5.0, 12.0, 19.0, 19.0, 19.0} // max(3,4,5), max(4,5,12), max(5,12,19), max(12,19,10), max(19,-10,NaN)
  1296  
  1297  	testMovingFunction(t, "movingMax(foo.bar.baz, '30s')", "movingMax(foo.bar.baz,\"30s\")", values, bootstrap, expected)
  1298  	testMovingFunction(t, "movingMax(foo.bar.baz, '30s')", "movingMax(foo.bar.baz,3)", nil, nil, nil)
  1299  }
  1300  
  1301  func TestMovingMaxError(t *testing.T) {
  1302  	testMovingFunctionError(t, "movingMax(foo.bar.baz, '-30s')")
  1303  	testMovingFunctionError(t, "movingMax(foo.bar.baz, 0)")
  1304  }
  1305  
  1306  func TestMovingMinSuccess(t *testing.T) {
  1307  	values := []float64{12.0, 19.0, -10.0, math.NaN(), 10.0}
  1308  	bootstrap := []float64{3.0, 4.0, 5.0}
  1309  	expected := []float64{3.0, 4.0, 5.0, -10.0, -10.0} // min(3,4,5), min(4,5,12), min(5,12,19), min(12,19,-10), min(19,-10,NaN)
  1310  
  1311  	testMovingFunction(t, "movingMin(foo.bar.baz, '30s')", "movingMin(foo.bar.baz,\"30s\")", values, bootstrap, expected)
  1312  	testMovingFunction(t, "movingMin(foo.bar.baz, '30s')", "movingMin(foo.bar.baz,3)", nil, nil, nil)
  1313  }
  1314  
  1315  func TestMovingMinError(t *testing.T) {
  1316  	testMovingFunctionError(t, "movingMin(foo.bar.baz, '-30s')")
  1317  	testMovingFunctionError(t, "movingMin(foo.bar.baz, 0)")
  1318  }
  1319  
  1320  func TestIsNonNull(t *testing.T) {
  1321  	ctx := common.NewTestContext()
  1322  	defer func() { _ = ctx.Close() }()
  1323  
  1324  	tests := []struct {
  1325  		inputs  []float64
  1326  		outputs []float64
  1327  	}{
  1328  		{
  1329  			[]float64{0, math.NaN(), 2.0, math.NaN(), 3.0},
  1330  			[]float64{1, 0, 1, 0, 1},
  1331  		},
  1332  		{
  1333  			[]float64{0, 1.0, 2.0, math.NaN(), 3.0},
  1334  			[]float64{1, 1, 1, 0, 1},
  1335  		},
  1336  	}
  1337  
  1338  	start := time.Now()
  1339  	for _, test := range tests {
  1340  		input := ts.NewSeries(ctx, "foo", start, common.NewTestSeriesValues(ctx, 100, test.inputs))
  1341  		r, err := isNonNull(ctx, singlePathSpec{
  1342  			Values: []*ts.Series{input},
  1343  		})
  1344  		require.NoError(t, err)
  1345  
  1346  		outputs := r.Values
  1347  		require.Equal(t, 1, len(outputs))
  1348  		require.Equal(t, 100, outputs[0].MillisPerStep())
  1349  		require.Equal(t, len(test.inputs), outputs[0].Len())
  1350  		require.Equal(t, start, outputs[0].StartTime())
  1351  		assert.Equal(t, "isNonNull(foo)", outputs[0].Name())
  1352  
  1353  		for step := 0; step < outputs[0].Len(); step++ {
  1354  			v := outputs[0].ValueAt(step)
  1355  			assert.Equal(t, test.outputs[step], v, "invalid value for %d", step)
  1356  		}
  1357  	}
  1358  }
  1359  
  1360  func TestKeepLastValue(t *testing.T) {
  1361  	ctx := common.NewTestContext()
  1362  	defer func() { _ = ctx.Close() }()
  1363  
  1364  	tests := []struct {
  1365  		inputs  []float64
  1366  		outputs []float64
  1367  		limit   int
  1368  	}{
  1369  		{
  1370  			[]float64{0, math.NaN(), 2.0, math.NaN(), 3.0},
  1371  			[]float64{0, 0, 2.0, 2.0, 3.0},
  1372  			-1,
  1373  		},
  1374  		{
  1375  			[]float64{math.NaN(), 1.0, 2.0, math.NaN(), 3.0},
  1376  			[]float64{math.NaN(), 1.0, 2.0, 2.0, 3.0},
  1377  			-1,
  1378  		},
  1379  		{
  1380  			[]float64{1.0, math.NaN(), math.NaN(), math.NaN(), 3.0, math.NaN(), math.NaN(), 2.0},
  1381  			[]float64{1.0, math.NaN(), math.NaN(), math.NaN(), 3.0, 3.0, 3.0, 2.0},
  1382  			2,
  1383  		},
  1384  	}
  1385  
  1386  	start := time.Now()
  1387  	for _, test := range tests {
  1388  		input := ts.NewSeries(ctx, "foo", start, common.NewTestSeriesValues(ctx, 100, test.inputs))
  1389  		outputs, err := keepLastValue(ctx, singlePathSpec{
  1390  			Values: []*ts.Series{input},
  1391  		}, test.limit)
  1392  		expected := common.TestSeries{Name: "keepLastValue(foo)", Data: test.outputs}
  1393  		require.NoError(t, err)
  1394  		common.CompareOutputsAndExpected(t, 100, start,
  1395  			[]common.TestSeries{expected}, outputs.Values)
  1396  	}
  1397  }
  1398  
  1399  func TestRoundFunction(t *testing.T) {
  1400  	ctx := common.NewTestContext()
  1401  	defer func() { _ = ctx.Close() }()
  1402  
  1403  	tests := []struct {
  1404  		name       string
  1405  		inputs     []float64
  1406  		outputs    []float64
  1407  		outputName string
  1408  		precision  int
  1409  	}{
  1410  		{
  1411  			"foo",
  1412  			[]float64{111.1, math.NaN(), 111.11, math.NaN(), 111.111},
  1413  			[]float64{110, math.NaN(), 110, math.NaN(), 110},
  1414  			"roundFunction(foo,-1)",
  1415  			-1,
  1416  		},
  1417  		{
  1418  			"foo",
  1419  			[]float64{1.1, math.NaN(), 1.11, math.NaN(), 1.111},
  1420  			[]float64{1, math.NaN(), 1, math.NaN(), 1},
  1421  			"roundFunction(foo)",
  1422  			0,
  1423  		},
  1424  		{
  1425  			"foo",
  1426  			[]float64{1.1, math.NaN(), 1.11, math.NaN(), 1.111},
  1427  			[]float64{1.1, math.NaN(), 1.1, math.NaN(), 1.1},
  1428  			"roundFunction(foo,1)",
  1429  			1,
  1430  		},
  1431  		{
  1432  			"foo",
  1433  			[]float64{1.1, math.NaN(), 1.11, math.NaN(), 1.111},
  1434  			[]float64{1.10, math.NaN(), 1.11, math.NaN(), 1.11},
  1435  			"roundFunction(foo,2)",
  1436  			2,
  1437  		},
  1438  	}
  1439  
  1440  	start := time.Now()
  1441  	for _, test := range tests {
  1442  		input := ts.NewSeries(ctx, test.name, start, common.NewTestSeriesValues(ctx, 100, test.inputs))
  1443  		outputs, err := roundFunction(ctx, singlePathSpec{
  1444  			Values: []*ts.Series{input},
  1445  		}, test.precision)
  1446  		expected := common.TestSeries{Name: test.outputName, Data: test.outputs}
  1447  		require.NoError(t, err)
  1448  		common.CompareOutputsAndExpected(t, 100, start,
  1449  			[]common.TestSeries{expected}, outputs.Values)
  1450  	}
  1451  }
  1452  
  1453  func TestSustainedAbove(t *testing.T) {
  1454  	ctx := common.NewTestContext()
  1455  	defer func() { _ = ctx.Close() }()
  1456  
  1457  	tests := []struct {
  1458  		inputs    []float64
  1459  		outputs   []float64
  1460  		threshold float64
  1461  		interval  string
  1462  	}{
  1463  		{
  1464  			[]float64{0, 0, 3, 3, 4, 0, 0},
  1465  			[]float64{0, 0, 3, 3, 4, 0, 0},
  1466  			2,
  1467  			"10s",
  1468  		},
  1469  		{
  1470  			[]float64{0, 0, 3, 3, 4, 0, 0},
  1471  			[]float64{0, 0, 0, 3, 4, 0, 0},
  1472  			2,
  1473  			"20s",
  1474  		},
  1475  		{
  1476  			[]float64{0, 0, 3, 3, 4, 0, 0},
  1477  			[]float64{0, 0, 0, 0, 4, 0, 0},
  1478  			2,
  1479  			"30s",
  1480  		},
  1481  		{
  1482  			[]float64{0, 0, 3, 3, 4, 0, 0},
  1483  			[]float64{0, 0, 0, 0, 0, 0, 0},
  1484  			2,
  1485  			"40s",
  1486  		},
  1487  		{
  1488  			[]float64{0, 3, 3, 4, 4, 2, 0},
  1489  			[]float64{0, 0, 0, 0, 4, 0, 0},
  1490  			4,
  1491  			"20s",
  1492  		},
  1493  		{
  1494  			[]float64{1, 2, 3, 4, 9, 9, 9, 9, 9, 3},
  1495  			[]float64{0, 0, 0, 0, 0, 0, 9, 9, 9, 0},
  1496  			8,
  1497  			"30s",
  1498  		},
  1499  		{
  1500  			[]float64{1, 2, 3, 4, 5, 5, 5, 5, 5, 3},
  1501  			[]float64{0, 0, 0, 4, 5, 5, 5, 5, 5, 0},
  1502  			4,
  1503  			"10s",
  1504  		},
  1505  		{
  1506  			[]float64{-3, -4, -1, 3, 0, -1, -5, -6, -3},
  1507  			[]float64{-4, -4, -4, 3, 0, -1, -4, -4, -4},
  1508  			-2,
  1509  			"20s",
  1510  		},
  1511  	}
  1512  
  1513  	start := time.Now()
  1514  	for _, test := range tests {
  1515  		input := ts.NewSeries(ctx, "foo", start, common.NewTestSeriesValues(ctx, 10000, test.inputs))
  1516  		r, err := sustainedAbove(ctx, singlePathSpec{
  1517  			Values: []*ts.Series{input},
  1518  		}, test.threshold, test.interval)
  1519  		require.NoError(t, err)
  1520  
  1521  		outputs := r.Values
  1522  		require.Equal(t, 1, len(outputs))
  1523  		require.Equal(t, 10000, outputs[0].MillisPerStep())
  1524  		require.Equal(t, len(test.inputs), outputs[0].Len())
  1525  		require.Equal(t, start, outputs[0].StartTime())
  1526  
  1527  		str := fmt.Sprintf("sustainedAbove(foo, %f, '%s')", test.threshold, test.interval)
  1528  
  1529  		assert.Equal(t, str, outputs[0].Name())
  1530  
  1531  		for step := 0; step < outputs[0].Len(); step++ {
  1532  			v := outputs[0].ValueAt(step)
  1533  
  1534  			assert.Equal(t, test.outputs[step], v, "invalid value for %d", step)
  1535  		}
  1536  	}
  1537  }
  1538  
  1539  func TestSustainedAboveFail(t *testing.T) {
  1540  	ctx := common.NewTestContext()
  1541  	defer func() { _ = ctx.Close() }()
  1542  
  1543  	input := ts.NewSeries(ctx, "foo", time.Now(), common.NewTestSeriesValues(ctx, 10000, []float64{0}))
  1544  	outputs, err := sustainedAbove(ctx, singlePathSpec{
  1545  		Values: []*ts.Series{input},
  1546  	}, 10, "wat")
  1547  	require.Error(t, err)
  1548  	require.Equal(t, 0, outputs.Len())
  1549  }
  1550  
  1551  func TestSustainedBelow(t *testing.T) {
  1552  	ctx := common.NewTestContext()
  1553  	defer func() { _ = ctx.Close() }()
  1554  
  1555  	tests := []struct {
  1556  		inputs    []float64
  1557  		outputs   []float64
  1558  		threshold float64
  1559  		interval  string
  1560  	}{
  1561  		{
  1562  			[]float64{4, 4, 1, 1, 1, 4, 4},
  1563  			[]float64{4, 4, 1, 1, 1, 4, 4},
  1564  			2,
  1565  			"10s",
  1566  		},
  1567  		{
  1568  			[]float64{7, 8, 3, 3, 2, 6, 7},
  1569  			[]float64{6, 6, 6, 3, 2, 6, 6},
  1570  			3,
  1571  			"20s",
  1572  		},
  1573  		{
  1574  			[]float64{9, 7, 3, 3, 2, 5, 6},
  1575  			[]float64{6, 6, 6, 6, 2, 6, 6},
  1576  			3,
  1577  			"30s",
  1578  		},
  1579  		{
  1580  			[]float64{8, 5, 3, 3, 2, 5, 8},
  1581  			[]float64{6, 6, 6, 6, 6, 6, 6},
  1582  			3,
  1583  			"40s",
  1584  		},
  1585  		{
  1586  			[]float64{4, 3, 3, 1, 1, 2, 4},
  1587  			[]float64{2, 2, 2, 2, 1, 2, 2},
  1588  			1,
  1589  			"20s",
  1590  		},
  1591  		{
  1592  			[]float64{7, 8, 9, 2, 2, 4, 2, 5, 3, 2},
  1593  			[]float64{8, 8, 8, 8, 8, 8, 2, 8, 8, 8},
  1594  			4,
  1595  			"40s",
  1596  		},
  1597  		{
  1598  			[]float64{1, 2, 3, 4, 9, 9, 9, 9, 9, 3},
  1599  			[]float64{8, 2, 3, 4, 8, 8, 8, 8, 8, 8},
  1600  			4,
  1601  			"20s",
  1602  		},
  1603  		{
  1604  			[]float64{-3, -4, -3, -1, 3, 2, -5, -4, -3, -3},
  1605  			[]float64{0, -4, -3, 0, 0, 0, 0, -4, -3, -3},
  1606  			-2,
  1607  			"20s",
  1608  		},
  1609  	}
  1610  
  1611  	start := time.Now()
  1612  	for _, test := range tests {
  1613  		input := ts.NewSeries(ctx, "foo", start, common.NewTestSeriesValues(ctx, 10000, test.inputs))
  1614  		r, err := sustainedBelow(ctx, singlePathSpec{
  1615  			Values: []*ts.Series{input},
  1616  		}, test.threshold, test.interval)
  1617  		require.NoError(t, err)
  1618  
  1619  		outputs := r.Values
  1620  		require.Equal(t, 1, len(outputs))
  1621  		require.Equal(t, 10000, outputs[0].MillisPerStep())
  1622  		require.Equal(t, len(test.inputs), outputs[0].Len())
  1623  		require.Equal(t, start, outputs[0].StartTime())
  1624  
  1625  		str := fmt.Sprintf("sustainedBelow(foo, %f, '%s')", test.threshold, test.interval)
  1626  
  1627  		assert.Equal(t, str, outputs[0].Name())
  1628  		for step := 0; step < outputs[0].Len(); step++ {
  1629  			v := outputs[0].ValueAt(step)
  1630  
  1631  			assert.Equal(t, test.outputs[step], v, "invalid value for %d", step)
  1632  		}
  1633  	}
  1634  }
  1635  
  1636  func TestSustainedBelowFail(t *testing.T) {
  1637  	ctx := common.NewTestContext()
  1638  	defer func() { _ = ctx.Close() }()
  1639  
  1640  	input := ts.NewSeries(ctx, "foo", time.Now(), common.NewTestSeriesValues(ctx, 10000, []float64{0}))
  1641  	outputs, err := sustainedBelow(ctx, singlePathSpec{
  1642  		Values: []*ts.Series{input},
  1643  	}, 10, "wat")
  1644  	require.Error(t, err)
  1645  	require.Equal(t, 0, outputs.Len())
  1646  }
  1647  
  1648  // nIntParamGoldenData holds test data for functions that take an additional "n" int parameter
  1649  type nIntParamGoldenData struct {
  1650  	inputs  []common.TestSeries
  1651  	n       int
  1652  	outputs []common.TestSeries
  1653  }
  1654  
  1655  // nIntParamGoldenDataWithAgg holds test data for functions that take an additional "n" int parameter
  1656  // It also holds an aggregation function
  1657  type nIntParamGoldenDataWithAgg struct {
  1658  	nIntParamGoldenData
  1659  	aggFunc string
  1660  }
  1661  
  1662  // rankingFunc selects the n lowest or highest series based on certain metric of the
  1663  // series (e.g., maximum, minimum, average).
  1664  type rankingFunc func(ctx *common.Context, input singlePathSpec, n int) (ts.SeriesList, error)
  1665  
  1666  // testRanking can be used to test the ranking alias functions
  1667  // (e.g. lowestAverage, highestMax, highestAverage, lowestCurrent)
  1668  // these functions are all aliases of the "meta-ranking" functions (i.e. highest and lowest)
  1669  func testRanking(t *testing.T, ctx *common.Context, tests []nIntParamGoldenData, f rankingFunc) {
  1670  	start := time.Now()
  1671  	step := 100
  1672  	for _, test := range tests {
  1673  		outputs, err := f(ctx, singlePathSpec{
  1674  			Values: generateSeriesList(ctx, start, test.inputs, step),
  1675  		}, test.n)
  1676  		if test.n < 0 {
  1677  			require.NotNil(t, err)
  1678  			require.Equal(t, "n must be positive", err.Error())
  1679  			assert.Nil(t, outputs.Values, "Nil timeseries should be returned")
  1680  			continue
  1681  		}
  1682  		require.NoError(t, err)
  1683  		common.CompareOutputsAndExpected(t, step, start,
  1684  			test.outputs, outputs.Values)
  1685  	}
  1686  }
  1687  
  1688  // testOrderedAggregationFunc is a helper function for testing lowest and highest
  1689  func testOrderedAggregationFunc(t *testing.T, ctx *common.Context, tests []nIntParamGoldenDataWithAgg, isLowest bool) {
  1690  	f := highest
  1691  	if isLowest {
  1692  		f = lowest
  1693  	}
  1694  
  1695  	start := time.Now()
  1696  	step := 100
  1697  	for _, test := range tests {
  1698  		input := singlePathSpec{Values: generateSeriesList(ctx, start, test.inputs, step)}
  1699  		outputs, err := f(ctx, input, test.n, test.aggFunc)
  1700  
  1701  		if test.n < 0 {
  1702  			require.NotNil(t, err)
  1703  			require.Equal(t, "n must be positive", err.Error())
  1704  			assert.Nil(t, outputs.Values, "Nil timeseries should be returned")
  1705  			continue
  1706  		}
  1707  
  1708  		require.NoError(t, err)
  1709  		common.CompareOutputsAndExpected(t, step, start,
  1710  			test.outputs, outputs.Values)
  1711  	}
  1712  }
  1713  
  1714  func TestHighest(t *testing.T) {
  1715  	ctx := common.NewTestContext()
  1716  	defer func() { _ = ctx.Close() }()
  1717  
  1718  	tests := []nIntParamGoldenDataWithAgg{
  1719  		{
  1720  			nIntParamGoldenData{
  1721  				testInput,
  1722  				0,
  1723  				nil,
  1724  			},
  1725  			"sum",
  1726  		},
  1727  		{
  1728  			nIntParamGoldenData{
  1729  				testInput,
  1730  				1,
  1731  				[]common.TestSeries{testInput[0]},
  1732  			},
  1733  			"current",
  1734  		},
  1735  		{
  1736  			nIntParamGoldenData{
  1737  				testInput,
  1738  				2,
  1739  				[]common.TestSeries{testInput[4], testInput[2]},
  1740  			},
  1741  			"average",
  1742  		},
  1743  		{
  1744  			nIntParamGoldenData{
  1745  				testInput,
  1746  				len(testInput) + 10, // force sort
  1747  				[]common.TestSeries{testInput[0], testInput[3], testInput[4], testInput[2], testInput[1]},
  1748  			},
  1749  			"last",
  1750  		},
  1751  	}
  1752  	testOrderedAggregationFunc(t, ctx, tests, false)
  1753  }
  1754  
  1755  func TestHighestCurrent(t *testing.T) {
  1756  	ctx := common.NewTestContext()
  1757  	defer func() { _ = ctx.Close() }()
  1758  
  1759  	tests := []nIntParamGoldenData{
  1760  		{
  1761  			testInput,
  1762  			0,
  1763  			nil,
  1764  		},
  1765  		{
  1766  			testInput,
  1767  			1,
  1768  			[]common.TestSeries{testInput[0]},
  1769  		},
  1770  		{
  1771  			testInput,
  1772  			2,
  1773  			[]common.TestSeries{testInput[0], testInput[3]},
  1774  		},
  1775  		{
  1776  			testInput,
  1777  			len(testInput) + 10, // force sort
  1778  			[]common.TestSeries{testInput[0], testInput[3], testInput[4], testInput[2], testInput[1]},
  1779  		},
  1780  	}
  1781  	testRanking(t, ctx, tests, highestCurrent)
  1782  }
  1783  
  1784  func TestHighestCurrentWithNaNSeries(t *testing.T) {
  1785  	ctx := common.NewTestContext()
  1786  	defer func() { _ = ctx.Close() }()
  1787  
  1788  	tests := []nIntParamGoldenData{
  1789  		{
  1790  			testInputWithNaNSeries,
  1791  			0,
  1792  			nil,
  1793  		},
  1794  		{
  1795  			testInputWithNaNSeries,
  1796  			1,
  1797  			[]common.TestSeries{testInputWithNaNSeries[0]},
  1798  		},
  1799  		{
  1800  			testInputWithNaNSeries,
  1801  			2,
  1802  			[]common.TestSeries{testInputWithNaNSeries[0], testInputWithNaNSeries[2]},
  1803  		},
  1804  		{
  1805  			testInputWithNaNSeries,
  1806  			3,
  1807  			[]common.TestSeries{testInputWithNaNSeries[0], testInputWithNaNSeries[2], testInputWithNaNSeries[1]},
  1808  		},
  1809  		{
  1810  			testInputWithNaNSeries,
  1811  			4,
  1812  			[]common.TestSeries{testInputWithNaNSeries[0], testInputWithNaNSeries[2], testInputWithNaNSeries[1], testInputWithNaNSeries[3]},
  1813  		},
  1814  	}
  1815  	testRanking(t, ctx, tests, highestCurrent)
  1816  }
  1817  
  1818  func TestHighestAverage(t *testing.T) {
  1819  	ctx := common.NewTestContext()
  1820  	defer func() { _ = ctx.Close() }()
  1821  
  1822  	tests := []nIntParamGoldenData{
  1823  		{
  1824  			testInput,
  1825  			1,
  1826  			[]common.TestSeries{testInput[4]},
  1827  		},
  1828  		{
  1829  			testInput,
  1830  			2,
  1831  			[]common.TestSeries{testInput[4], testInput[2]},
  1832  		},
  1833  	}
  1834  	testRanking(t, ctx, tests, highestAverage)
  1835  }
  1836  
  1837  func TestHighestMax(t *testing.T) {
  1838  	ctx := common.NewTestContext()
  1839  	defer func() { _ = ctx.Close() }()
  1840  
  1841  	tests := []nIntParamGoldenData{
  1842  		{
  1843  			testInput,
  1844  			1,
  1845  			[]common.TestSeries{testInput[4]},
  1846  		},
  1847  		{
  1848  			testInput,
  1849  			2,
  1850  			[]common.TestSeries{testInput[4], testInput[0]},
  1851  		},
  1852  	}
  1853  	testRanking(t, ctx, tests, highestMax)
  1854  }
  1855  
  1856  //nolint:govet
  1857  func TestFallbackSeries(t *testing.T) {
  1858  	ctx := common.NewTestContext()
  1859  	defer func() { _ = ctx.Close() }()
  1860  
  1861  	tests := []struct {
  1862  		input    []common.TestSeries
  1863  		fallback []common.TestSeries
  1864  		output   []common.TestSeries
  1865  	}{
  1866  		{
  1867  			nil,
  1868  			[]common.TestSeries{{Name: "output", Data: []float64{0, 1.0}}},
  1869  			[]common.TestSeries{{Name: "output", Data: []float64{0, 1.0}}},
  1870  		},
  1871  		{
  1872  			[]common.TestSeries{},
  1873  			[]common.TestSeries{{Name: "output", Data: []float64{0, 1.0}}},
  1874  			[]common.TestSeries{{Name: "output", Data: []float64{0, 1.0}}},
  1875  		},
  1876  		{
  1877  			[]common.TestSeries{{Name: "output", Data: []float64{0, 2.0}}},
  1878  			[]common.TestSeries{{Name: "fallback", Data: []float64{0, 1.0}}},
  1879  			[]common.TestSeries{{Name: "output", Data: []float64{0, 2.0}}},
  1880  		},
  1881  	}
  1882  
  1883  	start := time.Now()
  1884  	step := 100
  1885  	for _, test := range tests {
  1886  
  1887  		inputs := generateSeriesList(ctx, start, test.input, step)
  1888  		fallbacks := generateSeriesList(ctx, start, test.fallback, step)
  1889  
  1890  		outputs, err := fallbackSeries(ctx, singlePathSpec{
  1891  			Values: inputs,
  1892  		}, singlePathSpec{
  1893  			Values: fallbacks,
  1894  		})
  1895  		require.NoError(t, err)
  1896  
  1897  		common.CompareOutputsAndExpected(t, step, start,
  1898  			test.output, outputs.Values)
  1899  	}
  1900  }
  1901  
  1902  func TestMostDeviant(t *testing.T) {
  1903  	ctx := common.NewTestContext()
  1904  	defer func() { _ = ctx.Close() }()
  1905  
  1906  	tests := []nIntParamGoldenData{
  1907  		{
  1908  			testInput,
  1909  			-2,
  1910  			nil,
  1911  		},
  1912  		{
  1913  			testInput,
  1914  			1,
  1915  			[]common.TestSeries{testInput[4]},
  1916  		},
  1917  		{
  1918  			testInput,
  1919  			2,
  1920  			[]common.TestSeries{testInput[4], testInput[3]},
  1921  		},
  1922  	}
  1923  	testRanking(t, ctx, tests, mostDeviant)
  1924  }
  1925  
  1926  func TestLowest(t *testing.T) {
  1927  	ctx := common.NewTestContext()
  1928  	defer func() { _ = ctx.Close() }()
  1929  
  1930  	tests := []nIntParamGoldenDataWithAgg{
  1931  		{
  1932  			nIntParamGoldenData{
  1933  				testInput,
  1934  				0,
  1935  				nil,
  1936  			},
  1937  			"max",
  1938  		},
  1939  		{
  1940  			nIntParamGoldenData{
  1941  				testInput,
  1942  				2,
  1943  				[]common.TestSeries{testInput[1], testInput[3]},
  1944  			},
  1945  			"sum",
  1946  		},
  1947  		{
  1948  			nIntParamGoldenData{
  1949  				testInput,
  1950  				2,
  1951  				[]common.TestSeries{testInput[1], testInput[2]},
  1952  			},
  1953  			"current",
  1954  		},
  1955  		{
  1956  			nIntParamGoldenData{
  1957  				testInput,
  1958  				3,
  1959  				[]common.TestSeries{testInput[1], testInput[3], testInput[0]},
  1960  			},
  1961  			"average",
  1962  		},
  1963  	}
  1964  	testOrderedAggregationFunc(t, ctx, tests, true)
  1965  }
  1966  
  1967  func TestLowestAverage(t *testing.T) {
  1968  	ctx := common.NewTestContext()
  1969  	defer func() { _ = ctx.Close() }()
  1970  
  1971  	tests := []nIntParamGoldenData{
  1972  		{
  1973  			testInput,
  1974  			0,
  1975  			nil,
  1976  		},
  1977  		{
  1978  			testInput,
  1979  			1,
  1980  			[]common.TestSeries{testInput[1]},
  1981  		},
  1982  		{
  1983  			testInput,
  1984  			2,
  1985  			[]common.TestSeries{testInput[1], testInput[3]},
  1986  		},
  1987  		{
  1988  			testInput,
  1989  			3,
  1990  			[]common.TestSeries{testInput[1], testInput[3], testInput[0]},
  1991  		},
  1992  	}
  1993  	testRanking(t, ctx, tests, lowestAverage)
  1994  }
  1995  
  1996  func TestLowestCurrent(t *testing.T) {
  1997  	ctx := common.NewTestContext()
  1998  	defer func() { _ = ctx.Close() }()
  1999  
  2000  	tests := []nIntParamGoldenData{
  2001  		{
  2002  			testInput,
  2003  			0,
  2004  			nil,
  2005  		},
  2006  		{
  2007  			testInput,
  2008  			1,
  2009  			[]common.TestSeries{testInput[1]},
  2010  		},
  2011  		{
  2012  			testInput,
  2013  			2,
  2014  			[]common.TestSeries{testInput[1], testInput[2]},
  2015  		},
  2016  		{
  2017  			testInput,
  2018  			3,
  2019  			[]common.TestSeries{testInput[1], testInput[2], testInput[4]},
  2020  		},
  2021  	}
  2022  	testRanking(t, ctx, tests, lowestCurrent)
  2023  }
  2024  
  2025  type comparatorFunc func(ctx *common.Context, series singlePathSpec, n float64) (ts.SeriesList, error)
  2026  
  2027  func testComparatorFunc(
  2028  	t *testing.T,
  2029  	f comparatorFunc,
  2030  	n float64,
  2031  	resultIndexes []int,
  2032  ) {
  2033  	ctx := common.NewTestContext()
  2034  	defer func() { _ = ctx.Close() }()
  2035  
  2036  	input := getTestInput(ctx)
  2037  	results, err := f(ctx, singlePathSpec{
  2038  		Values: input,
  2039  	}, n)
  2040  	require.Nil(t, err)
  2041  	require.Equal(t, len(resultIndexes), results.Len())
  2042  	for i, idx := range resultIndexes {
  2043  		require.Equal(t, input[idx], results.Values[i])
  2044  	}
  2045  }
  2046  
  2047  func TestMaximumAbove(t *testing.T) {
  2048  	testComparatorFunc(t, maximumAbove, -10, []int{0, 2, 3, 4})
  2049  	testComparatorFunc(t, maximumAbove, 600, []int{0, 4})
  2050  	testComparatorFunc(t, maximumAbove, 100000, nil)
  2051  }
  2052  
  2053  func TestMinimumAbove(t *testing.T) {
  2054  	testComparatorFunc(t, minimumAbove, -1000, []int{0, 2, 3, 4})
  2055  	testComparatorFunc(t, minimumAbove, -100, []int{0, 2, 4})
  2056  	testComparatorFunc(t, minimumAbove, 1, nil)
  2057  }
  2058  
  2059  func TestAverageAbove(t *testing.T) {
  2060  	testComparatorFunc(t, averageAbove, 0, []int{0, 2, 3, 4})
  2061  	testComparatorFunc(t, averageAbove, 1, []int{0, 2, 4})
  2062  	testComparatorFunc(t, averageAbove, 12000, nil)
  2063  }
  2064  
  2065  func TestAverageBelow(t *testing.T) {
  2066  	testComparatorFunc(t, averageBelow, 0, nil)
  2067  	testComparatorFunc(t, averageBelow, 600, []int{0, 2, 3})
  2068  	testComparatorFunc(t, averageBelow, 12000, []int{0, 2, 3, 4})
  2069  }
  2070  
  2071  func TestCurrentAbove(t *testing.T) {
  2072  	testComparatorFunc(t, currentAbove, -10, []int{0, 2, 3, 4})
  2073  	testComparatorFunc(t, currentAbove, -5, []int{0, 3, 4})
  2074  	testComparatorFunc(t, currentAbove, 5, nil)
  2075  }
  2076  
  2077  func TestCurrentBelow(t *testing.T) {
  2078  	testComparatorFunc(t, currentBelow, 5, []int{0, 2, 3, 4})
  2079  	testComparatorFunc(t, currentBelow, 0, []int{2, 4})
  2080  	testComparatorFunc(t, currentBelow, -5, []int{2})
  2081  	testComparatorFunc(t, currentBelow, -10, nil)
  2082  }
  2083  
  2084  func TestRemoveBelowValue(t *testing.T) {
  2085  	ctx := common.NewTestContext()
  2086  	defer func() { _ = ctx.Close() }()
  2087  
  2088  	nan := math.NaN()
  2089  	tests := []struct {
  2090  		inputs  []common.TestSeries
  2091  		n       float64
  2092  		outputs []common.TestSeries
  2093  	}{
  2094  		{
  2095  			testSmallInput,
  2096  			500,
  2097  			[]common.TestSeries{
  2098  				{"foo", []float64{nan, 601, nan, nan}},
  2099  				{"bar", []float64{500, nan}},
  2100  			},
  2101  		},
  2102  		{
  2103  			testSmallInput,
  2104  			4,
  2105  			[]common.TestSeries{
  2106  				{"foo", []float64{nan, 601, nan, 4}},
  2107  				{"bar", []float64{500, nan}},
  2108  			},
  2109  		},
  2110  	}
  2111  	start := time.Now()
  2112  	step := 100
  2113  	for _, test := range tests {
  2114  		outputs, err := removeBelowValue(ctx, singlePathSpec{
  2115  			Values: generateSeriesList(ctx, start, test.inputs, step),
  2116  		}, test.n)
  2117  		require.NoError(t, err)
  2118  		for i := range test.outputs { // overwrite series names
  2119  			name := fmt.Sprintf("removeBelowValue(%s, "+common.FloatingPointFormat+")",
  2120  				test.outputs[i].Name, test.n)
  2121  			test.outputs[i].Name = name
  2122  		}
  2123  		common.CompareOutputsAndExpected(t, step, start,
  2124  			test.outputs, outputs.Values)
  2125  	}
  2126  }
  2127  
  2128  func TestRemoveAboveValue(t *testing.T) {
  2129  	ctx := common.NewTestContext()
  2130  	defer func() { _ = ctx.Close() }()
  2131  
  2132  	nan := math.NaN()
  2133  	tests := []struct {
  2134  		inputs  []common.TestSeries
  2135  		n       float64
  2136  		outputs []common.TestSeries
  2137  	}{
  2138  		{
  2139  			testSmallInput,
  2140  			500,
  2141  			[]common.TestSeries{
  2142  				{"foo", []float64{0, nan, 3, 4}},
  2143  				{"bar", []float64{500, -8}},
  2144  			},
  2145  		},
  2146  		{
  2147  			testSmallInput,
  2148  			3,
  2149  			[]common.TestSeries{
  2150  				{"foo", []float64{0, nan, 3, nan}},
  2151  				{"bar", []float64{nan, -8}},
  2152  			},
  2153  		},
  2154  	}
  2155  	start := time.Now()
  2156  	step := 100
  2157  	for _, test := range tests {
  2158  		outputs, err := removeAboveValue(ctx, singlePathSpec{
  2159  			Values: generateSeriesList(ctx, start, test.inputs, step),
  2160  		}, test.n)
  2161  		require.NoError(t, err)
  2162  		for i := range test.outputs { // overwrite series names
  2163  			test.outputs[i].Name = fmt.Sprintf(
  2164  				"removeAboveValue(%s, "+common.FloatingPointFormat+")",
  2165  				test.outputs[i].Name,
  2166  				test.n,
  2167  			)
  2168  		}
  2169  		common.CompareOutputsAndExpected(t, step, start,
  2170  			test.outputs, outputs.Values)
  2171  	}
  2172  }
  2173  
  2174  func TestRemoveEmptySeries(t *testing.T) {
  2175  	ctx := common.NewTestContext()
  2176  	defer func() { _ = ctx.Close() }()
  2177  
  2178  	nan := math.NaN()
  2179  	tests := []struct {
  2180  		inputs       []common.TestSeries
  2181  		xFilesFactor float64
  2182  		outputs      []common.TestSeries
  2183  	}{
  2184  		{
  2185  			[]common.TestSeries{
  2186  				{Name: "foo", Data: []float64{500, 600, 700}},
  2187  				{Name: "bar", Data: []float64{500, 600, nan}},
  2188  				{Name: "baz", Data: []float64{500, nan, nan}},
  2189  				{Name: "qux", Data: []float64{nan, nan, nan}},
  2190  			},
  2191  			0,
  2192  			[]common.TestSeries{
  2193  				{Name: "foo", Data: []float64{500, 600, 700}},
  2194  				{Name: "bar", Data: []float64{500, 600, nan}},
  2195  				{Name: "baz", Data: []float64{500, nan, nan}},
  2196  			},
  2197  		},
  2198  		{
  2199  			[]common.TestSeries{
  2200  				{Name: "foo", Data: []float64{500, 600, 700}},
  2201  				{Name: "bar", Data: []float64{500, 600, nan}},
  2202  				{Name: "baz", Data: []float64{500, nan, nan}},
  2203  				{Name: "qux", Data: []float64{nan, nan, nan}},
  2204  			},
  2205  			0.5,
  2206  			[]common.TestSeries{
  2207  				{Name: "foo", Data: []float64{500, 600, 700}},
  2208  				{Name: "bar", Data: []float64{500, 600, nan}},
  2209  			},
  2210  		},
  2211  		{
  2212  			[]common.TestSeries{
  2213  				{Name: "foo", Data: []float64{500, 600, 700}},
  2214  				{Name: "bar", Data: []float64{500, 600, nan}},
  2215  				{Name: "baz", Data: []float64{500, nan, nan}},
  2216  				{Name: "qux", Data: []float64{nan, nan, nan}},
  2217  			},
  2218  			1,
  2219  			[]common.TestSeries{
  2220  				{Name: "foo", Data: []float64{500, 600, 700}},
  2221  			},
  2222  		},
  2223  	}
  2224  	start := time.Now()
  2225  	step := 100
  2226  	for _, test := range tests {
  2227  		outputs, err := removeEmptySeries(ctx,
  2228  			singlePathSpec{Values: generateSeriesList(ctx, start, test.inputs, step)},
  2229  			test.xFilesFactor)
  2230  		require.NoError(t, err)
  2231  		common.CompareOutputsAndExpected(t, step, start,
  2232  			test.outputs, outputs.Values)
  2233  	}
  2234  }
  2235  
  2236  func TestFilterSeries(t *testing.T) {
  2237  	ctx := common.NewTestContext()
  2238  	defer func() { _ = ctx.Close() }()
  2239  
  2240  	nan := math.NaN()
  2241  	tests := []struct {
  2242  		inputs        []common.TestSeries
  2243  		aggregationFn string
  2244  		comparator    string
  2245  		threashold    float64
  2246  		outputs       []common.TestSeries
  2247  	}{
  2248  		{
  2249  			[]common.TestSeries{
  2250  				{Name: "foo", Data: []float64{500, 600, 700}},
  2251  				{Name: "bar", Data: []float64{500, 600, nan}},
  2252  				{Name: "baz", Data: []float64{500, nan, nan}},
  2253  				{Name: "qux", Data: []float64{nan, nan, nan}},
  2254  			},
  2255  			"max",
  2256  			">",
  2257  			600,
  2258  			[]common.TestSeries{
  2259  				{Name: "foo", Data: []float64{500, 600, 700}},
  2260  			},
  2261  		},
  2262  		{
  2263  			[]common.TestSeries{
  2264  				{Name: "foo", Data: []float64{500, 600, 700}},
  2265  				{Name: "bar", Data: []float64{500, 600, nan}},
  2266  				{Name: "baz", Data: []float64{500, nan, nan}},
  2267  				{Name: "qux", Data: []float64{nan, nan, nan}},
  2268  			},
  2269  			"max",
  2270  			">=",
  2271  			600,
  2272  			[]common.TestSeries{
  2273  				{Name: "foo", Data: []float64{500, 600, 700}},
  2274  				{Name: "bar", Data: []float64{500, 600, nan}},
  2275  			},
  2276  		},
  2277  	}
  2278  	start := time.Now()
  2279  	step := 100
  2280  	for _, test := range tests {
  2281  		outputs, err := filterSeries(ctx,
  2282  			singlePathSpec{Values: generateSeriesList(ctx, start, test.inputs, step)},
  2283  			test.aggregationFn, test.comparator, test.threashold)
  2284  		require.NoError(t, err)
  2285  		common.CompareOutputsAndExpected(t, step, start,
  2286  			test.outputs, outputs.Values)
  2287  	}
  2288  }
  2289  
  2290  func generateSeriesList(ctx *common.Context, start time.Time, inputs []common.TestSeries, step int) []*ts.Series {
  2291  	tSeriesList := make([]*ts.Series, 0, len(inputs))
  2292  	for _, in := range inputs {
  2293  		tSeries := ts.NewSeries(ctx, in.Name, start, common.NewTestSeriesValues(ctx, step, in.Data))
  2294  		tSeriesList = append(tSeriesList, tSeries)
  2295  	}
  2296  	return tSeriesList
  2297  }
  2298  
  2299  func TestScaleToSeconds(t *testing.T) {
  2300  	ctx := common.NewTestContext()
  2301  	defer func() { _ = ctx.Close() }()
  2302  
  2303  	tests := []struct {
  2304  		millisPerStep int
  2305  		values        []float64
  2306  		expected      []float64
  2307  		seconds       int
  2308  	}{
  2309  		{
  2310  			1000,
  2311  			[]float64{1000.0, 2000.0, 3000.0, 4000.0, 5000.0},
  2312  			[]float64{2000.0, 4000.0, 6000.0, 8000.0, 10000.0},
  2313  			2,
  2314  		},
  2315  		// expected values should double when step is halved
  2316  		// relative to the original expected values
  2317  		{
  2318  			500,
  2319  			[]float64{1000.0, 2000.0, 3000.0, 4000.0, 5000.0},
  2320  			[]float64{4000.0, 8000.0, 12000.0, 16000.0, 20000.0},
  2321  			2,
  2322  		},
  2323  		// expected values should drop by a factor of 1/5 when step is multiplied by 5
  2324  		// relative to the original expected values
  2325  		{
  2326  			5000,
  2327  			[]float64{1000.0, 2000.0, 3000.0, 4000.0, 5000.0},
  2328  			[]float64{400.0, 800.0, 1200.0, 1600.0, 2000.0},
  2329  			2,
  2330  		},
  2331  	}
  2332  
  2333  	for _, test := range tests {
  2334  		timeSeries := ts.NewSeries(ctx, "<values>", ctx.StartTime,
  2335  			common.NewTestSeriesValues(ctx, test.millisPerStep, test.values))
  2336  
  2337  		r, err := scaleToSeconds(ctx, singlePathSpec{
  2338  			Values: []*ts.Series{timeSeries},
  2339  		}, test.seconds)
  2340  		require.NoError(t, err)
  2341  
  2342  		output := r.Values
  2343  		require.Equal(t, 1, len(output))
  2344  		assert.Equal(t, "scaleToSeconds(<values>,2)", output[0].Name())
  2345  		for step := 0; step < output[0].Len(); step++ {
  2346  			v := output[0].ValueAt(step)
  2347  			assert.Equal(t, test.expected[step], v)
  2348  		}
  2349  	}
  2350  }
  2351  
  2352  func TestAsPercentWithSeriesTotal(t *testing.T) {
  2353  	ctx := common.NewTestContext()
  2354  	defer func() { _ = ctx.Close() }()
  2355  
  2356  	tests := []struct {
  2357  		valuesStep int
  2358  		values     []float64
  2359  		totalsStep int
  2360  		totals     []float64
  2361  		outputStep int
  2362  		output     []float64
  2363  	}{
  2364  		{
  2365  			100,
  2366  			[]float64{10.0, 20.0, 30.0, 40.0, 50.0},
  2367  			100,
  2368  			[]float64{1000.0, 1000.0, 1000.0, 1000.0, 1000.0},
  2369  			100,
  2370  			[]float64{1.0, 2.0, 3.0, 4.0, 5.0},
  2371  		},
  2372  		{
  2373  			100,
  2374  			[]float64{12.0, 14.0, 16.0, math.NaN(), 20.0},
  2375  			150,
  2376  			[]float64{50.0, 50.0, 25.0, 50.0, 50.0},
  2377  			300,
  2378  			[]float64{28.0, 53.0},
  2379  		},
  2380  	}
  2381  
  2382  	for _, test := range tests {
  2383  		timeSeries := ts.NewSeries(ctx, "<values>", ctx.StartTime,
  2384  			common.NewTestSeriesValues(ctx, test.valuesStep, test.values))
  2385  		totalSeries := ts.NewSeries(ctx, "<totals>", ctx.StartTime,
  2386  			common.NewTestSeriesValues(ctx, test.totalsStep, test.totals))
  2387  
  2388  		r, err := asPercent(ctx, singlePathSpec{
  2389  			Values: []*ts.Series{timeSeries},
  2390  		}, ts.SeriesList{
  2391  			Values: []*ts.Series{totalSeries},
  2392  		})
  2393  		require.NoError(t, err, fmt.Sprintf("err: %v", err))
  2394  
  2395  		output := r.Values
  2396  		require.Equal(t, 1, len(output))
  2397  		require.Equal(t, output[0].MillisPerStep(), test.outputStep)
  2398  		assert.Equal(t, "asPercent(<values>,<totals>)", output[0].Name())
  2399  
  2400  		for step := 0; step < output[0].Len(); step++ {
  2401  			v := output[0].ValueAt(step)
  2402  			assert.Equal(t, math.Trunc(v), test.output[step])
  2403  		}
  2404  	}
  2405  }
  2406  
  2407  func TestAsPercentWithFloatTotal(t *testing.T) {
  2408  	ctx := common.NewTestContext()
  2409  	defer func() { _ = ctx.Close() }()
  2410  
  2411  	nan := math.NaN()
  2412  	tests := []struct {
  2413  		valuesStep int
  2414  		values     []float64
  2415  		total      float64
  2416  		outputStep int
  2417  		output     []float64
  2418  	}{
  2419  		{
  2420  			100,
  2421  			[]float64{12.0, 14.0, 16.0, nan, 20.0},
  2422  			20.0,
  2423  			100,
  2424  			[]float64{60, 70, 80, nan, 100},
  2425  		},
  2426  		{
  2427  			100,
  2428  			[]float64{12.0, 14.0, 16.0, nan, 20.0},
  2429  			0,
  2430  			100,
  2431  			[]float64{nan, nan, nan, nan, nan},
  2432  		},
  2433  	}
  2434  
  2435  	for _, test := range tests {
  2436  		timeSeries := ts.NewSeries(ctx, "<values>", ctx.StartTime,
  2437  			common.NewTestSeriesValues(ctx, test.valuesStep, test.values))
  2438  		r, err := asPercent(ctx, singlePathSpec{
  2439  			Values: []*ts.Series{timeSeries},
  2440  		}, test.total)
  2441  		require.NoError(t, err)
  2442  
  2443  		output := r.Values
  2444  		require.Equal(t, 1, len(output))
  2445  		require.Equal(t, output[0].MillisPerStep(), test.outputStep)
  2446  		expectedName := fmt.Sprintf("asPercent(<values>,"+common.FloatingPointFormat+")",
  2447  			test.total)
  2448  		assert.Equal(t, expectedName, output[0].Name())
  2449  
  2450  		for step := 0; step < output[0].Len(); step++ {
  2451  			v := output[0].ValueAt(step)
  2452  			xtest.Equalish(t, math.Trunc(v), test.output[step])
  2453  		}
  2454  	}
  2455  }
  2456  
  2457  func TestAsPercentWithNilTotal(t *testing.T) {
  2458  	ctx := common.NewTestContext()
  2459  	defer func() { _ = ctx.Close() }()
  2460  
  2461  	nan := math.NaN()
  2462  	tests := []struct {
  2463  		valuesStep int
  2464  		values     []float64
  2465  		outputStep int
  2466  		output     []float64
  2467  	}{
  2468  		{
  2469  			60,
  2470  			[]float64{12.0, 14.0, 16.0, nan, 20.0},
  2471  			60,
  2472  			[]float64{100, 100, 100, nan, 100},
  2473  		},
  2474  	}
  2475  
  2476  	for _, test := range tests {
  2477  		timeSeries := ts.NewSeries(ctx, "<values>", ctx.StartTime,
  2478  			common.NewTestSeriesValues(ctx, test.valuesStep, test.values))
  2479  		r, err := asPercent(ctx, singlePathSpec{
  2480  			Values: []*ts.Series{timeSeries},
  2481  		}, nil)
  2482  		require.NoError(t, err)
  2483  
  2484  		output := r.Values
  2485  		require.Equal(t, 1, len(output))
  2486  		require.Equal(t, output[0].MillisPerStep(), test.outputStep)
  2487  		expectedName := "asPercent(<values>,sumSeries(<values>))"
  2488  		assert.Equal(t, expectedName, output[0].Name())
  2489  
  2490  		for step := 0; step < output[0].Len(); step++ {
  2491  			v := output[0].ValueAt(step)
  2492  			xtest.Equalish(t, math.Trunc(v), test.output[step])
  2493  		}
  2494  	}
  2495  }
  2496  
  2497  func TestAsPercentWithSeriesList(t *testing.T) {
  2498  	ctx := common.NewTestContext()
  2499  	defer func() { _ = ctx.Close() }()
  2500  
  2501  	nan := math.NaN()
  2502  	inputs := []struct {
  2503  		name   string
  2504  		step   int
  2505  		values []float64
  2506  	}{
  2507  		{
  2508  			"foo",
  2509  			100,
  2510  			[]float64{12.0, 14.0, 16.0, nan, 20.0, 30.0},
  2511  		},
  2512  		{
  2513  			"bar",
  2514  			200,
  2515  			[]float64{7.0, nan, 25.0},
  2516  		},
  2517  	}
  2518  	outputs := []struct {
  2519  		name   string
  2520  		step   int
  2521  		values []float64
  2522  	}{
  2523  		{
  2524  			"asPercent(foo,sumSeries(foo,bar))",
  2525  			200,
  2526  			[]float64{65.0, 100.0, 50.0},
  2527  		},
  2528  		{
  2529  			"asPercent(bar,sumSeries(foo,bar))",
  2530  			200,
  2531  			[]float64{35.0, nan, 50.0},
  2532  		},
  2533  	}
  2534  
  2535  	inputSeries := make([]*ts.Series, 0, len(inputs))
  2536  	for _, input := range inputs {
  2537  		timeSeries := ts.NewSeries(
  2538  			ctx,
  2539  			input.name,
  2540  			ctx.StartTime,
  2541  			common.NewTestSeriesValues(ctx, input.step, input.values),
  2542  		)
  2543  		inputSeries = append(inputSeries, timeSeries)
  2544  	}
  2545  
  2546  	expected := make([]*ts.Series, 0, len(outputs))
  2547  	for _, output := range outputs {
  2548  		timeSeries := ts.NewSeries(
  2549  			ctx,
  2550  			output.name,
  2551  			ctx.StartTime,
  2552  			common.NewTestSeriesValues(ctx, output.step, output.values),
  2553  		)
  2554  		expected = append(expected, timeSeries)
  2555  	}
  2556  
  2557  	r, err := asPercent(ctx, singlePathSpec{
  2558  		Values: inputSeries,
  2559  	}, nil)
  2560  	require.NoError(t, err)
  2561  	requireEqual(t, expected, r.Values)
  2562  }
  2563  
  2564  func requireEqual(t *testing.T, expected, results []*ts.Series) {
  2565  	require.Equal(t, len(expected), len(results))
  2566  	for i := 0; i < len(results); i++ {
  2567  		require.Equal(t, expected[i].MillisPerStep(), results[i].MillisPerStep())
  2568  		require.Equal(t, expected[i].Len(), results[i].Len())
  2569  		require.Equal(t, expected[i].Name(), results[i].Name())
  2570  		for step := 0; step < results[i].Len(); step++ {
  2571  			xtest.Equalish(t, expected[i].ValueAt(step), results[i].ValueAt(step))
  2572  		}
  2573  	}
  2574  }
  2575  
  2576  func TestAsPercentWithSeriesListAndTotalSeriesList(t *testing.T) {
  2577  	ctx := common.NewTestContext()
  2578  	defer func() { _ = ctx.Close() }()
  2579  
  2580  	nan := math.NaN()
  2581  	inputs := []struct {
  2582  		name   string
  2583  		step   int
  2584  		values []float64
  2585  	}{
  2586  		{
  2587  			"foo.value",
  2588  			100,
  2589  			[]float64{12.0, 14.0, 16.0, nan, 20.0, 30.0},
  2590  		},
  2591  		{
  2592  			"bar.value",
  2593  			200,
  2594  			[]float64{7.0, nan, 25.0},
  2595  		},
  2596  	}
  2597  	totals := []struct {
  2598  		name   string
  2599  		step   int
  2600  		values []float64
  2601  	}{
  2602  		{
  2603  			"foo.total",
  2604  			100,
  2605  			[]float64{24.0, 28.0, 48.0, nan, 40.0, 60.0},
  2606  		},
  2607  		{
  2608  			"bar.total",
  2609  			200,
  2610  			[]float64{14.0, nan, 75.0},
  2611  		},
  2612  	}
  2613  	outputs := []struct {
  2614  		name   string
  2615  		step   int
  2616  		values []float64
  2617  	}{
  2618  		{
  2619  			"asPercent(bar.value,bar.total)",
  2620  			200,
  2621  			[]float64{50.0, nan, 33.33333333333333},
  2622  		},
  2623  		{
  2624  			"asPercent(foo.value,foo.total)",
  2625  			100,
  2626  			[]float64{50.0, 50.0, 33.33333333333333, nan, 50.0, 50.0},
  2627  		},
  2628  	}
  2629  
  2630  	var inputSeries []*ts.Series // nolint: prealloc
  2631  	for _, input := range inputs {
  2632  		timeSeries := ts.NewSeries(
  2633  			ctx,
  2634  			input.name,
  2635  			ctx.StartTime,
  2636  			common.NewTestSeriesValues(ctx, input.step, input.values),
  2637  		)
  2638  		inputSeries = append(inputSeries, timeSeries)
  2639  	}
  2640  
  2641  	var totalSeries []*ts.Series // nolint: prealloc
  2642  	for _, input := range totals {
  2643  		timeSeries := ts.NewSeries(
  2644  			ctx,
  2645  			input.name,
  2646  			ctx.StartTime,
  2647  			common.NewTestSeriesValues(ctx, input.step, input.values),
  2648  		)
  2649  		totalSeries = append(totalSeries, timeSeries)
  2650  	}
  2651  
  2652  	var expected []*ts.Series // nolint: prealloc
  2653  	for _, output := range outputs {
  2654  		timeSeries := ts.NewSeries(
  2655  			ctx,
  2656  			output.name,
  2657  			ctx.StartTime,
  2658  			common.NewTestSeriesValues(ctx, output.step, output.values),
  2659  		)
  2660  		expected = append(expected, timeSeries)
  2661  	}
  2662  
  2663  	r, err := asPercent(ctx, singlePathSpec{
  2664  		Values: inputSeries,
  2665  	}, singlePathSpec{
  2666  		Values: totalSeries,
  2667  	})
  2668  	require.NoError(t, err)
  2669  	requireEqual(t, expected, r.Values)
  2670  }
  2671  
  2672  func TestAsPercentWithSeriesListAndEmptyTotalSeriesList(t *testing.T) {
  2673  	ctx := common.NewTestContext()
  2674  	defer func() { _ = ctx.Close() }()
  2675  
  2676  	inputs := []struct {
  2677  		name   string
  2678  		step   int
  2679  		values []float64
  2680  	}{
  2681  		{
  2682  			"foo.bar",
  2683  			100,
  2684  			[]float64{2.5, 5, 7.5, 10},
  2685  		},
  2686  		{
  2687  			"foo.baz",
  2688  			100,
  2689  			[]float64{10, 20, 30, 40},
  2690  		},
  2691  	}
  2692  	outputs := []struct {
  2693  		name   string
  2694  		step   int
  2695  		values []float64
  2696  	}{
  2697  		{
  2698  			"asPercent(foo.bar,sumSeries(foo.*))",
  2699  			100,
  2700  			[]float64{20, 20, 20, 20},
  2701  		},
  2702  		{
  2703  			"asPercent(foo.baz,sumSeries(foo.*))",
  2704  			100,
  2705  			[]float64{80, 80, 80, 80},
  2706  		},
  2707  	}
  2708  
  2709  	var inputSeries []*ts.Series // nolint: prealloc
  2710  	for _, input := range inputs {
  2711  		timeSeries := ts.NewSeries(
  2712  			ctx,
  2713  			input.name,
  2714  			ctx.StartTime,
  2715  			common.NewTestSeriesValues(ctx, input.step, input.values),
  2716  		)
  2717  		timeSeries.Specification = "foo.*"
  2718  		inputSeries = append(inputSeries, timeSeries)
  2719  	}
  2720  
  2721  	var expected []*ts.Series // nolint: prealloc
  2722  	for _, output := range outputs {
  2723  		timeSeries := ts.NewSeries(
  2724  			ctx,
  2725  			output.name,
  2726  			ctx.StartTime,
  2727  			common.NewTestSeriesValues(ctx, output.step, output.values),
  2728  		)
  2729  		expected = append(expected, timeSeries)
  2730  	}
  2731  
  2732  	r, err := asPercent(ctx, singlePathSpec{
  2733  		Values: inputSeries,
  2734  	}, nil)
  2735  	require.NoError(t, err)
  2736  	requireEqual(t, expected, r.Values)
  2737  }
  2738  
  2739  func TestAsPercentWithNodesAndTotalNil(t *testing.T) {
  2740  	ctx := common.NewTestContext()
  2741  	defer func() { _ = ctx.Close() }()
  2742  
  2743  	inputs := []struct {
  2744  		name   string
  2745  		step   int
  2746  		values []float64
  2747  	}{
  2748  		{
  2749  			"cpu.foo.core1",
  2750  			200,
  2751  			[]float64{12.0, 5.0, 48.0},
  2752  		},
  2753  		{
  2754  			"cpu.foo.core2",
  2755  			200,
  2756  			[]float64{12.0, 15.0, 16.0},
  2757  		},
  2758  		{
  2759  			"cpu.bar.core1",
  2760  			200,
  2761  			[]float64{12.0, 14.0, 16.0},
  2762  		},
  2763  	}
  2764  	outputs := []struct {
  2765  		name   string
  2766  		step   int
  2767  		values []float64
  2768  	}{
  2769  		{
  2770  			"asPercent(cpu.bar.core1,cpu.bar.core1)",
  2771  			200,
  2772  			[]float64{100.0, 100.0, 100.0},
  2773  		},
  2774  		{
  2775  			"asPercent(cpu.foo.core1,sumSeries(cpu.foo.core1,cpu.foo.core2))",
  2776  			200,
  2777  			[]float64{50.0, 25.0, 75.0},
  2778  		},
  2779  		{
  2780  			"asPercent(cpu.foo.core2,sumSeries(cpu.foo.core1,cpu.foo.core2))",
  2781  			200,
  2782  			[]float64{50.0, 75.0, 25.0},
  2783  		},
  2784  	}
  2785  
  2786  	var inputSeries []*ts.Series // nolint: prealloc
  2787  	for _, input := range inputs {
  2788  		timeSeries := ts.NewSeries(
  2789  			ctx,
  2790  			input.name,
  2791  			ctx.StartTime,
  2792  			common.NewTestSeriesValues(ctx, input.step, input.values),
  2793  		)
  2794  		inputSeries = append(inputSeries, timeSeries)
  2795  	}
  2796  
  2797  	var expected []*ts.Series // nolint: prealloc
  2798  	for _, output := range outputs {
  2799  		timeSeries := ts.NewSeries(
  2800  			ctx,
  2801  			output.name,
  2802  			ctx.StartTime,
  2803  			common.NewTestSeriesValues(ctx, output.step, output.values),
  2804  		)
  2805  		expected = append(expected, timeSeries)
  2806  	}
  2807  
  2808  	r, err := asPercent(ctx, singlePathSpec{
  2809  		Values: inputSeries,
  2810  	}, nil, 1)
  2811  	require.NoError(t, err)
  2812  	requireEqual(t, expected, r.Values)
  2813  }
  2814  
  2815  func TestAsPercentWithNodesAndTotalSeriesList(t *testing.T) {
  2816  	ctx := common.NewTestContext()
  2817  	defer func() { _ = ctx.Close() }()
  2818  
  2819  	nan := math.NaN()
  2820  	inputs := []struct {
  2821  		name   string
  2822  		step   int
  2823  		values []float64
  2824  	}{
  2825  		{
  2826  			"cpu.foo.core1",
  2827  			200,
  2828  			[]float64{12.0, 5.0, 48.0},
  2829  		},
  2830  		{
  2831  			"cpu.foo.core2",
  2832  			200,
  2833  			[]float64{12.0, 15.0, 16.0},
  2834  		},
  2835  		{
  2836  			"cpu.bar.core1",
  2837  			200,
  2838  			[]float64{12.0, 14.0, 16.0},
  2839  		},
  2840  		{
  2841  			"cpu.qux.core1",
  2842  			200,
  2843  			[]float64{12.0, 14.0, 16.0},
  2844  		},
  2845  	}
  2846  	totals := []struct {
  2847  		name   string
  2848  		step   int
  2849  		values []float64
  2850  	}{
  2851  		{
  2852  			"cpu_cluster.foo.zone-a",
  2853  			200,
  2854  			[]float64{24.0, 40.0, 256.0},
  2855  		},
  2856  		{
  2857  			"cpu_cluster.foo.zone-b",
  2858  			200,
  2859  			[]float64{24.0, 40.0, 256.0},
  2860  		},
  2861  		{
  2862  			"cpu_cluster.bar",
  2863  			200,
  2864  			[]float64{48.0, 14.0, 16.0},
  2865  		},
  2866  		{
  2867  			"cpu_cluster.baz",
  2868  			200,
  2869  			[]float64{12.0, 14.0, 16.0},
  2870  		},
  2871  	}
  2872  	outputs := []struct {
  2873  		name   string
  2874  		step   int
  2875  		values []float64
  2876  	}{
  2877  		{
  2878  			"asPercent(cpu.bar.core1,cpu_cluster.bar)",
  2879  			200,
  2880  			[]float64{25.0, 100.0, 100.0},
  2881  		},
  2882  		{
  2883  			"asPercent(MISSING,cpu_cluster.baz)",
  2884  			200,
  2885  			[]float64{nan, nan, nan},
  2886  		},
  2887  		{
  2888  			"asPercent(cpu.foo.core1,sumSeries(cpu_cluster.foo.zone-a,cpu_cluster.foo.zone-b))",
  2889  			200,
  2890  			[]float64{25.0, 6.25, 9.375},
  2891  		},
  2892  		{
  2893  			"asPercent(cpu.foo.core2,sumSeries(cpu_cluster.foo.zone-a,cpu_cluster.foo.zone-b))",
  2894  			200,
  2895  			[]float64{25.0, 18.75, 3.125},
  2896  		},
  2897  		{
  2898  			"asPercent(cpu.qux.core1,MISSING)",
  2899  			200,
  2900  			[]float64{nan, nan, nan},
  2901  		},
  2902  	}
  2903  
  2904  	var inputSeries []*ts.Series // nolint: prealloc
  2905  	for _, input := range inputs {
  2906  		timeSeries := ts.NewSeries(
  2907  			ctx,
  2908  			input.name,
  2909  			ctx.StartTime,
  2910  			common.NewTestSeriesValues(ctx, input.step, input.values),
  2911  		)
  2912  		inputSeries = append(inputSeries, timeSeries)
  2913  	}
  2914  
  2915  	var totalSeries []*ts.Series // nolint: prealloc
  2916  	for _, input := range totals {
  2917  		timeSeries := ts.NewSeries(
  2918  			ctx,
  2919  			input.name,
  2920  			ctx.StartTime,
  2921  			common.NewTestSeriesValues(ctx, input.step, input.values),
  2922  		)
  2923  		totalSeries = append(totalSeries, timeSeries)
  2924  	}
  2925  
  2926  	var expected []*ts.Series // nolint: prealloc
  2927  	for _, output := range outputs {
  2928  		timeSeries := ts.NewSeries(
  2929  			ctx,
  2930  			output.name,
  2931  			ctx.StartTime,
  2932  			common.NewTestSeriesValues(ctx, output.step, output.values),
  2933  		)
  2934  		expected = append(expected, timeSeries)
  2935  	}
  2936  
  2937  	r, err := asPercent(ctx, singlePathSpec{
  2938  		Values: inputSeries,
  2939  	}, singlePathSpec{
  2940  		Values: totalSeries,
  2941  	}, 1)
  2942  	require.NoError(t, err)
  2943  	requireEqual(t, expected, r.Values)
  2944  }
  2945  
  2946  func testLogarithm(t *testing.T, base float64, asserts func(*ts.Series)) {
  2947  	ctx := common.NewTestContext()
  2948  	defer func() { _ = ctx.Close() }()
  2949  
  2950  	invals := make([]float64, 101)
  2951  	for i := range invals {
  2952  		invals[i] = float64(i)
  2953  	}
  2954  
  2955  	series := ts.NewSeries(ctx, "hello", time.Now(),
  2956  		common.NewTestSeriesValues(ctx, 10000, invals))
  2957  
  2958  	r, err := logarithm(ctx, singlePathSpec{
  2959  		Values: []*ts.Series{series},
  2960  	}, base)
  2961  	require.NoError(t, err)
  2962  
  2963  	output := r.Values
  2964  	require.Equal(t, 1, len(output))
  2965  	assert.Equal(t, fmt.Sprintf("log(hello, %f)", base), output[0].Name())
  2966  	assert.Equal(t, series.StartTime(), output[0].StartTime())
  2967  	require.Equal(t, len(invals), output[0].Len())
  2968  	xtest.Equalish(t, math.NaN(), output[0].ValueAt(0))
  2969  	asserts(output[0])
  2970  }
  2971  
  2972  func TestLogarithm(t *testing.T) {
  2973  	testLogarithm(t, 10, func(output *ts.Series) {
  2974  		xtest.Equalish(t, 0, output.ValueAt(1))
  2975  		xtest.Equalish(t, 1, output.ValueAt(10))
  2976  		xtest.Equalish(t, 2, output.ValueAt(100))
  2977  	})
  2978  	testLogarithm(t, 2, func(output *ts.Series) {
  2979  		xtest.Equalish(t, 0, output.ValueAt(1))
  2980  		xtest.Equalish(t, 1, output.ValueAt(2))
  2981  		xtest.Equalish(t, 2, output.ValueAt(4))
  2982  	})
  2983  	testLogarithm(t, 3.142, func(output *ts.Series) {
  2984  		xtest.Equalish(t, 0, output.ValueAt(1))
  2985  		xtest.Equalish(t, 0.6054429879326457, output.ValueAt(2))
  2986  		xtest.Equalish(t, 0.9596044321978149, output.ValueAt(3))
  2987  	})
  2988  
  2989  	_, err := logarithm(nil, singlePathSpec{}, -1)
  2990  	require.NotNil(t, err)
  2991  }
  2992  
  2993  func TestIntegral(t *testing.T) {
  2994  	ctx := common.NewTestContext()
  2995  	defer func() { _ = ctx.Close() }()
  2996  
  2997  	invals := []float64{
  2998  		0, 1, 2, 3, 4, 5, 6, math.NaN(), 8, math.NaN(),
  2999  	}
  3000  
  3001  	outvals := []float64{
  3002  		0, 1, 3, 6, 10, 15, 21, math.NaN(), 29, math.NaN(),
  3003  	}
  3004  
  3005  	series := ts.NewSeries(ctx, "hello", time.Now(),
  3006  		common.NewTestSeriesValues(ctx, 10000, invals))
  3007  
  3008  	r, err := integral(ctx, singlePathSpec{
  3009  		Values: []*ts.Series{series},
  3010  	})
  3011  	require.NoError(t, err)
  3012  
  3013  	output := r.Values
  3014  	require.Equal(t, 1, len(output))
  3015  	assert.Equal(t, "integral(hello)", output[0].Name())
  3016  	assert.Equal(t, series.StartTime(), output[0].StartTime())
  3017  	require.Equal(t, len(outvals), output[0].Len())
  3018  	for i, expected := range outvals {
  3019  		xtest.Equalish(t, expected, output[0].ValueAt(i), "incorrect value at %d", i)
  3020  	}
  3021  }
  3022  
  3023  func TestInterpolate(t *testing.T) {
  3024  	ctx := common.NewTestContext()
  3025  	defer func() { _ = ctx.Close() }()
  3026  
  3027  	tests := []struct {
  3028  		values []float64
  3029  		output []float64
  3030  		limit  int
  3031  	}{
  3032  		{
  3033  			[]float64{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0},
  3034  			[]float64{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0},
  3035  			-1,
  3036  		},
  3037  		{
  3038  			[]float64{math.NaN(), 2.0, math.NaN(), 4.0, math.NaN(), 6.0, math.NaN(), 8.0, math.NaN(), 10.0, math.NaN(), 12.0, math.NaN(), 14.0, math.NaN(), 16.0, math.NaN(), 18.0, math.NaN(), 20.0},
  3039  			[]float64{math.NaN(), 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0},
  3040  			-1,
  3041  		},
  3042  		{
  3043  			[]float64{1.0, 2.0, math.NaN(), math.NaN(), math.NaN(), 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, math.NaN(), math.NaN(), math.NaN()},
  3044  			[]float64{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, math.NaN(), math.NaN(), math.NaN()},
  3045  			-1,
  3046  		},
  3047  		{
  3048  			[]float64{1.0, 2.0, 3.0, 4.0, math.NaN(), 6.0, math.NaN(), math.NaN(), 9.0, 10.0, 11.0, math.NaN(), 13.0, math.NaN(), math.NaN(), math.NaN(), math.NaN(), 18.0, 19.0, 20.0},
  3049  			[]float64{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0},
  3050  			-1,
  3051  		},
  3052  		{
  3053  			[]float64{1.0, 2.0, math.NaN(), math.NaN(), math.NaN(), 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, math.NaN(), math.NaN()},
  3054  			[]float64{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, math.NaN(), math.NaN()},
  3055  			-1,
  3056  		},
  3057  		{
  3058  			[]float64{1.0, 2.0, math.NaN(), math.NaN(), math.NaN(), math.NaN(), math.NaN(), 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, math.NaN(), math.NaN()},
  3059  			[]float64{1.0, 2.0, math.NaN(), math.NaN(), math.NaN(), math.NaN(), math.NaN(), 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, math.NaN(), math.NaN()},
  3060  			3,
  3061  		},
  3062  		{
  3063  			[]float64{math.NaN(), math.NaN(), math.NaN(), math.NaN(), math.NaN(), 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0},
  3064  			[]float64{math.NaN(), math.NaN(), math.NaN(), math.NaN(), math.NaN(), 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0},
  3065  			-1,
  3066  		},
  3067  	}
  3068  
  3069  	start := time.Now()
  3070  	step := 100
  3071  	for _, test := range tests {
  3072  		input := []common.TestSeries{{Name: "foo", Data: test.values}}
  3073  		expected := []common.TestSeries{{Name: "interpolate(foo)", Data: test.output}}
  3074  		timeSeries := generateSeriesList(ctx, start, input, step)
  3075  		output, err := interpolate(ctx, singlePathSpec{
  3076  			Values: timeSeries,
  3077  		}, test.limit)
  3078  		require.NoError(t, err)
  3079  		common.CompareOutputsAndExpected(t, step, start,
  3080  			expected, output.Values)
  3081  	}
  3082  }
  3083  
  3084  func TestIntegralByInterval(t *testing.T) {
  3085  	ctx := common.NewTestContext()
  3086  	defer func() { _ = ctx.Close() }()
  3087  
  3088  	invals := []float64{
  3089  		math.NaN(), 1, 2, 3, 4, 5, math.NaN(), 6, 7, 8,
  3090  	}
  3091  
  3092  	outvals := []float64{
  3093  		0, 1, 2, 5, 4, 9, 0, 6, 7, 15,
  3094  	}
  3095  
  3096  	series := ts.NewSeries(ctx, "hello", time.Now(),
  3097  		common.NewTestSeriesValues(ctx, 60000, invals))
  3098  
  3099  	r, err := integralByInterval(ctx, singlePathSpec{
  3100  		Values: []*ts.Series{series},
  3101  	}, "2min")
  3102  	require.NoError(t, err)
  3103  
  3104  	output := r.Values
  3105  	require.Equal(t, 1, len(output))
  3106  	assert.Equal(t, "integralByInterval(hello, 2min)", output[0].Name())
  3107  	assert.Equal(t, series.StartTime(), output[0].StartTime())
  3108  	require.Equal(t, len(outvals), output[0].Len())
  3109  	for i, expected := range outvals {
  3110  		xtest.Equalish(t, expected, output[0].ValueAt(i), "incorrect value at %d", i)
  3111  	}
  3112  }
  3113  
  3114  func TestDerivative(t *testing.T) {
  3115  	ctx := common.NewTestContext()
  3116  	defer func() { _ = ctx.Close() }()
  3117  
  3118  	tests := []struct {
  3119  		values []float64
  3120  		output []float64
  3121  	}{
  3122  		{
  3123  			[]float64{10.0, 20.0, 30.0, 5.0, 5.0},
  3124  			[]float64{math.NaN(), 10.0, 10.0, -25.0, 0.0},
  3125  		},
  3126  		{
  3127  			[]float64{50.0, 50.0, 25.0, 250.0, 350.0},
  3128  			[]float64{math.NaN(), 0.0, -25.0, 225.0, 100.0},
  3129  		},
  3130  	}
  3131  
  3132  	start := time.Now()
  3133  	step := 100
  3134  	for _, test := range tests {
  3135  		input := []common.TestSeries{{Name: "foo", Data: test.values}}
  3136  		expected := []common.TestSeries{{Name: "derivative(foo)", Data: test.output}}
  3137  		timeSeries := generateSeriesList(ctx, start, input, step)
  3138  		output, err := derivative(ctx, singlePathSpec{
  3139  			Values: timeSeries,
  3140  		})
  3141  		require.NoError(t, err)
  3142  		common.CompareOutputsAndExpected(t, step, start,
  3143  			expected, output.Values)
  3144  	}
  3145  }
  3146  
  3147  func TestNonNegativeDerivative(t *testing.T) {
  3148  	ctx := common.NewTestContext()
  3149  	defer func() { _ = ctx.Close() }()
  3150  
  3151  	tests := []struct {
  3152  		values   []float64
  3153  		maxValue float64
  3154  		output   []float64
  3155  	}{
  3156  		{
  3157  			[]float64{10.0, 20.0, 30.0, 5.0, 5.0},
  3158  			math.NaN(),
  3159  			[]float64{math.NaN(), 10.0, 10.0, math.NaN(), 0.0},
  3160  		},
  3161  		{
  3162  			[]float64{50.0, 50.0, 25.0, 250.0, 350.0},
  3163  			100.0,
  3164  			[]float64{math.NaN(), 0.0, 76.0, 225.0, 100.0},
  3165  		},
  3166  	}
  3167  
  3168  	start := time.Now()
  3169  	step := 100
  3170  	for _, test := range tests {
  3171  		input := []common.TestSeries{{Name: "foo", Data: test.values}}
  3172  		expected := []common.TestSeries{{Name: "nonNegativeDerivative(foo)", Data: test.output}}
  3173  		timeSeries := generateSeriesList(ctx, start, input, step)
  3174  		output, err := nonNegativeDerivative(ctx, singlePathSpec{
  3175  			Values: timeSeries,
  3176  		}, test.maxValue)
  3177  		require.NoError(t, err)
  3178  		common.CompareOutputsAndExpected(t, step, start, expected, output.Values)
  3179  	}
  3180  }
  3181  
  3182  type TimeSeriesPtrVector []*ts.Series
  3183  
  3184  func (o TimeSeriesPtrVector) Len() int           { return len(o) }
  3185  func (o TimeSeriesPtrVector) Less(i, j int) bool { return o[i].Name() < o[j].Name() }
  3186  func (o TimeSeriesPtrVector) Swap(i, j int)      { o[i], o[j] = o[j], o[i] }
  3187  
  3188  func TestConstantLine(t *testing.T) {
  3189  	ctx := common.NewTestContext()
  3190  	defer func() { _ = ctx.Close() }()
  3191  
  3192  	testValue := 5.0
  3193  	r, err := constantLine(ctx, testValue)
  3194  	require.Nil(t, err)
  3195  
  3196  	testSeries := r.Values
  3197  	require.Equal(t, 1, len(testSeries))
  3198  	require.Equal(t, 3, testSeries[0].Len())
  3199  	expectedName := fmt.Sprintf(common.FloatingPointFormat, testValue)
  3200  	require.Equal(t, expectedName, testSeries[0].Name())
  3201  	for i := 0; i < testSeries[0].Len(); i++ {
  3202  		require.Equal(t, float64(testValue), testSeries[0].ValueAt(i))
  3203  	}
  3204  }
  3205  
  3206  func TestIdentity(t *testing.T) {
  3207  	ctx := common.NewTestContext()
  3208  	defer func() { _ = ctx.Close() }()
  3209  
  3210  	testName := "testName.mytest"
  3211  	r, err := identity(ctx, testName)
  3212  	require.Nil(t, err)
  3213  
  3214  	testSeries := r.Values
  3215  	require.Equal(t, 1, len(testSeries))
  3216  	require.Equal(t, testName, testSeries[0].Name())
  3217  	require.Equal(t, 60, testSeries[0].Len())
  3218  	expectedValue := ctx.StartTime.Unix()
  3219  	for i := 0; i < testSeries[0].Len(); i++ {
  3220  		require.Equal(t, float64(expectedValue), testSeries[0].ValueAt(i))
  3221  		expectedValue += 60
  3222  	}
  3223  }
  3224  
  3225  func TestLimit(t *testing.T) {
  3226  	ctx := common.NewTestContext()
  3227  	defer func() { _ = ctx.Close() }()
  3228  
  3229  	// invalid input
  3230  	testInput := getTestInput(ctx)
  3231  	testSeries, err := limit(ctx, singlePathSpec{
  3232  		Values: testInput,
  3233  	}, -1)
  3234  	require.NotNil(t, err)
  3235  
  3236  	// valid input
  3237  	testSeries, err = limit(ctx, singlePathSpec{
  3238  		Values: testInput,
  3239  	}, 1)
  3240  	require.Nil(t, err)
  3241  	require.Equal(t, 1, testSeries.Len())
  3242  
  3243  	// input bigger than length of series
  3244  	testSeries, err = limit(ctx, singlePathSpec{
  3245  		Values: testInput,
  3246  	}, 10)
  3247  	require.Nil(t, err)
  3248  	require.Equal(t, len(testInput), testSeries.Len())
  3249  }
  3250  
  3251  func TestLimitSortStable(t *testing.T) {
  3252  	ctx := common.NewTestContext()
  3253  	defer func() { _ = ctx.Close() }()
  3254  
  3255  	constValues := common.NewTestSeriesValues(ctx, 1000, []float64{1, 2, 3, 4})
  3256  	series := []*ts.Series{
  3257  		ts.NewSeries(ctx, "qux", time.Now(), constValues),
  3258  		ts.NewSeries(ctx, "bar", time.Now(), constValues),
  3259  		ts.NewSeries(ctx, "foo", time.Now(), constValues),
  3260  		ts.NewSeries(ctx, "baz", time.Now(), constValues),
  3261  	}
  3262  
  3263  	// Check that if input order is random that the same first
  3264  	// series is chosen deterministically each time if the results weren't
  3265  	// already ordered.
  3266  	var lastOrder []string
  3267  	for i := 0; i < 100; i++ {
  3268  		rand.Shuffle(len(series), func(i, j int) {
  3269  			series[i], series[j] = series[j], series[i]
  3270  		})
  3271  
  3272  		result, err := limit(ctx, singlePathSpec(ts.SeriesList{
  3273  			Values:      series,
  3274  			SortApplied: false,
  3275  		}), 2)
  3276  		require.NoError(t, err)
  3277  
  3278  		order := make([]string, 0, len(result.Values))
  3279  		for _, series := range result.Values {
  3280  			order = append(order, series.Name())
  3281  		}
  3282  
  3283  		expectedOrder := lastOrder
  3284  		lastOrder = order
  3285  		if expectedOrder == nil {
  3286  			continue
  3287  		}
  3288  
  3289  		require.Equal(t, expectedOrder, order)
  3290  	}
  3291  }
  3292  
  3293  func TestHitcount(t *testing.T) {
  3294  	now := time.Now()
  3295  	tests := []struct {
  3296  		name           string
  3297  		startTime      time.Time
  3298  		stepInMilli    int
  3299  		values         []float64
  3300  		intervalString string
  3301  		newStartTime   time.Time
  3302  		newStep        int
  3303  		output         []float64
  3304  	}{
  3305  		{
  3306  			"foo",
  3307  			now,
  3308  			1000,
  3309  			[]float64{1.0, 2.0, 3.0, 4.0, 5.0, math.NaN(), 6.0},
  3310  			"2s",
  3311  			now.Add(-time.Second),
  3312  			2000,
  3313  			[]float64{1.0, 5.0, 9.0, 6.0},
  3314  		},
  3315  		{
  3316  			"bar",
  3317  			now,
  3318  			1000,
  3319  			[]float64{1.0, 2.0, 3.0, 4.0, 5.0, math.NaN(), 6.0},
  3320  			"10s",
  3321  			now.Add(-3 * time.Second),
  3322  			10000,
  3323  			[]float64{21.0},
  3324  		},
  3325  	}
  3326  
  3327  	for i, input := range tests {
  3328  		input := input
  3329  		t.Run(fmt.Sprintf("test_%d_%s", i, input.name), func(t *testing.T) {
  3330  			ctrl := xgomock.NewController(t)
  3331  			defer ctrl.Finish()
  3332  
  3333  			store := storage.NewMockStorage(ctrl)
  3334  			engine := NewEngine(store, CompileOptions{})
  3335  
  3336  			ctx := common.NewContext(common.ContextOptions{
  3337  				Start:  input.startTime,
  3338  				End:    input.startTime.Add(time.Second * 10),
  3339  				Engine: engine,
  3340  			})
  3341  			defer func() { _ = ctx.Close() }()
  3342  
  3343  			series := ts.NewSeries(ctx, input.name, input.startTime,
  3344  				common.NewTestSeriesValues(ctx, input.stepInMilli, input.values))
  3345  
  3346  			target := fmt.Sprintf("hitcount(%s, %q, false)", input.name, input.intervalString)
  3347  			testSeriesFn := func(
  3348  				*common.Context,
  3349  				string,
  3350  				storage.FetchOptions,
  3351  			) (*storage.FetchResult, error) {
  3352  				return &storage.FetchResult{SeriesList: []*ts.Series{series}}, nil
  3353  			}
  3354  
  3355  			store.EXPECT().
  3356  				FetchByQuery(gomock.Any(), gomock.Any(), gomock.Any()).
  3357  				DoAndReturn(testSeriesFn).
  3358  				AnyTimes()
  3359  			expr, err := engine.Compile(target)
  3360  			require.NoError(t, err)
  3361  			res, err := expr.Execute(ctx)
  3362  			require.NoError(t, err)
  3363  			expected := common.TestSeries{
  3364  				Name: fmt.Sprintf("hitcount(%s, %q)", input.name, input.intervalString),
  3365  				Data: input.output,
  3366  			}
  3367  			require.NoError(t, err)
  3368  			common.CompareOutputsAndExpected(t, input.newStep, input.newStartTime,
  3369  				[]common.TestSeries{expected}, res.Values)
  3370  		})
  3371  	}
  3372  }
  3373  
  3374  func TestSubstr(t *testing.T) {
  3375  	ctx := common.NewTestContext()
  3376  	defer func() { _ = ctx.Close() }()
  3377  
  3378  	now := ctx.StartTime
  3379  	input := struct {
  3380  		name        string
  3381  		startTime   time.Time
  3382  		stepInMilli int
  3383  		values      []float64
  3384  	}{
  3385  		"aliasByName(foo.bar,baz)",
  3386  		now,
  3387  		1000,
  3388  		[]float64{1.0, 2.0, 3.0},
  3389  	}
  3390  
  3391  	series := ts.NewSeries(
  3392  		ctx,
  3393  		input.name,
  3394  		input.startTime,
  3395  		common.NewTestSeriesValues(ctx, input.stepInMilli, input.values),
  3396  	)
  3397  	results, err := substr(ctx, singlePathSpec{
  3398  		Values: []*ts.Series{series},
  3399  	}, 1, 0)
  3400  	expected := common.TestSeries{Name: "bar", Data: input.values}
  3401  	require.NoError(t, err)
  3402  	common.CompareOutputsAndExpected(t, input.stepInMilli, input.startTime,
  3403  		[]common.TestSeries{expected}, results.Values)
  3404  
  3405  	results, err = substr(ctx, singlePathSpec{
  3406  		Values: []*ts.Series{series},
  3407  	}, 0, 2)
  3408  	expected = common.TestSeries{Name: "foo.bar", Data: input.values}
  3409  	require.NoError(t, err)
  3410  	common.CompareOutputsAndExpected(t, input.stepInMilli, input.startTime,
  3411  		[]common.TestSeries{expected}, results.Values)
  3412  
  3413  	results, err = substr(ctx, singlePathSpec{
  3414  		Values: []*ts.Series{series},
  3415  	}, 0, 0)
  3416  	expected = common.TestSeries{Name: "foo.bar", Data: input.values}
  3417  	require.NoError(t, err)
  3418  	common.CompareOutputsAndExpected(t, input.stepInMilli, input.startTime,
  3419  		[]common.TestSeries{expected}, results.Values)
  3420  
  3421  	// Negative support -1, 0.
  3422  	results, err = substr(ctx, singlePathSpec{
  3423  		Values: []*ts.Series{series},
  3424  	}, -1, 0)
  3425  	expected = common.TestSeries{Name: "bar", Data: input.values}
  3426  	require.NoError(t, err)
  3427  	common.CompareOutputsAndExpected(t, input.stepInMilli, input.startTime,
  3428  		[]common.TestSeries{expected}, results.Values)
  3429  
  3430  	// Negative support -3, 0.
  3431  	results, err = substr(ctx, singlePathSpec{
  3432  		Values: []*ts.Series{series},
  3433  	}, -3, 0)
  3434  	expected = common.TestSeries{Name: "bar", Data: input.values}
  3435  	require.NoError(t, err)
  3436  	common.CompareOutputsAndExpected(t, input.stepInMilli, input.startTime,
  3437  		[]common.TestSeries{expected}, results.Values)
  3438  
  3439  	results, err = substr(ctx, singlePathSpec{
  3440  		Values: []*ts.Series{series},
  3441  	}, 2, 1)
  3442  	require.Error(t, err)
  3443  
  3444  	results, err = substr(ctx, singlePathSpec{
  3445  		Values: []*ts.Series{series},
  3446  	}, 3, 4)
  3447  	require.Error(t, err)
  3448  }
  3449  
  3450  type mockStorage struct{}
  3451  
  3452  func (*mockStorage) FetchByQuery(
  3453  	ctx xctx.Context,
  3454  	query string,
  3455  	opts storage.FetchOptions,
  3456  ) (*storage.FetchResult, error) {
  3457  	return storage.NewFetchResult(ctx, nil, block.NewResultMetadata()), nil
  3458  }
  3459  
  3460  func (*mockStorage) CompleteTags(
  3461  	ctx context.Context,
  3462  	query *querystorage.CompleteTagsQuery,
  3463  	opts *querystorage.FetchOptions,
  3464  ) (*consolidators.CompleteTagsResult, error) {
  3465  	return nil, fmt.Errorf("not implemented")
  3466  }
  3467  
  3468  func TestHoltWintersForecast(t *testing.T) {
  3469  	ctx := common.NewTestContext()
  3470  	ctx.Engine = NewEngine(&mockStorage{}, CompileOptions{})
  3471  	defer func() { _ = ctx.Close() }()
  3472  
  3473  	now := ctx.StartTime
  3474  	tests := []struct {
  3475  		name         string
  3476  		startTime    time.Time
  3477  		stepInMilli  int
  3478  		values       []float64
  3479  		duration     time.Duration
  3480  		newStartTime time.Time
  3481  		newStep      int
  3482  		output       []float64
  3483  	}{
  3484  		{
  3485  			"foo",
  3486  			now,
  3487  			1000,
  3488  			[]float64{4, 5.0, 6.0},
  3489  			3 * time.Second,
  3490  			now,
  3491  			1000,
  3492  			[]float64{math.NaN(), 4.0, 4.10035},
  3493  		},
  3494  	}
  3495  
  3496  	for _, input := range tests {
  3497  		series := ts.NewSeries(
  3498  			ctx,
  3499  			input.name,
  3500  			input.startTime,
  3501  			common.NewTestSeriesValues(ctx, input.stepInMilli, input.values),
  3502  		)
  3503  
  3504  		results, err := holtWintersForecastInternal(ctx, singlePathSpec{
  3505  			Values: []*ts.Series{series},
  3506  		}, input.duration)
  3507  		expected := common.TestSeries{
  3508  			Name: fmt.Sprintf(`holtWintersForecast(%s)`, input.name),
  3509  			Data: input.output,
  3510  		}
  3511  		require.Nil(t, err)
  3512  
  3513  		common.CompareOutputsAndExpected(t, input.newStep, input.newStartTime,
  3514  			[]common.TestSeries{expected}, results.Values)
  3515  	}
  3516  }
  3517  
  3518  func TestHoltWintersConfidenceBands(t *testing.T) {
  3519  	ctx := common.NewTestContext()
  3520  	ctx.Engine = NewEngine(&mockStorage{}, CompileOptions{})
  3521  	defer func() { _ = ctx.Close() }()
  3522  
  3523  	now := ctx.StartTime
  3524  	tests := []struct {
  3525  		name           string
  3526  		startTime      time.Time
  3527  		stepInMilli    int
  3528  		values         []float64
  3529  		duration       time.Duration
  3530  		lowerStartTime time.Time
  3531  		lowerStep      int
  3532  		lowerOutput    []float64
  3533  		upperStartTime time.Time
  3534  		upperStep      int
  3535  		upperOutput    []float64
  3536  	}{
  3537  		{
  3538  			"foo",
  3539  			now,
  3540  			1000,
  3541  			[]float64{4.0, 5.0, 6.0},
  3542  			3 * time.Second,
  3543  			now,
  3544  			1000,
  3545  			[]float64{math.NaN(), 3.7, 3.5305},
  3546  			now,
  3547  			1000,
  3548  			[]float64{math.NaN(), 4.3, 4.6702},
  3549  		},
  3550  	}
  3551  
  3552  	for _, input := range tests {
  3553  		series := ts.NewSeries(
  3554  			ctx,
  3555  			input.name,
  3556  			input.startTime,
  3557  			common.NewTestSeriesValues(ctx, input.stepInMilli, input.values),
  3558  		)
  3559  		results, err := holtWintersConfidenceBandsInternal(ctx, singlePathSpec{
  3560  			Values: []*ts.Series{series},
  3561  		}, 3, input.duration)
  3562  		lowerExpected := common.TestSeries{
  3563  			Name: fmt.Sprintf(`holtWintersConfidenceLower(%s)`, input.name),
  3564  			Data: input.lowerOutput,
  3565  		}
  3566  		upperExpected := common.TestSeries{
  3567  			Name: fmt.Sprintf(`holtWintersConfidenceUpper(%s)`, input.name),
  3568  			Data: input.upperOutput,
  3569  		}
  3570  		require.Nil(t, err)
  3571  		common.CompareOutputsAndExpected(t, input.lowerStep, input.lowerStartTime,
  3572  			[]common.TestSeries{lowerExpected}, []*ts.Series{results.Values[0]})
  3573  		common.CompareOutputsAndExpected(t, input.upperStep, input.upperStartTime,
  3574  			[]common.TestSeries{upperExpected}, []*ts.Series{results.Values[1]})
  3575  	}
  3576  }
  3577  
  3578  func TestHoltWintersAberration(t *testing.T) {
  3579  	ctx := common.NewTestContext()
  3580  	ctx.Engine = NewEngine(&mockStorage{}, CompileOptions{})
  3581  	defer func() { _ = ctx.Close() }()
  3582  
  3583  	now := ctx.StartTime
  3584  	tests := []struct {
  3585  		name                string
  3586  		startTime           time.Time
  3587  		stepInMilli         int
  3588  		values              []float64
  3589  		duration            time.Duration
  3590  		aberrationStartTime time.Time
  3591  		aberrationStep      int
  3592  		aberrationOutput    []float64
  3593  	}{
  3594  		{
  3595  			"foo",
  3596  			now,
  3597  			1000,
  3598  			[]float64{4.0, 5.0, 6.0},
  3599  			3 * time.Second,
  3600  			now,
  3601  			1000,
  3602  			[]float64{0, 0.7, 1.3298},
  3603  		},
  3604  	}
  3605  
  3606  	for _, input := range tests {
  3607  		series := ts.NewSeries(
  3608  			ctx,
  3609  			input.name,
  3610  			input.startTime,
  3611  			common.NewTestSeriesValues(ctx, input.stepInMilli, input.values),
  3612  		)
  3613  		results, err := holtWintersAberrationInternal(ctx, singlePathSpec{
  3614  			Values: []*ts.Series{series},
  3615  		}, 3, input.duration)
  3616  		expected := common.TestSeries{
  3617  			Name: fmt.Sprintf(`holtWintersAberration(%s)`, input.name),
  3618  			Data: input.aberrationOutput,
  3619  		}
  3620  		require.Nil(t, err)
  3621  		common.CompareOutputsAndExpected(t, input.aberrationStep, input.aberrationStartTime,
  3622  			[]common.TestSeries{expected}, results.Values)
  3623  	}
  3624  }
  3625  
  3626  func TestSquareRoot(t *testing.T) {
  3627  	ctx := common.NewTestContext()
  3628  	defer func() { _ = ctx.Close() }()
  3629  
  3630  	nan := math.NaN()
  3631  	startTime := ctx.StartTime
  3632  	stepSize := 10000
  3633  	inputs := []struct {
  3634  		name        string
  3635  		startTime   time.Time
  3636  		stepInMilli int
  3637  		values      []float64
  3638  	}{
  3639  		{
  3640  			"foo",
  3641  			startTime,
  3642  			stepSize,
  3643  			[]float64{1.0, -2.0, 3.0, nan},
  3644  		},
  3645  		{
  3646  			"bar",
  3647  			startTime,
  3648  			stepSize,
  3649  			[]float64{4.0},
  3650  		},
  3651  	}
  3652  
  3653  	inputSeries := make([]*ts.Series, 0, len(inputs))
  3654  	for _, input := range inputs {
  3655  		series := ts.NewSeries(
  3656  			ctx,
  3657  			input.name,
  3658  			input.startTime,
  3659  			common.NewTestSeriesValues(ctx, input.stepInMilli, input.values),
  3660  		)
  3661  		inputSeries = append(inputSeries, series)
  3662  	}
  3663  	expected := []common.TestSeries{
  3664  		{Name: "squareRoot(foo)", Data: []float64{1.0, nan, 1.73205, nan}},
  3665  		{Name: "squareRoot(bar)", Data: []float64{2.0}},
  3666  	}
  3667  	results, err := squareRoot(ctx, singlePathSpec{
  3668  		Values: inputSeries,
  3669  	})
  3670  	require.Nil(t, err)
  3671  	common.CompareOutputsAndExpected(t, stepSize, startTime,
  3672  		expected, results.Values)
  3673  }
  3674  
  3675  func TestStdev(t *testing.T) {
  3676  	ctx := common.NewTestContext()
  3677  	defer func() { _ = ctx.Close() }()
  3678  
  3679  	nan := math.NaN()
  3680  	startTime := ctx.StartTime
  3681  	stepSize := 10000
  3682  	inputs := []struct {
  3683  		name        string
  3684  		startTime   time.Time
  3685  		stepInMilli int
  3686  		values      []float64
  3687  	}{
  3688  		{
  3689  			"foo",
  3690  			startTime,
  3691  			stepSize,
  3692  			[]float64{1.0, 2.0, 3.0, 4.0, nan, nan, nan, 5.0, 6.0, nan, nan},
  3693  		},
  3694  	}
  3695  
  3696  	inputSeries := make([]*ts.Series, 0, len(inputs))
  3697  	for _, input := range inputs {
  3698  		series := ts.NewSeries(
  3699  			ctx,
  3700  			input.name,
  3701  			input.startTime,
  3702  			common.NewTestSeriesValues(ctx, input.stepInMilli, input.values),
  3703  		)
  3704  		inputSeries = append(inputSeries, series)
  3705  	}
  3706  	expected := []common.TestSeries{
  3707  		{Name: "stddev(foo,3)", Data: []float64{0.0, 0.5, 0.8165, 0.8165, 0.5, 0.0, nan, 0.0, 0.5, 0.5, 0.0}},
  3708  	}
  3709  	results, err := stdev(ctx, singlePathSpec{
  3710  		Values: inputSeries,
  3711  	}, 3, 0.1)
  3712  	require.Nil(t, err)
  3713  	common.CompareOutputsAndExpected(t, stepSize, startTime,
  3714  		expected, results.Values)
  3715  }
  3716  
  3717  func TestRangeOfSeries(t *testing.T) {
  3718  	ctx, input := newConsolidationTestSeries()
  3719  	defer func() { _ = ctx.Close() }()
  3720  
  3721  	expectedStart := ctx.StartTime.Add(-30 * time.Second)
  3722  	expectedStep := 10000
  3723  	rangeSeries, err := rangeOfSeries(ctx, singlePathSpec{
  3724  		Values: input,
  3725  	})
  3726  	require.Nil(t, err)
  3727  	expected := common.TestSeries{
  3728  		Name: "rangeOfSeries(a,b,c,d)",
  3729  		Data: []float64{0, 0, 0, 12, 12, 12, 14, 14, 14, 0, 0, 0},
  3730  	}
  3731  	common.CompareOutputsAndExpected(t, expectedStep, expectedStart,
  3732  		[]common.TestSeries{expected}, rangeSeries.Values)
  3733  }
  3734  
  3735  type percentileFunction func(ctx *common.Context, seriesList singlePathSpec, percentile float64) (ts.SeriesList, error)
  3736  
  3737  func testPercentileFunction(t *testing.T, f percentileFunction, expected []common.TestSeries) {
  3738  	ctx := common.NewTestContext()
  3739  	defer func() { _ = ctx.Close() }()
  3740  
  3741  	nan := math.NaN()
  3742  	startTime := ctx.StartTime
  3743  	stepSize := 10000
  3744  	inputs := []struct {
  3745  		name        string
  3746  		startTime   time.Time
  3747  		stepInMilli int
  3748  		values      []float64
  3749  	}{
  3750  		{
  3751  			"foo",
  3752  			startTime,
  3753  			stepSize,
  3754  			[]float64{nan, nan, nan, nan, nan},
  3755  		},
  3756  		{
  3757  			"bar",
  3758  			startTime,
  3759  			stepSize,
  3760  			[]float64{3.0, 2.0, 4.0, nan, 1.0, 6.0, nan, 5.0},
  3761  		},
  3762  		{
  3763  			"baz",
  3764  			startTime,
  3765  			stepSize,
  3766  			[]float64{1.0},
  3767  		},
  3768  	}
  3769  
  3770  	inputSeries := make([]*ts.Series, 0, len(inputs))
  3771  	for _, input := range inputs {
  3772  		series := ts.NewSeries(
  3773  			ctx,
  3774  			input.name,
  3775  			input.startTime,
  3776  			common.NewTestSeriesValues(ctx, input.stepInMilli, input.values),
  3777  		)
  3778  		inputSeries = append(inputSeries, series)
  3779  	}
  3780  	percentile := 40.123
  3781  	results, err := f(ctx, singlePathSpec{
  3782  		Values: inputSeries,
  3783  	}, percentile)
  3784  	require.Nil(t, err)
  3785  	common.CompareOutputsAndExpected(t, stepSize, startTime,
  3786  		expected, results.Values)
  3787  }
  3788  
  3789  func TestNPercentile(t *testing.T) {
  3790  	expected := []common.TestSeries{
  3791  		{
  3792  			Name: "nPercentile(bar, 40.123)",
  3793  			Data: []float64{3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0},
  3794  		},
  3795  		{
  3796  			Name: "nPercentile(baz, 40.123)",
  3797  			Data: []float64{1.0},
  3798  		},
  3799  	}
  3800  	testPercentileFunction(t, nPercentile, expected)
  3801  }
  3802  
  3803  func TestRemoveAbovePercentile(t *testing.T) {
  3804  	nan := math.NaN()
  3805  	expected := []common.TestSeries{
  3806  		{
  3807  			Name: "removeAbovePercentile(foo, 40.123)",
  3808  			Data: []float64{nan, nan, nan, nan, nan},
  3809  		},
  3810  		{
  3811  			Name: "removeAbovePercentile(bar, 40.123)",
  3812  			Data: []float64{3.0, 2.0, nan, nan, 1.0, nan, nan, nan},
  3813  		},
  3814  		{
  3815  			Name: "removeAbovePercentile(baz, 40.123)",
  3816  			Data: []float64{1.0},
  3817  		},
  3818  	}
  3819  
  3820  	testPercentileFunction(t, removeAbovePercentile, expected)
  3821  }
  3822  
  3823  func TestRemoveBelowPercentile(t *testing.T) {
  3824  	nan := math.NaN()
  3825  
  3826  	expected := []common.TestSeries{
  3827  		{
  3828  			Name: "removeBelowPercentile(foo, 40.123)",
  3829  			Data: []float64{nan, nan, nan, nan, nan},
  3830  		},
  3831  		{
  3832  			Name: "removeBelowPercentile(bar, 40.123)",
  3833  			Data: []float64{3.0, nan, 4.0, nan, nan, 6.0, nan, 5.0},
  3834  		},
  3835  		{
  3836  			Name: "removeBelowPercentile(baz, 40.123)",
  3837  			Data: []float64{1.0},
  3838  		},
  3839  	}
  3840  
  3841  	testPercentileFunction(t, removeBelowPercentile, expected)
  3842  }
  3843  
  3844  func testRandomWalkFunctionInternal(t *testing.T, ctx *common.Context, stepSize, expectedLen int) {
  3845  	r, err := randomWalkFunction(ctx, "foo", stepSize)
  3846  	require.Nil(t, err)
  3847  
  3848  	results := r.Values
  3849  	require.Equal(t, 1, len(results))
  3850  	require.Equal(t, expectedLen, results[0].Len())
  3851  	for i := 0; i < expectedLen; i++ {
  3852  		v := results[0].ValueAt(i)
  3853  		require.True(t, v >= -0.5 && v < 0.5)
  3854  	}
  3855  }
  3856  
  3857  func TestRandomWalkFunction(t *testing.T) {
  3858  	ctx := common.NewTestContext()
  3859  	defer func() { _ = ctx.Close() }()
  3860  
  3861  	ctx.EndTime = ctx.StartTime.Add(1100 * time.Millisecond)
  3862  	testRandomWalkFunctionInternal(t, ctx, 1, 2)
  3863  
  3864  	ctx.EndTime = ctx.StartTime.Add(1600 * time.Millisecond)
  3865  	testRandomWalkFunctionInternal(t, ctx, 1, 2)
  3866  }
  3867  
  3868  func testAggregateLineInternal(t *testing.T, f string, expectedName string, expectedVal float64) {
  3869  	ctx := common.NewTestContext()
  3870  	defer func() { _ = ctx.Close() }()
  3871  
  3872  	input := struct {
  3873  		name        string
  3874  		startTime   time.Time
  3875  		stepInMilli int
  3876  		values      []float64
  3877  	}{
  3878  		"foo",
  3879  		ctx.StartTime,
  3880  		10000,
  3881  		[]float64{1.0, 2.0, 3.0, 4.0},
  3882  	}
  3883  
  3884  	series := ts.NewSeries(
  3885  		ctx,
  3886  		input.name,
  3887  		input.startTime,
  3888  		common.NewTestSeriesValues(ctx, input.stepInMilli, input.values),
  3889  	)
  3890  
  3891  	r, err := aggregateLine(ctx, singlePathSpec{
  3892  		Values: []*ts.Series{series},
  3893  	}, f)
  3894  	require.Nil(t, err)
  3895  
  3896  	results := r.Values
  3897  	require.Equal(t, 1, len(results))
  3898  	require.Equal(t, expectedName, results[0].Name())
  3899  	require.Equal(t, 3, results[0].Len())
  3900  	for i := 0; i < 2; i++ {
  3901  		require.Equal(t, expectedVal, results[0].ValueAt(i))
  3902  	}
  3903  }
  3904  
  3905  func TestAggregateLine(t *testing.T) {
  3906  	testAggregateLineInternal(t, "avg", "aggregateLine(foo,2.500)", 2.5)
  3907  	testAggregateLineInternal(t, "max", "aggregateLine(foo,4.000)", 4.0)
  3908  	testAggregateLineInternal(t, "min", "aggregateLine(foo,1.000)", 1.0)
  3909  }
  3910  
  3911  func TestChanged(t *testing.T) {
  3912  	ctx := common.NewTestContext()
  3913  	defer func() { _ = ctx.Close() }()
  3914  
  3915  	nan := math.NaN()
  3916  	startTime := ctx.StartTime
  3917  	stepSize := 10000
  3918  	input := struct {
  3919  		name        string
  3920  		startTime   time.Time
  3921  		stepInMilli int
  3922  		values      []float64
  3923  	}{
  3924  		"foo",
  3925  		startTime,
  3926  		stepSize,
  3927  		[]float64{1.0, 1.0, 2.0, 3.0, nan, 3.0, nan, 4.0, nan},
  3928  	}
  3929  
  3930  	series := ts.NewSeries(
  3931  		ctx,
  3932  		input.name,
  3933  		input.startTime,
  3934  		common.NewTestSeriesValues(ctx, input.stepInMilli, input.values),
  3935  	)
  3936  
  3937  	expected := []common.TestSeries{
  3938  		{
  3939  			Name: "changed(foo)",
  3940  			Data: []float64{0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0},
  3941  		},
  3942  	}
  3943  	results, err := changed(ctx, singlePathSpec{
  3944  		Values: []*ts.Series{series},
  3945  	})
  3946  	require.Nil(t, err)
  3947  	common.CompareOutputsAndExpected(t, stepSize, startTime,
  3948  		expected, results.Values)
  3949  }
  3950  
  3951  func TestEffectiveXFilesFactor(t *testing.T) {
  3952  	require.True(t, effectiveXFF(10, 9, 0))
  3953  	require.True(t, effectiveXFF(10, 4, 0.5))
  3954  	require.True(t, effectiveXFF(10, 0, 1.0))
  3955  
  3956  	require.False(t, effectiveXFF(10, 10, 0.1))
  3957  	require.False(t, effectiveXFF(10, 6, 0.5))
  3958  	require.False(t, effectiveXFF(10, 1, 1.0))
  3959  }
  3960  
  3961  func TestMovingMedian(t *testing.T) {
  3962  	ctrl := xgomock.NewController(t)
  3963  	defer ctrl.Finish()
  3964  
  3965  	store := storage.NewMockStorage(ctrl)
  3966  	now := time.Now().Truncate(time.Hour)
  3967  	engine := NewEngine(store, CompileOptions{})
  3968  	startTime := now.Add(-3 * time.Minute)
  3969  	// Make sure two full steps considered.
  3970  	endTime := now.Add(-time.Minute)
  3971  	ctx := common.NewContext(common.ContextOptions{Start: startTime, End: endTime, Engine: engine})
  3972  	defer func() { _ = ctx.Close() }()
  3973  
  3974  	stepSize := 60000
  3975  	target := "movingMedian(foo.bar.q.zed, '1min')"
  3976  	store.EXPECT().FetchByQuery(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(
  3977  		buildTestSeriesFn(stepSize, "foo.bar.q.zed")).Times(2)
  3978  	expr, err := engine.Compile(target)
  3979  	require.NoError(t, err)
  3980  	res, err := expr.Execute(ctx)
  3981  	require.NoError(t, err)
  3982  	expected := common.TestSeries{
  3983  		Name: "movingMedian(foo.bar.q.zed,\"1min\")",
  3984  		Data: []float64{0.0, 0.0},
  3985  	}
  3986  	common.CompareOutputsAndExpected(t, stepSize, startTime,
  3987  		[]common.TestSeries{expected}, res.Values)
  3988  }
  3989  
  3990  func TestMovingAverage(t *testing.T) {
  3991  	ctrl := xgomock.NewController(t)
  3992  	defer ctrl.Finish()
  3993  
  3994  	store := storage.NewMockStorage(ctrl)
  3995  	now := time.Now().Truncate(time.Hour)
  3996  	engine := NewEngine(store, CompileOptions{})
  3997  	startTime := now.Add(-3 * time.Minute)
  3998  	endTime := now.Add(-1 * time.Minute)
  3999  	ctx := common.NewContext(common.ContextOptions{Start: startTime, End: endTime, Engine: engine})
  4000  	defer func() { _ = ctx.Close() }()
  4001  
  4002  	stepSize := 60000
  4003  	target := `movingAverage(timeShift(foo.bar.g.zed,'-1d'), '1min', 0.7)`
  4004  	store.EXPECT().FetchByQuery(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(
  4005  		buildTestSeriesFn(stepSize, "foo.bar.g.zed")).AnyTimes()
  4006  	expr, err := engine.Compile(target)
  4007  	require.NoError(t, err)
  4008  	res, err := expr.Execute(ctx)
  4009  	require.NoError(t, err)
  4010  	expected := common.TestSeries{
  4011  		Name: `movingAverage(timeShift(foo.bar.g.zed,"-1d"),"1min")`,
  4012  		Data: []float64{1, 1},
  4013  	}
  4014  	common.CompareOutputsAndExpected(t, stepSize, startTime,
  4015  		[]common.TestSeries{expected}, res.Values)
  4016  }
  4017  
  4018  // nolint: dupl
  4019  func TestMovingWindow(t *testing.T) {
  4020  	ctrl := xgomock.NewController(t)
  4021  	defer ctrl.Finish()
  4022  
  4023  	store := storage.NewMockStorage(ctrl)
  4024  	now := time.Now().Truncate(time.Hour)
  4025  	engine := NewEngine(store, CompileOptions{})
  4026  	startTime := now.Add(-3 * time.Minute)
  4027  	endTime := now.Add(-1 * time.Minute)
  4028  	ctx := common.NewContext(common.ContextOptions{Start: startTime, End: endTime, Engine: engine})
  4029  	defer func() { _ = ctx.Close() }()
  4030  
  4031  	stepSize := 60000
  4032  	target := `movingWindow(timeShift(foo.bar.g.zed,'-1d'), '1min', 'avg', 0.7)`
  4033  	store.EXPECT().FetchByQuery(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(
  4034  		buildTestSeriesFn(stepSize, "foo.bar.g.zed")).AnyTimes()
  4035  	expr, err := engine.Compile(target)
  4036  	require.NoError(t, err)
  4037  	res, err := expr.Execute(ctx)
  4038  	require.NoError(t, err)
  4039  	expected := common.TestSeries{
  4040  		Name: `movingAverage(timeShift(foo.bar.g.zed,"-1d"),"1min")`,
  4041  		Data: []float64{1, 1},
  4042  	}
  4043  	common.CompareOutputsAndExpected(t, stepSize, startTime,
  4044  		[]common.TestSeries{expected}, res.Values)
  4045  }
  4046  
  4047  func TestLegendValue(t *testing.T) {
  4048  	ctx := common.NewTestContext()
  4049  	defer func() { _ = ctx.Close() }()
  4050  
  4051  	vals := []float64{1.0, 2.0, 3.0, 4.0, math.NaN()}
  4052  	input := struct {
  4053  		name        string
  4054  		startTime   time.Time
  4055  		stepInMilli int
  4056  		values      []float64
  4057  	}{
  4058  		"foo",
  4059  		ctx.StartTime,
  4060  		10000,
  4061  		vals,
  4062  	}
  4063  
  4064  	series := ts.NewSeries(
  4065  		ctx,
  4066  		input.name,
  4067  		input.startTime,
  4068  		common.NewTestSeriesValues(ctx, input.stepInMilli, input.values),
  4069  	)
  4070  
  4071  	results, err := legendValue(ctx, singlePathSpec{
  4072  		Values: []*ts.Series{series},
  4073  	}, "avg")
  4074  	expected := common.TestSeries{Name: "foo (avg: 2.500)", Data: vals}
  4075  	require.Nil(t, err)
  4076  	common.CompareOutputsAndExpected(t, input.stepInMilli, input.startTime,
  4077  		[]common.TestSeries{expected}, results.Values)
  4078  
  4079  	results, err = legendValue(ctx, singlePathSpec{
  4080  		Values: []*ts.Series{series},
  4081  	}, "last")
  4082  	expected = common.TestSeries{Name: "foo (last: 4.000)", Data: vals}
  4083  	require.Nil(t, err)
  4084  	common.CompareOutputsAndExpected(t, input.stepInMilli, input.startTime,
  4085  		[]common.TestSeries{expected}, results.Values)
  4086  
  4087  	results, err = legendValue(ctx, singlePathSpec{
  4088  		Values: []*ts.Series{series},
  4089  	}, "unknown")
  4090  	require.NotNil(t, err)
  4091  }
  4092  
  4093  func TestCactiStyle(t *testing.T) {
  4094  	ctx := common.NewTestContext()
  4095  	defer func() { _ = ctx.Close() }()
  4096  
  4097  	stepSize := 10000
  4098  	inputs := []struct {
  4099  		name        string
  4100  		startTime   time.Time
  4101  		stepInMilli int
  4102  		values      []float64
  4103  	}{
  4104  		{
  4105  			"foo",
  4106  			ctx.StartTime,
  4107  			stepSize,
  4108  			[]float64{1.0, 2.0, 3.0, 4.0, math.NaN()},
  4109  		},
  4110  		{
  4111  			"barbaz",
  4112  			ctx.StartTime,
  4113  			stepSize,
  4114  			[]float64{10.0, -5.0, 80.0, 100.0, math.NaN()},
  4115  		},
  4116  		{
  4117  			"test",
  4118  			ctx.StartTime,
  4119  			stepSize,
  4120  			[]float64{math.NaN()},
  4121  		},
  4122  	}
  4123  
  4124  	inputSeries := make([]*ts.Series, 0, len(inputs))
  4125  	for _, input := range inputs {
  4126  		series := ts.NewSeries(
  4127  			ctx,
  4128  			input.name,
  4129  			input.startTime,
  4130  			common.NewTestSeriesValues(ctx, input.stepInMilli, input.values),
  4131  		)
  4132  		inputSeries = append(inputSeries, series)
  4133  	}
  4134  
  4135  	results, err := cactiStyle(ctx, singlePathSpec{
  4136  		Values: inputSeries,
  4137  	})
  4138  	expected := []common.TestSeries{
  4139  		{Name: "foo    Current:4.00      Max:4.00      Min:1.00     ", Data: inputs[0].values},
  4140  		{Name: "barbaz Current:100.00    Max:100.00    Min:-5.00    ", Data: inputs[1].values},
  4141  		{Name: "test   Current:nan       Max:nan       Min:nan      ", Data: inputs[2].values},
  4142  	}
  4143  	require.Nil(t, err)
  4144  	common.CompareOutputsAndExpected(t, stepSize, ctx.StartTime,
  4145  		expected, results.Values)
  4146  }
  4147  
  4148  func TestConsolidateBy(t *testing.T) {
  4149  	start := common.NewTestContext().StartTime
  4150  	stepSize := 10000
  4151  	for i, test := range []struct {
  4152  		name               string
  4153  		fn                 string
  4154  		startTime          time.Time
  4155  		stepMillis         int
  4156  		maxDataPoints      int64
  4157  		values             []float64
  4158  		expectedValues     []float64
  4159  		expectedStepMillis int
  4160  		expectedErr        bool
  4161  	}{
  4162  		{
  4163  			name:               "foo",
  4164  			fn:                 "min",
  4165  			startTime:          start,
  4166  			stepMillis:         stepSize,
  4167  			values:             []float64{1.0, 2.0, 3.0, 4.0, 5.0, math.NaN()},
  4168  			expectedStepMillis: stepSize,
  4169  			expectedValues:     []float64{1.0, 2.0, 3.0, 4.0, 5.0, math.NaN()},
  4170  		},
  4171  		{
  4172  			name:               "foo",
  4173  			fn:                 "min",
  4174  			startTime:          start,
  4175  			stepMillis:         stepSize,
  4176  			maxDataPoints:      2,
  4177  			values:             []float64{1.0, 2.0, 3.0, 4.0, 5.0, math.NaN()},
  4178  			expectedStepMillis: 3 * stepSize,
  4179  			expectedValues:     []float64{1.0, 4.0},
  4180  		},
  4181  		{
  4182  			name:               "foo",
  4183  			fn:                 "last",
  4184  			startTime:          start,
  4185  			stepMillis:         stepSize,
  4186  			maxDataPoints:      2,
  4187  			values:             []float64{1.0, 2.0, 3.0, 4.0, 5.0, math.NaN()},
  4188  			expectedStepMillis: 3 * stepSize,
  4189  			expectedValues:     []float64{3.0, 5.0},
  4190  		},
  4191  		{
  4192  			name:        "foo",
  4193  			fn:          "nonexistent",
  4194  			startTime:   start,
  4195  			stepMillis:  stepSize,
  4196  			values:      []float64{1.0, 2.0, 3.0, 4.0, math.NaN(), 5.0},
  4197  			expectedErr: true,
  4198  		},
  4199  	} {
  4200  		input := test
  4201  		t.Run(fmt.Sprintf("%d-%s", i, input.name), func(t *testing.T) {
  4202  			ctx := common.NewTestContext()
  4203  			ctx.MaxDataPoints = input.maxDataPoints
  4204  			defer func() { _ = ctx.Close() }()
  4205  
  4206  			series := ts.NewSeries(
  4207  				ctx,
  4208  				input.name,
  4209  				input.startTime,
  4210  				common.NewTestSeriesValues(ctx, input.stepMillis, input.values),
  4211  			)
  4212  
  4213  			results, err := consolidateBy(ctx, singlePathSpec{
  4214  				Values: []*ts.Series{series},
  4215  			}, input.fn)
  4216  			if input.expectedErr {
  4217  				require.Error(t, err)
  4218  				return
  4219  			}
  4220  
  4221  			expected := common.TestSeries{
  4222  				Name: fmt.Sprintf(`consolidateBy(%s,"%s")`, input.name, input.fn),
  4223  				Data: input.expectedValues,
  4224  			}
  4225  			require.NoError(t, err)
  4226  			common.CompareOutputsAndExpected(t, input.expectedStepMillis, input.startTime,
  4227  				[]common.TestSeries{expected}, results.Values)
  4228  		})
  4229  	}
  4230  }
  4231  
  4232  func TestPow(t *testing.T) {
  4233  	var (
  4234  		ctx           = common.NewTestContext()
  4235  		millisPerStep = 10000
  4236  	)
  4237  
  4238  	defer func() { _ = ctx.Close() }()
  4239  
  4240  	inputs := []struct {
  4241  		name     string
  4242  		values   []float64
  4243  		pow      float64
  4244  		expected []float64
  4245  	}{
  4246  		{
  4247  			"foo",
  4248  			[]float64{1.0, 2.0, 3.0, 4.0, 5.0},
  4249  			2,
  4250  			[]float64{1.0, 4.0, 9.0, 16.0, 25.0},
  4251  		},
  4252  		{
  4253  			"bar",
  4254  			[]float64{0.0, 2.0, 4.0, 6.0, 8.0},
  4255  			2,
  4256  			[]float64{0.0, 4.0, 16.0, 36.0, 64.0},
  4257  		},
  4258  	}
  4259  
  4260  	for _, input := range inputs {
  4261  		series := ts.NewSeries(
  4262  			ctx,
  4263  			input.name,
  4264  			ctx.StartTime,
  4265  			common.NewTestSeriesValues(ctx, millisPerStep, input.values),
  4266  		)
  4267  		results, err := pow(ctx, singlePathSpec{
  4268  			Values: []*ts.Series{series},
  4269  		}, input.pow)
  4270  		require.NoError(t, err)
  4271  		expected := common.TestSeries{
  4272  			Name: fmt.Sprintf("pow(%s, %f)", input.name, input.pow),
  4273  			Data: input.expected,
  4274  		}
  4275  		common.CompareOutputsAndExpected(t, millisPerStep, ctx.StartTime,
  4276  			[]common.TestSeries{expected}, results.Values)
  4277  	}
  4278  }
  4279  
  4280  func TestInvert(t *testing.T) {
  4281  	var (
  4282  		ctx           = common.NewTestContext()
  4283  		millisPerStep = 10000
  4284  	)
  4285  
  4286  	defer func() { _ = ctx.Close() }()
  4287  
  4288  	inputs := []struct {
  4289  		name     string
  4290  		values   []float64
  4291  		expected []float64
  4292  	}{
  4293  		{
  4294  			"foo",
  4295  			[]float64{1.0, 2.0, 4.0},
  4296  			[]float64{1.0, 1 / 2.0, 1 / 4.0},
  4297  		},
  4298  	}
  4299  
  4300  	for _, input := range inputs {
  4301  		series := ts.NewSeries(
  4302  			ctx,
  4303  			input.name,
  4304  			ctx.StartTime,
  4305  			common.NewTestSeriesValues(ctx, millisPerStep, input.values),
  4306  		)
  4307  		results, err := invert(ctx, singlePathSpec{
  4308  			Values: []*ts.Series{series},
  4309  		})
  4310  		require.NoError(t, err)
  4311  		expected := common.TestSeries{
  4312  			Name: fmt.Sprintf("invert(%s)", input.name),
  4313  			Data: input.expected,
  4314  		}
  4315  		common.CompareOutputsAndExpected(t, millisPerStep, ctx.StartTime,
  4316  			[]common.TestSeries{expected}, results.Values)
  4317  	}
  4318  }
  4319  
  4320  func TestCumulative(t *testing.T) {
  4321  	ctx := common.NewTestContext()
  4322  	defer func() { _ = ctx.Close() }()
  4323  
  4324  	stepSize := 10000
  4325  	input := struct {
  4326  		name        string
  4327  		startTime   time.Time
  4328  		stepInMilli int
  4329  		values      []float64
  4330  	}{
  4331  		"foo",
  4332  		ctx.StartTime,
  4333  		stepSize,
  4334  		[]float64{1.0, 2.0, 3.0, 4.0, math.NaN()},
  4335  	}
  4336  
  4337  	series := ts.NewSeries(
  4338  		ctx,
  4339  		input.name,
  4340  		input.startTime,
  4341  		common.NewTestSeriesValues(ctx, input.stepInMilli, input.values),
  4342  	)
  4343  
  4344  	results, err := cumulative(ctx, singlePathSpec{
  4345  		Values: []*ts.Series{series},
  4346  	})
  4347  	expected := common.TestSeries{Name: `consolidateBy(foo,"sum")`, Data: input.values}
  4348  	require.Nil(t, err)
  4349  	common.CompareOutputsAndExpected(t, input.stepInMilli, input.startTime,
  4350  		[]common.TestSeries{expected}, results.Values)
  4351  }
  4352  
  4353  func TestOffsetToZero(t *testing.T) {
  4354  	ctx := common.NewTestContext()
  4355  	defer func() { _ = ctx.Close() }()
  4356  
  4357  	nan := math.NaN()
  4358  	startTime := ctx.StartTime
  4359  	stepSize := 10000
  4360  	inputs := []struct {
  4361  		name     string
  4362  		values   []float64
  4363  		expected []float64
  4364  	}{
  4365  		{
  4366  			"foo",
  4367  			[]float64{nan, nan, nan, nan, nan},
  4368  			[]float64{nan, nan, nan, nan, nan},
  4369  		},
  4370  		{
  4371  			"bar",
  4372  			[]float64{3.0, 2.0, 4.0, nan, 1.0, 6.0, nan, 5.0},
  4373  			[]float64{2.0, 1.0, 3.0, nan, 0.0, 5.0, nan, 4.0},
  4374  		},
  4375  		{
  4376  			"baz",
  4377  			[]float64{1.0},
  4378  			[]float64{0.0},
  4379  		},
  4380  	}
  4381  
  4382  	for _, input := range inputs {
  4383  		series := ts.NewSeries(
  4384  			ctx,
  4385  			input.name,
  4386  			startTime,
  4387  			common.NewTestSeriesValues(ctx, stepSize, input.values),
  4388  		)
  4389  		results, err := offsetToZero(ctx, singlePathSpec{
  4390  			Values: []*ts.Series{series},
  4391  		})
  4392  		require.NoError(t, err)
  4393  		expected := common.TestSeries{
  4394  			Name: fmt.Sprintf("offsetToZero(%s)", input.name),
  4395  			Data: input.expected,
  4396  		}
  4397  		common.CompareOutputsAndExpected(t, stepSize, startTime,
  4398  			[]common.TestSeries{expected}, results.Values)
  4399  	}
  4400  }
  4401  
  4402  func TestTimeFunction(t *testing.T) {
  4403  	ctx := common.NewTestContext()
  4404  	now := time.Now()
  4405  	truncatedNow := float64(now.Truncate(time.Second).Unix())
  4406  	ctx.StartTime = now
  4407  	ctx.EndTime = now.Add(2 * time.Minute)
  4408  	defer func() { _ = ctx.Close() }()
  4409  
  4410  	results, err := timeFunction(ctx, "foo", 30)
  4411  	require.NoError(t, err)
  4412  	expected := common.TestSeries{
  4413  		Name: "foo",
  4414  		Data: []float64{truncatedNow, truncatedNow + 30, truncatedNow + 60, truncatedNow + 90},
  4415  	}
  4416  	common.CompareOutputsAndExpected(t, 30000, now.Truncate(time.Second),
  4417  		[]common.TestSeries{expected}, results.Values)
  4418  }
  4419  
  4420  func TestTimeShift(t *testing.T) {
  4421  	ctrl := xgomock.NewController(t)
  4422  	defer ctrl.Finish()
  4423  
  4424  	store := storage.NewMockStorage(ctrl)
  4425  	now := time.Now().Truncate(time.Hour)
  4426  	engine := NewEngine(store, CompileOptions{})
  4427  	startTime := now.Add(-3 * time.Minute)
  4428  	endTime := now.Add(-time.Minute)
  4429  	ctx := common.NewContext(common.ContextOptions{
  4430  		Start:  startTime,
  4431  		End:    endTime,
  4432  		Engine: engine,
  4433  	})
  4434  	defer func() { _ = ctx.Close() }()
  4435  
  4436  	stepSize := 60000
  4437  	target := "timeShift(foo.bar.q.zed, '1min', false)"
  4438  
  4439  	store.EXPECT().FetchByQuery(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(
  4440  		buildTestSeriesFn(stepSize, "foo.bar.q.zed")).AnyTimes()
  4441  
  4442  	expr, err := engine.Compile(target)
  4443  	require.NoError(t, err)
  4444  	res, err := expr.Execute(ctx)
  4445  	require.NoError(t, err)
  4446  	expected := common.TestSeries{
  4447  		Name: `timeShift(foo.bar.q.zed,"-1min")`,
  4448  		Data: []float64{0.0, 0.0},
  4449  	}
  4450  	common.CompareOutputsAndExpected(t, stepSize, startTime,
  4451  		[]common.TestSeries{expected}, res.Values)
  4452  }
  4453  
  4454  func TestDelay(t *testing.T) {
  4455  	values := [3][]float64{
  4456  		{54.0, 48.0, 92.0, 54.0, 14.0, 1.2},
  4457  		{4.0, 5.0, math.NaN(), 6.4, 7.2, math.NaN()},
  4458  		{math.NaN(), 8.0, 9.0, 10.6, 11.2, 12.2},
  4459  	}
  4460  	expected := [3][]float64{
  4461  		{math.NaN(), math.NaN(), math.NaN(), 54.0, 48.0, 92.0},
  4462  		{math.NaN(), math.NaN(), math.NaN(), 4.0, 5.0, math.NaN()},
  4463  		{math.NaN(), math.NaN(), math.NaN(), math.NaN(), 8.0, 9.0},
  4464  	}
  4465  
  4466  	for index, value := range values {
  4467  		e := expected[index]
  4468  		testDelay(t, "delay(foo.bar.baz, 3)", "delay(foo.bar.baz,3)", value, e)
  4469  	}
  4470  }
  4471  
  4472  var (
  4473  	testDelayStart = time.Now().Truncate(time.Minute)
  4474  	testDelayEnd   = testMovingFunctionEnd.Add(time.Minute)
  4475  )
  4476  
  4477  func testDelay(t *testing.T, target, expectedName string, values, output []float64) {
  4478  	ctx := common.NewTestContext()
  4479  	defer func() { _ = ctx.Close() }()
  4480  
  4481  	engine := NewEngine(&common.MovingFunctionStorage{
  4482  		StepMillis: 10000,
  4483  		Values:     values,
  4484  	}, CompileOptions{})
  4485  	phonyContext := common.NewContext(common.ContextOptions{
  4486  		Start:  testDelayStart,
  4487  		End:    testDelayEnd,
  4488  		Engine: engine,
  4489  	})
  4490  
  4491  	expr, err := phonyContext.Engine.(*Engine).Compile(target)
  4492  	require.NoError(t, err)
  4493  	res, err := expr.Execute(phonyContext)
  4494  	require.NoError(t, err)
  4495  	var expected []common.TestSeries
  4496  
  4497  	if output != nil {
  4498  		expectedSeries := common.TestSeries{
  4499  			Name: expectedName,
  4500  			Data: output,
  4501  		}
  4502  		expected = append(expected, expectedSeries)
  4503  	}
  4504  	common.CompareOutputsAndExpected(t, 10000, testDelayStart, expected, res.Values)
  4505  }
  4506  
  4507  func TestTimeSlice(t *testing.T) {
  4508  	values := []float64{math.NaN(), 1.0, 2.0, 3.0, math.NaN(), 5.0, 6.0, math.NaN(), 7.0, 8.0, 9.0}
  4509  	expected := []float64{math.NaN(), math.NaN(), math.NaN(), 3.0, math.NaN(), 5.0, 6.0, math.NaN(), 7.0, math.NaN(), math.NaN()}
  4510  
  4511  	testGeneralFunction(t,
  4512  		"timeSlice(foo.bar.baz, '-9min','-3min')",
  4513  		`timeSlice(foo.bar.baz, "-9min", "-3min")`,
  4514  		values, expected)
  4515  }
  4516  
  4517  func TestDashed(t *testing.T) {
  4518  	ctx := common.NewTestContext()
  4519  	defer func() { _ = ctx.Close() }()
  4520  
  4521  	nan := math.NaN()
  4522  	startTime := ctx.StartTime
  4523  	stepSize := 10000
  4524  	inputs := []struct {
  4525  		name     string
  4526  		values   []float64
  4527  		expected []float64
  4528  	}{
  4529  		{
  4530  			"foo",
  4531  			[]float64{nan, nan, nan, nan, nan},
  4532  			[]float64{nan, nan, nan, nan, nan},
  4533  		},
  4534  	}
  4535  
  4536  	for _, input := range inputs {
  4537  		series := ts.NewSeries(
  4538  			ctx,
  4539  			input.name,
  4540  			startTime,
  4541  			common.NewTestSeriesValues(ctx, stepSize, input.values),
  4542  		)
  4543  		results, err := dashed(ctx, singlePathSpec{
  4544  			Values: []*ts.Series{series},
  4545  		}, 3.0)
  4546  		require.NoError(t, err)
  4547  		expected := common.TestSeries{
  4548  			Name: fmt.Sprintf("dashed(%s, 3.000)", input.name),
  4549  			Data: input.expected,
  4550  		}
  4551  		common.CompareOutputsAndExpected(t, stepSize, startTime,
  4552  			[]common.TestSeries{expected}, results.Values)
  4553  	}
  4554  }
  4555  
  4556  func TestThreshold(t *testing.T) {
  4557  	ctx := common.NewTestContext()
  4558  	defer func() { _ = ctx.Close() }()
  4559  
  4560  	r, err := threshold(ctx, 1.0, "bar", "yellow")
  4561  	require.NoError(t, err)
  4562  
  4563  	results := r.Values
  4564  	require.Equal(t, 1, len(results))
  4565  	require.Equal(t, "bar", results[0].Name())
  4566  
  4567  	r, err = threshold(ctx, 1.0, "", "red")
  4568  	require.NoError(t, err)
  4569  
  4570  	results = r.Values
  4571  	require.Equal(t, 1, len(results))
  4572  	require.Equal(t, "1.000", results[0].Name())
  4573  
  4574  	r, err = threshold(ctx, 1.0, "", "")
  4575  	require.NoError(t, err)
  4576  
  4577  	results = r.Values
  4578  	require.Equal(t, 1, len(results))
  4579  	require.Equal(t, "1.000", results[0].Name())
  4580  }
  4581  
  4582  func TestFunctionsRegistered(t *testing.T) {
  4583  	fnames := []string{
  4584  		"abs",
  4585  		"absolute",
  4586  		"aggregate",
  4587  		"aggregateLine",
  4588  		"alias",
  4589  		"aliasByMetric",
  4590  		"aliasByNode",
  4591  		"aliasByTags",
  4592  		"aliasSub",
  4593  		"asPercent",
  4594  		"averageAbove",
  4595  		"averageSeries",
  4596  		"averageSeriesWithWildcards",
  4597  		"avg",
  4598  		"cactiStyle",
  4599  		"changed",
  4600  		"consolidateBy",
  4601  		"constantLine",
  4602  		"countSeries",
  4603  		"cumulative",
  4604  		"currentAbove",
  4605  		"currentBelow",
  4606  		"dashed",
  4607  		"delay",
  4608  		"derivative",
  4609  		"diffSeries",
  4610  		"divideSeries",
  4611  		"divideSeriesLists",
  4612  		"exclude",
  4613  		"exponentialMovingAverage",
  4614  		"fallbackSeries",
  4615  		"grep",
  4616  		"group",
  4617  		"groupByNode",
  4618  		"groupByNodes",
  4619  		"highest",
  4620  		"highestAverage",
  4621  		"highestCurrent",
  4622  		"highestMax",
  4623  		"hitcount",
  4624  		"holtWintersAberration",
  4625  		"holtWintersConfidenceBands",
  4626  		"holtWintersForecast",
  4627  		"identity",
  4628  		"integral",
  4629  		"integralByInterval",
  4630  		"interpolate",
  4631  		"isNonNull",
  4632  		"keepLastValue",
  4633  		"legendValue",
  4634  		"limit",
  4635  		"log",
  4636  		"logarithm",
  4637  		"lowest",
  4638  		"lowestAverage",
  4639  		"lowestCurrent",
  4640  		"max",
  4641  		"maxSeries",
  4642  		"maximumAbove",
  4643  		"min",
  4644  		"minSeries",
  4645  		"minimumAbove",
  4646  		"mostDeviant",
  4647  		"movingAverage",
  4648  		"movingMedian",
  4649  		"movingSum",
  4650  		"movingMax",
  4651  		"movingMin",
  4652  		"multiplySeries",
  4653  		"nonNegativeDerivative",
  4654  		"nPercentile",
  4655  		"offset",
  4656  		"offsetToZero",
  4657  		"perSecond",
  4658  		"pow",
  4659  		"powSeries",
  4660  		"randomWalk",
  4661  		"randomWalkFunction",
  4662  		"rangeOfSeries",
  4663  		"removeAbovePercentile",
  4664  		"removeAboveValue",
  4665  		"removeBelowPercentile",
  4666  		"removeBelowValue",
  4667  		"removeEmptySeries",
  4668  		"scale",
  4669  		"scaleToSeconds",
  4670  		"smartSummarize",
  4671  		"sortByMaxima",
  4672  		"sortByMinima",
  4673  		"sortByName",
  4674  		"sortByTotal",
  4675  		"squareRoot",
  4676  		"stdev",
  4677  		"stddevSeries",
  4678  		"substr",
  4679  		"sum",
  4680  		"sumSeries",
  4681  		"summarize",
  4682  		"threshold",
  4683  		"time",
  4684  		"timeFunction",
  4685  		"timeShift",
  4686  		"timeSlice",
  4687  		"transformNull",
  4688  		"useSeriesAbove",
  4689  		"weightedAverage",
  4690  	}
  4691  
  4692  	for _, fname := range fnames {
  4693  		assert.NotNil(t, findFunction(fname), "could not find function: %s", fname)
  4694  	}
  4695  }