github.com/go-graphite/carbonapi@v0.17.0/tests/helper.go (about) 1 package tests 2 3 import ( 4 "context" 5 "fmt" 6 "testing" 7 "time" 8 9 "github.com/ansel1/merry" 10 zipperTypes "github.com/go-graphite/carbonapi/zipper/types" 11 pb "github.com/go-graphite/protocol/carbonapi_v3_pb" 12 13 "github.com/go-graphite/carbonapi/expr/helper" 14 "github.com/go-graphite/carbonapi/expr/interfaces" 15 "github.com/go-graphite/carbonapi/expr/metadata" 16 "github.com/go-graphite/carbonapi/expr/types" 17 "github.com/go-graphite/carbonapi/pkg/parser" 18 "github.com/go-graphite/carbonapi/tests/compare" 19 ) 20 21 type FuncEvaluator struct { 22 eval func(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) 23 } 24 25 func (evaluator *FuncEvaluator) Fetch(_ context.Context, _ []parser.Expr, _, _ int64, values map[parser.MetricRequest][]*types.MetricData) (map[parser.MetricRequest][]*types.MetricData, error) { 26 return values, nil 27 } 28 29 func (evaluator *FuncEvaluator) Eval(ctx context.Context, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) { 30 if e.IsName() { 31 return values[parser.MetricRequest{Metric: e.Target(), From: from, Until: until}], nil 32 } else if e.IsConst() { 33 p := types.MetricData{ 34 FetchResponse: pb.FetchResponse{ 35 Name: e.Target(), 36 Values: []float64{e.FloatValue()}, 37 }, 38 Tags: map[string]string{"name": e.Target()}, 39 } 40 return []*types.MetricData{&p}, nil 41 } 42 // evaluate the function 43 44 // all functions have arguments -- check we do too 45 if e.ArgsLen() == 0 { 46 return nil, parser.ErrMissingArgument 47 } 48 49 if evaluator.eval != nil { 50 return evaluator.eval(context.Background(), evaluator, e, from, until, values) 51 } 52 53 return nil, helper.ErrUnknownFunction(e.Target()) 54 } 55 56 func DummyEvaluator() interfaces.Evaluator { 57 e := &FuncEvaluator{ 58 eval: nil, 59 } 60 61 return e 62 } 63 64 func EvaluatorFromFunc(function interfaces.Function) interfaces.Evaluator { 65 e := &FuncEvaluator{ 66 eval: function.Do, 67 } 68 69 return e 70 } 71 72 func EvaluatorFromFuncWithMetadata(metadata map[string]interfaces.Function) interfaces.Evaluator { 73 e := &FuncEvaluator{ 74 eval: func(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) { 75 if f, ok := metadata[e.Target()]; ok { 76 return f.Do(context.Background(), eval, e, from, until, values) 77 } 78 return nil, fmt.Errorf("unknown function: %v", e.Target()) 79 }, 80 } 81 return e 82 } 83 84 func DeepClone(original map[parser.MetricRequest][]*types.MetricData) map[parser.MetricRequest][]*types.MetricData { 85 clone := map[parser.MetricRequest][]*types.MetricData{} 86 for key, originalMetrics := range original { 87 copiedMetrics := make([]*types.MetricData, 0, len(originalMetrics)) 88 for _, originalMetric := range originalMetrics { 89 copiedMetric := types.MetricData{ 90 FetchResponse: pb.FetchResponse{ 91 Name: originalMetric.Name, 92 StartTime: originalMetric.StartTime, 93 StopTime: originalMetric.StopTime, 94 StepTime: originalMetric.StepTime, 95 Values: make([]float64, len(originalMetric.Values)), 96 PathExpression: originalMetric.PathExpression, 97 ConsolidationFunc: originalMetric.ConsolidationFunc, 98 XFilesFactor: originalMetric.XFilesFactor, 99 HighPrecisionTimestamps: originalMetric.HighPrecisionTimestamps, 100 AppliedFunctions: make([]string, len(originalMetric.AppliedFunctions)), 101 RequestStartTime: originalMetric.RequestStartTime, 102 RequestStopTime: originalMetric.RequestStopTime, 103 }, 104 GraphOptions: originalMetric.GraphOptions, 105 ValuesPerPoint: originalMetric.ValuesPerPoint, 106 Tags: make(map[string]string), 107 AggregateFunction: originalMetric.AggregateFunction, 108 } 109 110 copy(copiedMetric.Values, originalMetric.Values) 111 copy(copiedMetric.AppliedFunctions, originalMetric.AppliedFunctions) 112 copiedMetrics = append(copiedMetrics, &copiedMetric) 113 for k, v := range originalMetric.Tags { 114 copiedMetric.Tags[k] = v 115 } 116 } 117 118 clone[key] = copiedMetrics 119 } 120 121 return clone 122 } 123 124 func DeepEqual(t *testing.T, target string, original, modified map[parser.MetricRequest][]*types.MetricData, compareTags bool) { 125 for key := range original { 126 if len(original[key]) == len(modified[key]) { 127 for i := range original[key] { 128 if !compare.MetricDataIsEqual(original[key][i], modified[key][i], compareTags) { 129 t.Errorf( 130 "%s: source data was modified key %v index %v original:\n%v\n modified:\n%v", 131 target, 132 key, 133 i, 134 original[key][i], 135 modified[key][i], 136 ) 137 } 138 } 139 } else { 140 t.Errorf( 141 "%s: source data was modified key %v original length %d, new length %d", 142 target, 143 key, 144 len(original[key]), 145 len(modified[key]), 146 ) 147 } 148 } 149 } 150 151 type SummarizeEvalTestItem struct { 152 Target string 153 M map[parser.MetricRequest][]*types.MetricData 154 Want []float64 155 From int64 156 Until int64 157 Name string 158 Step int64 159 Start int64 160 Stop int64 161 } 162 163 func InitTestSummarize() (int64, int64, int64) { 164 t0, err := time.Parse(time.UnixDate, "Wed Sep 10 10:32:00 CEST 2014") 165 if err != nil { 166 panic(err) 167 } 168 169 tenThirtyTwo := t0.Unix() 170 171 t0, err = time.Parse(time.UnixDate, "Wed Sep 10 10:59:00 CEST 2014") 172 if err != nil { 173 panic(err) 174 } 175 176 tenFiftyNine := t0.Unix() 177 178 t0, err = time.Parse(time.UnixDate, "Wed Sep 10 10:30:00 CEST 2014") 179 if err != nil { 180 panic(err) 181 } 182 183 tenThirty := t0.Unix() 184 185 return tenThirtyTwo, tenFiftyNine, tenThirty 186 } 187 188 func TestSummarizeEvalExpr(t *testing.T, eval interfaces.Evaluator, tt *SummarizeEvalTestItem) { 189 t.Run(tt.Name, func(t *testing.T) { 190 originalMetrics := DeepClone(tt.M) 191 exp, _, _ := parser.ParseExpr(tt.Target) 192 g, err := eval.Eval(context.Background(), exp, tt.From, tt.Until, tt.M) 193 if err != nil { 194 t.Errorf("failed to eval %v: %+v", tt.Name, err) 195 return 196 } 197 DeepEqual(t, g[0].Name, originalMetrics, tt.M, false) 198 if g[0].StepTime != tt.Step { 199 t.Errorf("bad Step for %s:\ngot %d\nwant %d", g[0].Name, g[0].StepTime, tt.Step) 200 } 201 if g[0].StartTime != tt.Start { 202 t.Errorf("bad Start for %s: got %s want %s", g[0].Name, time.Unix(g[0].StartTime, 0).Format(time.StampNano), time.Unix(tt.Start, 0).Format(time.StampNano)) 203 } 204 if g[0].StopTime != tt.Stop { 205 t.Errorf("bad Stop for %s: got %s want %s", g[0].Name, time.Unix(g[0].StopTime, 0).Format(time.StampNano), time.Unix(tt.Stop, 0).Format(time.StampNano)) 206 } 207 208 if !compare.NearlyEqual(g[0].Values, tt.Want) { 209 t.Errorf("failed: %s:\ngot %+v,\nwant %+v", g[0].Name, g[0].Values, tt.Want) 210 } 211 if g[0].Name != tt.Name { 212 t.Errorf("bad Name for %+v: got %v, want %v", g, g[0].Name, tt.Name) 213 } 214 if _, ok := g[0].Tags["name"]; !ok { 215 t.Errorf("metric with name %v doesn't contain 'name' tag", g[0].Name) 216 } 217 }) 218 } 219 220 type MultiReturnEvalTestItem struct { 221 Target string 222 M map[parser.MetricRequest][]*types.MetricData 223 Name string 224 Results map[string][]*types.MetricData 225 } 226 227 func TestMultiReturnEvalExpr(t *testing.T, eval interfaces.Evaluator, tt *MultiReturnEvalTestItem) { 228 originalMetrics := DeepClone(tt.M) 229 exp, _, err := parser.ParseExpr(tt.Target) 230 if err != nil { 231 t.Errorf("failed to parse %v: %+v", tt.Target, err) 232 return 233 } 234 g, err := eval.Eval(context.Background(), exp, 0, 1, tt.M) 235 if err != nil { 236 t.Errorf("failed to eval %v: %+v", tt.Name, err) 237 return 238 } 239 DeepEqual(t, tt.Name, originalMetrics, tt.M, true) 240 if len(g) == 0 { 241 t.Errorf("returned no data %v", tt.Name) 242 return 243 } 244 if g[0] == nil { 245 t.Errorf("returned no value %v", tt.Name) 246 return 247 } 248 if g[0].StepTime == 0 { 249 t.Errorf("missing Step for %+v", g) 250 } 251 if len(g) != len(tt.Results) { 252 t.Errorf("unexpected results len: got %d, want %d for %s", len(g), len(tt.Results), tt.Target) 253 } 254 for i, actual := range g { 255 if actual == nil { 256 t.Errorf("result[%d] mismatch, got nil", i) 257 continue 258 } 259 wants, ok := tt.Results[actual.Name] 260 if !ok { 261 t.Errorf("missing result Name: %v", actual.Name) 262 continue 263 } 264 265 if wants[0].Name != actual.Name { 266 t.Errorf("result Name mismatch, got\n%#v,\nwant\n%#v", actual.Name, wants[0].Name) 267 } 268 269 for k, v := range wants[0].Tags { 270 if aTag, ok := actual.Tags[k]; ok { 271 if aTag != v { 272 t.Errorf("metric %+v with name '%s' tag['%s'] value '%s' not equal '%s'", actual, actual.Name, k, aTag, v) 273 } 274 } else { 275 t.Errorf("metric %+v with name %v doesn't contain '%s' tag", actual, actual.Name, k) 276 } 277 } 278 279 for k := range actual.Tags { 280 if _, ok := wants[0].Tags[k]; !ok { 281 t.Errorf("metric %+v with name %v contain unwanted '%s' tag", actual, actual.Name, k) 282 } 283 } 284 285 if !compare.NearlyEqual(wants[0].Values, actual.Values) || 286 wants[0].StartTime != actual.StartTime || 287 wants[0].StopTime != actual.StopTime || 288 wants[0].StepTime != actual.StepTime { 289 t.Errorf("result mismatch, got\n%#v,\nwant\n%#v", actual, wants) 290 } 291 } 292 } 293 294 type RewriteTestResult struct { 295 Rewritten bool 296 Targets []string 297 Err error 298 } 299 300 type RewriteTestItem struct { 301 //E parser.Expr 302 Target string 303 M map[parser.MetricRequest][]*types.MetricData 304 Want RewriteTestResult 305 } 306 307 type RewriteTestError struct { 308 //E parser.Expr 309 Target string 310 M map[parser.MetricRequest][]*types.MetricData 311 Want error 312 } 313 314 func rewriteExpr(e parser.Expr, eval interfaces.Evaluator, from, until int64, values map[parser.MetricRequest][]*types.MetricData) (bool, []string, error) { 315 if e.IsFunc() { 316 metadata.FunctionMD.RLock() 317 f, ok := metadata.FunctionMD.RewriteFunctions[e.Target()] 318 metadata.FunctionMD.RUnlock() 319 if ok { 320 return f.Do(context.Background(), eval, e, from, until, values) 321 } 322 } 323 return false, nil, nil 324 } 325 326 func TestRewriteExpr(t *testing.T, eval interfaces.Evaluator, tt *RewriteTestItem) { 327 originalMetrics := DeepClone(tt.M) 328 testName := tt.Target 329 exp, _, err := parser.ParseExpr(tt.Target) 330 if err != nil { 331 t.Fatalf("failed to parse %s: %+v", tt.Target, err) 332 } 333 334 rewritten, targets, err := rewriteExpr(exp, eval, 0, 1, tt.M) 335 if err != tt.Want.Err { 336 if err == nil || tt.Want.Err == nil || !merry.Is(err, tt.Want.Err) { 337 t.Fatalf("unexpected error while calling rewrite for '%s': got '%+v', expected '%+v'", testName, err, tt.Want.Err) 338 } 339 } 340 if rewritten != tt.Want.Rewritten { 341 t.Fatalf("unexpected result for rewritten for '%s': got '%v', expected '%v'", testName, rewritten, tt.Want.Rewritten) 342 return 343 } 344 345 if len(targets) != len(tt.Want.Targets) { 346 t.Fatalf("%s returned a different number of metrics, actual %v, Want %v", testName, len(targets), len(tt.Want.Targets)) 347 } 348 DeepEqual(t, testName, originalMetrics, tt.M, false) 349 350 for i, want := range tt.Want.Targets { 351 if want != targets[i] { 352 t.Errorf("unexpected result for rewrite for '%s': got='%s', expected='%s'", testName, targets[i], want) 353 } 354 } 355 } 356 357 type EvalTestItem struct { 358 //E parser.Expr 359 Target string 360 M map[parser.MetricRequest][]*types.MetricData 361 Want []*types.MetricData 362 } 363 364 type EvalTestItemWithCustomValidation struct { 365 Target string 366 M map[parser.MetricRequest][]*types.MetricData 367 Validator func(*testing.T, []*types.MetricData) 368 From int64 369 Until int64 370 } 371 372 type EvalTestItemWithError struct { 373 Target string 374 M map[parser.MetricRequest][]*types.MetricData 375 Want []*types.MetricData 376 Error error 377 } 378 379 type EvalTestItemWithRange struct { 380 Target string 381 M map[parser.MetricRequest][]*types.MetricData 382 Want []*types.MetricData 383 From int64 384 Until int64 385 } 386 387 func (r *EvalTestItemWithRange) TestItem() *EvalTestItem { 388 return &EvalTestItem{ 389 Target: r.Target, 390 M: r.M, 391 Want: r.Want, 392 } 393 } 394 395 func TestEvalExprWithCustomValidation(t *testing.T, eval interfaces.Evaluator, tt *EvalTestItemWithCustomValidation) { 396 exp, _, err := parser.ParseExpr(tt.Target) 397 if err != nil { 398 t.Errorf("failed to parse %s: %+v", tt.Target, err) 399 } 400 g, err := eval.Eval(context.Background(), exp, tt.From, tt.Until, tt.M) 401 if err != nil { 402 t.Errorf("failed to eval %s: %+v", tt.Target, err) 403 } 404 tt.Validator(t, g) 405 } 406 407 func TestEvalExprModifiedOrigin(t *testing.T, eval interfaces.Evaluator, tt *EvalTestItem, from, until int64, strictOrder, compareTags bool) error { 408 testName := tt.Target 409 exp, _, err := parser.ParseExpr(tt.Target) 410 if err != nil { 411 t.Errorf("failed to parse %s: %+v", tt.Target, err) 412 return nil 413 } 414 g, err := eval.Eval(context.Background(), exp, from, until, tt.M) 415 if err != nil { 416 return err 417 } 418 if len(g) != len(tt.Want) { 419 t.Errorf("%s returned a different number of metrics, actual %v, Want %v", testName, len(g), len(tt.Want)) 420 } 421 422 for i, want := range tt.Want { 423 if i > len(g)-1 || g[i] == nil { 424 t.Errorf("returned no value %+s[%d]: want %+v", tt.Target, i, want) 425 return nil 426 } 427 actual := g[i] 428 if compareTags { 429 if _, ok := actual.Tags["name"]; !ok { 430 t.Errorf("metric[%d] %+v with name %v doesn't contain 'name' tag", i, actual, actual.Name) 431 } 432 for k, v := range want.Tags { 433 if aTag, ok := actual.Tags[k]; ok { 434 if aTag != v { 435 t.Errorf("metric[%d] %+v with name '%s' tag['%s'] value '%s' not equal '%s'", i, actual, actual.Name, k, aTag, v) 436 } 437 } else { 438 t.Errorf("metric[%d] %+v with name %v doesn't contain '%s' tag", i, actual, actual.Name, k) 439 } 440 } 441 for k := range actual.Tags { 442 if _, ok := want.Tags[k]; !ok { 443 t.Errorf("metric[%d] %+v with name %v contain unwanted '%s' tag", i, actual, actual.Name, k) 444 } 445 } 446 } 447 if actual.StepTime == 0 { 448 t.Errorf("missing Step for %+v", g) 449 } 450 if actual.Name != want.Name { 451 t.Errorf("bad Name for %s metric[%d]: got %s, Want %s", testName, i, actual.Name, want.Name) 452 } 453 if actual.ConsolidationFunc != want.ConsolidationFunc { 454 t.Errorf("different ConsolidationFunc for %s metric[%d] %s: got %v, Want %v", testName, i, actual.Name, actual.ConsolidationFunc, want.ConsolidationFunc) 455 } 456 if !compare.NearlyEqualMetrics(actual, want) { 457 t.Errorf("different values for %s metric[%d] %s: got %v, Want %v", testName, i, actual.Name, actual.Values, want.Values) 458 } 459 if actual.StepTime != want.StepTime { 460 t.Errorf("different StepTime for %s metric[%d] %s: got %v, Want %v", testName, i, actual.Name, actual.StepTime, want.StepTime) 461 } 462 if actual.StartTime != want.StartTime { 463 t.Errorf("different StartTime for %s metric[%d] %s: got %v, Want %v", testName, i, actual.Name, actual.StartTime, want.StartTime) 464 } 465 if actual.StopTime != want.StopTime { 466 t.Errorf("different StopTime for %s metric[%d] %s: got %v, Want %v", testName, i, actual.Name, actual.StopTime, want.StopTime) 467 } 468 } 469 return nil 470 } 471 472 func TestEvalExpr(t *testing.T, eval interfaces.Evaluator, tt *EvalTestItem) { 473 TestEvalExprWithOptions(t, eval, tt, true) 474 } 475 476 func TestEvalExprWithOptions(t *testing.T, eval interfaces.Evaluator, tt *EvalTestItem, compareTags bool) { 477 originalMetrics := DeepClone(tt.M) 478 err := TestEvalExprModifiedOrigin(t, eval, tt, 0, 1, false, compareTags) 479 if err != nil { 480 t.Errorf("unexpected error while evaluating %s: got `%+v`", tt.Target, err) 481 return 482 } 483 DeepEqual(t, tt.Target, originalMetrics, tt.M, true) 484 } 485 486 func TestEvalExprResult(t *testing.T, eval interfaces.Evaluator, tt *EvalTestItem) { 487 err := TestEvalExprModifiedOrigin(t, eval, tt, 0, 1, false, true) 488 if err != nil { 489 t.Errorf("unexpected error while evaluating %s: got `%+v`", tt.Target, err) 490 return 491 } 492 // 493 } 494 495 func TestEvalExprWithRange(t *testing.T, eval interfaces.Evaluator, tt *EvalTestItemWithRange) { 496 originalMetrics := DeepClone(tt.M) 497 tt2 := tt.TestItem() 498 err := TestEvalExprModifiedOrigin(t, eval, tt2, tt.From, tt.Until, false, true) 499 if err != nil { 500 t.Errorf("unexpected error while evaluating %s: got `%+v`", tt.Target, err) 501 return 502 } 503 DeepEqual(t, tt.Target, originalMetrics, tt.M, true) 504 } 505 506 func TestEvalExprWithError(t *testing.T, eval interfaces.Evaluator, tt *EvalTestItemWithError) { 507 originalMetrics := DeepClone(tt.M) 508 tt2 := &EvalTestItem{ 509 Target: tt.Target, 510 M: tt.M, 511 Want: tt.Want, 512 } 513 err := TestEvalExprModifiedOrigin(t, eval, tt2, 0, 1, false, true) 514 if !merry.Is(err, tt.Error) { 515 t.Errorf("unexpected error while evaluating %s: got `%+v`, expected `%+v`", tt.Target, err, tt.Error) 516 return 517 } 518 DeepEqual(t, tt.Target, originalMetrics, tt.M, true) 519 } 520 521 func TestEvalExprOrdered(t *testing.T, eval interfaces.Evaluator, tt *EvalTestItem) { 522 originalMetrics := DeepClone(tt.M) 523 err := TestEvalExprModifiedOrigin(t, eval, tt, 0, 1, true, true) 524 if err != nil { 525 t.Errorf("unexpected error while evaluating %s: got `%+v`", tt.Target, err) 526 return 527 } 528 DeepEqual(t, tt.Target, originalMetrics, tt.M, true) 529 } 530 531 type TestZipper struct { 532 M map[parser.MetricRequest][]*types.MetricData 533 } 534 535 func NewTestZipper(m map[parser.MetricRequest][]*types.MetricData) TestZipper { 536 return TestZipper{M: m} 537 } 538 539 func (zp TestZipper) Find(ctx context.Context, request pb.MultiGlobRequest) (*pb.MultiGlobResponse, *zipperTypes.Stats, merry.Error) { 540 return nil, nil, zipperTypes.ErrNotImplementedYet 541 } 542 543 func (zp TestZipper) Info(ctx context.Context, metrics []string) (*pb.ZipperInfoResponse, *zipperTypes.Stats, merry.Error) { 544 return nil, nil, zipperTypes.ErrNotImplementedYet 545 } 546 547 func (zp TestZipper) RenderCompat(ctx context.Context, metrics []string, from, until int64) ([]*types.MetricData, *zipperTypes.Stats, merry.Error) { 548 return nil, nil, zipperTypes.ErrNotImplementedYet 549 } 550 551 func (zp TestZipper) Render(ctx context.Context, request pb.MultiFetchRequest) ([]*types.MetricData, *zipperTypes.Stats, merry.Error) { 552 var resp []*types.MetricData 553 for _, r := range request.Metrics { 554 metricRequest := parser.MetricRequest{Metric: r.PathExpression, From: r.StartTime, Until: r.StopTime} 555 if v, ok := zp.M[metricRequest]; ok { 556 resp = append(resp, v...) 557 } 558 } 559 return resp, nil, nil 560 } 561 562 func (zp TestZipper) TagNames(ctx context.Context, query string, limit int64) ([]string, merry.Error) { 563 return nil, zipperTypes.ErrNotImplementedYet 564 } 565 566 func (zp TestZipper) TagValues(ctx context.Context, query string, limit int64) ([]string, merry.Error) { 567 return nil, zipperTypes.ErrNotImplementedYet 568 } 569 570 func (zp TestZipper) ScaleToCommonStep() bool { 571 return false 572 } 573 574 func GenerateValues(start, stop, step int64) (values []float64) { 575 for i := start; i < stop; i += step { 576 values = append(values, float64(i)) 577 } 578 return 579 }