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