github.com/m3db/m3@v1.5.0/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 }