1package protocol 2 3import ( 4 "bytes" 5 "reflect" 6 "sort" 7 "strconv" 8 "strings" 9 "testing" 10 "time" 11) 12 13func MustMetric(v Metric, err error) Metric { 14 if err != nil { 15 panic(err) 16 } 17 return v 18} 19 20var DefaultTime = func() time.Time { 21 return time.Unix(42, 0) 22} 23 24var ptests = []struct { 25 name string 26 input []byte 27 timeFunc func() time.Time 28 metrics []Metric 29 err error 30}{ 31 { 32 name: "minimal", 33 input: []byte("cpu value=42 0"), 34 metrics: []Metric{ 35 MustMetric( 36 New( 37 "cpu", 38 map[string]string{}, 39 map[string]interface{}{ 40 "value": 42.0, 41 }, 42 time.Unix(0, 0), 43 ), 44 ), 45 }, 46 err: nil, 47 }, 48 { 49 name: "minimal with newline", 50 input: []byte("cpu value=42 0\n"), 51 metrics: []Metric{ 52 MustMetric( 53 New( 54 "cpu", 55 map[string]string{}, 56 map[string]interface{}{ 57 "value": 42.0, 58 }, 59 time.Unix(0, 0), 60 ), 61 ), 62 }, 63 err: nil, 64 }, 65 { 66 name: "measurement escape space", 67 input: []byte(`c\ pu value=42`), 68 metrics: []Metric{ 69 MustMetric( 70 New( 71 "c pu", 72 map[string]string{}, 73 map[string]interface{}{ 74 "value": 42.0, 75 }, 76 time.Unix(42, 0), 77 ), 78 ), 79 }, 80 err: nil, 81 }, 82 { 83 name: "measurement escape comma", 84 input: []byte(`c\,pu value=42`), 85 metrics: []Metric{ 86 MustMetric( 87 New( 88 "c,pu", 89 map[string]string{}, 90 map[string]interface{}{ 91 "value": 42.0, 92 }, 93 time.Unix(42, 0), 94 ), 95 ), 96 }, 97 err: nil, 98 }, 99 { 100 name: "tags", 101 input: []byte(`cpu,cpu=cpu0,host=localhost value=42`), 102 metrics: []Metric{ 103 MustMetric( 104 New( 105 "cpu", 106 map[string]string{ 107 "cpu": "cpu0", 108 "host": "localhost", 109 }, 110 map[string]interface{}{ 111 "value": 42.0, 112 }, 113 time.Unix(42, 0), 114 ), 115 ), 116 }, 117 err: nil, 118 }, 119 { 120 name: "tags escape unescapable", 121 input: []byte(`cpu,ho\st=localhost value=42`), 122 metrics: []Metric{ 123 MustMetric( 124 New( 125 "cpu", 126 map[string]string{ 127 `ho\st`: "localhost", 128 }, 129 map[string]interface{}{ 130 "value": 42.0, 131 }, 132 time.Unix(42, 0), 133 ), 134 ), 135 }, 136 err: nil, 137 }, 138 { 139 name: "tags escape equals", 140 input: []byte(`cpu,ho\=st=localhost value=42`), 141 metrics: []Metric{ 142 MustMetric( 143 New( 144 "cpu", 145 map[string]string{ 146 "ho=st": "localhost", 147 }, 148 map[string]interface{}{ 149 "value": 42.0, 150 }, 151 time.Unix(42, 0), 152 ), 153 ), 154 }, 155 err: nil, 156 }, 157 { 158 name: "tags escape comma", 159 input: []byte(`cpu,ho\,st=localhost value=42`), 160 metrics: []Metric{ 161 MustMetric( 162 New( 163 "cpu", 164 map[string]string{ 165 "ho,st": "localhost", 166 }, 167 map[string]interface{}{ 168 "value": 42.0, 169 }, 170 time.Unix(42, 0), 171 ), 172 ), 173 }, 174 err: nil, 175 }, 176 { 177 name: "tag value escape space", 178 input: []byte(`cpu,host=two\ words value=42`), 179 metrics: []Metric{ 180 MustMetric( 181 New( 182 "cpu", 183 map[string]string{ 184 "host": "two words", 185 }, 186 map[string]interface{}{ 187 "value": 42.0, 188 }, 189 time.Unix(42, 0), 190 ), 191 ), 192 }, 193 err: nil, 194 }, 195 { 196 name: "tag value double escape space", 197 input: []byte(`cpu,host=two\\ words value=42`), 198 metrics: []Metric{ 199 MustMetric( 200 New( 201 "cpu", 202 map[string]string{ 203 "host": `two\ words`, 204 }, 205 map[string]interface{}{ 206 "value": 42.0, 207 }, 208 time.Unix(42, 0), 209 ), 210 ), 211 }, 212 err: nil, 213 }, 214 { 215 name: "tag value triple escape space", 216 input: []byte(`cpu,host=two\\\ words value=42`), 217 metrics: []Metric{ 218 MustMetric( 219 New( 220 "cpu", 221 map[string]string{ 222 "host": `two\\ words`, 223 }, 224 map[string]interface{}{ 225 "value": 42.0, 226 }, 227 time.Unix(42, 0), 228 ), 229 ), 230 }, 231 err: nil, 232 }, 233 { 234 name: "field key escape not escapable", 235 input: []byte(`cpu va\lue=42`), 236 metrics: []Metric{ 237 MustMetric( 238 New( 239 "cpu", 240 map[string]string{}, 241 map[string]interface{}{ 242 `va\lue`: 42.0, 243 }, 244 time.Unix(42, 0), 245 ), 246 ), 247 }, 248 err: nil, 249 }, 250 { 251 name: "field key escape equals", 252 input: []byte(`cpu va\=lue=42`), 253 metrics: []Metric{ 254 MustMetric( 255 New( 256 "cpu", 257 map[string]string{}, 258 map[string]interface{}{ 259 `va=lue`: 42.0, 260 }, 261 time.Unix(42, 0), 262 ), 263 ), 264 }, 265 err: nil, 266 }, 267 { 268 name: "field key escape comma", 269 input: []byte(`cpu va\,lue=42`), 270 metrics: []Metric{ 271 MustMetric( 272 New( 273 "cpu", 274 map[string]string{}, 275 map[string]interface{}{ 276 `va,lue`: 42.0, 277 }, 278 time.Unix(42, 0), 279 ), 280 ), 281 }, 282 err: nil, 283 }, 284 { 285 name: "field key escape space", 286 input: []byte(`cpu va\ lue=42`), 287 metrics: []Metric{ 288 MustMetric( 289 New( 290 "cpu", 291 map[string]string{}, 292 map[string]interface{}{ 293 `va lue`: 42.0, 294 }, 295 time.Unix(42, 0), 296 ), 297 ), 298 }, 299 err: nil, 300 }, 301 { 302 name: "field int", 303 input: []byte("cpu value=42i"), 304 metrics: []Metric{ 305 MustMetric( 306 New( 307 "cpu", 308 map[string]string{}, 309 map[string]interface{}{ 310 "value": 42, 311 }, 312 time.Unix(42, 0), 313 ), 314 ), 315 }, 316 err: nil, 317 }, 318 { 319 name: "field int overflow", 320 input: []byte("cpu value=9223372036854775808i"), 321 metrics: nil, 322 err: &ParseError{ 323 Offset: 30, 324 LineNumber: 1, 325 Column: 31, 326 msg: strconv.ErrRange.Error(), 327 buf: "cpu value=9223372036854775808i", 328 }, 329 }, 330 { 331 name: "field int max value", 332 input: []byte("cpu value=9223372036854775807i"), 333 metrics: []Metric{ 334 MustMetric( 335 New( 336 "cpu", 337 map[string]string{}, 338 map[string]interface{}{ 339 "value": int64(9223372036854775807), 340 }, 341 time.Unix(42, 0), 342 ), 343 ), 344 }, 345 err: nil, 346 }, 347 { 348 name: "field uint", 349 input: []byte("cpu value=42u"), 350 metrics: []Metric{ 351 MustMetric( 352 New( 353 "cpu", 354 map[string]string{}, 355 map[string]interface{}{ 356 "value": uint64(42), 357 }, 358 time.Unix(42, 0), 359 ), 360 ), 361 }, 362 err: nil, 363 }, 364 { 365 name: "field uint overflow", 366 input: []byte("cpu value=18446744073709551616u"), 367 metrics: nil, 368 err: &ParseError{ 369 Offset: 31, 370 LineNumber: 1, 371 Column: 32, 372 msg: strconv.ErrRange.Error(), 373 buf: "cpu value=18446744073709551616u", 374 }, 375 }, 376 { 377 name: "field uint max value", 378 input: []byte("cpu value=18446744073709551615u"), 379 metrics: []Metric{ 380 MustMetric( 381 New( 382 "cpu", 383 map[string]string{}, 384 map[string]interface{}{ 385 "value": uint64(18446744073709551615), 386 }, 387 time.Unix(42, 0), 388 ), 389 ), 390 }, 391 err: nil, 392 }, 393 { 394 name: "field boolean", 395 input: []byte("cpu value=true"), 396 metrics: []Metric{ 397 MustMetric( 398 New( 399 "cpu", 400 map[string]string{}, 401 map[string]interface{}{ 402 "value": true, 403 }, 404 time.Unix(42, 0), 405 ), 406 ), 407 }, 408 err: nil, 409 }, 410 { 411 name: "field string", 412 input: []byte(`cpu value="42"`), 413 metrics: []Metric{ 414 MustMetric( 415 New( 416 "cpu", 417 map[string]string{}, 418 map[string]interface{}{ 419 "value": "42", 420 }, 421 time.Unix(42, 0), 422 ), 423 ), 424 }, 425 err: nil, 426 }, 427 { 428 name: "field string escape quote", 429 input: []byte(`cpu value="how\"dy"`), 430 metrics: []Metric{ 431 MustMetric( 432 New( 433 "cpu", 434 map[string]string{}, 435 map[string]interface{}{ 436 `value`: `how"dy`, 437 }, 438 time.Unix(42, 0), 439 ), 440 ), 441 }, 442 err: nil, 443 }, 444 { 445 name: "field string escape backslash", 446 input: []byte(`cpu value="how\\dy"`), 447 metrics: []Metric{ 448 MustMetric( 449 New( 450 "cpu", 451 map[string]string{}, 452 map[string]interface{}{ 453 `value`: `how\dy`, 454 }, 455 time.Unix(42, 0), 456 ), 457 ), 458 }, 459 err: nil, 460 }, 461 { 462 name: "field string newline", 463 input: []byte("cpu value=\"4\n2\""), 464 metrics: []Metric{ 465 MustMetric( 466 New( 467 "cpu", 468 map[string]string{}, 469 map[string]interface{}{ 470 "value": "4\n2", 471 }, 472 time.Unix(42, 0), 473 ), 474 ), 475 }, 476 err: nil, 477 }, 478 { 479 name: "no timestamp", 480 input: []byte("cpu value=42"), 481 metrics: []Metric{ 482 MustMetric( 483 New( 484 "cpu", 485 map[string]string{}, 486 map[string]interface{}{ 487 "value": 42.0, 488 }, 489 time.Unix(42, 0), 490 ), 491 ), 492 }, 493 err: nil, 494 }, 495 { 496 name: "no timestamp", 497 input: []byte("cpu value=42"), 498 timeFunc: func() time.Time { 499 return time.Unix(42, 123456789) 500 }, 501 metrics: []Metric{ 502 MustMetric( 503 New( 504 "cpu", 505 map[string]string{}, 506 map[string]interface{}{ 507 "value": 42.0, 508 }, 509 time.Unix(42, 123456789), 510 ), 511 ), 512 }, 513 err: nil, 514 }, 515 { 516 name: "multiple lines", 517 input: []byte("cpu value=42\ncpu value=42"), 518 metrics: []Metric{ 519 MustMetric( 520 New( 521 "cpu", 522 map[string]string{}, 523 map[string]interface{}{ 524 "value": 42.0, 525 }, 526 time.Unix(42, 0), 527 ), 528 ), 529 MustMetric( 530 New( 531 "cpu", 532 map[string]string{}, 533 map[string]interface{}{ 534 "value": 42.0, 535 }, 536 time.Unix(42, 0), 537 ), 538 ), 539 }, 540 err: nil, 541 }, 542 { 543 name: "invalid measurement only", 544 input: []byte("cpu"), 545 metrics: nil, 546 err: &ParseError{ 547 Offset: 3, 548 LineNumber: 1, 549 Column: 4, 550 msg: ErrTagParse.Error(), 551 buf: "cpu", 552 }, 553 }, 554 { 555 name: "procstat", 556 input: []byte("procstat,exe=bash,process_name=bash voluntary_context_switches=42i,memory_rss=5103616i,rlimit_memory_data_hard=2147483647i,cpu_time_user=0.02,rlimit_file_locks_soft=2147483647i,pid=29417i,cpu_time_nice=0,rlimit_memory_locked_soft=65536i,read_count=259i,rlimit_memory_vms_hard=2147483647i,memory_swap=0i,rlimit_num_fds_soft=1024i,rlimit_nice_priority_hard=0i,cpu_time_soft_irq=0,cpu_time=0i,rlimit_memory_locked_hard=65536i,realtime_priority=0i,signals_pending=0i,nice_priority=20i,cpu_time_idle=0,memory_stack=139264i,memory_locked=0i,rlimit_memory_stack_soft=8388608i,cpu_time_iowait=0,cpu_time_guest=0,cpu_time_guest_nice=0,rlimit_memory_data_soft=2147483647i,read_bytes=0i,rlimit_cpu_time_soft=2147483647i,involuntary_context_switches=2i,write_bytes=106496i,cpu_time_system=0,cpu_time_irq=0,cpu_usage=0,memory_vms=21659648i,memory_data=1576960i,rlimit_memory_stack_hard=2147483647i,num_threads=1i,rlimit_memory_rss_soft=2147483647i,rlimit_realtime_priority_soft=0i,num_fds=4i,write_count=35i,rlimit_signals_pending_soft=78994i,cpu_time_steal=0,rlimit_num_fds_hard=4096i,rlimit_file_locks_hard=2147483647i,rlimit_cpu_time_hard=2147483647i,rlimit_signals_pending_hard=78994i,rlimit_nice_priority_soft=0i,rlimit_memory_rss_hard=2147483647i,rlimit_memory_vms_soft=2147483647i,rlimit_realtime_priority_hard=0i 1517620624000000000"), 557 metrics: []Metric{ 558 MustMetric( 559 New( 560 "procstat", 561 map[string]string{ 562 "exe": "bash", 563 "process_name": "bash", 564 }, 565 map[string]interface{}{ 566 "cpu_time": 0, 567 "cpu_time_guest": float64(0), 568 "cpu_time_guest_nice": float64(0), 569 "cpu_time_idle": float64(0), 570 "cpu_time_iowait": float64(0), 571 "cpu_time_irq": float64(0), 572 "cpu_time_nice": float64(0), 573 "cpu_time_soft_irq": float64(0), 574 "cpu_time_steal": float64(0), 575 "cpu_time_system": float64(0), 576 "cpu_time_user": float64(0.02), 577 "cpu_usage": float64(0), 578 "involuntary_context_switches": 2, 579 "memory_data": 1576960, 580 "memory_locked": 0, 581 "memory_rss": 5103616, 582 "memory_stack": 139264, 583 "memory_swap": 0, 584 "memory_vms": 21659648, 585 "nice_priority": 20, 586 "num_fds": 4, 587 "num_threads": 1, 588 "pid": 29417, 589 "read_bytes": 0, 590 "read_count": 259, 591 "realtime_priority": 0, 592 "rlimit_cpu_time_hard": 2147483647, 593 "rlimit_cpu_time_soft": 2147483647, 594 "rlimit_file_locks_hard": 2147483647, 595 "rlimit_file_locks_soft": 2147483647, 596 "rlimit_memory_data_hard": 2147483647, 597 "rlimit_memory_data_soft": 2147483647, 598 "rlimit_memory_locked_hard": 65536, 599 "rlimit_memory_locked_soft": 65536, 600 "rlimit_memory_rss_hard": 2147483647, 601 "rlimit_memory_rss_soft": 2147483647, 602 "rlimit_memory_stack_hard": 2147483647, 603 "rlimit_memory_stack_soft": 8388608, 604 "rlimit_memory_vms_hard": 2147483647, 605 "rlimit_memory_vms_soft": 2147483647, 606 "rlimit_nice_priority_hard": 0, 607 "rlimit_nice_priority_soft": 0, 608 "rlimit_num_fds_hard": 4096, 609 "rlimit_num_fds_soft": 1024, 610 "rlimit_realtime_priority_hard": 0, 611 "rlimit_realtime_priority_soft": 0, 612 "rlimit_signals_pending_hard": 78994, 613 "rlimit_signals_pending_soft": 78994, 614 "signals_pending": 0, 615 "voluntary_context_switches": 42, 616 "write_bytes": 106496, 617 "write_count": 35, 618 }, 619 time.Unix(0, 1517620624000000000), 620 ), 621 ), 622 }, 623 err: nil, 624 }, 625} 626 627func TestParser(t *testing.T) { 628 for _, tt := range ptests { 629 t.Run(tt.name, func(t *testing.T) { 630 handler := NewMetricHandler() 631 parser := NewParser(handler) 632 parser.SetTimeFunc(DefaultTime) 633 if tt.timeFunc != nil { 634 parser.SetTimeFunc(tt.timeFunc) 635 } 636 637 metrics, err := parser.Parse(tt.input) 638 if (err != nil) != (tt.err != nil) { 639 t.Errorf("unexpected error difference: %v, want = %v", err, tt.err) 640 return 641 } else if tt.err != nil && err.Error() != tt.err.Error() { 642 t.Errorf("unexpected error difference: %v, want = %v", err, tt.err) 643 } 644 645 if got, want := len(metrics), len(tt.metrics); got != want { 646 t.Errorf("unexpected metric length difference: %d, want = %d", got, want) 647 } 648 649 for i, expected := range tt.metrics { 650 RequireMetricEqual(t, expected, metrics[i]) 651 } 652 }) 653 } 654} 655 656func BenchmarkParser(b *testing.B) { 657 for _, tt := range ptests { 658 b.Run(tt.name, func(b *testing.B) { 659 handler := NewMetricHandler() 660 parser := NewParser(handler) 661 for n := 0; n < b.N; n++ { 662 metrics, err := parser.Parse(tt.input) 663 _ = err 664 _ = metrics 665 } 666 }) 667 } 668} 669 670func TestStreamParser(t *testing.T) { 671 for _, tt := range ptests { 672 t.Run(tt.name, func(t *testing.T) { 673 r := bytes.NewBuffer(tt.input) 674 parser := NewStreamParser(r) 675 parser.SetTimeFunc(DefaultTime) 676 if tt.timeFunc != nil { 677 parser.SetTimeFunc(tt.timeFunc) 678 } 679 680 var i int 681 for { 682 m, err := parser.Next() 683 if err != nil { 684 if err == EOF { 685 break 686 } 687 if (err != nil) == (tt.err != nil) && err.Error() != tt.err.Error() { 688 t.Errorf("unexpected error difference: %v, want = %v", err, tt.err) 689 } 690 break 691 } 692 693 RequireMetricEqual(t, tt.metrics[i], m) 694 i++ 695 } 696 }) 697 } 698} 699 700func TestSeriesParser(t *testing.T) { 701 var tests = []struct { 702 name string 703 input []byte 704 timeFunc func() time.Time 705 metrics []Metric 706 err error 707 }{ 708 { 709 name: "empty", 710 input: []byte(""), 711 metrics: []Metric{}, 712 }, 713 { 714 name: "minimal", 715 input: []byte("cpu"), 716 metrics: []Metric{ 717 MustMetric( 718 New( 719 "cpu", 720 map[string]string{}, 721 map[string]interface{}{}, 722 time.Unix(0, 0), 723 ), 724 ), 725 }, 726 }, 727 { 728 name: "tags", 729 input: []byte("cpu,a=x,b=y"), 730 metrics: []Metric{ 731 MustMetric( 732 New( 733 "cpu", 734 map[string]string{ 735 "a": "x", 736 "b": "y", 737 }, 738 map[string]interface{}{}, 739 time.Unix(0, 0), 740 ), 741 ), 742 }, 743 }, 744 { 745 name: "missing tag value", 746 input: []byte("cpu,a="), 747 metrics: []Metric{}, 748 err: &ParseError{ 749 Offset: 6, 750 LineNumber: 1, 751 Column: 7, 752 msg: ErrTagParse.Error(), 753 buf: "cpu,a=", 754 }, 755 }, 756 } 757 for _, tt := range tests { 758 t.Run(tt.name, func(t *testing.T) { 759 handler := NewMetricHandler() 760 parser := NewSeriesParser(handler) 761 if tt.timeFunc != nil { 762 parser.SetTimeFunc(tt.timeFunc) 763 } 764 765 metrics, err := parser.Parse(tt.input) 766 767 if (err != nil) != (tt.err != nil) { 768 t.Errorf("unexpected error difference: %v, want = %v", err, tt.err) 769 return 770 } else if tt.err != nil && err.Error() != tt.err.Error() { 771 t.Errorf("unexpected error difference: %v, want = %v", err, tt.err) 772 } 773 774 if got, want := len(metrics), len(tt.metrics); got != want { 775 t.Errorf("unexpected metric length difference: %d, want = %d", got, want) 776 } 777 778 for i, expected := range tt.metrics { 779 if got, want := metrics[i].Name(), expected.Name(); got != want { 780 t.Errorf("unexpected metric name difference: %v, want = %v", got, want) 781 } 782 if got, want := len(metrics[i].TagList()), len(expected.TagList()); got != want { 783 t.Errorf("unexpected tag length difference: %d, want = %d", got, want) 784 break 785 } 786 787 got := metrics[i].TagList() 788 want := expected.TagList() 789 for i := range got { 790 if got[i].Key != want[i].Key { 791 t.Errorf("unexpected tag key difference: %v, want = %v", got[i].Key, want[i].Key) 792 } 793 if got[i].Value != want[i].Value { 794 t.Errorf("unexpected tag key difference: %v, want = %v", got[i].Value, want[i].Value) 795 } 796 } 797 } 798 }) 799 } 800} 801 802func TestParserErrorString(t *testing.T) { 803 var ptests = []struct { 804 name string 805 input []byte 806 errString string 807 }{ 808 { 809 name: "multiple line error", 810 input: []byte("cpu value=42\ncpu value=invalid\ncpu value=42"), 811 errString: `metric parse error: expected field at 2:11: "cpu value=invalid"`, 812 }, 813 { 814 name: "handler error", 815 input: []byte("cpu value=9223372036854775808i\ncpu value=42"), 816 errString: `metric parse error: value out of range at 1:31: "cpu value=9223372036854775808i"`, 817 }, 818 { 819 name: "buffer too long", 820 input: []byte("cpu " + strings.Repeat("ab", maxErrorBufferSize) + "=invalid\ncpu value=42"), 821 errString: "metric parse error: expected field at 1:2054: \"cpu " + strings.Repeat("ab", maxErrorBufferSize)[:maxErrorBufferSize-4] + "...\"", 822 }, 823 { 824 name: "multiple line error", 825 input: []byte("cpu value=42\ncpu value=invalid\ncpu value=42\ncpu value=invalid"), 826 errString: `metric parse error: expected field at 2:11: "cpu value=invalid"`, 827 }, 828 } 829 830 for _, tt := range ptests { 831 t.Run(tt.name, func(t *testing.T) { 832 handler := NewMetricHandler() 833 parser := NewParser(handler) 834 835 _, err := parser.Parse(tt.input) 836 if err.Error() != tt.errString { 837 t.Errorf("unexpected error difference: %v, want = %v", err.Error(), tt.errString) 838 } 839 }) 840 } 841} 842 843func TestStreamParserErrorString(t *testing.T) { 844 var ptests = []struct { 845 name string 846 input []byte 847 errs []string 848 }{ 849 { 850 name: "multiple line error", 851 input: []byte("cpu value=42\ncpu value=invalid\ncpu value=42"), 852 errs: []string{ 853 `metric parse error: expected field at 2:11: "cpu value="`, 854 }, 855 }, 856 { 857 name: "handler error", 858 input: []byte("cpu value=9223372036854775808i\ncpu value=42"), 859 errs: []string{ 860 `metric parse error: value out of range at 1:31: "cpu value=9223372036854775808i"`, 861 }, 862 }, 863 { 864 name: "buffer too long", 865 input: []byte("cpu " + strings.Repeat("ab", maxErrorBufferSize) + "=invalid\ncpu value=42"), 866 errs: []string{ 867 "metric parse error: expected field at 1:2054: \"cpu " + strings.Repeat("ab", maxErrorBufferSize)[:maxErrorBufferSize-4] + "...\"", 868 }, 869 }, 870 { 871 name: "multiple errors", 872 input: []byte("foo value=1asdf2.0\nfoo value=2.0\nfoo value=3asdf2.0\nfoo value=4.0"), 873 errs: []string{ 874 `metric parse error: expected field at 1:12: "foo value=1"`, 875 `metric parse error: expected field at 3:12: "foo value=3"`, 876 }, 877 }, 878 } 879 880 for _, tt := range ptests { 881 t.Run(tt.name, func(t *testing.T) { 882 parser := NewStreamParser(bytes.NewBuffer(tt.input)) 883 884 var errs []error 885 for i := 0; i < 20; i++ { 886 _, err := parser.Next() 887 if err == EOF { 888 break 889 } 890 891 if err != nil { 892 errs = append(errs, err) 893 } 894 } 895 896 if got, want := len(errs), len(tt.errs); got != want { 897 t.Errorf("unexpected error length difference: %d, want = %d", got, want) 898 } 899 900 for i, err := range errs { 901 if err.Error() != tt.errs[i] { 902 t.Errorf("unexpected error difference: %v, want = %v", err.Error(), tt.errs[i]) 903 } 904 } 905 }) 906 } 907} 908 909// RequireMetricEqual halts the test with an error if the metrics are not 910// equal. 911func RequireMetricEqual(t *testing.T, expected, actual Metric) { 912 t.Helper() 913 914 var lhs, rhs *metricDiff 915 if expected != nil { 916 lhs = newMetricDiff(expected) 917 } 918 if actual != nil { 919 rhs = newMetricDiff(actual) 920 } 921 922 if !reflect.DeepEqual(lhs, rhs) { 923 t.Fatalf("Metric %v, want=%v", rhs, lhs) 924 } 925} 926 927type metricDiff struct { 928 Measurement string 929 Tags []*Tag 930 Fields []*Field 931 Time time.Time 932} 933 934func newMetricDiff(metric Metric) *metricDiff { 935 if metric == nil { 936 return nil 937 } 938 939 m := &metricDiff{} 940 m.Measurement = metric.Name() 941 m.Tags = append(m.Tags, metric.TagList()...) 942 m.Fields = append(m.Fields, metric.FieldList()...) 943 944 sort.Slice(m.Tags, func(i, j int) bool { 945 return m.Tags[i].Key < m.Tags[j].Key 946 }) 947 948 sort.Slice(m.Fields, func(i, j int) bool { 949 return m.Fields[i].Key < m.Fields[j].Key 950 }) 951 952 m.Time = metric.Time() 953 return m 954} 955