github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/graphite/common/test_util.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 common
    22  
    23  import (
    24  	stdcontext "context"
    25  	"fmt"
    26  	"math"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/m3db/m3/src/query/block"
    31  	"github.com/m3db/m3/src/query/graphite/context"
    32  	"github.com/m3db/m3/src/query/graphite/storage"
    33  	xtest "github.com/m3db/m3/src/query/graphite/testing"
    34  	"github.com/m3db/m3/src/query/graphite/ts"
    35  	querystorage "github.com/m3db/m3/src/query/storage"
    36  	"github.com/m3db/m3/src/query/storage/m3/consolidators"
    37  
    38  	"github.com/stretchr/testify/assert"
    39  	"github.com/stretchr/testify/require"
    40  )
    41  
    42  // TestSeries is used to create a tsdb.timeSeries
    43  type TestSeries struct {
    44  	Name string
    45  	Data []float64
    46  }
    47  
    48  // NewTestContext creates a new test context.
    49  func NewTestContext() *Context {
    50  	now := time.Now().Truncate(time.Hour)
    51  	return NewContext(ContextOptions{Start: now.Add(-time.Hour), End: now})
    52  }
    53  
    54  // NewTestSeriesValues creates a new ts.Values with given step size and values.
    55  func NewTestSeriesValues(ctx context.Context, millisPerStep int, values []float64) ts.Values {
    56  	tsv := ts.NewValues(ctx, millisPerStep, len(values))
    57  
    58  	for i, n := range values {
    59  		tsv.SetValueAt(i, n)
    60  	}
    61  
    62  	return tsv
    63  }
    64  
    65  // NewTestSeriesList creates a test series and values from a set of inputs
    66  func NewTestSeriesList(ctx *Context, start time.Time, inputs []TestSeries, step int) []*ts.Series {
    67  	seriesList := make([]*ts.Series, 0, len(inputs))
    68  
    69  	for _, in := range inputs {
    70  		series := ts.NewSeries(ctx, in.Name, start, NewTestSeriesValues(ctx, step, in.Data))
    71  		seriesList = append(seriesList, series)
    72  	}
    73  
    74  	return seriesList
    75  }
    76  
    77  // NewConsolidationTestSeries returns multiple static series for consolidation
    78  func NewConsolidationTestSeries(start, end time.Time, duration time.Duration) (*Context, []*ts.Series) {
    79  	ctx := NewContext(ContextOptions{Start: start, End: end})
    80  
    81  	testSeries := []*ts.Series{
    82  		ts.NewSeries(ctx, "a", start,
    83  			ts.NewConstantValues(ctx, 10, 6, 10000)),
    84  		ts.NewSeries(ctx, "b", start.Add(-duration),
    85  			ts.NewConstantValues(ctx, 15, 6, 10000)),
    86  		ts.NewSeries(ctx, "c", start.Add(duration),
    87  			ts.NewConstantValues(ctx, 17, 6, 10000)),
    88  		ts.NewSeries(ctx, "d", start,
    89  			ts.NewConstantValues(ctx, 3, 60, 1000)),
    90  	}
    91  	return ctx, testSeries
    92  }
    93  
    94  // CompareOutputsAndExpected compares the actual output with the expected output.
    95  func CompareOutputsAndExpected(
    96  	t *testing.T,
    97  	step int,
    98  	start time.Time,
    99  	expected []TestSeries,
   100  	actual []*ts.Series,
   101  ) {
   102  	require.Equal(t, len(expected), len(actual), "mismatch series count")
   103  	for i := range expected {
   104  		i := i // To capture for wrapMsg.
   105  		e := expected[i].Data
   106  		a := actual[i]
   107  		wrapMsg := func(str string) string {
   108  			return fmt.Sprintf("%s\nseries=%d\nexpected=%v\nactual=%v\n"+
   109  				"expectedStart=%v\nactualStart=%v\n",
   110  				str, i, e, a.SafeValues(), start, a.StartTime())
   111  		}
   112  		require.Equal(t, expected[i].Name, a.Name())
   113  		assert.Equal(t, step, a.MillisPerStep(), wrapMsg(a.Name()+
   114  			": MillisPerStep in expected series do not match MillisPerStep in actual"))
   115  		diff := time.Duration(math.Abs(float64(start.Sub(a.StartTime()))))
   116  		assert.True(t, diff < time.Millisecond, wrapMsg(fmt.Sprintf(
   117  			"%s: StartTime in expected series (%v) does not match StartTime in actual (%v), diff %v",
   118  			a.Name(), start, a.StartTime(), diff)))
   119  
   120  		require.Equal(t, len(e), a.Len(),
   121  			wrapMsg(a.Name()+
   122  				": length of expected series does not match length of actual"))
   123  		for step := 0; step < a.Len(); step++ {
   124  			v := a.ValueAt(step)
   125  			if math.IsNaN(e[step]) {
   126  				msg := wrapMsg(fmt.Sprintf(
   127  					"%s: invalid value for step %d/%d, should be NaN but is %v",
   128  					a.Name(), 1+step, a.Len(), v))
   129  				assert.True(t, math.IsNaN(v), msg)
   130  			} else if math.IsNaN(v) {
   131  				msg := wrapMsg(fmt.Sprintf(
   132  					"%s: invalid value for step %d/%d, should be %v but is NaN ",
   133  					a.Name(), 1+step, a.Len(), e[step]))
   134  				assert.Fail(t, msg)
   135  			} else {
   136  				msg := wrapMsg(fmt.Sprintf(
   137  					"%s: invalid value for %d/%d",
   138  					a.Name(), 1+step, a.Len()))
   139  				xtest.InDeltaWithNaNs(t, e[step], v, 0.0001, msg)
   140  			}
   141  		}
   142  	}
   143  }
   144  
   145  // MovingFunctionStorage is a special test construct for all moving functions
   146  type MovingFunctionStorage struct {
   147  	StepMillis         int
   148  	BootstrapStart     time.Time
   149  	Bootstrap          []float64
   150  	Values             []float64
   151  	OriginalValues     []SeriesNameAndValues
   152  	ExplicitBootstraps []ExplicitBootstrap
   153  }
   154  
   155  // ExplicitBootstrap is an explicit bootstrap that's expected at a block start.
   156  type ExplicitBootstrap struct {
   157  	Start  time.Time
   158  	Values []SeriesNameAndValues
   159  	// StepMillis if set will override the step milliseconds for
   160  	// the bootstrapped values.
   161  	StepMillis int
   162  }
   163  
   164  // SeriesNameAndValues is a series name and a set of values.
   165  type SeriesNameAndValues struct {
   166  	Name   string
   167  	Values []float64
   168  }
   169  
   170  // FetchByPath builds a new series from the input path
   171  func (s *MovingFunctionStorage) FetchByPath(
   172  	ctx context.Context,
   173  	path string,
   174  	opts storage.FetchOptions,
   175  ) (*storage.FetchResult, error) {
   176  	return s.fetchByIDs(ctx, []string{path}, opts)
   177  }
   178  
   179  // FetchByQuery builds a new series from the input query
   180  func (s *MovingFunctionStorage) FetchByQuery(
   181  	ctx context.Context,
   182  	query string,
   183  	opts storage.FetchOptions,
   184  ) (*storage.FetchResult, error) {
   185  	return s.fetchByIDs(ctx, []string{query}, opts)
   186  }
   187  
   188  // FetchByIDs builds a new series from the input query
   189  func (s *MovingFunctionStorage) fetchByIDs(
   190  	ctx context.Context,
   191  	ids []string,
   192  	opts storage.FetchOptions,
   193  ) (*storage.FetchResult, error) {
   194  	if s.Bootstrap == nil && s.Values == nil && s.OriginalValues == nil && len(s.ExplicitBootstraps) == 0 {
   195  		return storage.NewFetchResult(ctx, nil, block.NewResultMetadata()), nil
   196  	}
   197  
   198  	var (
   199  		seriesList = make([]*ts.Series, 0, len(ids))
   200  		values     = make([]float64, 0, len(s.Bootstrap)+len(s.Values))
   201  		step       = s.StepMillis
   202  	)
   203  	for _, bootstrap := range s.ExplicitBootstraps {
   204  		if !opts.StartTime.Equal(bootstrap.Start) {
   205  			continue
   206  		}
   207  
   208  		if v := bootstrap.StepMillis; v > 0 {
   209  			step = v
   210  		}
   211  		for _, elem := range bootstrap.Values {
   212  			series := ts.NewSeries(ctx, elem.Name, opts.StartTime,
   213  				NewTestSeriesValues(ctx, step, elem.Values))
   214  			seriesList = append(seriesList, series)
   215  		}
   216  		return storage.NewFetchResult(ctx, seriesList, block.NewResultMetadata()), nil
   217  	}
   218  
   219  	if opts.StartTime.Equal(s.BootstrapStart) {
   220  		values = append(values, s.Bootstrap...)
   221  		values = append(values, s.Values...)
   222  	} else {
   223  		if s.OriginalValues != nil {
   224  			for _, elem := range s.OriginalValues {
   225  				series := ts.NewSeries(ctx, elem.Name, opts.StartTime,
   226  					NewTestSeriesValues(ctx, step, elem.Values))
   227  				seriesList = append(seriesList, series)
   228  			}
   229  			return storage.NewFetchResult(ctx, seriesList, block.NewResultMetadata()), nil
   230  		}
   231  
   232  		values = append(values, s.Values...)
   233  	}
   234  
   235  	for _, id := range ids {
   236  		series := ts.NewSeries(ctx, id, opts.StartTime,
   237  			NewTestSeriesValues(ctx, step, values))
   238  		seriesList = append(seriesList, series)
   239  	}
   240  
   241  	return storage.NewFetchResult(ctx, seriesList, block.NewResultMetadata()), nil
   242  }
   243  
   244  // CompleteTags implements the storage interface.
   245  func (s *MovingFunctionStorage) CompleteTags(
   246  	ctx stdcontext.Context,
   247  	query *querystorage.CompleteTagsQuery,
   248  	opts *querystorage.FetchOptions,
   249  ) (*consolidators.CompleteTagsResult, error) {
   250  	return nil, fmt.Errorf("not implemented")
   251  }