cloudflare/pint

Public

mirrored from https://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.81.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

internal/parser/parser_test.go

7058lines · modecode

1package parser_test
2
3import (
4 "bufio"
5 "bytes"
6 "errors"
7 "os"
8 "strconv"
9 "testing"
10 "time"
11
12 "github.com/prometheus/common/model"
13 "github.com/stretchr/testify/require"
14
15 "github.com/cloudflare/pint/internal/comments"
16 "github.com/cloudflare/pint/internal/diags"
17 "github.com/cloudflare/pint/internal/parser"
18
19 "github.com/google/go-cmp/cmp"
20 "github.com/google/go-cmp/cmp/cmpopts"
21)
22
23func TestParse(t *testing.T) {
24 type testCaseT struct {
25 input []byte
26 output parser.File
27 strict bool
28 schema parser.Schema
29 names model.ValidationScheme
30 }
31
32 testCases := []testCaseT{
33 {
34 input: nil,
35 output: parser.File{
36 IsRelaxed: true,
37 },
38 },
39 {
40 input: []byte{},
41 output: parser.File{
42 IsRelaxed: true,
43 },
44 },
45 {
46 input: []byte(""),
47 output: parser.File{
48 IsRelaxed: true,
49 },
50 },
51 {
52 input: []byte("\n"),
53 output: parser.File{
54 IsRelaxed: true,
55 },
56 },
57 {
58 input: []byte("\n\n\n"),
59 output: parser.File{
60 IsRelaxed: true,
61 },
62 },
63 {
64 input: []byte("---"),
65 output: parser.File{
66 IsRelaxed: true,
67 },
68 },
69 {
70 input: []byte("---\n"),
71 output: parser.File{
72 IsRelaxed: true,
73 },
74 },
75 {
76 input: []byte("\n---\n\n---\n"),
77 output: parser.File{
78 IsRelaxed: true,
79 },
80 },
81 {
82 input: []byte("\n---\n\n---\n---"),
83 output: parser.File{
84 IsRelaxed: true,
85 },
86 },
87 {
88 input: []byte(string("! !00 \xf6")),
89 output: parser.File{
90 IsRelaxed: true,
91 Error: parser.ParseError{
92 Err: errors.New("yaml: incomplete UTF-8 octet sequence"),
93 Line: 1,
94 },
95 },
96 },
97 {
98 input: []byte("- 0: 0\n 00000000: 000000\n 000000:00000000000: 00000000\n 00000000000:000000: 0000000000000000000000000000000000\n 000000: 0000000\n expr: |"),
99 output: parser.File{
100 IsRelaxed: true,
101 Groups: []parser.Group{
102 {
103 Rules: []parser.Rule{
104 {
105 Lines: diags.LineRange{First: 1, Last: 6},
106 Error: parser.ParseError{Err: errors.New("incomplete rule, no alert or record key"), Line: 6},
107 },
108 },
109 },
110 },
111 },
112 },
113 {
114 input: []byte("- record: |\n multiline\n"),
115 output: parser.File{
116 IsRelaxed: true,
117 Groups: []parser.Group{
118 {
119 Rules: []parser.Rule{
120 {
121 Lines: diags.LineRange{First: 1, Last: 2},
122 Error: parser.ParseError{Err: errors.New("missing expr key"), Line: 2},
123 },
124 },
125 },
126 },
127 },
128 },
129 {
130 input: []byte(`---
131- expr: foo
132 record: foo
133---
134- expr: bar
135`),
136 output: parser.File{
137 IsRelaxed: true,
138 Groups: []parser.Group{
139 {
140 Rules: []parser.Rule{
141 {
142 RecordingRule: &parser.RecordingRule{
143 Expr: parser.PromQLExpr{
144 Value: &parser.YamlNode{
145 Value: "foo",
146 Pos: diags.PositionRanges{
147 {Line: 2, FirstColumn: 9, LastColumn: 11},
148 },
149 },
150 },
151 Record: parser.YamlNode{
152 Value: "foo",
153 Pos: diags.PositionRanges{
154 {Line: 3, FirstColumn: 11, LastColumn: 13},
155 },
156 },
157 },
158 Lines: diags.LineRange{First: 2, Last: 3},
159 },
160 },
161 },
162 {
163 Rules: []parser.Rule{
164 {
165 Lines: diags.LineRange{First: 5, Last: 5},
166 Error: parser.ParseError{Err: errors.New("incomplete rule, no alert or record key"), Line: 5},
167 },
168 },
169 },
170 },
171 },
172 },
173 {
174 input: []byte("- expr: foo\n"),
175 output: parser.File{
176 IsRelaxed: true,
177 Groups: []parser.Group{
178 {
179 Rules: []parser.Rule{
180 {
181 Lines: diags.LineRange{First: 1, Last: 1},
182 Error: parser.ParseError{Err: errors.New("incomplete rule, no alert or record key"), Line: 1},
183 },
184 },
185 },
186 },
187 },
188 },
189 {
190 input: []byte("- alert: foo\n"),
191 output: parser.File{
192 IsRelaxed: true,
193 Groups: []parser.Group{
194 {
195 Rules: []parser.Rule{
196 {
197 Lines: diags.LineRange{First: 1, Last: 1},
198 Error: parser.ParseError{Err: errors.New("missing expr key"), Line: 1},
199 },
200 },
201 },
202 },
203 },
204 },
205 {
206 input: []byte("- alert: foo\n record: foo\n"),
207 output: parser.File{
208 IsRelaxed: true,
209 Groups: []parser.Group{
210 {
211 Rules: []parser.Rule{
212 {
213 Lines: diags.LineRange{First: 1, Last: 2},
214 Error: parser.ParseError{Err: errors.New("got both record and alert keys in a single rule"), Line: 1},
215 },
216 },
217 },
218 },
219 },
220 },
221 {
222 input: []byte("- record: foo\n labels:\n foo: bar\n"),
223 output: parser.File{
224 IsRelaxed: true,
225 Groups: []parser.Group{
226 {
227 Rules: []parser.Rule{
228 {
229 Lines: diags.LineRange{First: 1, Last: 3},
230 Error: parser.ParseError{Err: errors.New("missing expr key"), Line: 1},
231 },
232 },
233 },
234 },
235 },
236 },
237 {
238 input: []byte("- record: - foo\n"),
239 output: parser.File{
240 IsRelaxed: true,
241 Error: parser.ParseError{
242 Err: errors.New("yaml: block sequence entries are not allowed in this context"),
243 Line: 1,
244 },
245 },
246 },
247 {
248 input: []byte("- record: foo expr: sum(\n"),
249 output: parser.File{
250 IsRelaxed: true,
251 Error: parser.ParseError{
252 Err: errors.New("yaml: mapping values are not allowed in this context"),
253 Line: 1,
254 },
255 },
256 },
257 {
258 input: []byte("- record\n\texpr: foo\n"),
259 output: parser.File{
260 IsRelaxed: true,
261 Error: parser.ParseError{
262 Err: errors.New("found a tab character that violates indentation"),
263 Line: 2,
264 },
265 },
266 },
267 {
268 input: []byte(`
269- record: foo
270 expr: bar
271 expr: bar
272`),
273 output: parser.File{
274 IsRelaxed: true,
275 Groups: []parser.Group{
276 {
277 Rules: []parser.Rule{
278 {
279 Lines: diags.LineRange{First: 2, Last: 4},
280 Error: parser.ParseError{Err: errors.New("duplicated expr key"), Line: 4},
281 },
282 },
283 },
284 },
285 },
286 },
287 {
288 input: []byte(`
289- record: foo
290 expr: bar
291 record: bar
292`),
293 output: parser.File{
294 IsRelaxed: true,
295 Groups: []parser.Group{
296 {
297 Rules: []parser.Rule{
298 {
299 Lines: diags.LineRange{First: 2, Last: 4},
300 Error: parser.ParseError{Err: errors.New("duplicated record key"), Line: 4},
301 },
302 },
303 },
304 },
305 },
306 },
307 {
308 input: []byte(`
309- alert: foo
310 alert: bar
311 expr: bar
312`),
313 output: parser.File{
314 IsRelaxed: true,
315 Groups: []parser.Group{
316 {
317 Rules: []parser.Rule{
318 {
319 Lines: diags.LineRange{First: 2, Last: 3},
320 Error: parser.ParseError{Err: errors.New("duplicated alert key"), Line: 3},
321 },
322 },
323 },
324 },
325 },
326 },
327 {
328 input: []byte(`
329- alert: foo
330 for: 5m
331 expr: bar
332 for: 1m
333`),
334 output: parser.File{
335 IsRelaxed: true,
336 Groups: []parser.Group{
337 {
338 Rules: []parser.Rule{
339 {
340 Lines: diags.LineRange{First: 2, Last: 5},
341 Error: parser.ParseError{Err: errors.New("duplicated for key"), Line: 5},
342 },
343 },
344 },
345 },
346 },
347 },
348 {
349 input: []byte(`
350- alert: foo
351 keep_firing_for: 5m
352 expr: bar
353 keep_firing_for: 1m
354`),
355 output: parser.File{
356 IsRelaxed: true,
357 Groups: []parser.Group{
358 {
359 Rules: []parser.Rule{
360 {
361 Lines: diags.LineRange{First: 2, Last: 5},
362 Error: parser.ParseError{Err: errors.New("duplicated keep_firing_for key"), Line: 5},
363 },
364 },
365 },
366 },
367 },
368 },
369 {
370 input: []byte(`
371- alert: foo
372 labels: {}
373 expr: bar
374 labels: {}
375`),
376 output: parser.File{
377 IsRelaxed: true,
378 Groups: []parser.Group{
379 {
380 Rules: []parser.Rule{
381 {
382 Lines: diags.LineRange{First: 2, Last: 5},
383 Error: parser.ParseError{Err: errors.New("duplicated labels key"), Line: 5},
384 },
385 },
386 },
387 },
388 },
389 },
390 {
391 input: []byte(`
392- record: foo
393 labels: {}
394 expr: bar
395 labels: {}
396`),
397 output: parser.File{
398 IsRelaxed: true,
399 Groups: []parser.Group{
400 {
401 Rules: []parser.Rule{
402 {
403 Lines: diags.LineRange{First: 2, Last: 5},
404 Error: parser.ParseError{Err: errors.New("duplicated labels key"), Line: 5},
405 },
406 },
407 },
408 },
409 },
410 },
411 {
412 input: []byte(`
413- alert: foo
414 annotations: {}
415 expr: bar
416 annotations: {}
417`),
418 output: parser.File{
419 IsRelaxed: true,
420 Groups: []parser.Group{
421 {
422 Rules: []parser.Rule{
423 {
424 Lines: diags.LineRange{First: 2, Last: 5},
425 Error: parser.ParseError{Err: errors.New("duplicated annotations key"), Line: 5},
426 },
427 },
428 },
429 },
430 },
431 },
432 {
433 input: []byte("- record: foo\n expr: foo\n extra: true\n"),
434 output: parser.File{
435 IsRelaxed: true,
436 Groups: []parser.Group{
437 {
438 Rules: []parser.Rule{
439 {
440 Lines: diags.LineRange{First: 1, Last: 3},
441 Error: parser.ParseError{Err: errors.New("invalid key(s) found: extra"), Line: 3},
442 },
443 },
444 },
445 },
446 },
447 },
448 {
449 input: []byte(`- record: foo
450 expr: foo offset 10m
451`),
452 output: parser.File{
453 IsRelaxed: true,
454 Groups: []parser.Group{
455 {
456 Rules: []parser.Rule{
457 {
458 Lines: diags.LineRange{First: 1, Last: 2},
459 RecordingRule: &parser.RecordingRule{
460 Record: parser.YamlNode{
461 Value: "foo",
462 Pos: diags.PositionRanges{
463 {Line: 1, FirstColumn: 11, LastColumn: 13},
464 },
465 },
466 Expr: parser.PromQLExpr{
467 Value: &parser.YamlNode{
468 Pos: diags.PositionRanges{
469 {Line: 2, FirstColumn: 9, LastColumn: 22},
470 },
471 Value: "foo offset 10m",
472 },
473 },
474 },
475 },
476 },
477 },
478 },
479 },
480 },
481 {
482 input: []byte("- record: foo\n expr: foo offset -10m\n"),
483 output: parser.File{
484 IsRelaxed: true,
485 Groups: []parser.Group{
486 {
487 Rules: []parser.Rule{
488 {
489 Lines: diags.LineRange{First: 1, Last: 2},
490 RecordingRule: &parser.RecordingRule{
491 Record: parser.YamlNode{
492 Value: "foo",
493 Pos: diags.PositionRanges{
494 {Line: 1, FirstColumn: 11, LastColumn: 13},
495 },
496 },
497 Expr: parser.PromQLExpr{
498 Value: &parser.YamlNode{
499 Value: "foo offset -10m",
500 Pos: diags.PositionRanges{
501 {Line: 2, FirstColumn: 9, LastColumn: 23},
502 },
503 },
504 },
505 },
506 },
507 },
508 },
509 },
510 },
511 },
512 {
513 input: []byte(`
514# pint disable head comment
515- record: foo # pint disable record comment
516 expr: foo offset 10m # pint disable expr comment
517 # pint disable pre-labels comment
518 labels:
519 # pint disable pre-foo comment
520 foo: bar
521 # pint disable post-foo comment
522 bob: alice
523 # pint disable foot comment
524`),
525 output: parser.File{
526 IsRelaxed: true,
527 Groups: []parser.Group{
528 {
529 Rules: []parser.Rule{
530 {
531 Lines: diags.LineRange{First: 3, Last: 10},
532 Comments: []comments.Comment{
533 {
534 Type: comments.DisableType,
535 Value: comments.Disable{Match: "head comment"},
536 },
537 {
538 Type: comments.DisableType,
539 Value: comments.Disable{Match: "record comment"},
540 },
541 {
542 Type: comments.DisableType,
543 Value: comments.Disable{Match: "expr comment"},
544 },
545 {
546 Type: comments.DisableType,
547 Value: comments.Disable{Match: "pre-labels comment"},
548 },
549 {
550 Type: comments.DisableType,
551 Value: comments.Disable{Match: "foot comment"},
552 },
553 {
554 Type: comments.DisableType,
555 Value: comments.Disable{Match: "pre-foo comment"},
556 },
557 {
558 Type: comments.DisableType,
559 Value: comments.Disable{Match: "post-foo comment"},
560 },
561 },
562 RecordingRule: &parser.RecordingRule{
563 Record: parser.YamlNode{
564 Value: "foo",
565 Pos: diags.PositionRanges{
566 {Line: 3, FirstColumn: 11, LastColumn: 13},
567 },
568 },
569 Expr: parser.PromQLExpr{
570 Value: &parser.YamlNode{
571 Value: "foo offset 10m",
572 Pos: diags.PositionRanges{
573 {Line: 4, FirstColumn: 9, LastColumn: 22},
574 },
575 },
576 },
577 Labels: &parser.YamlMap{
578 Key: &parser.YamlNode{
579 Value: "labels",
580 Pos: diags.PositionRanges{
581 {Line: 6, FirstColumn: 3, LastColumn: 8},
582 },
583 },
584 Items: []*parser.YamlKeyValue{
585 {
586 Key: &parser.YamlNode{
587 Value: "foo",
588 Pos: diags.PositionRanges{
589 {Line: 8, FirstColumn: 5, LastColumn: 7},
590 },
591 },
592 Value: &parser.YamlNode{
593 Value: "bar",
594 Pos: diags.PositionRanges{
595 {Line: 8, FirstColumn: 10, LastColumn: 12},
596 },
597 },
598 },
599 {
600 Key: &parser.YamlNode{
601 Value: "bob",
602 Pos: diags.PositionRanges{
603 {Line: 10, FirstColumn: 5, LastColumn: 7},
604 },
605 },
606 Value: &parser.YamlNode{
607 Value: "alice",
608 Pos: diags.PositionRanges{
609 {Line: 10, FirstColumn: 10, LastColumn: 14},
610 },
611 },
612 },
613 },
614 },
615 },
616 },
617 },
618 },
619 },
620 },
621 },
622 {
623 input: []byte("- record: foo\n expr: foo[5m] offset 10m\n"),
624 output: parser.File{
625 IsRelaxed: true,
626 Groups: []parser.Group{
627 {
628 Rules: []parser.Rule{
629 {
630 Lines: diags.LineRange{First: 1, Last: 2},
631 RecordingRule: &parser.RecordingRule{
632 Record: parser.YamlNode{
633 Value: "foo",
634 Pos: diags.PositionRanges{
635 {Line: 1, FirstColumn: 11, LastColumn: 13},
636 },
637 },
638 Expr: parser.PromQLExpr{
639 Value: &parser.YamlNode{
640 Value: "foo[5m] offset 10m",
641 Pos: diags.PositionRanges{
642 {Line: 2, FirstColumn: 9, LastColumn: 26},
643 },
644 },
645 },
646 },
647 },
648 },
649 },
650 },
651 },
652 },
653 {
654 input: []byte(`
655- record: name
656 expr: sum(foo)
657 labels:
658 foo: bar
659 bob: alice
660`),
661 output: parser.File{
662 IsRelaxed: true,
663 Groups: []parser.Group{
664 {
665 Rules: []parser.Rule{
666 {
667 Lines: diags.LineRange{First: 2, Last: 6},
668 RecordingRule: &parser.RecordingRule{
669 Record: parser.YamlNode{
670 Value: "name",
671 Pos: diags.PositionRanges{
672 {Line: 2, FirstColumn: 11, LastColumn: 14},
673 },
674 },
675 Expr: parser.PromQLExpr{
676 Value: &parser.YamlNode{
677 Value: "sum(foo)",
678 Pos: diags.PositionRanges{
679 {Line: 3, FirstColumn: 9, LastColumn: 16},
680 },
681 },
682 },
683 Labels: &parser.YamlMap{
684 Key: &parser.YamlNode{
685 Value: "labels",
686 Pos: diags.PositionRanges{
687 {Line: 4, FirstColumn: 3, LastColumn: 8},
688 },
689 },
690 Items: []*parser.YamlKeyValue{
691 {
692 Key: &parser.YamlNode{
693 Value: "foo",
694 Pos: diags.PositionRanges{
695 {Line: 5, FirstColumn: 5, LastColumn: 7},
696 },
697 },
698 Value: &parser.YamlNode{
699 Value: "bar",
700 Pos: diags.PositionRanges{
701 {Line: 5, FirstColumn: 10, LastColumn: 12},
702 },
703 },
704 },
705 {
706 Key: &parser.YamlNode{
707 Value: "bob",
708 Pos: diags.PositionRanges{
709 {Line: 6, FirstColumn: 5, LastColumn: 7},
710 },
711 },
712 Value: &parser.YamlNode{
713 Value: "alice",
714 Pos: diags.PositionRanges{
715 {Line: 6, FirstColumn: 10, LastColumn: 14},
716 },
717 },
718 },
719 },
720 },
721 },
722 },
723 },
724 },
725 },
726 },
727 },
728 {
729 input: []byte(`
730groups:
731- name: custom_rules
732 rules:
733 - record: name
734 expr: sum(foo)
735 labels:
736 foo: bar
737 bob: alice
738`),
739 output: parser.File{
740 IsRelaxed: true,
741 Groups: []parser.Group{
742 {
743 Name: parser.YamlNode{
744 Value: "custom_rules",
745 Pos: diags.PositionRanges{
746 {Line: 3, FirstColumn: 9, LastColumn: 20},
747 },
748 },
749 Rules: []parser.Rule{
750 {
751 Lines: diags.LineRange{First: 5, Last: 9},
752 RecordingRule: &parser.RecordingRule{
753 Record: parser.YamlNode{
754 Value: "name",
755 Pos: diags.PositionRanges{
756 {Line: 5, FirstColumn: 15, LastColumn: 18},
757 },
758 },
759 Expr: parser.PromQLExpr{
760 Value: &parser.YamlNode{
761 Value: "sum(foo)",
762 Pos: diags.PositionRanges{
763 {Line: 6, FirstColumn: 13, LastColumn: 20},
764 },
765 },
766 },
767 Labels: &parser.YamlMap{
768 Key: &parser.YamlNode{
769 Value: "labels",
770 Pos: diags.PositionRanges{
771 {Line: 7, FirstColumn: 7, LastColumn: 12},
772 },
773 },
774 Items: []*parser.YamlKeyValue{
775 {
776 Key: &parser.YamlNode{
777 Value: "foo",
778 Pos: diags.PositionRanges{
779 {Line: 8, FirstColumn: 9, LastColumn: 11},
780 },
781 },
782 Value: &parser.YamlNode{
783 Value: "bar",
784 Pos: diags.PositionRanges{
785 {Line: 8, FirstColumn: 14, LastColumn: 16},
786 },
787 },
788 },
789 {
790 Key: &parser.YamlNode{
791 Value: "bob",
792 Pos: diags.PositionRanges{
793 {Line: 9, FirstColumn: 9, LastColumn: 11},
794 },
795 },
796 Value: &parser.YamlNode{
797 Value: "alice",
798 Pos: diags.PositionRanges{
799 {Line: 9, FirstColumn: 14, LastColumn: 18},
800 },
801 },
802 },
803 },
804 },
805 },
806 },
807 },
808 },
809 },
810 },
811 },
812 {
813 input: []byte(`- alert: Down
814 expr: |
815 up == 0
816 for: |+
817 11m
818 labels:
819 severity: critical
820 annotations:
821 uri: https://docs.example.com/down.html
822
823- record: foo
824 expr: |-
825 bar
826 /
827 baz > 1
828 labels: {}
829`),
830 output: parser.File{
831 IsRelaxed: true,
832 Groups: []parser.Group{
833 {
834 Rules: []parser.Rule{
835 {
836 Lines: diags.LineRange{First: 1, Last: 9},
837 AlertingRule: &parser.AlertingRule{
838 Alert: parser.YamlNode{
839 Value: "Down",
840 Pos: diags.PositionRanges{
841 {Line: 1, FirstColumn: 10, LastColumn: 13},
842 },
843 },
844 Expr: parser.PromQLExpr{
845 Value: &parser.YamlNode{
846 Value: "up == 0\n",
847 Pos: diags.PositionRanges{
848 {Line: 3, FirstColumn: 5, LastColumn: 11},
849 },
850 },
851 },
852 For: &parser.YamlDuration{
853 Raw: "11m\n",
854 ParseError: errors.New(`unknown unit "m\n" in duration "11m\n"`),
855 Pos: diags.PositionRanges{
856 {Line: 5, FirstColumn: 5, LastColumn: 7},
857 },
858 },
859 Labels: &parser.YamlMap{
860 Key: &parser.YamlNode{
861 Value: "labels",
862 Pos: diags.PositionRanges{
863 {Line: 6, FirstColumn: 3, LastColumn: 8},
864 },
865 },
866 Items: []*parser.YamlKeyValue{
867 {
868 Key: &parser.YamlNode{
869 Value: "severity",
870 Pos: diags.PositionRanges{
871 {Line: 7, FirstColumn: 5, LastColumn: 12},
872 },
873 },
874 Value: &parser.YamlNode{
875 Value: "critical",
876 Pos: diags.PositionRanges{
877 {Line: 7, FirstColumn: 15, LastColumn: 22},
878 },
879 },
880 },
881 },
882 },
883 Annotations: &parser.YamlMap{
884 Key: &parser.YamlNode{
885 Value: "annotations",
886 Pos: diags.PositionRanges{
887 {Line: 8, FirstColumn: 3, LastColumn: 13},
888 },
889 },
890 Items: []*parser.YamlKeyValue{
891 {
892 Key: &parser.YamlNode{
893 Value: "uri",
894 Pos: diags.PositionRanges{
895 {Line: 9, FirstColumn: 5, LastColumn: 7},
896 },
897 },
898 Value: &parser.YamlNode{
899 Value: "https://docs.example.com/down.html",
900 Pos: diags.PositionRanges{
901 {Line: 9, FirstColumn: 10, LastColumn: 43},
902 },
903 },
904 },
905 },
906 },
907 },
908 },
909 {
910 Lines: diags.LineRange{First: 11, Last: 16},
911 RecordingRule: &parser.RecordingRule{
912 Record: parser.YamlNode{
913 Value: "foo",
914 Pos: diags.PositionRanges{
915 {Line: 11, FirstColumn: 11, LastColumn: 13},
916 },
917 },
918 Expr: parser.PromQLExpr{
919 Value: &parser.YamlNode{
920 Value: "bar\n/\nbaz > 1",
921 Pos: diags.PositionRanges{
922 {Line: 13, FirstColumn: 5, LastColumn: 8},
923 {Line: 14, FirstColumn: 5, LastColumn: 6},
924 {Line: 15, FirstColumn: 5, LastColumn: 11},
925 },
926 },
927 },
928 Labels: &parser.YamlMap{
929 Key: &parser.YamlNode{
930 Value: "labels",
931 Pos: diags.PositionRanges{
932 {Line: 16, FirstColumn: 3, LastColumn: 8},
933 },
934 },
935 },
936 },
937 },
938 },
939 },
940 },
941 },
942 },
943 {
944 input: []byte(`- alert: Foo
945 expr:
946 (
947 xxx
948 -
949 yyy
950 ) * bar > 0
951 and on(instance, device) baz
952 for: 30m
953`),
954 output: parser.File{
955 IsRelaxed: true,
956 Groups: []parser.Group{
957 {
958 Rules: []parser.Rule{
959 {
960 Lines: diags.LineRange{First: 1, Last: 9},
961 AlertingRule: &parser.AlertingRule{
962 Alert: parser.YamlNode{
963 Value: "Foo",
964 Pos: diags.PositionRanges{
965 {Line: 1, FirstColumn: 10, LastColumn: 12},
966 },
967 },
968 Expr: parser.PromQLExpr{
969 Value: &parser.YamlNode{
970 Value: "( xxx - yyy ) * bar > 0 and on(instance, device) baz",
971 Pos: diags.PositionRanges{
972 {Line: 3, FirstColumn: 5, LastColumn: 6},
973 {Line: 4, FirstColumn: 7, LastColumn: 10},
974 {Line: 5, FirstColumn: 7, LastColumn: 8},
975 {Line: 6, FirstColumn: 7, LastColumn: 10},
976 {Line: 7, FirstColumn: 5, LastColumn: 16},
977 {Line: 8, FirstColumn: 5, LastColumn: 32},
978 },
979 },
980 },
981 For: &parser.YamlDuration{
982 Value: time.Minute * 30,
983 Raw: "30m",
984 Pos: diags.PositionRanges{
985 {Line: 9, FirstColumn: 8, LastColumn: 10},
986 },
987 },
988 },
989 },
990 },
991 },
992 },
993 },
994 },
995 {
996 input: []byte(`---
997kind: ConfigMap
998apiVersion: v1
999metadata:
1000 name: example-app-alerts
1001 labels:
1002 app: example-app
1003data:
1004 alerts: |
1005 groups:
1006 - name: example-app-alerts
1007 rules:
1008 - alert: Example_High_Restart_Rate
1009 expr: sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )
1010---
1011kind: ConfigMap
1012apiVersion: v1
1013metadata:
1014 name: other
1015 labels:
1016 app: other
1017data:
1018 alerts: |
1019
1020 groups:
1021 - name: other alerts
1022 rules:
1023 - alert: Example_High_Restart_Rate
1024 expr: "1"
1025
1026`),
1027 output: parser.File{
1028 IsRelaxed: true,
1029 Groups: []parser.Group{
1030 {
1031 Name: parser.YamlNode{
1032 Value: "example-app-alerts",
1033 Pos: diags.PositionRanges{
1034 {Line: 2, FirstColumn: 11, LastColumn: 28},
1035 },
1036 },
1037 Rules: []parser.Rule{
1038 {
1039 Lines: diags.LineRange{First: 13, Last: 14},
1040 AlertingRule: &parser.AlertingRule{
1041 Alert: parser.YamlNode{
1042 Value: "Example_High_Restart_Rate",
1043 Pos: diags.PositionRanges{
1044 {Line: 13, FirstColumn: 20, LastColumn: 44},
1045 },
1046 },
1047 Expr: parser.PromQLExpr{
1048 Value: &parser.YamlNode{
1049 Value: `sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )`,
1050 Pos: diags.PositionRanges{
1051 {Line: 14, FirstColumn: 19, LastColumn: 109},
1052 },
1053 },
1054 },
1055 },
1056 },
1057 },
1058 },
1059 {
1060 Name: parser.YamlNode{
1061 Value: "other alerts",
1062 Pos: diags.PositionRanges{
1063 {Line: 3, FirstColumn: 11, LastColumn: 22},
1064 },
1065 },
1066 Rules: []parser.Rule{
1067 {
1068 Lines: diags.LineRange{First: 28, Last: 29},
1069 AlertingRule: &parser.AlertingRule{
1070 Alert: parser.YamlNode{
1071 Value: "Example_High_Restart_Rate",
1072 Pos: diags.PositionRanges{
1073 {Line: 28, FirstColumn: 16, LastColumn: 40},
1074 },
1075 },
1076 Expr: parser.PromQLExpr{
1077 Value: &parser.YamlNode{
1078 Value: "1",
1079 Pos: diags.PositionRanges{
1080 {Line: 29, FirstColumn: 16, LastColumn: 16},
1081 },
1082 },
1083 },
1084 },
1085 },
1086 },
1087 },
1088 },
1089 },
1090 },
1091 {
1092 input: []byte(`---
1093kind: ConfigMap
1094apiVersion: v1
1095metadata:
1096 name: example-app-alerts
1097 labels:
1098 app: example-app
1099data:
1100 alerts: |
1101 groups:
1102 - name: example-app-alerts
1103 rules:
1104 - alert: Example_Is_Down
1105 expr: kube_deployment_status_replicas_available{namespace="example-app"} < 1
1106 for: 5m
1107 labels:
1108 priority: "2"
1109 environment: production
1110 annotations:
1111 summary: "No replicas for Example have been running for 5 minutes"
1112
1113 - alert: Example_High_Restart_Rate
1114 expr: sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )
1115`),
1116 output: parser.File{
1117 IsRelaxed: true,
1118 Groups: []parser.Group{
1119 {
1120 Name: parser.YamlNode{
1121 Value: "example-app-alerts",
1122 Pos: diags.PositionRanges{
1123 {Line: 2, FirstColumn: 11, LastColumn: 28},
1124 },
1125 },
1126 Rules: []parser.Rule{
1127 {
1128 Lines: diags.LineRange{First: 13, Last: 20},
1129 AlertingRule: &parser.AlertingRule{
1130 Alert: parser.YamlNode{
1131 Value: "Example_Is_Down",
1132 Pos: diags.PositionRanges{
1133 {Line: 13, FirstColumn: 20, LastColumn: 34},
1134 },
1135 },
1136 Expr: parser.PromQLExpr{
1137 Value: &parser.YamlNode{
1138 Value: `kube_deployment_status_replicas_available{namespace="example-app"} < 1`,
1139 Pos: diags.PositionRanges{
1140 {Line: 14, FirstColumn: 19, LastColumn: 88},
1141 },
1142 },
1143 },
1144 For: &parser.YamlDuration{
1145 Value: time.Minute * 5,
1146 Raw: "5m",
1147 Pos: diags.PositionRanges{
1148 {Line: 15, FirstColumn: 18, LastColumn: 19},
1149 },
1150 },
1151 Labels: &parser.YamlMap{
1152 Key: &parser.YamlNode{
1153 Value: "labels",
1154 Pos: diags.PositionRanges{
1155 {Line: 16, FirstColumn: 13, LastColumn: 18},
1156 },
1157 },
1158 Items: []*parser.YamlKeyValue{
1159 {
1160 Key: &parser.YamlNode{
1161 Value: "priority",
1162 Pos: diags.PositionRanges{
1163 {Line: 17, FirstColumn: 15, LastColumn: 22},
1164 },
1165 },
1166 Value: &parser.YamlNode{
1167 Value: "2",
1168 Pos: diags.PositionRanges{
1169 {Line: 17, FirstColumn: 26, LastColumn: 26},
1170 },
1171 },
1172 },
1173 {
1174 Key: &parser.YamlNode{
1175 Value: "environment",
1176 Pos: diags.PositionRanges{
1177 {Line: 18, FirstColumn: 15, LastColumn: 25},
1178 },
1179 },
1180 Value: &parser.YamlNode{
1181 Value: "production",
1182 Pos: diags.PositionRanges{
1183 {Line: 18, FirstColumn: 28, LastColumn: 37},
1184 },
1185 },
1186 },
1187 },
1188 },
1189 Annotations: &parser.YamlMap{
1190 Key: &parser.YamlNode{
1191 Value: "annotations",
1192 Pos: diags.PositionRanges{
1193 {Line: 19, FirstColumn: 13, LastColumn: 23},
1194 },
1195 },
1196 Items: []*parser.YamlKeyValue{
1197 {
1198 Key: &parser.YamlNode{
1199 Value: "summary",
1200 Pos: diags.PositionRanges{
1201 {Line: 20, FirstColumn: 15, LastColumn: 21},
1202 },
1203 },
1204 Value: &parser.YamlNode{
1205 Value: "No replicas for Example have been running for 5 minutes",
1206 Pos: diags.PositionRanges{
1207 {Line: 20, FirstColumn: 25, LastColumn: 79},
1208 },
1209 },
1210 },
1211 },
1212 },
1213 },
1214 },
1215 {
1216 Lines: diags.LineRange{First: 22, Last: 23},
1217 AlertingRule: &parser.AlertingRule{
1218 Alert: parser.YamlNode{
1219 Value: "Example_High_Restart_Rate",
1220 Pos: diags.PositionRanges{
1221 {Line: 22, FirstColumn: 20, LastColumn: 44},
1222 },
1223 },
1224 Expr: parser.PromQLExpr{
1225 Value: &parser.YamlNode{
1226 Value: `sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )`,
1227 Pos: diags.PositionRanges{
1228 {Line: 23, FirstColumn: 19, LastColumn: 109},
1229 },
1230 },
1231 },
1232 },
1233 },
1234 },
1235 },
1236 },
1237 },
1238 },
1239 {
1240 input: []byte(`groups:
1241- name: "haproxy.api_server.rules"
1242 rules:
1243 - alert: HaproxyServerHealthcheckFailure
1244 expr: increase(haproxy_server_check_failures_total[15m]) > 100
1245 for: 5m
1246 labels:
1247 severity: 24x7
1248 annotations:
1249 summary: "HAProxy server healthcheck failure (instance {{ $labels.instance }})"
1250 description: "Some server healthcheck are failing on {{ $labels.server }}\n VALUE = {{ $value }}\n LABELS: {{ $labels }}"
1251`),
1252 output: parser.File{
1253 IsRelaxed: true,
1254 Groups: []parser.Group{
1255 {
1256 Name: parser.YamlNode{
1257 Value: "haproxy.api_server.rules",
1258 Pos: diags.PositionRanges{
1259 {Line: 2, FirstColumn: 10, LastColumn: 33},
1260 },
1261 },
1262 Rules: []parser.Rule{
1263 {
1264 Lines: diags.LineRange{First: 4, Last: 11},
1265 AlertingRule: &parser.AlertingRule{
1266 Alert: parser.YamlNode{
1267 Value: "HaproxyServerHealthcheckFailure",
1268 Pos: diags.PositionRanges{
1269 {Line: 4, FirstColumn: 12, LastColumn: 42},
1270 },
1271 },
1272 Expr: parser.PromQLExpr{
1273 Value: &parser.YamlNode{
1274 Value: "increase(haproxy_server_check_failures_total[15m]) > 100",
1275 Pos: diags.PositionRanges{
1276 {Line: 5, FirstColumn: 11, LastColumn: 66},
1277 },
1278 },
1279 },
1280 For: &parser.YamlDuration{
1281 Value: time.Minute * 5,
1282 Raw: "5m",
1283 Pos: diags.PositionRanges{
1284 {Line: 6, FirstColumn: 10, LastColumn: 11},
1285 },
1286 },
1287 Labels: &parser.YamlMap{
1288 Key: &parser.YamlNode{
1289 Value: "labels",
1290 Pos: diags.PositionRanges{
1291 {Line: 7, FirstColumn: 5, LastColumn: 10},
1292 },
1293 },
1294 Items: []*parser.YamlKeyValue{
1295 {
1296 Key: &parser.YamlNode{
1297 Value: "severity",
1298 Pos: diags.PositionRanges{
1299 {Line: 8, FirstColumn: 7, LastColumn: 14},
1300 },
1301 },
1302 Value: &parser.YamlNode{
1303 Value: "24x7",
1304 Pos: diags.PositionRanges{
1305 {Line: 8, FirstColumn: 17, LastColumn: 20},
1306 },
1307 },
1308 },
1309 },
1310 },
1311 Annotations: &parser.YamlMap{
1312 Key: &parser.YamlNode{
1313 Value: "annotations",
1314 Pos: diags.PositionRanges{
1315 {Line: 9, FirstColumn: 5, LastColumn: 15},
1316 },
1317 },
1318 Items: []*parser.YamlKeyValue{
1319 {
1320 Key: &parser.YamlNode{
1321 Value: "summary",
1322 Pos: diags.PositionRanges{
1323 {Line: 10, FirstColumn: 7, LastColumn: 13},
1324 },
1325 },
1326 Value: &parser.YamlNode{
1327 Value: "HAProxy server healthcheck failure (instance {{ $labels.instance }})",
1328 Pos: diags.PositionRanges{
1329 {Line: 10, FirstColumn: 17, LastColumn: 84},
1330 },
1331 },
1332 },
1333 {
1334 Key: &parser.YamlNode{
1335 Value: "description",
1336 Pos: diags.PositionRanges{
1337 {Line: 11, FirstColumn: 7, LastColumn: 17},
1338 },
1339 },
1340 Value: &parser.YamlNode{
1341 Value: "Some server healthcheck are failing on {{ $labels.server }}\n VALUE = {{ $value }}\n LABELS: {{ $labels }}",
1342 Pos: diags.PositionRanges{
1343 {Line: 11, FirstColumn: 21, LastColumn: 79},
1344 },
1345 },
1346 },
1347 },
1348 },
1349 },
1350 },
1351 },
1352 },
1353 },
1354 },
1355 },
1356 {
1357 input: []byte(`groups:
1358- name: certmanager
1359 rules:
1360 # pint disable before recordAnchor
1361 - &recordAnchor # pint disable recordAnchor
1362 record: name1 # pint disable name1
1363 expr: expr1 # pint disable expr1
1364 # pint disable after expr1
1365 - <<: *recordAnchor
1366 expr: expr2
1367 - <<: *recordAnchor
1368`),
1369 output: parser.File{
1370 IsRelaxed: true,
1371 Groups: []parser.Group{
1372 {
1373 Name: parser.YamlNode{
1374 Value: "certmanager",
1375 Pos: diags.PositionRanges{
1376 {Line: 2, FirstColumn: 9, LastColumn: 19},
1377 },
1378 },
1379 Rules: []parser.Rule{
1380 {
1381 Lines: diags.LineRange{First: 6, Last: 7},
1382 Comments: []comments.Comment{
1383 {
1384 Type: comments.DisableType,
1385 Value: comments.Disable{Match: "before recordAnchor"},
1386 },
1387 {
1388 Type: comments.DisableType,
1389 Value: comments.Disable{Match: "recordAnchor"},
1390 },
1391 {
1392 Type: comments.DisableType,
1393 Value: comments.Disable{Match: "name1"},
1394 },
1395 {
1396 Type: comments.DisableType,
1397 Value: comments.Disable{Match: "after expr1"},
1398 },
1399 {
1400 Type: comments.DisableType,
1401 Value: comments.Disable{Match: "expr1"},
1402 },
1403 },
1404 RecordingRule: &parser.RecordingRule{
1405 Record: parser.YamlNode{
1406 Value: "name1",
1407 Pos: diags.PositionRanges{
1408 {Line: 6, FirstColumn: 13, LastColumn: 17},
1409 },
1410 },
1411 Expr: parser.PromQLExpr{
1412 Value: &parser.YamlNode{
1413 Value: "expr1",
1414 Pos: diags.PositionRanges{
1415 {Line: 7, FirstColumn: 11, LastColumn: 15},
1416 },
1417 },
1418 },
1419 },
1420 },
1421 {
1422 Comments: []comments.Comment{
1423 {
1424 Type: comments.DisableType,
1425 Value: comments.Disable{Match: "before recordAnchor"},
1426 },
1427 {
1428 Type: comments.DisableType,
1429 Value: comments.Disable{Match: "recordAnchor"},
1430 },
1431 {
1432 Type: comments.DisableType,
1433 Value: comments.Disable{Match: "name1"},
1434 },
1435 },
1436 Lines: diags.LineRange{First: 6, Last: 10},
1437 RecordingRule: &parser.RecordingRule{
1438 Record: parser.YamlNode{
1439 Value: "name1",
1440 Pos: diags.PositionRanges{
1441 {Line: 6, FirstColumn: 13, LastColumn: 17},
1442 },
1443 },
1444 Expr: parser.PromQLExpr{
1445 Value: &parser.YamlNode{
1446 Value: "expr2",
1447 Pos: diags.PositionRanges{
1448 {Line: 10, FirstColumn: 11, LastColumn: 15},
1449 },
1450 },
1451 },
1452 },
1453 },
1454 {
1455 Comments: []comments.Comment{
1456 {
1457 Type: comments.DisableType,
1458 Value: comments.Disable{Match: "before recordAnchor"},
1459 },
1460 {
1461 Type: comments.DisableType,
1462 Value: comments.Disable{Match: "recordAnchor"},
1463 },
1464 {
1465 Type: comments.DisableType,
1466 Value: comments.Disable{Match: "name1"},
1467 },
1468 {
1469 Type: comments.DisableType,
1470 Value: comments.Disable{Match: "after expr1"},
1471 },
1472 {
1473 Type: comments.DisableType,
1474 Value: comments.Disable{Match: "expr1"},
1475 },
1476 },
1477 Lines: diags.LineRange{First: 6, Last: 7},
1478 RecordingRule: &parser.RecordingRule{
1479 Record: parser.YamlNode{
1480 Value: "name1",
1481 Pos: diags.PositionRanges{
1482 {Line: 6, FirstColumn: 13, LastColumn: 17},
1483 },
1484 },
1485 Expr: parser.PromQLExpr{
1486 Value: &parser.YamlNode{
1487 Value: "expr1",
1488 Pos: diags.PositionRanges{
1489 {Line: 7, FirstColumn: 11, LastColumn: 15},
1490 },
1491 },
1492 },
1493 },
1494 },
1495 },
1496 },
1497 },
1498 },
1499 },
1500 {
1501 input: []byte(`groups:
1502- name: certmanager
1503 rules:
1504 - record: name1
1505 expr: expr1
1506 labels: &labelsAnchor
1507 label1: val1
1508 label2: val2
1509 - record: name2
1510 expr: expr2
1511 labels: *labelsAnchor
1512 # pint disable foot comment
1513`),
1514 output: parser.File{
1515 IsRelaxed: true,
1516 Groups: []parser.Group{
1517 {
1518 Name: parser.YamlNode{
1519 Value: "certmanager",
1520 Pos: diags.PositionRanges{
1521 {Line: 2, FirstColumn: 9, LastColumn: 19},
1522 },
1523 },
1524 Rules: []parser.Rule{
1525 {
1526 Lines: diags.LineRange{First: 4, Last: 8},
1527 RecordingRule: &parser.RecordingRule{
1528 Record: parser.YamlNode{
1529 Value: "name1",
1530 Pos: diags.PositionRanges{
1531 {Line: 4, FirstColumn: 13, LastColumn: 17},
1532 },
1533 },
1534 Expr: parser.PromQLExpr{
1535 Value: &parser.YamlNode{
1536 Value: "expr1",
1537 Pos: diags.PositionRanges{
1538 {Line: 5, FirstColumn: 11, LastColumn: 15},
1539 },
1540 },
1541 },
1542 Labels: &parser.YamlMap{
1543 Key: &parser.YamlNode{
1544 Value: "labels",
1545 Pos: diags.PositionRanges{
1546 {Line: 6, FirstColumn: 5, LastColumn: 10},
1547 },
1548 },
1549 Items: []*parser.YamlKeyValue{
1550 {
1551 Key: &parser.YamlNode{
1552 Value: "label1",
1553 Pos: diags.PositionRanges{
1554 {Line: 7, FirstColumn: 7, LastColumn: 12},
1555 },
1556 },
1557 Value: &parser.YamlNode{
1558 Value: "val1",
1559 Pos: diags.PositionRanges{
1560 {Line: 7, FirstColumn: 15, LastColumn: 18},
1561 },
1562 },
1563 },
1564 {
1565 Key: &parser.YamlNode{
1566 Value: "label2",
1567 Pos: diags.PositionRanges{
1568 {Line: 8, FirstColumn: 7, LastColumn: 12},
1569 },
1570 },
1571 Value: &parser.YamlNode{
1572 Value: "val2",
1573 Pos: diags.PositionRanges{
1574 {Line: 8, FirstColumn: 15, LastColumn: 18},
1575 },
1576 },
1577 },
1578 },
1579 },
1580 },
1581 },
1582 {
1583 Comments: []comments.Comment{
1584 {
1585 Type: comments.DisableType,
1586 Value: comments.Disable{Match: "foot comment"},
1587 },
1588 },
1589 Lines: diags.LineRange{First: 9, Last: 11},
1590 RecordingRule: &parser.RecordingRule{
1591 Record: parser.YamlNode{
1592 Value: "name2",
1593 Pos: diags.PositionRanges{
1594 {Line: 9, FirstColumn: 13, LastColumn: 17},
1595 },
1596 },
1597 Expr: parser.PromQLExpr{
1598 Value: &parser.YamlNode{
1599 Value: "expr2",
1600 Pos: diags.PositionRanges{
1601 {Line: 10, FirstColumn: 11, LastColumn: 15},
1602 },
1603 },
1604 },
1605 Labels: &parser.YamlMap{
1606 Key: &parser.YamlNode{
1607 Value: "labels",
1608 Pos: diags.PositionRanges{
1609 {Line: 11, FirstColumn: 5, LastColumn: 10},
1610 },
1611 },
1612 Items: []*parser.YamlKeyValue{
1613 {
1614 Key: &parser.YamlNode{
1615 Value: "label1",
1616 Pos: diags.PositionRanges{
1617 {Line: 7, FirstColumn: 7, LastColumn: 12},
1618 },
1619 },
1620 Value: &parser.YamlNode{
1621 Value: "val1",
1622 Pos: diags.PositionRanges{
1623 {Line: 7, FirstColumn: 15, LastColumn: 18},
1624 },
1625 },
1626 },
1627 {
1628 Key: &parser.YamlNode{
1629 Value: "label2",
1630 Pos: diags.PositionRanges{
1631 {Line: 8, FirstColumn: 7, LastColumn: 12},
1632 },
1633 },
1634 Value: &parser.YamlNode{
1635 Value: "val2",
1636 Pos: diags.PositionRanges{
1637 {Line: 8, FirstColumn: 15, LastColumn: 18},
1638 },
1639 },
1640 },
1641 },
1642 },
1643 },
1644 },
1645 },
1646 },
1647 },
1648 },
1649 },
1650 {
1651 input: []byte("- alert:\n expr: vector(1)\n"),
1652 output: parser.File{
1653 IsRelaxed: true,
1654 Groups: []parser.Group{
1655 {
1656 Rules: []parser.Rule{
1657 {
1658 Lines: diags.LineRange{First: 1, Last: 2},
1659 Error: parser.ParseError{Err: errors.New("alert value cannot be empty"), Line: 1},
1660 },
1661 },
1662 },
1663 },
1664 },
1665 },
1666 {
1667 input: []byte("- alert: foo\n expr:\n"),
1668 output: parser.File{
1669 IsRelaxed: true,
1670 Groups: []parser.Group{
1671 {
1672 Rules: []parser.Rule{
1673 {
1674 Lines: diags.LineRange{First: 1, Last: 2},
1675 Error: parser.ParseError{Err: errors.New("expr value cannot be empty"), Line: 2},
1676 },
1677 },
1678 },
1679 },
1680 },
1681 },
1682 {
1683 input: []byte("- alert: foo\n"),
1684 output: parser.File{
1685 IsRelaxed: true,
1686 Groups: []parser.Group{
1687 {
1688 Rules: []parser.Rule{
1689 {
1690 Lines: diags.LineRange{First: 1, Last: 1},
1691 Error: parser.ParseError{Err: errors.New("missing expr key"), Line: 1},
1692 },
1693 },
1694 },
1695 },
1696 },
1697 },
1698 {
1699 input: []byte("- record:\n expr:\n"),
1700 output: parser.File{
1701 IsRelaxed: true,
1702 Groups: []parser.Group{
1703 {
1704 Rules: []parser.Rule{
1705 {
1706 Lines: diags.LineRange{First: 1, Last: 2},
1707 Error: parser.ParseError{Err: errors.New("record value cannot be empty"), Line: 1},
1708 },
1709 },
1710 },
1711 },
1712 },
1713 },
1714 {
1715 input: []byte("- record: foo\n expr:\n"),
1716 output: parser.File{
1717 IsRelaxed: true,
1718 Groups: []parser.Group{
1719 {
1720 Rules: []parser.Rule{
1721 {
1722 Lines: diags.LineRange{First: 1, Last: 2},
1723 Error: parser.ParseError{Err: errors.New("expr value cannot be empty"), Line: 2},
1724 },
1725 },
1726 },
1727 },
1728 },
1729 },
1730 {
1731 input: []byte("- record: foo\n"),
1732 output: parser.File{
1733 IsRelaxed: true,
1734 Groups: []parser.Group{
1735 {
1736 Rules: []parser.Rule{
1737 {
1738 Lines: diags.LineRange{First: 1, Last: 1},
1739 Error: parser.ParseError{Err: errors.New("missing expr key"), Line: 1},
1740 },
1741 },
1742 },
1743 },
1744 },
1745 },
1746 {
1747 input: []byte(string(`
1748# pint file/owner bob
1749# pint ignore/begin
1750# pint ignore/end
1751# pint disable up
1752
1753- record: foo
1754 expr: up
1755
1756# pint file/owner alice
1757
1758- record: foo
1759 expr: up
1760
1761# pint ignore/next-line
1762`)),
1763 output: parser.File{
1764 IsRelaxed: true,
1765 Comments: []comments.Comment{
1766 {
1767 Type: comments.FileOwnerType,
1768 Value: comments.Owner{
1769 Name: "bob",
1770 Line: 2,
1771 },
1772 },
1773 {
1774 Type: comments.FileOwnerType,
1775 Value: comments.Owner{
1776 Name: "alice",
1777 Line: 10,
1778 },
1779 },
1780 },
1781 Groups: []parser.Group{
1782 {
1783 Rules: []parser.Rule{
1784 {
1785 Lines: diags.LineRange{First: 7, Last: 8},
1786 RecordingRule: &parser.RecordingRule{
1787 Record: parser.YamlNode{
1788 Value: "foo",
1789 Pos: diags.PositionRanges{
1790 {Line: 7, FirstColumn: 11, LastColumn: 13},
1791 },
1792 },
1793 Expr: parser.PromQLExpr{
1794 Value: &parser.YamlNode{
1795 Value: "up",
1796 Pos: diags.PositionRanges{
1797 {Line: 8, FirstColumn: 9, LastColumn: 10},
1798 },
1799 },
1800 },
1801 },
1802 },
1803 {
1804 Lines: diags.LineRange{First: 12, Last: 13},
1805 RecordingRule: &parser.RecordingRule{
1806 Record: parser.YamlNode{
1807 Value: "foo",
1808 Pos: diags.PositionRanges{
1809 {Line: 12, FirstColumn: 11, LastColumn: 13},
1810 },
1811 },
1812 Expr: parser.PromQLExpr{
1813 Value: &parser.YamlNode{
1814 Value: "up",
1815 Pos: diags.PositionRanges{
1816 {Line: 13, FirstColumn: 9, LastColumn: 10},
1817 },
1818 },
1819 },
1820 },
1821 },
1822 },
1823 },
1824 },
1825 },
1826 },
1827 {
1828 input: []byte(string(`
1829- alert: Template
1830 expr: &expr up == 0
1831 labels:
1832 notify: &maybe_escalate_notify chat-alerts
1833- alert: Service Down
1834 expr: *expr
1835 labels:
1836 notify: *maybe_escalate_notify
1837 summary: foo
1838`)),
1839 output: parser.File{
1840 IsRelaxed: true,
1841 Groups: []parser.Group{
1842 {
1843 Rules: []parser.Rule{
1844 {
1845 Lines: diags.LineRange{First: 2, Last: 5},
1846 AlertingRule: &parser.AlertingRule{
1847 Alert: parser.YamlNode{
1848 Value: "Template",
1849 Pos: diags.PositionRanges{
1850 {Line: 2, FirstColumn: 10, LastColumn: 17},
1851 },
1852 },
1853 Expr: parser.PromQLExpr{
1854 Value: &parser.YamlNode{
1855 Value: "up == 0",
1856 Pos: diags.PositionRanges{
1857 {Line: 3, FirstColumn: 15, LastColumn: 21},
1858 },
1859 },
1860 },
1861 Labels: &parser.YamlMap{
1862 Key: &parser.YamlNode{
1863 Value: "labels",
1864 Pos: diags.PositionRanges{
1865 {Line: 4, FirstColumn: 3, LastColumn: 8},
1866 },
1867 },
1868 Items: []*parser.YamlKeyValue{
1869 {
1870 Key: &parser.YamlNode{
1871 Value: "notify",
1872 Pos: diags.PositionRanges{
1873 {Line: 5, FirstColumn: 5, LastColumn: 10},
1874 },
1875 },
1876 Value: &parser.YamlNode{
1877 Value: "chat-alerts",
1878 Pos: diags.PositionRanges{
1879 {Line: 5, FirstColumn: 22, LastColumn: 22},
1880 {Line: 5, FirstColumn: 37, LastColumn: 46},
1881 },
1882 },
1883 },
1884 },
1885 },
1886 },
1887 },
1888 {
1889 Lines: diags.LineRange{First: 6, Last: 10},
1890 AlertingRule: &parser.AlertingRule{
1891 Alert: parser.YamlNode{
1892 Value: "Service Down",
1893 Pos: diags.PositionRanges{
1894 {Line: 6, FirstColumn: 10, LastColumn: 21},
1895 },
1896 },
1897 Expr: parser.PromQLExpr{
1898 Value: &parser.YamlNode{
1899 Value: "up == 0",
1900 Pos: diags.PositionRanges{
1901 {Line: 7, FirstColumn: 10, LastColumn: 13}, // points at anchor
1902 },
1903 },
1904 },
1905 Labels: &parser.YamlMap{
1906 Key: &parser.YamlNode{
1907 Value: "labels",
1908 Pos: diags.PositionRanges{
1909 {Line: 8, FirstColumn: 3, LastColumn: 8},
1910 },
1911 },
1912 Items: []*parser.YamlKeyValue{
1913 {
1914 Key: &parser.YamlNode{
1915 Value: "notify",
1916 Pos: diags.PositionRanges{
1917 {Line: 9, FirstColumn: 5, LastColumn: 10},
1918 },
1919 },
1920 Value: &parser.YamlNode{
1921 Value: "chat-alerts",
1922 Pos: diags.PositionRanges{
1923 {Line: 9, FirstColumn: 14, LastColumn: 34},
1924 },
1925 },
1926 },
1927 {
1928 Key: &parser.YamlNode{
1929 Value: "summary",
1930 Pos: diags.PositionRanges{
1931 {Line: 10, FirstColumn: 5, LastColumn: 11},
1932 },
1933 },
1934 Value: &parser.YamlNode{
1935 Value: "foo",
1936 Pos: diags.PositionRanges{
1937 {Line: 10, FirstColumn: 14, LastColumn: 16},
1938 },
1939 },
1940 },
1941 },
1942 },
1943 },
1944 },
1945 },
1946 },
1947 },
1948 },
1949 },
1950 {
1951 // YAML alias on record value is resolved correctly
1952 input: []byte(`
1953- record: &name foo
1954 expr: expr1
1955- record: *name
1956 expr: expr2
1957`),
1958 output: parser.File{
1959 IsRelaxed: true,
1960 Groups: []parser.Group{
1961 {
1962 Rules: []parser.Rule{
1963 {
1964 Lines: diags.LineRange{First: 2, Last: 3},
1965 RecordingRule: &parser.RecordingRule{
1966 Record: parser.YamlNode{
1967 Value: "foo",
1968 Pos: diags.PositionRanges{
1969 {Line: 2, FirstColumn: 17, LastColumn: 19},
1970 },
1971 },
1972 Expr: parser.PromQLExpr{
1973 Value: &parser.YamlNode{
1974 Value: "expr1",
1975 Pos: diags.PositionRanges{
1976 {Line: 3, FirstColumn: 9, LastColumn: 13},
1977 },
1978 },
1979 },
1980 },
1981 },
1982 {
1983 Lines: diags.LineRange{First: 4, Last: 5},
1984 RecordingRule: &parser.RecordingRule{
1985 Record: parser.YamlNode{
1986 Value: "foo",
1987 Pos: diags.PositionRanges{
1988 {Line: 4, FirstColumn: 12, LastColumn: 15},
1989 },
1990 },
1991 Expr: parser.PromQLExpr{
1992 Value: &parser.YamlNode{
1993 Value: "expr2",
1994 Pos: diags.PositionRanges{
1995 {Line: 5, FirstColumn: 9, LastColumn: 13},
1996 },
1997 },
1998 },
1999 },
2000 },
2001 },
2002 },
2003 },
2004 },
2005 },
2006 {
2007 // YAML alias on alert value is resolved correctly
2008 input: []byte(`
2009- alert: &aname MyAlert
2010 expr: up == 0
2011- alert: *aname
2012 expr: up == 1
2013`),
2014 output: parser.File{
2015 IsRelaxed: true,
2016 Groups: []parser.Group{
2017 {
2018 Rules: []parser.Rule{
2019 {
2020 Lines: diags.LineRange{First: 2, Last: 3},
2021 AlertingRule: &parser.AlertingRule{
2022 Alert: parser.YamlNode{
2023 Value: "MyAlert",
2024 Pos: diags.PositionRanges{
2025 {Line: 2, FirstColumn: 17, LastColumn: 23},
2026 },
2027 },
2028 Expr: parser.PromQLExpr{
2029 Value: &parser.YamlNode{
2030 Value: "up == 0",
2031 Pos: diags.PositionRanges{
2032 {Line: 3, FirstColumn: 9, LastColumn: 15},
2033 },
2034 },
2035 },
2036 },
2037 },
2038 {
2039 Lines: diags.LineRange{First: 4, Last: 5},
2040 AlertingRule: &parser.AlertingRule{
2041 Alert: parser.YamlNode{
2042 Value: "MyAlert",
2043 Pos: diags.PositionRanges{
2044 {Line: 4, FirstColumn: 11, LastColumn: 15},
2045 },
2046 },
2047 Expr: parser.PromQLExpr{
2048 Value: &parser.YamlNode{
2049 Value: "up == 1",
2050 Pos: diags.PositionRanges{
2051 {Line: 5, FirstColumn: 9, LastColumn: 15},
2052 },
2053 },
2054 },
2055 },
2056 },
2057 },
2058 },
2059 },
2060 },
2061 },
2062 {
2063 // YAML alias on for duration is resolved correctly
2064 input: []byte(`
2065- alert: alert1
2066 expr: up == 0
2067 for: &dur 5m
2068- alert: alert2
2069 expr: up == 1
2070 for: *dur
2071`),
2072 output: parser.File{
2073 IsRelaxed: true,
2074 Groups: []parser.Group{
2075 {
2076 Rules: []parser.Rule{
2077 {
2078 Lines: diags.LineRange{First: 2, Last: 4},
2079 AlertingRule: &parser.AlertingRule{
2080 Alert: parser.YamlNode{
2081 Value: "alert1",
2082 Pos: diags.PositionRanges{
2083 {Line: 2, FirstColumn: 10, LastColumn: 15},
2084 },
2085 },
2086 Expr: parser.PromQLExpr{
2087 Value: &parser.YamlNode{
2088 Value: "up == 0",
2089 Pos: diags.PositionRanges{
2090 {Line: 3, FirstColumn: 9, LastColumn: 15},
2091 },
2092 },
2093 },
2094 For: &parser.YamlDuration{
2095 Raw: "5m",
2096 Value: 5 * time.Minute,
2097 Pos: diags.PositionRanges{
2098 {Line: 4, FirstColumn: 13, LastColumn: 14},
2099 },
2100 },
2101 },
2102 },
2103 {
2104 Lines: diags.LineRange{First: 5, Last: 7},
2105 AlertingRule: &parser.AlertingRule{
2106 Alert: parser.YamlNode{
2107 Value: "alert2",
2108 Pos: diags.PositionRanges{
2109 {Line: 5, FirstColumn: 10, LastColumn: 15},
2110 },
2111 },
2112 Expr: parser.PromQLExpr{
2113 Value: &parser.YamlNode{
2114 Value: "up == 1",
2115 Pos: diags.PositionRanges{
2116 {Line: 6, FirstColumn: 9, LastColumn: 15},
2117 },
2118 },
2119 },
2120 For: &parser.YamlDuration{
2121 Raw: "5m",
2122 Value: 5 * time.Minute,
2123 Pos: diags.PositionRanges{
2124 {Line: 7, FirstColumn: 9, LastColumn: 11},
2125 },
2126 },
2127 },
2128 },
2129 },
2130 },
2131 },
2132 },
2133 },
2134 {
2135 // YAML alias on keep_firing_for duration is resolved correctly
2136 input: []byte(`
2137- alert: alert1
2138 expr: up == 0
2139 keep_firing_for: &kff 10m
2140- alert: alert2
2141 expr: up == 1
2142 keep_firing_for: *kff
2143`),
2144 output: parser.File{
2145 IsRelaxed: true,
2146 Groups: []parser.Group{
2147 {
2148 Rules: []parser.Rule{
2149 {
2150 Lines: diags.LineRange{First: 2, Last: 4},
2151 AlertingRule: &parser.AlertingRule{
2152 Alert: parser.YamlNode{
2153 Value: "alert1",
2154 Pos: diags.PositionRanges{
2155 {Line: 2, FirstColumn: 10, LastColumn: 15},
2156 },
2157 },
2158 Expr: parser.PromQLExpr{
2159 Value: &parser.YamlNode{
2160 Value: "up == 0",
2161 Pos: diags.PositionRanges{
2162 {Line: 3, FirstColumn: 9, LastColumn: 15},
2163 },
2164 },
2165 },
2166 KeepFiringFor: &parser.YamlDuration{
2167 Raw: "10m",
2168 Value: 10 * time.Minute,
2169 Pos: diags.PositionRanges{
2170 {Line: 4, FirstColumn: 25, LastColumn: 27},
2171 },
2172 },
2173 },
2174 },
2175 {
2176 Lines: diags.LineRange{First: 5, Last: 7},
2177 AlertingRule: &parser.AlertingRule{
2178 Alert: parser.YamlNode{
2179 Value: "alert2",
2180 Pos: diags.PositionRanges{
2181 {Line: 5, FirstColumn: 10, LastColumn: 15},
2182 },
2183 },
2184 Expr: parser.PromQLExpr{
2185 Value: &parser.YamlNode{
2186 Value: "up == 1",
2187 Pos: diags.PositionRanges{
2188 {Line: 6, FirstColumn: 9, LastColumn: 15},
2189 },
2190 },
2191 },
2192 KeepFiringFor: &parser.YamlDuration{
2193 Raw: "10m",
2194 Value: 10 * time.Minute,
2195 Pos: diags.PositionRanges{
2196 {Line: 7, FirstColumn: 21, LastColumn: 23},
2197 },
2198 },
2199 },
2200 },
2201 },
2202 },
2203 },
2204 },
2205 },
2206 {
2207 // YAML alias on annotations map is resolved correctly
2208 input: []byte(`
2209- alert: alert1
2210 expr: up == 0
2211 annotations: &ann
2212 summary: foo is down
2213- alert: alert2
2214 expr: up == 1
2215 annotations: *ann
2216`),
2217 output: parser.File{
2218 IsRelaxed: true,
2219 Groups: []parser.Group{
2220 {
2221 Rules: []parser.Rule{
2222 {
2223 Lines: diags.LineRange{First: 2, Last: 5},
2224 AlertingRule: &parser.AlertingRule{
2225 Alert: parser.YamlNode{
2226 Value: "alert1",
2227 Pos: diags.PositionRanges{
2228 {Line: 2, FirstColumn: 10, LastColumn: 15},
2229 },
2230 },
2231 Expr: parser.PromQLExpr{
2232 Value: &parser.YamlNode{
2233 Value: "up == 0",
2234 Pos: diags.PositionRanges{
2235 {Line: 3, FirstColumn: 9, LastColumn: 15},
2236 },
2237 },
2238 },
2239 Annotations: &parser.YamlMap{
2240 Key: &parser.YamlNode{
2241 Value: "annotations",
2242 Pos: diags.PositionRanges{
2243 {Line: 4, FirstColumn: 3, LastColumn: 13},
2244 },
2245 },
2246 Items: []*parser.YamlKeyValue{
2247 {
2248 Key: &parser.YamlNode{
2249 Value: "summary",
2250 Pos: diags.PositionRanges{
2251 {Line: 5, FirstColumn: 5, LastColumn: 11},
2252 },
2253 },
2254 Value: &parser.YamlNode{
2255 Value: "foo is down",
2256 Pos: diags.PositionRanges{
2257 {Line: 5, FirstColumn: 14, LastColumn: 24},
2258 },
2259 },
2260 },
2261 },
2262 },
2263 },
2264 },
2265 {
2266 Lines: diags.LineRange{First: 6, Last: 8},
2267 AlertingRule: &parser.AlertingRule{
2268 Alert: parser.YamlNode{
2269 Value: "alert2",
2270 Pos: diags.PositionRanges{
2271 {Line: 6, FirstColumn: 10, LastColumn: 15},
2272 },
2273 },
2274 Expr: parser.PromQLExpr{
2275 Value: &parser.YamlNode{
2276 Value: "up == 1",
2277 Pos: diags.PositionRanges{
2278 {Line: 7, FirstColumn: 9, LastColumn: 15},
2279 },
2280 },
2281 },
2282 Annotations: &parser.YamlMap{
2283 Key: &parser.YamlNode{
2284 Value: "annotations",
2285 Pos: diags.PositionRanges{
2286 {Line: 8, FirstColumn: 3, LastColumn: 13},
2287 },
2288 },
2289 Items: []*parser.YamlKeyValue{
2290 {
2291 Key: &parser.YamlNode{
2292 Value: "summary",
2293 Pos: diags.PositionRanges{
2294 {Line: 5, FirstColumn: 5, LastColumn: 11},
2295 },
2296 },
2297 Value: &parser.YamlNode{
2298 Value: "foo is down",
2299 Pos: diags.PositionRanges{
2300 {Line: 5, FirstColumn: 14, LastColumn: 24},
2301 },
2302 },
2303 },
2304 },
2305 },
2306 },
2307 },
2308 },
2309 },
2310 },
2311 },
2312 },
2313 {
2314 // Merge key merges default alert options into alert rules
2315 input: []byte(`
2316- &defaults
2317 for: 5m
2318 labels:
2319 severity: warning
2320- alert: foo
2321 expr: up == 0
2322 <<: *defaults
2323`),
2324 output: parser.File{
2325 IsRelaxed: true,
2326 Groups: []parser.Group{
2327 {
2328 Rules: []parser.Rule{
2329 {
2330 Lines: diags.LineRange{First: 3, Last: 7},
2331 AlertingRule: &parser.AlertingRule{
2332 Alert: parser.YamlNode{
2333 Value: "foo",
2334 Pos: diags.PositionRanges{
2335 {Line: 6, FirstColumn: 10, LastColumn: 12},
2336 },
2337 },
2338 Expr: parser.PromQLExpr{
2339 Value: &parser.YamlNode{
2340 Value: "up == 0",
2341 Pos: diags.PositionRanges{
2342 {Line: 7, FirstColumn: 9, LastColumn: 15},
2343 },
2344 },
2345 },
2346 For: &parser.YamlDuration{
2347 Raw: "5m",
2348 Value: 5 * time.Minute,
2349 Pos: diags.PositionRanges{
2350 {Line: 3, FirstColumn: 8, LastColumn: 9},
2351 },
2352 },
2353 Labels: &parser.YamlMap{
2354 Key: &parser.YamlNode{
2355 Value: "labels",
2356 Pos: diags.PositionRanges{
2357 {Line: 4, FirstColumn: 3, LastColumn: 8},
2358 },
2359 },
2360 Items: []*parser.YamlKeyValue{
2361 {
2362 Key: &parser.YamlNode{
2363 Value: "severity",
2364 Pos: diags.PositionRanges{
2365 {Line: 5, FirstColumn: 5, LastColumn: 12},
2366 },
2367 },
2368 Value: &parser.YamlNode{
2369 Value: "warning",
2370 Pos: diags.PositionRanges{
2371 {Line: 5, FirstColumn: 15, LastColumn: 21},
2372 },
2373 },
2374 },
2375 },
2376 },
2377 },
2378 },
2379 },
2380 },
2381 },
2382 },
2383 },
2384 {
2385 // Merge key with local override - local for value takes precedence over merged for
2386 input: []byte(`
2387- &defaults
2388 for: 5m
2389 labels:
2390 severity: warning
2391- alert: foo
2392 expr: up == 0
2393 for: 10m
2394 <<: *defaults
2395`),
2396 output: parser.File{
2397 IsRelaxed: true,
2398 Groups: []parser.Group{
2399 {
2400 Rules: []parser.Rule{
2401 {
2402 Lines: diags.LineRange{First: 4, Last: 8},
2403 AlertingRule: &parser.AlertingRule{
2404 Alert: parser.YamlNode{
2405 Value: "foo",
2406 Pos: diags.PositionRanges{
2407 {Line: 6, FirstColumn: 10, LastColumn: 12},
2408 },
2409 },
2410 Expr: parser.PromQLExpr{
2411 Value: &parser.YamlNode{
2412 Value: "up == 0",
2413 Pos: diags.PositionRanges{
2414 {Line: 7, FirstColumn: 9, LastColumn: 15},
2415 },
2416 },
2417 },
2418 For: &parser.YamlDuration{
2419 Raw: "10m",
2420 Value: 10 * time.Minute,
2421 Pos: diags.PositionRanges{
2422 {Line: 8, FirstColumn: 8, LastColumn: 10},
2423 },
2424 },
2425 Labels: &parser.YamlMap{
2426 Key: &parser.YamlNode{
2427 Value: "labels",
2428 Pos: diags.PositionRanges{
2429 {Line: 4, FirstColumn: 3, LastColumn: 8},
2430 },
2431 },
2432 Items: []*parser.YamlKeyValue{
2433 {
2434 Key: &parser.YamlNode{
2435 Value: "severity",
2436 Pos: diags.PositionRanges{
2437 {Line: 5, FirstColumn: 5, LastColumn: 12},
2438 },
2439 },
2440 Value: &parser.YamlNode{
2441 Value: "warning",
2442 Pos: diags.PositionRanges{
2443 {Line: 5, FirstColumn: 15, LastColumn: 21},
2444 },
2445 },
2446 },
2447 },
2448 },
2449 },
2450 },
2451 },
2452 },
2453 },
2454 },
2455 },
2456 {
2457 // Sequence merge key <<: [*a, *b] merges defaults from both aliases
2458 input: []byte(`
2459- &defaults_a
2460 for: 5m
2461- &defaults_b
2462 labels:
2463 severity: warning
2464- alert: foo
2465 expr: up == 0
2466 <<: [*defaults_a, *defaults_b]
2467`),
2468 output: parser.File{
2469 IsRelaxed: true,
2470 Groups: []parser.Group{
2471 {
2472 Rules: []parser.Rule{
2473 {
2474 Lines: diags.LineRange{First: 3, Last: 8},
2475 AlertingRule: &parser.AlertingRule{
2476 Alert: parser.YamlNode{
2477 Value: "foo",
2478 Pos: diags.PositionRanges{
2479 {Line: 7, FirstColumn: 10, LastColumn: 12},
2480 },
2481 },
2482 Expr: parser.PromQLExpr{
2483 Value: &parser.YamlNode{
2484 Value: "up == 0",
2485 Pos: diags.PositionRanges{
2486 {Line: 8, FirstColumn: 9, LastColumn: 15},
2487 },
2488 },
2489 },
2490 For: &parser.YamlDuration{
2491 Raw: "5m",
2492 Value: 5 * time.Minute,
2493 Pos: diags.PositionRanges{
2494 {Line: 3, FirstColumn: 8, LastColumn: 9},
2495 },
2496 },
2497 Labels: &parser.YamlMap{
2498 Key: &parser.YamlNode{
2499 Value: "labels",
2500 Pos: diags.PositionRanges{
2501 {Line: 5, FirstColumn: 3, LastColumn: 8},
2502 },
2503 },
2504 Items: []*parser.YamlKeyValue{
2505 {
2506 Key: &parser.YamlNode{
2507 Value: "severity",
2508 Pos: diags.PositionRanges{
2509 {Line: 6, FirstColumn: 5, LastColumn: 12},
2510 },
2511 },
2512 Value: &parser.YamlNode{
2513 Value: "warning",
2514 Pos: diags.PositionRanges{
2515 {Line: 6, FirstColumn: 15, LastColumn: 21},
2516 },
2517 },
2518 },
2519 },
2520 },
2521 },
2522 },
2523 },
2524 },
2525 },
2526 },
2527 },
2528 {
2529 input: []byte(`
2530- record: invalid metric name
2531 expr: bar
2532`),
2533 output: parser.File{
2534 IsRelaxed: true,
2535 Groups: []parser.Group{
2536 {
2537 Rules: []parser.Rule{
2538 {
2539 Lines: diags.LineRange{First: 2, Last: 3},
2540 Error: parser.ParseError{Err: errors.New("invalid recording rule name: invalid metric name"), Line: 2},
2541 },
2542 },
2543 },
2544 },
2545 },
2546 },
2547 {
2548 input: []byte(`
2549- record: utf-8 enabled name
2550 expr: bar
2551 labels:
2552 "a b c": bar
2553`),
2554 names: model.UTF8Validation,
2555 output: parser.File{
2556 IsRelaxed: true,
2557 Groups: []parser.Group{
2558 {
2559 Rules: []parser.Rule{
2560 {
2561 Lines: diags.LineRange{First: 2, Last: 5},
2562 RecordingRule: &parser.RecordingRule{
2563 Record: parser.YamlNode{
2564 Value: "utf-8 enabled name",
2565 Pos: diags.PositionRanges{
2566 {Line: 2, FirstColumn: 11, LastColumn: 28},
2567 },
2568 },
2569 Expr: parser.PromQLExpr{
2570 Value: &parser.YamlNode{
2571 Value: "bar",
2572 Pos: diags.PositionRanges{
2573 {Line: 3, FirstColumn: 9, LastColumn: 11},
2574 },
2575 },
2576 },
2577 Labels: &parser.YamlMap{
2578 Key: &parser.YamlNode{
2579 Value: "labels",
2580 Pos: diags.PositionRanges{
2581 {Line: 4, FirstColumn: 3, LastColumn: 8},
2582 },
2583 },
2584 Items: []*parser.YamlKeyValue{
2585 {
2586 Key: &parser.YamlNode{
2587 Value: "a b c",
2588 Pos: diags.PositionRanges{
2589 {Line: 5, FirstColumn: 6, LastColumn: 10},
2590 },
2591 },
2592 Value: &parser.YamlNode{
2593 Value: "bar",
2594 Pos: diags.PositionRanges{
2595 {Line: 5, FirstColumn: 14, LastColumn: 16},
2596 },
2597 },
2598 },
2599 },
2600 },
2601 },
2602 },
2603 },
2604 },
2605 },
2606 },
2607 },
2608 {
2609 input: []byte(`
2610- record: foo
2611 expr: bar
2612 labels:
2613 "foo bar": yes
2614`),
2615 output: parser.File{
2616 IsRelaxed: true,
2617 Groups: []parser.Group{
2618 {
2619 Rules: []parser.Rule{
2620 {
2621 Lines: diags.LineRange{First: 2, Last: 5},
2622 Error: parser.ParseError{Err: errors.New("invalid label name: foo bar"), Line: 5},
2623 },
2624 },
2625 },
2626 },
2627 },
2628 },
2629 {
2630 input: []byte(`
2631- alert: foo
2632 expr: bar
2633 labels:
2634 "foo bar": yes
2635`),
2636 output: parser.File{
2637 IsRelaxed: true,
2638 Groups: []parser.Group{
2639 {
2640 Rules: []parser.Rule{
2641 {
2642 Lines: diags.LineRange{First: 2, Last: 5},
2643 Error: parser.ParseError{Err: errors.New("invalid label name: foo bar"), Line: 5},
2644 },
2645 },
2646 },
2647 },
2648 },
2649 },
2650 {
2651 input: []byte(`
2652- alert: foo
2653 expr: bar
2654 labels:
2655 "{{ $value }}": yes
2656`),
2657 output: parser.File{
2658 IsRelaxed: true,
2659 Groups: []parser.Group{
2660 {
2661 Rules: []parser.Rule{
2662 {
2663 Lines: diags.LineRange{First: 2, Last: 5},
2664 Error: parser.ParseError{Err: errors.New("invalid label name: {{ $value }}"), Line: 5},
2665 },
2666 },
2667 },
2668 },
2669 },
2670 },
2671 {
2672 input: []byte(`
2673- alert: foo
2674 expr: bar
2675 annotations:
2676 "foo bar": yes
2677`),
2678 output: parser.File{
2679 IsRelaxed: true,
2680 Groups: []parser.Group{
2681 {
2682 Rules: []parser.Rule{
2683 {
2684 Lines: diags.LineRange{First: 2, Last: 5},
2685 Error: parser.ParseError{Err: errors.New("invalid annotation name: foo bar"), Line: 5},
2686 },
2687 },
2688 },
2689 },
2690 },
2691 },
2692 {
2693 input: []byte(`
2694- alert: foo
2695 expr: bar
2696 labels:
2697 foo: ` + string("\xed\xbf\xbf")),
2698 // Label values are invalid only if they aren't valid UTF-8 strings
2699 // which also makes them unparsable by YAML.
2700 output: parser.File{
2701 IsRelaxed: true,
2702 Error: parser.ParseError{
2703 Err: errors.New("yaml: invalid Unicode character"),
2704 Line: 1,
2705 },
2706 },
2707 },
2708 {
2709 input: []byte(`
2710- alert: foo
2711 expr: bar
2712 annotations:
2713 "{{ $value }}": yes
2714`),
2715 output: parser.File{
2716 IsRelaxed: true,
2717 Groups: []parser.Group{
2718 {
2719 Rules: []parser.Rule{
2720 {
2721 Lines: diags.LineRange{First: 2, Last: 5},
2722 Error: parser.ParseError{Err: errors.New("invalid annotation name: {{ $value }}"), Line: 5},
2723 },
2724 },
2725 },
2726 },
2727 },
2728 },
2729 {
2730 input: []byte(`
2731- record: foo
2732 expr: bar
2733 keep_firing_for: 5m
2734`),
2735 output: parser.File{
2736 IsRelaxed: true,
2737 Groups: []parser.Group{
2738 {
2739 Rules: []parser.Rule{
2740 {
2741 Lines: diags.LineRange{First: 2, Last: 4},
2742 Error: parser.ParseError{Err: errors.New("invalid field 'keep_firing_for' in recording rule"), Line: 4},
2743 },
2744 },
2745 },
2746 },
2747 },
2748 },
2749 {
2750 input: []byte(`
2751- record: foo
2752 expr: bar
2753 for: 5m
2754`),
2755 output: parser.File{
2756 IsRelaxed: true,
2757 Groups: []parser.Group{
2758 {
2759 Rules: []parser.Rule{
2760 {
2761 Lines: diags.LineRange{First: 2, Last: 4},
2762 Error: parser.ParseError{Err: errors.New("invalid field 'for' in recording rule"), Line: 4},
2763 },
2764 },
2765 },
2766 },
2767 },
2768 },
2769 {
2770 input: []byte(`
2771- record: foo
2772 expr: bar
2773 annotations:
2774 foo: bar
2775`),
2776 output: parser.File{
2777 IsRelaxed: true,
2778 Groups: []parser.Group{
2779 {
2780 Rules: []parser.Rule{
2781 {
2782 Lines: diags.LineRange{First: 2, Last: 5},
2783 Error: parser.ParseError{Err: errors.New("invalid field 'annotations' in recording rule"), Line: 4},
2784 },
2785 },
2786 },
2787 },
2788 },
2789 },
2790 // Tag tests
2791 {
2792 input: []byte(`
2793- record: 5
2794 expr: bar
2795`),
2796 output: parser.File{
2797 IsRelaxed: true,
2798 Groups: []parser.Group{
2799 {
2800 Rules: []parser.Rule{
2801 {
2802 Lines: diags.LineRange{First: 2, Last: 3},
2803 Error: parser.ParseError{Err: errors.New("record value must be a string, got integer instead"), Line: 2},
2804 },
2805 },
2806 },
2807 },
2808 },
2809 },
2810 {
2811 input: []byte(`
2812- alert: 5
2813 expr: bar
2814`),
2815 output: parser.File{
2816 IsRelaxed: true,
2817 Groups: []parser.Group{
2818 {
2819 Rules: []parser.Rule{
2820 {
2821 Lines: diags.LineRange{First: 2, Last: 3},
2822 Error: parser.ParseError{Err: errors.New("alert value must be a string, got integer instead"), Line: 2},
2823 },
2824 },
2825 },
2826 },
2827 },
2828 },
2829 {
2830 input: []byte(`
2831- record: foo
2832 expr: 5
2833`),
2834 output: parser.File{
2835 IsRelaxed: true,
2836 Groups: []parser.Group{
2837 {
2838 Rules: []parser.Rule{
2839 {
2840 Lines: diags.LineRange{First: 2, Last: 3},
2841 Error: parser.ParseError{Err: errors.New("expr value must be a string, got integer instead"), Line: 3},
2842 },
2843 },
2844 },
2845 },
2846 },
2847 },
2848 {
2849 input: []byte(`
2850- alert: foo
2851 expr: bar
2852 for: 5
2853`),
2854 output: parser.File{
2855 IsRelaxed: true,
2856 Groups: []parser.Group{
2857 {
2858 Rules: []parser.Rule{
2859 {
2860 Lines: diags.LineRange{First: 2, Last: 4},
2861 Error: parser.ParseError{Err: errors.New("for value must be a string, got integer instead"), Line: 4},
2862 },
2863 },
2864 },
2865 },
2866 },
2867 },
2868 {
2869 input: []byte(`
2870- alert: foo
2871 expr: bar
2872 keep_firing_for: 5
2873`),
2874 output: parser.File{
2875 IsRelaxed: true,
2876 Groups: []parser.Group{
2877 {
2878 Rules: []parser.Rule{
2879 {
2880 Lines: diags.LineRange{First: 2, Last: 4},
2881 Error: parser.ParseError{Err: errors.New("keep_firing_for value must be a string, got integer instead"), Line: 4},
2882 },
2883 },
2884 },
2885 },
2886 },
2887 },
2888 {
2889 input: []byte(`
2890- record: foo
2891 expr: bar
2892 labels: []
2893`),
2894 output: parser.File{
2895 IsRelaxed: true,
2896 Groups: []parser.Group{
2897 {
2898 Rules: []parser.Rule{
2899 {
2900 Lines: diags.LineRange{First: 2, Last: 4},
2901 Error: parser.ParseError{Err: errors.New("labels value must be a mapping, got list instead"), Line: 4},
2902 },
2903 },
2904 },
2905 },
2906 },
2907 },
2908 {
2909 input: []byte(`
2910- alert: foo
2911 expr: bar
2912 labels: {}
2913 annotations: []
2914`),
2915 output: parser.File{
2916 IsRelaxed: true,
2917 Groups: []parser.Group{
2918 {
2919 Rules: []parser.Rule{
2920 {
2921 Lines: diags.LineRange{First: 2, Last: 5},
2922 Error: parser.ParseError{Err: errors.New("annotations value must be a mapping, got list instead"), Line: 5},
2923 },
2924 },
2925 },
2926 },
2927 },
2928 },
2929 {
2930 input: []byte(`
2931- alert: foo
2932 expr: bar
2933 labels:
2934 foo: 3
2935 annotations:
2936 bar: "5"
2937`),
2938 output: parser.File{
2939 IsRelaxed: true,
2940 Groups: []parser.Group{
2941 {
2942 Rules: []parser.Rule{
2943 {
2944 Lines: diags.LineRange{First: 2, Last: 7},
2945 Error: parser.ParseError{Err: errors.New("labels foo value must be a string, got integer instead"), Line: 5},
2946 },
2947 },
2948 },
2949 },
2950 },
2951 },
2952 {
2953 input: []byte(`
2954- alert: foo
2955 expr: bar
2956 labels: {}
2957 annotations:
2958 foo: "3"
2959 bar: 5
2960`),
2961 output: parser.File{
2962 IsRelaxed: true,
2963 Groups: []parser.Group{
2964 {
2965 Rules: []parser.Rule{
2966 {
2967 Lines: diags.LineRange{First: 2, Last: 7},
2968 Error: parser.ParseError{Err: errors.New("annotations bar value must be a string, got integer instead"), Line: 7},
2969 },
2970 },
2971 },
2972 },
2973 },
2974 },
2975 {
2976 input: []byte(`
2977- record: foo
2978 expr: bar
2979 labels: 4
2980`),
2981 output: parser.File{
2982 IsRelaxed: true,
2983 Groups: []parser.Group{
2984 {
2985 Rules: []parser.Rule{
2986 {
2987 Lines: diags.LineRange{First: 2, Last: 4},
2988 Error: parser.ParseError{Err: errors.New("labels value must be a mapping, got integer instead"), Line: 4},
2989 },
2990 },
2991 },
2992 },
2993 },
2994 },
2995 {
2996 input: []byte(`
2997- record: foo
2998 expr: bar
2999 labels: true
3000`),
3001 output: parser.File{
3002 IsRelaxed: true,
3003 Groups: []parser.Group{
3004 {
3005 Rules: []parser.Rule{
3006 {
3007 Lines: diags.LineRange{First: 2, Last: 4},
3008 Error: parser.ParseError{Err: errors.New("labels value must be a mapping, got bool instead"), Line: 4},
3009 },
3010 },
3011 },
3012 },
3013 },
3014 },
3015 {
3016 input: []byte(`
3017- record: foo
3018 expr: bar
3019 labels: null
3020`),
3021 output: parser.File{
3022 IsRelaxed: true,
3023 Groups: []parser.Group{
3024 {
3025 Rules: []parser.Rule{
3026 {
3027 Lines: diags.LineRange{First: 2, Last: 4},
3028 RecordingRule: &parser.RecordingRule{
3029 Record: parser.YamlNode{
3030 Value: "foo",
3031 Pos: diags.PositionRanges{
3032 {Line: 2, FirstColumn: 11, LastColumn: 13},
3033 },
3034 },
3035 Expr: parser.PromQLExpr{
3036 Value: &parser.YamlNode{
3037 Value: "bar",
3038 Pos: diags.PositionRanges{
3039 {Line: 3, FirstColumn: 9, LastColumn: 11},
3040 },
3041 },
3042 },
3043 Labels: &parser.YamlMap{
3044 Key: &parser.YamlNode{
3045 Value: "labels",
3046 Pos: diags.PositionRanges{
3047 {Line: 4, FirstColumn: 3, LastColumn: 8},
3048 },
3049 },
3050 },
3051 },
3052 },
3053 },
3054 },
3055 },
3056 },
3057 },
3058 {
3059 input: []byte(`
3060- record: true
3061 expr: bar
3062`),
3063 output: parser.File{
3064 IsRelaxed: true,
3065 Groups: []parser.Group{
3066 {
3067 Rules: []parser.Rule{
3068 {
3069 Lines: diags.LineRange{First: 2, Last: 3},
3070 Error: parser.ParseError{Err: errors.New("record value must be a string, got bool instead"), Line: 2},
3071 },
3072 },
3073 },
3074 },
3075 },
3076 },
3077 {
3078 input: []byte(`
3079- record:
3080 query: foo
3081 expr: bar
3082`),
3083 output: parser.File{
3084 IsRelaxed: true,
3085 Groups: []parser.Group{
3086 {
3087 Rules: []parser.Rule{
3088 {
3089 Lines: diags.LineRange{First: 2, Last: 4},
3090 Error: parser.ParseError{Err: errors.New("record value must be a string, got mapping instead"), Line: 3},
3091 },
3092 },
3093 },
3094 },
3095 },
3096 },
3097 {
3098 input: []byte(`
3099- record: foo
3100 expr: bar
3101 labels: some
3102`),
3103 output: parser.File{
3104 IsRelaxed: true,
3105 Groups: []parser.Group{
3106 {
3107 Rules: []parser.Rule{
3108 {
3109 Lines: diags.LineRange{First: 2, Last: 4},
3110 Error: parser.ParseError{Err: errors.New("labels value must be a mapping, got string instead"), Line: 4},
3111 },
3112 },
3113 },
3114 },
3115 },
3116 },
3117 {
3118 input: []byte(`
3119- record: foo
3120 expr: bar
3121 labels: !!binary "SGVsbG8sIFdvcmxkIQ=="
3122`),
3123 output: parser.File{
3124 IsRelaxed: true,
3125 Groups: []parser.Group{
3126 {
3127 Rules: []parser.Rule{
3128 {
3129 Lines: diags.LineRange{First: 2, Last: 4},
3130 Error: parser.ParseError{
3131 Err: errors.New("labels value must be a mapping, got binary data instead"),
3132 Line: 4,
3133 },
3134 },
3135 },
3136 },
3137 },
3138 },
3139 },
3140 {
3141 input: []byte(`
3142- alert: foo
3143 expr: bar
3144 for: 1.23
3145`),
3146 output: parser.File{
3147 IsRelaxed: true,
3148 Groups: []parser.Group{
3149 {
3150 Rules: []parser.Rule{
3151 {
3152 Lines: diags.LineRange{First: 2, Last: 4},
3153 Error: parser.ParseError{
3154 Err: errors.New("for value must be a string, got float instead"),
3155 Line: 4,
3156 },
3157 },
3158 },
3159 },
3160 },
3161 },
3162 },
3163 {
3164 input: []byte(`
3165- record: foo
3166 expr: bar
3167 labels: !!garbage "SGVsbG8sIFdvcmxkIQ=="
3168`),
3169 output: parser.File{
3170 IsRelaxed: true,
3171 Groups: []parser.Group{
3172 {
3173 Rules: []parser.Rule{
3174 {
3175 Lines: diags.LineRange{First: 2, Last: 4},
3176 Error: parser.ParseError{Err: errors.New("labels value must be a mapping, got garbage instead"), Line: 4},
3177 },
3178 },
3179 },
3180 },
3181 },
3182 },
3183 {
3184 input: []byte(`
3185- record: foo
3186 expr: bar
3187 labels: !! "SGVsbG8sIFdvcmxkIQ=="
3188`),
3189 output: parser.File{
3190 IsRelaxed: true,
3191 Error: parser.ParseError{
3192 Err: errors.New("did not find expected tag URI"),
3193 Line: 4,
3194 },
3195 },
3196 },
3197 {
3198 input: []byte(`
3199- record: &foo foo
3200 expr: bar
3201 labels: *foo
3202`),
3203 output: parser.File{
3204 IsRelaxed: true,
3205 Groups: []parser.Group{
3206 {
3207 Rules: []parser.Rule{
3208 {
3209 Lines: diags.LineRange{First: 2, Last: 4},
3210 Error: parser.ParseError{
3211 Err: errors.New("labels value must be a mapping, got string instead"),
3212 Line: 4,
3213 },
3214 },
3215 },
3216 },
3217 },
3218 },
3219 },
3220 // Multi-document tests
3221 {
3222 input: []byte(`---
3223- expr: foo
3224 record: foo
3225---
3226- expr: bar
3227`),
3228 output: parser.File{
3229 IsRelaxed: true,
3230 Groups: []parser.Group{
3231 {
3232 Rules: []parser.Rule{
3233 {
3234 Lines: diags.LineRange{First: 2, Last: 3},
3235 RecordingRule: &parser.RecordingRule{
3236 Record: parser.YamlNode{
3237 Value: "foo",
3238 Pos: diags.PositionRanges{
3239 {Line: 3, FirstColumn: 11, LastColumn: 13},
3240 },
3241 },
3242 Expr: parser.PromQLExpr{
3243 Value: &parser.YamlNode{
3244 Value: "foo",
3245 Pos: diags.PositionRanges{
3246 {Line: 2, FirstColumn: 9, LastColumn: 11},
3247 },
3248 },
3249 },
3250 },
3251 },
3252 },
3253 },
3254 {
3255 Rules: []parser.Rule{
3256 {
3257 Lines: diags.LineRange{First: 5, Last: 5},
3258 Error: parser.ParseError{
3259 Err: errors.New("incomplete rule, no alert or record key"),
3260 Line: 5,
3261 },
3262 },
3263 },
3264 },
3265 },
3266 },
3267 },
3268 {
3269 input: []byte(`---
3270- expr: foo
3271 record: foo
3272---
3273- expr: bar
3274 record: bar
3275 expr: bar
3276`),
3277 output: parser.File{
3278 IsRelaxed: true,
3279 Groups: []parser.Group{
3280 {
3281 Rules: []parser.Rule{
3282 {
3283 Lines: diags.LineRange{First: 2, Last: 3},
3284 RecordingRule: &parser.RecordingRule{
3285 Record: parser.YamlNode{
3286 Value: "foo",
3287 Pos: diags.PositionRanges{
3288 {Line: 3, FirstColumn: 11, LastColumn: 13},
3289 },
3290 },
3291 Expr: parser.PromQLExpr{
3292 Value: &parser.YamlNode{
3293 Value: "foo",
3294 Pos: diags.PositionRanges{
3295 {Line: 2, FirstColumn: 9, LastColumn: 11},
3296 },
3297 },
3298 },
3299 },
3300 },
3301 },
3302 },
3303 {
3304 Rules: []parser.Rule{
3305 {
3306 Lines: diags.LineRange{First: 5, Last: 7},
3307 Error: parser.ParseError{Err: errors.New("duplicated expr key"), Line: 7},
3308 },
3309 },
3310 },
3311 },
3312 },
3313 },
3314 {
3315 input: []byte(`---
3316- expr: foo
3317 record: foo
3318---
3319- expr: bar
3320 alert: foo
3321`),
3322 output: parser.File{
3323 IsRelaxed: true,
3324 Groups: []parser.Group{
3325 {
3326 Rules: []parser.Rule{
3327 {
3328 Lines: diags.LineRange{First: 2, Last: 3},
3329 RecordingRule: &parser.RecordingRule{
3330 Record: parser.YamlNode{
3331 Value: "foo",
3332 Pos: diags.PositionRanges{
3333 {Line: 3, FirstColumn: 11, LastColumn: 13},
3334 },
3335 },
3336 Expr: parser.PromQLExpr{
3337 Value: &parser.YamlNode{
3338 Value: "foo",
3339 Pos: diags.PositionRanges{
3340 {Line: 2, FirstColumn: 9, LastColumn: 11},
3341 },
3342 },
3343 },
3344 },
3345 },
3346 },
3347 },
3348 {
3349 Rules: []parser.Rule{
3350 {
3351 Lines: diags.LineRange{First: 5, Last: 6},
3352 AlertingRule: &parser.AlertingRule{
3353 Alert: parser.YamlNode{
3354 Value: "foo",
3355 Pos: diags.PositionRanges{
3356 {Line: 6, FirstColumn: 10, LastColumn: 12},
3357 },
3358 },
3359 Expr: parser.PromQLExpr{
3360 Value: &parser.YamlNode{
3361 Value: "bar",
3362 Pos: diags.PositionRanges{
3363 {Line: 5, FirstColumn: 9, LastColumn: 11},
3364 },
3365 },
3366 },
3367 },
3368 },
3369 },
3370 },
3371 },
3372 },
3373 },
3374 {
3375 input: []byte(`---
3376groups:
3377- name: v1
3378 rules:
3379 - record: up:count
3380 expr: count(up)
3381 labels:
3382 foo:
3383 bar: foo
3384`),
3385 output: parser.File{
3386 IsRelaxed: true,
3387 Groups: []parser.Group{
3388 {
3389 Name: parser.YamlNode{
3390 Value: "v1",
3391 Pos: diags.PositionRanges{
3392 {Line: 3, FirstColumn: 9, LastColumn: 10},
3393 },
3394 },
3395 Rules: []parser.Rule{
3396 {
3397 Lines: diags.LineRange{First: 5, Last: 9},
3398 Error: parser.ParseError{
3399 Err: errors.New("labels foo value must be a string, got mapping instead"),
3400 Line: 9,
3401 },
3402 },
3403 },
3404 },
3405 },
3406 },
3407 },
3408 {
3409 input: []byte(`
3410groups:
3411- name: v1
3412 rules:
3413 - record: up:count
3414 expr: count(up)
3415`),
3416 strict: true,
3417 output: parser.File{
3418 Groups: []parser.Group{
3419 {
3420 Name: parser.YamlNode{
3421 Value: "v1",
3422 Pos: diags.PositionRanges{
3423 {Line: 3, FirstColumn: 9, LastColumn: 10},
3424 },
3425 },
3426 Rules: []parser.Rule{
3427 {
3428 Lines: diags.LineRange{First: 5, Last: 6},
3429 RecordingRule: &parser.RecordingRule{
3430 Record: parser.YamlNode{
3431 Value: "up:count",
3432 Pos: diags.PositionRanges{
3433 {Line: 5, FirstColumn: 13, LastColumn: 20},
3434 },
3435 },
3436 Expr: parser.PromQLExpr{
3437 Value: &parser.YamlNode{
3438 Value: "count(up)",
3439 Pos: diags.PositionRanges{
3440 {Line: 6, FirstColumn: 11, LastColumn: 19},
3441 },
3442 },
3443 },
3444 },
3445 },
3446 },
3447 },
3448 },
3449 },
3450 },
3451 {
3452 input: []byte(`
3453groups:
3454- name: v1
3455 rules:
3456 - record: up:count
3457`),
3458 strict: true,
3459 output: parser.File{
3460 Groups: []parser.Group{
3461 {
3462 Name: parser.YamlNode{
3463 Value: "v1",
3464 Pos: diags.PositionRanges{
3465 {Line: 3, FirstColumn: 9, LastColumn: 10},
3466 },
3467 },
3468 Rules: []parser.Rule{
3469 {
3470 Lines: diags.LineRange{First: 5, Last: 5},
3471 Error: parser.ParseError{
3472 Err: errors.New("missing expr key"),
3473 Line: 5,
3474 },
3475 },
3476 },
3477 },
3478 },
3479 },
3480 },
3481 {
3482 input: []byte(`
3483- record: up:count
3484 expr: count(up)
3485`),
3486 strict: true,
3487 output: parser.File{
3488 Error: parser.ParseError{
3489 Err: errors.New("top level field must be a groups key, got list"),
3490 Line: 2,
3491 },
3492 },
3493 },
3494 {
3495 input: []byte(`
3496rules:
3497 - record: up:count
3498 expr: count(up)
3499`),
3500 strict: true,
3501 output: parser.File{
3502 Error: parser.ParseError{
3503 Err: errors.New("unexpected key rules"),
3504 Line: 2,
3505 },
3506 },
3507 },
3508 {
3509 input: []byte(`
3510groups:
3511 - record: up:count
3512 expr: count(up)
3513`),
3514 strict: true,
3515 output: parser.File{
3516 Groups: []parser.Group{
3517 {
3518 Error: parser.ParseError{
3519 Err: errors.New("invalid group key record"),
3520 Line: 3,
3521 },
3522 },
3523 },
3524 },
3525 },
3526 {
3527 input: []byte(`
3528groups:
3529- rules:
3530 - record: up:count
3531 expr: count(up)
3532`),
3533 strict: true,
3534 output: parser.File{
3535 Groups: []parser.Group{
3536 {
3537 Error: parser.ParseError{
3538 Err: errors.New("incomplete group definition, name is required and must be set"),
3539 Line: 3,
3540 },
3541 Rules: []parser.Rule{
3542 {
3543 Lines: diags.LineRange{First: 4, Last: 5},
3544 RecordingRule: &parser.RecordingRule{
3545 Record: parser.YamlNode{
3546 Value: "up:count",
3547 Pos: diags.PositionRanges{
3548 {Line: 4, FirstColumn: 13, LastColumn: 20},
3549 },
3550 },
3551 Expr: parser.PromQLExpr{
3552 Value: &parser.YamlNode{
3553 Value: "count(up)",
3554 Pos: diags.PositionRanges{
3555 {Line: 5, FirstColumn: 11, LastColumn: 19},
3556 },
3557 },
3558 },
3559 },
3560 },
3561 },
3562 },
3563 },
3564 },
3565 },
3566 {
3567 input: []byte(`
3568groups:
3569- name: foo
3570`),
3571 strict: true,
3572 output: parser.File{
3573 Groups: []parser.Group{
3574 {
3575 Name: parser.YamlNode{
3576 Value: "foo",
3577 Pos: diags.PositionRanges{
3578 {Line: 3, FirstColumn: 9, LastColumn: 11},
3579 },
3580 },
3581 },
3582 },
3583 },
3584 },
3585 {
3586 input: []byte(`
3587groups: {}
3588`),
3589 strict: true,
3590 output: parser.File{
3591 Error: parser.ParseError{
3592 Err: errors.New("groups value must be a list, got mapping"),
3593 Line: 2,
3594 },
3595 },
3596 },
3597 {
3598 input: []byte(`
3599groups:
3600- name: []
3601`),
3602 strict: true,
3603 output: parser.File{
3604 Groups: []parser.Group{
3605 {
3606 Error: parser.ParseError{
3607 Err: errors.New("group name must be a string, got list"),
3608 Line: 3,
3609 },
3610 },
3611 },
3612 },
3613 },
3614 {
3615 input: []byte(`
3616groups:
3617- name: foo
3618 name: bar
3619 name: bob
3620`),
3621 strict: true,
3622 output: parser.File{
3623 Groups: []parser.Group{
3624 {
3625 Name: parser.YamlNode{
3626 Value: "bar",
3627 Pos: diags.PositionRanges{
3628 {Line: 4, FirstColumn: 9, LastColumn: 11},
3629 },
3630 },
3631 Error: parser.ParseError{
3632 Err: errors.New("duplicated key name"),
3633 Line: 4,
3634 },
3635 },
3636 },
3637 },
3638 },
3639 {
3640 input: []byte(`
3641groups:
3642- name: v1
3643 rules:
3644 rules:
3645 - record: up:count
3646 expr: count(up)
3647`),
3648 strict: true,
3649 output: parser.File{
3650 Groups: []parser.Group{
3651 {
3652 Name: parser.YamlNode{
3653 Value: "v1",
3654 Pos: diags.PositionRanges{
3655 {Line: 3, FirstColumn: 9, LastColumn: 10},
3656 },
3657 },
3658 Error: parser.ParseError{
3659 Err: errors.New("rules must be a list, got mapping"),
3660 Line: 4,
3661 },
3662 },
3663 },
3664 },
3665 },
3666 {
3667 input: []byte(`
3668groups:
3669- name: v1
3670 rules:
3671 - rules:
3672 - record: up:count
3673 expr: count(up)
3674`),
3675 strict: true,
3676 output: parser.File{
3677 Groups: []parser.Group{
3678 {
3679 Name: parser.YamlNode{
3680 Value: "v1",
3681 Pos: diags.PositionRanges{
3682 {Line: 3, FirstColumn: 9, LastColumn: 10},
3683 },
3684 },
3685 Rules: []parser.Rule{
3686 {
3687 Error: parser.ParseError{
3688 Err: errors.New("invalid rule key rules"),
3689 Line: 5,
3690 },
3691 },
3692 },
3693 },
3694 },
3695 },
3696 },
3697 {
3698 input: []byte(`
3699groups:
3700- name: v1
3701 rules:
3702 - rules:
3703 - record: up:count
3704 expr: count(up)
3705`),
3706 strict: true,
3707 output: parser.File{
3708 Error: parser.ParseError{
3709 Err: errors.New("found a tab character that violates indentation"),
3710 Line: 6,
3711 },
3712 },
3713 },
3714 {
3715 input: []byte(`
3716---
3717groups:
3718- name: v1
3719 rules:
3720 - record: up:count
3721 expr: count(up)
3722---
3723groups:
3724- name: v1
3725 rules:
3726 - rules:
3727 - record: up:count
3728 expr: count(up)
3729`),
3730 strict: true,
3731 output: parser.File{
3732 Groups: []parser.Group{
3733 {
3734 Name: parser.YamlNode{
3735 Value: "v1",
3736 Pos: diags.PositionRanges{
3737 {Line: 4, FirstColumn: 9, LastColumn: 10},
3738 },
3739 },
3740 Rules: []parser.Rule{
3741 {
3742 Lines: diags.LineRange{First: 6, Last: 7},
3743 RecordingRule: &parser.RecordingRule{
3744 Record: parser.YamlNode{
3745 Value: "up:count",
3746 Pos: diags.PositionRanges{
3747 {Line: 6, FirstColumn: 15, LastColumn: 22},
3748 },
3749 },
3750 Expr: parser.PromQLExpr{
3751 Value: &parser.YamlNode{
3752 Value: "count(up)",
3753 Pos: diags.PositionRanges{
3754 {Line: 7, FirstColumn: 13, LastColumn: 21},
3755 },
3756 },
3757 },
3758 },
3759 },
3760 },
3761 },
3762 {
3763 Name: parser.YamlNode{
3764 Value: "v1",
3765 Pos: diags.PositionRanges{
3766 {Line: 10, FirstColumn: 9, LastColumn: 10},
3767 },
3768 },
3769 Rules: []parser.Rule{
3770 {
3771 Error: parser.ParseError{
3772 Err: errors.New("invalid rule key rules"),
3773 Line: 12,
3774 },
3775 },
3776 },
3777 },
3778 },
3779 Error: parser.ParseError{
3780 Line: 8,
3781 Err: errors.New("multi-document YAML files are not allowed"),
3782 },
3783 },
3784 },
3785 {
3786 input: []byte(`
3787---
3788groups: []
3789---
3790groups:
3791- name: foo
3792 rules:
3793 - labels: !!binary "SGVsbG8sIFdvcmxkIQ=="
3794 record: foo
3795 expr: foo
3796`),
3797 strict: true,
3798 output: parser.File{
3799 Groups: []parser.Group{
3800 {
3801 Name: parser.YamlNode{
3802 Value: "foo",
3803 Pos: diags.PositionRanges{
3804 {Line: 6, FirstColumn: 9, LastColumn: 11},
3805 },
3806 },
3807 Rules: []parser.Rule{
3808 {
3809 Lines: diags.LineRange{First: 8, Last: 10},
3810 Error: parser.ParseError{
3811 Line: 8,
3812 Err: errors.New("labels value must be a mapping, got binary data instead"),
3813 },
3814 },
3815 },
3816 },
3817 },
3818 Error: parser.ParseError{
3819 Line: 4,
3820 Err: errors.New("multi-document YAML files are not allowed"),
3821 },
3822 },
3823 },
3824 {
3825 input: []byte("[]"),
3826 strict: true,
3827 output: parser.File{
3828 Error: parser.ParseError{
3829 Err: errors.New("top level field must be a groups key, got list"),
3830 Line: 1,
3831 },
3832 },
3833 },
3834 {
3835 input: []byte("\n\n[]"),
3836 strict: true,
3837 output: parser.File{
3838 Error: parser.ParseError{
3839 Err: errors.New("top level field must be a groups key, got list"),
3840 Line: 3,
3841 },
3842 },
3843 },
3844 {
3845 input: []byte("groups: {}"),
3846 strict: true,
3847 output: parser.File{
3848 Error: parser.ParseError{
3849 Err: errors.New("groups value must be a list, got mapping"),
3850 Line: 1,
3851 },
3852 },
3853 },
3854 {
3855 input: []byte("groups: []"),
3856 strict: true,
3857 },
3858 {
3859 input: []byte("xgroups: {}"),
3860 strict: true,
3861 output: parser.File{
3862 Error: parser.ParseError{
3863 Err: errors.New("unexpected key xgroups"),
3864 Line: 1,
3865 },
3866 },
3867 },
3868 {
3869 input: []byte("\nbob\n"),
3870 strict: true,
3871 output: parser.File{
3872 Error: parser.ParseError{
3873 Err: errors.New("top level field must be a groups key, got string"),
3874 Line: 2,
3875 },
3876 },
3877 },
3878 {
3879 input: []byte(`groups: []
3880
3881rules: []
3882`),
3883 strict: true,
3884 output: parser.File{
3885 Error: parser.ParseError{
3886 Err: errors.New("unexpected key rules"),
3887 Line: 3,
3888 },
3889 },
3890 },
3891 {
3892 input: []byte(`
3893groups:
3894- name: foo
3895 rules: []
3896`),
3897 strict: true,
3898 output: parser.File{
3899 Groups: []parser.Group{
3900 {
3901 Name: parser.YamlNode{
3902 Value: "foo",
3903 Pos: diags.PositionRanges{
3904 {Line: 3, FirstColumn: 9, LastColumn: 11},
3905 },
3906 },
3907 },
3908 },
3909 },
3910 },
3911 {
3912 input: []byte(`
3913groups:
3914- name:
3915 rules: []
3916`),
3917 strict: true,
3918 output: parser.File{
3919 Groups: []parser.Group{
3920 {
3921 Error: parser.ParseError{
3922 Err: errors.New("group name must be a string, got null"),
3923 Line: 3,
3924 },
3925 },
3926 },
3927 },
3928 },
3929 {
3930 // YAML alias on group name in strict mode is resolved correctly
3931 input: []byte(`
3932groups:
3933- name: &gname mygroup
3934 rules:
3935 - record: foo
3936 expr: sum(up)
3937- name: *gname
3938 rules:
3939 - record: bar
3940 expr: sum(down)
3941`),
3942 strict: true,
3943 output: parser.File{
3944 Error: parser.ParseError{
3945 Err: errors.New("duplicated group name"),
3946 Line: 7,
3947 },
3948 },
3949 },
3950 {
3951 // YAML alias on group interval in strict mode is resolved correctly
3952 input: []byte(`
3953groups:
3954- name: mygroup
3955 interval: &ivl 1m
3956 rules:
3957 - record: foo
3958 expr: sum(up)
3959`),
3960 strict: true,
3961 output: parser.File{
3962 Groups: []parser.Group{
3963 {
3964 Name: parser.YamlNode{
3965 Value: "mygroup",
3966 Pos: diags.PositionRanges{
3967 {Line: 3, FirstColumn: 9, LastColumn: 15},
3968 },
3969 },
3970 Interval: &parser.YamlDuration{
3971 Raw: "1m",
3972 Value: 1 * time.Minute,
3973 Pos: diags.PositionRanges{
3974 {Line: 4, FirstColumn: 18, LastColumn: 19},
3975 },
3976 },
3977 Rules: []parser.Rule{
3978 {
3979 Lines: diags.LineRange{First: 6, Last: 7},
3980 RecordingRule: &parser.RecordingRule{
3981 Record: parser.YamlNode{
3982 Value: "foo",
3983 Pos: diags.PositionRanges{
3984 {Line: 6, FirstColumn: 13, LastColumn: 15},
3985 },
3986 },
3987 Expr: parser.PromQLExpr{
3988 Value: &parser.YamlNode{
3989 Value: "sum(up)",
3990 Pos: diags.PositionRanges{
3991 {Line: 7, FirstColumn: 11, LastColumn: 17},
3992 },
3993 },
3994 },
3995 },
3996 },
3997 },
3998 },
3999 },
4000 },
4001 },
4002 {
4003 input: []byte(`
4004groups:
4005- name: foo
4006 rules:
4007 - record: foo
4008 expr: sum(up)
4009 labels:
4010 job: foo
4011`),
4012 strict: true,
4013 output: parser.File{
4014 Groups: []parser.Group{
4015 {
4016 Name: parser.YamlNode{
4017 Value: "foo",
4018 Pos: diags.PositionRanges{
4019 {Line: 3, FirstColumn: 9, LastColumn: 11},
4020 },
4021 },
4022 Rules: []parser.Rule{
4023 {
4024 Lines: diags.LineRange{First: 5, Last: 8},
4025 RecordingRule: &parser.RecordingRule{
4026 Record: parser.YamlNode{
4027 Value: "foo",
4028 Pos: diags.PositionRanges{
4029 {Line: 5, FirstColumn: 13, LastColumn: 15},
4030 },
4031 },
4032 Expr: parser.PromQLExpr{
4033 Value: &parser.YamlNode{
4034 Value: "sum(up)",
4035 Pos: diags.PositionRanges{
4036 {Line: 6, FirstColumn: 11, LastColumn: 17},
4037 },
4038 },
4039 },
4040 Labels: &parser.YamlMap{
4041 Key: &parser.YamlNode{
4042 Value: "labels",
4043 Pos: diags.PositionRanges{
4044 {Line: 7, FirstColumn: 5, LastColumn: 10},
4045 },
4046 },
4047 Items: []*parser.YamlKeyValue{
4048 {
4049 Key: &parser.YamlNode{
4050 Value: "job",
4051 Pos: diags.PositionRanges{
4052 {Line: 8, FirstColumn: 7, LastColumn: 9},
4053 },
4054 },
4055 Value: &parser.YamlNode{
4056 Value: "foo",
4057 Pos: diags.PositionRanges{
4058 {Line: 8, FirstColumn: 12, LastColumn: 14},
4059 },
4060 },
4061 },
4062 },
4063 },
4064 },
4065 },
4066 },
4067 },
4068 },
4069 },
4070 },
4071 {
4072 input: []byte(`
4073groups:
4074- name: foo
4075 rules:
4076 - record: foo
4077 expr: sum(up)
4078 xxx: 1
4079 labels:
4080 job: foo
4081`),
4082 strict: true,
4083 output: parser.File{
4084 Groups: []parser.Group{
4085 {
4086 Name: parser.YamlNode{
4087 Value: "foo",
4088 Pos: diags.PositionRanges{
4089 {Line: 3, FirstColumn: 9, LastColumn: 11},
4090 },
4091 },
4092 Rules: []parser.Rule{
4093 {
4094 Error: parser.ParseError{
4095 Err: errors.New("invalid rule key xxx"),
4096 Line: 7,
4097 },
4098 },
4099 },
4100 },
4101 },
4102 },
4103 },
4104 {
4105 input: []byte(`
4106groups:
4107- name: foo
4108 rules:
4109 record: foo
4110 expr: sum(up)
4111 xxx: 1
4112 labels:
4113 job: foo
4114`),
4115 strict: true,
4116 output: parser.File{
4117 Groups: []parser.Group{
4118 {
4119 Name: parser.YamlNode{
4120 Value: "foo",
4121 Pos: diags.PositionRanges{
4122 {Line: 3, FirstColumn: 9, LastColumn: 11},
4123 },
4124 },
4125 Error: parser.ParseError{
4126 Err: errors.New("rules must be a list, got mapping"),
4127 Line: 4,
4128 },
4129 },
4130 },
4131 },
4132 },
4133 {
4134 input: []byte(`
4135groups:
4136- name: foo
4137 rules:
4138 - record: foo
4139 expr: sum(up)
4140 labels:
4141 job:
4142 foo: bar
4143`),
4144 strict: true,
4145 output: parser.File{
4146 Groups: []parser.Group{
4147 {
4148 Name: parser.YamlNode{
4149 Value: "foo",
4150 Pos: diags.PositionRanges{
4151 {Line: 3, FirstColumn: 9, LastColumn: 11},
4152 },
4153 },
4154 Rules: []parser.Rule{
4155 {
4156 Lines: diags.LineRange{First: 5, Last: 9},
4157 Error: parser.ParseError{
4158 Line: 9,
4159 Err: errors.New("labels job value must be a string, got mapping instead"),
4160 },
4161 },
4162 },
4163 },
4164 },
4165 },
4166 },
4167 {
4168 input: []byte(`
4169groups:
4170- name: foo
4171 rules:
4172 - record: foo
4173 expr:
4174 sum: sum(up)
4175`),
4176 strict: true,
4177 output: parser.File{
4178 Groups: []parser.Group{
4179 {
4180 Name: parser.YamlNode{
4181 Value: "foo",
4182 Pos: diags.PositionRanges{
4183 {Line: 3, FirstColumn: 9, LastColumn: 11},
4184 },
4185 },
4186 Rules: []parser.Rule{
4187 {
4188 Lines: diags.LineRange{First: 5, Last: 7},
4189 Error: parser.ParseError{
4190 Line: 7,
4191 Err: errors.New("expr value must be a string, got mapping instead"),
4192 },
4193 },
4194 },
4195 },
4196 },
4197 },
4198 },
4199 {
4200 input: []byte(`
4201groups:
4202- name: foo
4203 rules: []
4204- name: foo
4205 rules: []
4206`),
4207 strict: true,
4208 output: parser.File{
4209 Error: parser.ParseError{
4210 Err: errors.New("duplicated group name"),
4211 Line: 5,
4212 },
4213 },
4214 },
4215 {
4216 input: []byte(`
4217groups:
4218- name: foo
4219 rules:
4220 - record: foo
4221 expr: sum(up)
4222 labels:
4223 foo: bob
4224 foo: bar
4225`),
4226 strict: true,
4227 output: parser.File{
4228 Groups: []parser.Group{
4229 {
4230 Name: parser.YamlNode{
4231 Value: "foo",
4232 Pos: diags.PositionRanges{
4233 {Line: 3, FirstColumn: 9, LastColumn: 11},
4234 },
4235 },
4236 Rules: []parser.Rule{
4237 {
4238 Lines: diags.LineRange{First: 8, Last: 9},
4239 Error: parser.ParseError{
4240 Line: 9,
4241 Err: errors.New("duplicated labels key foo"),
4242 },
4243 },
4244 },
4245 },
4246 },
4247 },
4248 },
4249 {
4250 input: []byte(`
4251groups:
4252- name: v2
4253 rules:
4254 - record: up:count
4255 expr: count(up)
4256 expr: sum(up)`),
4257 strict: true,
4258 output: parser.File{
4259 Groups: []parser.Group{
4260 {
4261 Name: parser.YamlNode{
4262 Value: "v2",
4263 Pos: diags.PositionRanges{
4264 {Line: 3, FirstColumn: 9, LastColumn: 10},
4265 },
4266 },
4267 Rules: []parser.Rule{
4268 {
4269 Lines: diags.LineRange{First: 5, Last: 7},
4270 Error: parser.ParseError{
4271 Line: 7,
4272 Err: errors.New("duplicated expr key"),
4273 },
4274 },
4275 },
4276 },
4277 },
4278 },
4279 },
4280 {
4281 input: []byte(`
4282groups:
4283- name: v2
4284 rules:
4285 - record: up:count
4286 expr: count(up)
4287bogus: 1
4288`),
4289 strict: true,
4290 output: parser.File{
4291 Error: parser.ParseError{
4292 Err: errors.New("unexpected key bogus"),
4293 Line: 7,
4294 },
4295 },
4296 },
4297 {
4298 input: []byte(`
4299groups:
4300- name: v2
4301 rules:
4302 - record: up:count
4303 expr: count(up)
4304 bogus: 1
4305`),
4306 strict: true,
4307 output: parser.File{
4308 Groups: []parser.Group{
4309 {
4310 Name: parser.YamlNode{
4311 Value: "v2",
4312 Pos: diags.PositionRanges{
4313 {Line: 3, FirstColumn: 9, LastColumn: 10},
4314 },
4315 },
4316 Rules: []parser.Rule{
4317 {
4318 Error: parser.ParseError{
4319 Err: errors.New("invalid rule key bogus"),
4320 Line: 7,
4321 },
4322 },
4323 },
4324 },
4325 },
4326 },
4327 },
4328 {
4329 input: []byte(`
4330groups:
4331- name: v2
4332 rules:
4333 - alert: up:count
4334 for: 5m
4335 keep_firing_for: 5m
4336 expr: count(up)
4337 labels: {}
4338 annotations: {}
4339 bogus: 1
4340`),
4341 strict: true,
4342 output: parser.File{
4343 Groups: []parser.Group{
4344 {
4345 Name: parser.YamlNode{
4346 Value: "v2",
4347 Pos: diags.PositionRanges{
4348 {Line: 3, FirstColumn: 9, LastColumn: 10},
4349 },
4350 },
4351 Rules: []parser.Rule{
4352 {
4353 Error: parser.ParseError{
4354 Err: errors.New("invalid rule key bogus"),
4355 Line: 11,
4356 },
4357 },
4358 },
4359 },
4360 },
4361 },
4362 },
4363 {
4364 input: []byte(`
4365groups:
4366
4367- name: CloudflareKafkaZookeeperExporter
4368
4369 rules:
4370`),
4371 strict: true,
4372 output: parser.File{
4373 Groups: []parser.Group{
4374 {
4375 Name: parser.YamlNode{
4376 Value: "CloudflareKafkaZookeeperExporter",
4377 Pos: diags.PositionRanges{
4378 {Line: 4, FirstColumn: 9, LastColumn: 40},
4379 },
4380 },
4381 },
4382 },
4383 },
4384 },
4385 {
4386 input: []byte(`
4387groups:
4388- name: foo
4389 rules:
4390 expr: 1
4391`),
4392 strict: true,
4393 output: parser.File{
4394 Groups: []parser.Group{
4395 {
4396 Name: parser.YamlNode{
4397 Value: "foo",
4398 Pos: diags.PositionRanges{
4399 {Line: 3, FirstColumn: 9, LastColumn: 11},
4400 },
4401 },
4402 Error: parser.ParseError{
4403 Err: errors.New("rules must be a list, got mapping"),
4404 Line: 4,
4405 },
4406 },
4407 },
4408 },
4409 },
4410 {
4411 input: []byte(`
4412groups:
4413- name: foo
4414 rules:
4415 - expr: 1
4416`),
4417 strict: true,
4418 output: parser.File{
4419 Groups: []parser.Group{
4420 {
4421 Name: parser.YamlNode{
4422 Value: "foo",
4423 Pos: diags.PositionRanges{
4424 {Line: 3, FirstColumn: 9, LastColumn: 11},
4425 },
4426 },
4427 Rules: []parser.Rule{
4428 {
4429 Lines: diags.LineRange{First: 5, Last: 5},
4430 Error: parser.ParseError{
4431 Line: 5,
4432 Err: errors.New("incomplete rule, no alert or record key"),
4433 },
4434 },
4435 },
4436 },
4437 },
4438 },
4439 },
4440 {
4441 input: []byte(`
4442groups:
4443- name: foo
4444 rules:
4445 - expr: null
4446`),
4447 strict: true,
4448 output: parser.File{
4449 Groups: []parser.Group{
4450 {
4451 Name: parser.YamlNode{
4452 Value: "foo",
4453 Pos: diags.PositionRanges{
4454 {Line: 3, FirstColumn: 9, LastColumn: 11},
4455 },
4456 },
4457 Rules: []parser.Rule{
4458 {
4459 Lines: diags.LineRange{First: 5, Last: 5},
4460 Error: parser.ParseError{
4461 Line: 5,
4462 Err: errors.New("incomplete rule, no alert or record key"),
4463 },
4464 },
4465 },
4466 },
4467 },
4468 },
4469 },
4470 {
4471 input: []byte(`
4472groups:
4473- name: foo
4474 rules:
4475 - 1: null
4476`),
4477 strict: true,
4478 output: parser.File{
4479 Groups: []parser.Group{
4480 {
4481 Name: parser.YamlNode{
4482 Value: "foo",
4483 Pos: diags.PositionRanges{
4484 {Line: 3, FirstColumn: 9, LastColumn: 11},
4485 },
4486 },
4487 Rules: []parser.Rule{
4488 {
4489 Error: parser.ParseError{
4490 Err: errors.New("invalid rule key 1"),
4491 Line: 5,
4492 },
4493 },
4494 },
4495 },
4496 },
4497 },
4498 },
4499 {
4500 input: []byte(`
4501groups:
4502- name: foo
4503 rules:
4504 - true: !!binary "SGVsbG8sIFdvcmxkIQ=="
4505`),
4506 strict: true,
4507 output: parser.File{
4508 Groups: []parser.Group{
4509 {
4510 Name: parser.YamlNode{
4511 Value: "foo",
4512 Pos: diags.PositionRanges{
4513 {Line: 3, FirstColumn: 9, LastColumn: 11},
4514 },
4515 },
4516 Rules: []parser.Rule{
4517 {
4518 Error: parser.ParseError{
4519 Err: errors.New("invalid rule key true"),
4520 Line: 5,
4521 },
4522 },
4523 },
4524 },
4525 },
4526 },
4527 },
4528 {
4529 input: []byte(`
4530groups:
4531- name: foo
4532 rules:
4533 - expr: !!binary "SGVsbG8sIFdvcmxkIQ=="
4534`),
4535 strict: true,
4536 output: parser.File{
4537 Groups: []parser.Group{
4538 {
4539 Name: parser.YamlNode{
4540 Value: "foo",
4541 Pos: diags.PositionRanges{
4542 {Line: 3, FirstColumn: 9, LastColumn: 11},
4543 },
4544 },
4545 Rules: []parser.Rule{
4546 {
4547 Lines: diags.LineRange{First: 5, Last: 5},
4548 Error: parser.ParseError{
4549 Line: 5,
4550 Err: errors.New("incomplete rule, no alert or record key"),
4551 },
4552 },
4553 },
4554 },
4555 },
4556 },
4557 },
4558 {
4559 input: []byte(`
4560groups:
4561- name: foo
4562 rules:
4563 - expr: !!binary "SGVsbG8sIFdvcmxkIQ=="
4564 record: foo
4565`),
4566 strict: true,
4567 output: parser.File{
4568 Groups: []parser.Group{
4569 {
4570 Name: parser.YamlNode{
4571 Value: "foo",
4572 Pos: diags.PositionRanges{
4573 {Line: 3, FirstColumn: 9, LastColumn: 11},
4574 },
4575 },
4576 Rules: []parser.Rule{
4577 {
4578 Lines: diags.LineRange{First: 5, Last: 6},
4579 Error: parser.ParseError{
4580 Line: 5,
4581 Err: errors.New("expr value must be a string, got binary data instead"),
4582 },
4583 },
4584 },
4585 },
4586 },
4587 },
4588 },
4589 {
4590 input: []byte(`
4591groups:
4592- name: foo
4593 rules:
4594 - labels: !!binary "SGVsbG8sIFdvcmxkIQ=="
4595 record: foo
4596 expr: foo
4597`),
4598 strict: true,
4599 output: parser.File{
4600 Groups: []parser.Group{
4601 {
4602 Name: parser.YamlNode{
4603 Value: "foo",
4604 Pos: diags.PositionRanges{
4605 {Line: 3, FirstColumn: 9, LastColumn: 11},
4606 },
4607 },
4608 Rules: []parser.Rule{
4609 {
4610 Lines: diags.LineRange{First: 5, Last: 7},
4611 Error: parser.ParseError{
4612 Line: 5,
4613 Err: errors.New("labels value must be a mapping, got binary data instead"),
4614 },
4615 },
4616 },
4617 },
4618 },
4619 },
4620 },
4621 {
4622 input: []byte(`
4623---
4624groups:
4625- name: foo
4626 rules:
4627 - record: foo
4628 expr: bar
4629---
4630groups:
4631- name: foo
4632 rules:
4633 - record: foo
4634 expr: bar
4635`),
4636 strict: true,
4637 output: parser.File{
4638 Error: parser.ParseError{
4639 Line: 8,
4640 Err: errors.New("multi-document YAML files are not allowed"),
4641 },
4642 Groups: []parser.Group{
4643 {
4644 Name: parser.YamlNode{
4645 Value: "foo",
4646 Pos: diags.PositionRanges{
4647 {Line: 4, FirstColumn: 9, LastColumn: 11},
4648 },
4649 },
4650 Rules: []parser.Rule{
4651 {
4652 Lines: diags.LineRange{First: 6, Last: 7},
4653 RecordingRule: &parser.RecordingRule{
4654 Record: parser.YamlNode{
4655 Value: "foo",
4656 Pos: diags.PositionRanges{
4657 {Line: 6, FirstColumn: 15, LastColumn: 17},
4658 },
4659 },
4660 Expr: parser.PromQLExpr{
4661 Value: &parser.YamlNode{
4662 Value: "bar",
4663 Pos: diags.PositionRanges{
4664 {Line: 7, FirstColumn: 13, LastColumn: 15},
4665 },
4666 },
4667 },
4668 },
4669 },
4670 },
4671 },
4672 {
4673 Name: parser.YamlNode{
4674 Value: "foo",
4675 Pos: diags.PositionRanges{
4676 {Line: 10, FirstColumn: 9, LastColumn: 11},
4677 },
4678 },
4679 Rules: []parser.Rule{
4680 {
4681 Lines: diags.LineRange{First: 12, Last: 13},
4682 RecordingRule: &parser.RecordingRule{
4683 Record: parser.YamlNode{
4684 Value: "foo",
4685 Pos: diags.PositionRanges{
4686 {Line: 12, FirstColumn: 15, LastColumn: 17},
4687 },
4688 },
4689 Expr: parser.PromQLExpr{
4690 Value: &parser.YamlNode{
4691 Value: "bar",
4692 Pos: diags.PositionRanges{
4693 {Line: 13, FirstColumn: 13, LastColumn: 15},
4694 },
4695 },
4696 },
4697 },
4698 },
4699 },
4700 },
4701 },
4702 },
4703 },
4704 {
4705 input: []byte(`
4706groups:
4707- name: foo
4708 rules:
4709 - record: foo
4710 expr: foo
4711 expr: foo
4712`),
4713 strict: true,
4714 output: parser.File{
4715 Groups: []parser.Group{
4716 {
4717 Name: parser.YamlNode{
4718 Value: "foo",
4719 Pos: diags.PositionRanges{
4720 {Line: 3, FirstColumn: 9, LastColumn: 11},
4721 },
4722 },
4723 Rules: []parser.Rule{
4724 {
4725 Lines: diags.LineRange{First: 5, Last: 7},
4726 Error: parser.ParseError{
4727 Line: 7,
4728 Err: errors.New("duplicated expr key"),
4729 },
4730 },
4731 },
4732 },
4733 },
4734 },
4735 },
4736 {
4737 input: []byte(`
4738groups:
4739- name: foo
4740 rules:
4741 - record: foo
4742 keep_firing_for: 1m
4743 keep_firing_for: 2m
4744`),
4745 strict: true,
4746 output: parser.File{
4747 Groups: []parser.Group{
4748 {
4749 Name: parser.YamlNode{
4750 Value: "foo",
4751 Pos: diags.PositionRanges{
4752 {Line: 3, FirstColumn: 9, LastColumn: 11},
4753 },
4754 },
4755 Rules: []parser.Rule{
4756 {
4757 Lines: diags.LineRange{First: 5, Last: 7},
4758 Error: parser.ParseError{
4759 Line: 7,
4760 Err: errors.New("duplicated keep_firing_for key"),
4761 },
4762 },
4763 },
4764 },
4765 },
4766 },
4767 },
4768 {
4769 input: []byte(`
4770groups:
4771- name: foo
4772 rules:
4773 - record: foo
4774 keep_firing_for: 1m
4775 record: 2m
4776`),
4777 strict: true,
4778 output: parser.File{
4779 Groups: []parser.Group{
4780 {
4781 Name: parser.YamlNode{
4782 Value: "foo",
4783 Pos: diags.PositionRanges{
4784 {Line: 3, FirstColumn: 9, LastColumn: 11},
4785 },
4786 },
4787 Rules: []parser.Rule{
4788 {
4789 Lines: diags.LineRange{First: 5, Last: 7},
4790 Error: parser.ParseError{
4791 Line: 7,
4792 Err: errors.New("duplicated record key"),
4793 },
4794 },
4795 },
4796 },
4797 },
4798 },
4799 },
4800 {
4801 input: []byte(`
4802groups:
4803- name: foo
4804 rules:
4805 - []
4806`),
4807 strict: true,
4808 output: parser.File{
4809 Groups: []parser.Group{
4810 {
4811 Name: parser.YamlNode{
4812 Value: "foo",
4813 Pos: diags.PositionRanges{
4814 {Line: 3, FirstColumn: 9, LastColumn: 11},
4815 },
4816 },
4817 Rules: []parser.Rule{
4818 {
4819 Error: parser.ParseError{
4820 Err: errors.New("rule definition must be a mapping, got list"),
4821 Line: 5,
4822 },
4823 },
4824 },
4825 },
4826 },
4827 },
4828 },
4829 {
4830 input: []byte(`
48311: 0
4832`),
4833 strict: true,
4834 output: parser.File{
4835 Error: parser.ParseError{
4836 Err: errors.New("groups key must be a string, got a integer"),
4837 Line: 2,
4838 },
4839 },
4840 },
4841 {
4842 input: []byte(`
4843true: 0
4844`),
4845 strict: true,
4846 output: parser.File{
4847 Error: parser.ParseError{
4848 Err: errors.New("groups key must be a string, got a bool"),
4849 Line: 2,
4850 },
4851 },
4852 },
4853 {
4854 input: []byte(`
4855groups: !!binary "SGVsbG8sIFdvcmxkIQ=="
4856`),
4857 strict: true,
4858 output: parser.File{
4859 Error: parser.ParseError{
4860 Err: errors.New("groups value must be a list, got binary data"),
4861 Line: 2,
4862 },
4863 },
4864 },
4865 {
4866 input: []byte(`
4867groups:
4868 - true: null"
4869`),
4870 strict: true,
4871 output: parser.File{
4872 Groups: []parser.Group{
4873 {
4874 Error: parser.ParseError{
4875 Err: errors.New("invalid group key true"),
4876 Line: 3,
4877 },
4878 },
4879 },
4880 },
4881 },
4882 {
4883 input: []byte(`
4884!!!binary "groups": true"
4885`),
4886 strict: true,
4887 output: parser.File{
4888 Error: parser.ParseError{
4889 Err: errors.New("groups key must be a string, got a binary"),
4890 Line: 2,
4891 },
4892 },
4893 },
4894 {
4895 input: []byte("[]"),
4896 strict: true,
4897 output: parser.File{
4898 Error: parser.ParseError{
4899 Err: errors.New("top level field must be a groups key, got list"),
4900 Line: 1,
4901 },
4902 },
4903 },
4904 {
4905 input: []byte(`
4906groups:
4907 - true
4908`),
4909 strict: true,
4910 output: parser.File{
4911 Groups: []parser.Group{
4912 {
4913 Error: parser.ParseError{
4914 Err: errors.New("group must be a mapping, got bool"),
4915 Line: 3,
4916 },
4917 },
4918 },
4919 },
4920 },
4921 {
4922 input: []byte(`
4923groups:
4924 - name:
4925 rules: []
4926`),
4927 strict: true,
4928 output: parser.File{
4929 Groups: []parser.Group{
4930 {
4931 Error: parser.ParseError{
4932 Err: errors.New("group name must be a string, got null"),
4933 Line: 3,
4934 },
4935 },
4936 },
4937 },
4938 },
4939 {
4940 input: []byte(`
4941groups:
4942 - name: ""
4943 rules: []
4944`),
4945 strict: true,
4946 output: parser.File{
4947 Groups: []parser.Group{
4948 {
4949 Error: parser.ParseError{
4950 Err: errors.New("group name cannot be empty"),
4951 Line: 3,
4952 },
4953 },
4954 },
4955 },
4956 },
4957 {
4958 input: []byte(`
4959groups:
4960 - name: 1
4961 rules: []
4962`),
4963 strict: true,
4964 output: parser.File{
4965 Groups: []parser.Group{
4966 {
4967 Error: parser.ParseError{
4968 Err: errors.New("group name must be a string, got integer"),
4969 Line: 3,
4970 },
4971 },
4972 },
4973 },
4974 },
4975 {
4976 input: []byte(`
4977groups:
4978 - name: foo
4979 interval: 1
4980 rules: []
4981`),
4982 strict: true,
4983 output: parser.File{
4984 Groups: []parser.Group{
4985 {
4986 Name: parser.YamlNode{
4987 Value: "foo",
4988 Pos: diags.PositionRanges{
4989 {Line: 3, FirstColumn: 11, LastColumn: 13},
4990 },
4991 },
4992 Error: parser.ParseError{
4993 Err: errors.New("group interval must be a string, got integer"),
4994 Line: 4,
4995 },
4996 },
4997 },
4998 },
4999 },
5000 {
5001 input: []byte(`
5002groups:
5003 - name: foo
5004 interval: xxx
5005 rules: []
5006`),
5007 strict: true,
5008 output: parser.File{
5009 Groups: []parser.Group{
5010 {
5011 Name: parser.YamlNode{
5012 Value: "foo",
5013 Pos: diags.PositionRanges{
5014 {Line: 3, FirstColumn: 11, LastColumn: 13},
5015 },
5016 },
5017 Interval: &parser.YamlDuration{
5018 ParseError: errors.New(`not a valid duration string: "xxx"`),
5019 Raw: "xxx",
5020 Pos: diags.PositionRanges{
5021 {Line: 4, FirstColumn: 15, LastColumn: 17},
5022 },
5023 },
5024 Error: parser.ParseError{
5025 Err: errors.New("invalid interval value: not a valid duration string: \"xxx\""),
5026 Line: 4,
5027 },
5028 },
5029 },
5030 },
5031 },
5032 {
5033 input: []byte(`
5034groups:
5035 - name: foo
5036 query_offset: 1
5037 rules: []
5038`),
5039 strict: true,
5040 output: parser.File{
5041 Groups: []parser.Group{
5042 {
5043 Name: parser.YamlNode{
5044 Value: "foo",
5045 Pos: diags.PositionRanges{
5046 {Line: 3, FirstColumn: 11, LastColumn: 13},
5047 },
5048 },
5049 Error: parser.ParseError{
5050 Err: errors.New("group query_offset must be a string, got integer"),
5051 Line: 4,
5052 },
5053 },
5054 },
5055 },
5056 },
5057 {
5058 input: []byte(`
5059groups:
5060 - name: foo
5061 query_offset: xxx
5062 rules: []
5063`),
5064 strict: true,
5065 output: parser.File{
5066 Groups: []parser.Group{
5067 {
5068 Name: parser.YamlNode{
5069 Value: "foo",
5070 Pos: diags.PositionRanges{
5071 {Line: 3, FirstColumn: 11, LastColumn: 13},
5072 },
5073 },
5074 QueryOffset: &parser.YamlDuration{
5075 ParseError: errors.New(`not a valid duration string: "xxx"`),
5076 Raw: "xxx",
5077 Pos: diags.PositionRanges{
5078 {Line: 4, FirstColumn: 19, LastColumn: 21},
5079 },
5080 },
5081 Error: parser.ParseError{
5082 Err: errors.New("invalid query_offset value: not a valid duration string: \"xxx\""),
5083 Line: 4,
5084 },
5085 },
5086 },
5087 },
5088 },
5089 {
5090 input: []byte(`
5091groups:
5092 - name: foo
5093 query_offset: 1m
5094 limit: abc
5095 rules: []
5096`),
5097 strict: true,
5098 output: parser.File{
5099 Groups: []parser.Group{
5100 {
5101 Name: parser.YamlNode{
5102 Value: "foo",
5103 Pos: diags.PositionRanges{
5104 {Line: 3, FirstColumn: 11, LastColumn: 13},
5105 },
5106 },
5107 QueryOffset: &parser.YamlDuration{
5108 Value: time.Minute,
5109 Raw: "1m",
5110 Pos: diags.PositionRanges{
5111 {Line: 4, FirstColumn: 19, LastColumn: 20},
5112 },
5113 },
5114 Error: parser.ParseError{
5115 Err: errors.New("group limit must be a integer, got string"),
5116 Line: 5,
5117 },
5118 },
5119 },
5120 },
5121 },
5122 {
5123 input: []byte(`
5124groups:
5125- name: v2
5126 rules:
5127 - alert: up:count
5128 for: 5m &timeout
5129 keep_firing_for: **timeout
5130 expr: count(up)
5131 labels: {}
5132 annotations: {}
5133 bogus: 1
5134`),
5135 strict: true,
5136 output: parser.File{
5137 Error: parser.ParseError{
5138 Err: errors.New("did not find expected alphabetic or numeric character"),
5139 Line: 7,
5140 },
5141 },
5142 },
5143 {
5144 // Non-strict parser accepts any scalar value for limit and records
5145 // the strconv error on the YamlInt itself.
5146 input: []byte(`
5147groups:
5148- name: foo
5149 limit: abc
5150 rules: []
5151`),
5152 output: parser.File{
5153 IsRelaxed: true,
5154 Groups: []parser.Group{
5155 {
5156 Name: parser.YamlNode{
5157 Value: "foo",
5158 Pos: diags.PositionRanges{
5159 {Line: 3, FirstColumn: 9, LastColumn: 11},
5160 },
5161 },
5162 Limit: &parser.YamlInt{
5163 ParseError: errors.New(`strconv.Atoi: parsing "abc": invalid syntax`),
5164 Raw: "abc",
5165 Pos: diags.PositionRanges{
5166 {Line: 4, FirstColumn: 10, LastColumn: 12},
5167 },
5168 },
5169 },
5170 },
5171 },
5172 },
5173 {
5174 input: []byte(`
5175groups:
5176- name: v2
5177 rules:
5178 - alert: up:count
5179 for: &for 1
5180 keep_firing_for: *for
5181 expr: count(up)
5182 labels: {}
5183 annotations: {}
5184`),
5185 strict: true,
5186 output: parser.File{
5187 Groups: []parser.Group{
5188 {
5189 Name: parser.YamlNode{
5190 Value: "v2",
5191 Pos: diags.PositionRanges{
5192 {Line: 3, FirstColumn: 9, LastColumn: 10},
5193 },
5194 },
5195 Rules: []parser.Rule{
5196 {
5197 Lines: diags.LineRange{First: 5, Last: 10},
5198 Error: parser.ParseError{
5199 Line: 6,
5200 Err: errors.New("for value must be a string, got integer instead"),
5201 },
5202 },
5203 },
5204 },
5205 },
5206 },
5207 },
5208 {
5209 input: []byte(`
5210groups:
5211- name: v2
5212 limit: &for 1
5213 rules:
5214 - alert: up:count
5215 keep_firing_for: *for
5216 expr: count(up)
5217 labels: {}
5218 annotations: {}
5219`),
5220 strict: true,
5221 output: parser.File{
5222 Groups: []parser.Group{
5223 {
5224 Name: parser.YamlNode{
5225 Value: "v2",
5226 Pos: diags.PositionRanges{
5227 {Line: 3, FirstColumn: 9, LastColumn: 10},
5228 },
5229 },
5230 Limit: &parser.YamlInt{
5231 Value: 1,
5232 Raw: "1",
5233 Pos: diags.PositionRanges{
5234 {Line: 4, FirstColumn: 15, LastColumn: 15},
5235 },
5236 },
5237 Rules: []parser.Rule{
5238 {
5239 Lines: diags.LineRange{First: 6, Last: 10},
5240 Error: parser.ParseError{
5241 Line: 7,
5242 Err: errors.New("keep_firing_for value must be a string, got integer instead"),
5243 },
5244 },
5245 },
5246 },
5247 },
5248 },
5249 },
5250 {
5251 input: []byte(`
5252groups:
5253- name: "{{ source }}"
5254 rules:
5255# pint ignore/begin
5256
5257# pint ignore/end
5258`),
5259 strict: true,
5260 output: parser.File{
5261 Groups: []parser.Group{
5262 {
5263 Name: parser.YamlNode{
5264 Value: "{{ source }}",
5265 Pos: diags.PositionRanges{
5266 {Line: 3, FirstColumn: 10, LastColumn: 21},
5267 },
5268 },
5269 },
5270 },
5271 },
5272 },
5273 {
5274 input: []byte(`
5275groups:
5276- name: foo
5277 rules:
5278 - record: foo
5279 expr: |
5280 {"up"}
5281`),
5282 output: parser.File{
5283 IsRelaxed: true,
5284 Groups: []parser.Group{
5285 {
5286 Name: parser.YamlNode{
5287 Value: "foo",
5288 Pos: diags.PositionRanges{
5289 {Line: 3, FirstColumn: 9, LastColumn: 11},
5290 },
5291 },
5292 Rules: []parser.Rule{
5293 {
5294 Lines: diags.LineRange{First: 5, Last: 7},
5295 RecordingRule: &parser.RecordingRule{
5296 Record: parser.YamlNode{
5297 Value: "foo",
5298 Pos: diags.PositionRanges{
5299 {Line: 5, FirstColumn: 13, LastColumn: 15},
5300 },
5301 },
5302 Expr: parser.PromQLExpr{
5303 Value: &parser.YamlNode{
5304 Value: "{\"up\"}\n",
5305 Pos: diags.PositionRanges{
5306 {Line: 7, FirstColumn: 7, LastColumn: 12},
5307 },
5308 },
5309 },
5310 },
5311 },
5312 },
5313 },
5314 },
5315 },
5316 },
5317 {
5318 input: []byte(`
5319groups:
5320- name: foo
5321 rules:
5322 - record: foo
5323 expr: |
5324 {'up'}
5325`),
5326 output: parser.File{
5327 IsRelaxed: true,
5328 Groups: []parser.Group{
5329 {
5330 Name: parser.YamlNode{
5331 Value: "foo",
5332 Pos: diags.PositionRanges{
5333 {Line: 3, FirstColumn: 9, LastColumn: 11},
5334 },
5335 },
5336 Rules: []parser.Rule{
5337 {
5338 Lines: diags.LineRange{First: 5, Last: 7},
5339 RecordingRule: &parser.RecordingRule{
5340 Record: parser.YamlNode{
5341 Value: "foo",
5342 Pos: diags.PositionRanges{
5343 {Line: 5, FirstColumn: 13, LastColumn: 15},
5344 },
5345 },
5346 Expr: parser.PromQLExpr{
5347 Value: &parser.YamlNode{
5348 Value: "{'up'}\n",
5349 Pos: diags.PositionRanges{
5350 {Line: 7, FirstColumn: 7, LastColumn: 12},
5351 },
5352 },
5353 },
5354 },
5355 },
5356 },
5357 },
5358 },
5359 },
5360 },
5361 {
5362 input: []byte(`
5363groups:
5364- name: mygroup
5365 partial_response_strategy: bob
5366 rules:
5367 - record: up:count
5368 expr: count(up)
5369`),
5370 strict: true,
5371 output: parser.File{
5372 Groups: []parser.Group{
5373 {
5374 Name: parser.YamlNode{
5375 Value: "mygroup",
5376 Pos: diags.PositionRanges{
5377 {Line: 3, FirstColumn: 9, LastColumn: 15},
5378 },
5379 },
5380 Error: parser.ParseError{
5381 Err: errors.New("partial_response_strategy is only valid when parser is configured to use the Thanos rule schema"),
5382 Line: 4,
5383 },
5384 },
5385 },
5386 },
5387 },
5388 {
5389 input: []byte(`
5390groups:
5391- name: mygroup
5392 partial_response_strategy: warn
5393 rules:
5394 - record: up:count
5395 expr: count(up)
5396`),
5397 strict: true,
5398 schema: parser.ThanosSchema,
5399 output: parser.File{
5400 Groups: []parser.Group{
5401 {
5402 Name: parser.YamlNode{
5403 Value: "mygroup",
5404 Pos: diags.PositionRanges{
5405 {Line: 3, FirstColumn: 9, LastColumn: 15},
5406 },
5407 },
5408 Rules: []parser.Rule{
5409 {
5410 Lines: diags.LineRange{First: 6, Last: 7},
5411 RecordingRule: &parser.RecordingRule{
5412 Record: parser.YamlNode{
5413 Value: "up:count",
5414 Pos: diags.PositionRanges{
5415 {Line: 6, FirstColumn: 13, LastColumn: 20},
5416 },
5417 },
5418 Expr: parser.PromQLExpr{
5419 Value: &parser.YamlNode{
5420 Value: "count(up)",
5421 Pos: diags.PositionRanges{
5422 {Line: 7, FirstColumn: 11, LastColumn: 19},
5423 },
5424 },
5425 },
5426 },
5427 },
5428 },
5429 },
5430 },
5431 },
5432 },
5433 {
5434 input: []byte(`
5435groups:
5436- name: mygroup
5437 partial_response_strategy: abort
5438 rules:
5439 - record: up:count
5440 expr: count(up)
5441`),
5442 strict: true,
5443 schema: parser.ThanosSchema,
5444 output: parser.File{
5445 Groups: []parser.Group{
5446 {
5447 Name: parser.YamlNode{
5448 Value: "mygroup",
5449 Pos: diags.PositionRanges{
5450 {Line: 3, FirstColumn: 9, LastColumn: 15},
5451 },
5452 },
5453 Rules: []parser.Rule{
5454 {
5455 Lines: diags.LineRange{First: 6, Last: 7},
5456 RecordingRule: &parser.RecordingRule{
5457 Record: parser.YamlNode{
5458 Value: "up:count",
5459 Pos: diags.PositionRanges{
5460 {Line: 6, FirstColumn: 13, LastColumn: 20},
5461 },
5462 },
5463 Expr: parser.PromQLExpr{
5464 Value: &parser.YamlNode{
5465 Value: "count(up)",
5466 Pos: diags.PositionRanges{
5467 {Line: 7, FirstColumn: 11, LastColumn: 19},
5468 },
5469 },
5470 },
5471 },
5472 },
5473 },
5474 },
5475 },
5476 },
5477 },
5478 {
5479 input: []byte(`
5480groups:
5481- name: mygroup
5482 partial_response_strategy: abort
5483 rules:
5484 - record: up:count
5485 expr: count(up)
5486`),
5487 strict: false,
5488 schema: parser.PrometheusSchema,
5489 output: parser.File{
5490 IsRelaxed: true,
5491 Groups: []parser.Group{
5492 {
5493 Name: parser.YamlNode{
5494 Value: "mygroup",
5495 Pos: diags.PositionRanges{
5496 {Line: 3, FirstColumn: 9, LastColumn: 15},
5497 },
5498 },
5499 Rules: []parser.Rule{
5500 {
5501 Lines: diags.LineRange{First: 6, Last: 7},
5502 RecordingRule: &parser.RecordingRule{
5503 Record: parser.YamlNode{
5504 Value: "up:count",
5505 Pos: diags.PositionRanges{
5506 {Line: 6, FirstColumn: 13, LastColumn: 20},
5507 },
5508 },
5509 Expr: parser.PromQLExpr{
5510 Value: &parser.YamlNode{
5511 Value: "count(up)",
5512 Pos: diags.PositionRanges{
5513 {Line: 7, FirstColumn: 11, LastColumn: 19},
5514 },
5515 },
5516 },
5517 },
5518 },
5519 },
5520 },
5521 },
5522 },
5523 },
5524 {
5525 input: []byte(`
5526groups:
5527- name: mygroup
5528 partial_response_strategy: bob
5529 rules:
5530 - record: up:count
5531 expr: count(up)
5532`),
5533 strict: true,
5534 schema: parser.ThanosSchema,
5535 output: parser.File{
5536 Groups: []parser.Group{
5537 {
5538 Name: parser.YamlNode{
5539 Value: "mygroup",
5540 Pos: diags.PositionRanges{
5541 {Line: 3, FirstColumn: 9, LastColumn: 15},
5542 },
5543 },
5544 Error: parser.ParseError{
5545 Err: errors.New("invalid partial_response_strategy value: bob"),
5546 Line: 4,
5547 },
5548 },
5549 },
5550 },
5551 },
5552 {
5553 // YAML alias on partial_response_strategy in Thanos strict mode is resolved correctly
5554 input: []byte(`
5555groups:
5556- name: group1
5557 partial_response_strategy: &prs warn
5558 rules:
5559 - record: up:count
5560 expr: count(up)
5561- name: group2
5562 partial_response_strategy: *prs
5563 rules:
5564 - record: down:count
5565 expr: count(down)
5566`),
5567 strict: true,
5568 schema: parser.ThanosSchema,
5569 output: parser.File{
5570 Groups: []parser.Group{
5571 {
5572 Name: parser.YamlNode{
5573 Value: "group1",
5574 Pos: diags.PositionRanges{
5575 {Line: 3, FirstColumn: 9, LastColumn: 14},
5576 },
5577 },
5578 Rules: []parser.Rule{
5579 {
5580 Lines: diags.LineRange{First: 6, Last: 7},
5581 RecordingRule: &parser.RecordingRule{
5582 Record: parser.YamlNode{
5583 Value: "up:count",
5584 Pos: diags.PositionRanges{
5585 {Line: 6, FirstColumn: 13, LastColumn: 20},
5586 },
5587 },
5588 Expr: parser.PromQLExpr{
5589 Value: &parser.YamlNode{
5590 Value: "count(up)",
5591 Pos: diags.PositionRanges{
5592 {Line: 7, FirstColumn: 11, LastColumn: 19},
5593 },
5594 },
5595 },
5596 },
5597 },
5598 },
5599 },
5600 {
5601 Name: parser.YamlNode{
5602 Value: "group2",
5603 Pos: diags.PositionRanges{
5604 {Line: 8, FirstColumn: 9, LastColumn: 14},
5605 },
5606 },
5607 Rules: []parser.Rule{
5608 {
5609 Lines: diags.LineRange{First: 11, Last: 12},
5610 RecordingRule: &parser.RecordingRule{
5611 Record: parser.YamlNode{
5612 Value: "down:count",
5613 Pos: diags.PositionRanges{
5614 {Line: 11, FirstColumn: 13, LastColumn: 22},
5615 },
5616 },
5617 Expr: parser.PromQLExpr{
5618 Value: &parser.YamlNode{
5619 Value: "count(down)",
5620 Pos: diags.PositionRanges{
5621 {Line: 12, FirstColumn: 11, LastColumn: 21},
5622 },
5623 },
5624 },
5625 },
5626 },
5627 },
5628 },
5629 },
5630 },
5631 },
5632 {
5633 input: []byte(`
5634groups:
5635- name: mygroup
5636 partial_response_strategy: 1
5637 rules:
5638 - record: up:count
5639 expr: count(up)
5640`),
5641 strict: true,
5642 schema: parser.ThanosSchema,
5643 output: parser.File{
5644 Groups: []parser.Group{
5645 {
5646 Name: parser.YamlNode{
5647 Value: "mygroup",
5648 Pos: diags.PositionRanges{
5649 {Line: 3, FirstColumn: 9, LastColumn: 15},
5650 },
5651 },
5652 Error: parser.ParseError{
5653 Err: errors.New("partial_response_strategy must be a string, got integer"),
5654 Line: 4,
5655 },
5656 },
5657 },
5658 },
5659 },
5660 {
5661 input: []byte(`
5662- alert: Multi Line
5663 expr: foo
5664 AND ON (instance)
5665 bar
5666`),
5667 strict: false,
5668 schema: parser.PrometheusSchema,
5669 output: parser.File{
5670 IsRelaxed: true,
5671 Groups: []parser.Group{
5672 {
5673 Rules: []parser.Rule{
5674 {
5675 Lines: diags.LineRange{First: 2, Last: 5},
5676 AlertingRule: &parser.AlertingRule{
5677 Alert: parser.YamlNode{
5678 Value: "Multi Line",
5679 Pos: diags.PositionRanges{
5680 {Line: 2, FirstColumn: 10, LastColumn: 19},
5681 },
5682 },
5683 Expr: parser.PromQLExpr{
5684 Value: &parser.YamlNode{
5685 Value: "foo AND ON (instance) bar",
5686 Pos: diags.PositionRanges{
5687 {Line: 3, FirstColumn: 9, LastColumn: 12},
5688 {Line: 4, FirstColumn: 11, LastColumn: 28},
5689 {Line: 5, FirstColumn: 11, LastColumn: 13},
5690 },
5691 },
5692 },
5693 },
5694 },
5695 },
5696 },
5697 },
5698 },
5699 },
5700 {
5701 input: []byte(`
5702 - alert: FooBar
5703 expr: >-
5704 count(
5705 foo
5706 or
5707 bar
5708 ) > 0
5709`),
5710 strict: false,
5711 schema: parser.PrometheusSchema,
5712 output: parser.File{
5713 IsRelaxed: true,
5714 Groups: []parser.Group{
5715 {
5716 Rules: []parser.Rule{
5717 {
5718 Lines: diags.LineRange{First: 2, Last: 8},
5719 AlertingRule: &parser.AlertingRule{
5720 Alert: parser.YamlNode{
5721 Value: "FooBar",
5722 Pos: diags.PositionRanges{
5723 {Line: 2, FirstColumn: 12, LastColumn: 17},
5724 },
5725 },
5726 Expr: parser.PromQLExpr{
5727 Value: &parser.YamlNode{
5728 Value: "count(\n foo\n or\n bar\n) > 0",
5729 Pos: diags.PositionRanges{
5730 {Line: 4, FirstColumn: 7, LastColumn: 13},
5731 {Line: 5, FirstColumn: 7, LastColumn: 12},
5732 {Line: 6, FirstColumn: 7, LastColumn: 11},
5733 {Line: 7, FirstColumn: 7, LastColumn: 12},
5734 {Line: 8, FirstColumn: 7, LastColumn: 11},
5735 },
5736 },
5737 },
5738 },
5739 },
5740 },
5741 },
5742 },
5743 },
5744 },
5745 {
5746 input: []byte(`
5747 - alert: FooBar
5748 expr: >-
5749 aaaaaaaaaaaaaaaaaaaaaaaa
5750 AND ON (colo_id) bbbbbbbbbbb
5751 > 2
5752 for: 1m
5753`),
5754 strict: false,
5755 schema: parser.PrometheusSchema,
5756 output: parser.File{
5757 IsRelaxed: true,
5758 Groups: []parser.Group{
5759 {
5760 Rules: []parser.Rule{
5761 {
5762 Lines: diags.LineRange{First: 2, Last: 7},
5763 AlertingRule: &parser.AlertingRule{
5764 Alert: parser.YamlNode{
5765 Value: "FooBar",
5766 Pos: diags.PositionRanges{
5767 {Line: 2, FirstColumn: 12, LastColumn: 17},
5768 },
5769 },
5770 Expr: parser.PromQLExpr{
5771 Value: &parser.YamlNode{
5772 Value: "aaaaaaaaaaaaaaaaaaaaaaaa AND ON (colo_id) bbbbbbbbbbb > 2",
5773 Pos: diags.PositionRanges{
5774 {Line: 4, FirstColumn: 7, LastColumn: 31},
5775 {Line: 5, FirstColumn: 7, LastColumn: 35},
5776 {Line: 6, FirstColumn: 7, LastColumn: 9},
5777 },
5778 },
5779 },
5780 For: &parser.YamlDuration{
5781 Value: time.Minute,
5782 Raw: "1m",
5783 Pos: diags.PositionRanges{
5784 {Line: 7, FirstColumn: 10, LastColumn: 11},
5785 },
5786 },
5787 },
5788 },
5789 },
5790 },
5791 },
5792 },
5793 },
5794 {
5795 input: []byte(`
5796 - alert: FooBar
5797 expr: 'aaaaaaaaaaaaaaaaaaaaaaaa
5798 AND ON (colo_id) bbbbbbbbbbb
5799 > 2'
5800 for: 1m
5801`),
5802 strict: false,
5803 schema: parser.PrometheusSchema,
5804 output: parser.File{
5805 IsRelaxed: true,
5806 Groups: []parser.Group{
5807 {
5808 Rules: []parser.Rule{
5809 {
5810 Lines: diags.LineRange{First: 2, Last: 6},
5811 AlertingRule: &parser.AlertingRule{
5812 Alert: parser.YamlNode{
5813 Value: "FooBar",
5814 Pos: diags.PositionRanges{
5815 {Line: 2, FirstColumn: 12, LastColumn: 17},
5816 },
5817 },
5818 Expr: parser.PromQLExpr{
5819 Value: &parser.YamlNode{
5820 Value: "aaaaaaaaaaaaaaaaaaaaaaaa AND ON (colo_id) bbbbbbbbbbb > 2",
5821 Pos: diags.PositionRanges{
5822 {Line: 3, FirstColumn: 12, LastColumn: 36},
5823 {Line: 4, FirstColumn: 11, LastColumn: 39},
5824 {Line: 5, FirstColumn: 11, LastColumn: 13},
5825 },
5826 },
5827 },
5828 For: &parser.YamlDuration{
5829 Value: time.Minute,
5830 Raw: "1m",
5831 Pos: diags.PositionRanges{
5832 {Line: 6, FirstColumn: 10, LastColumn: 11},
5833 },
5834 },
5835 },
5836 },
5837 },
5838 },
5839 },
5840 },
5841 },
5842 {
5843 input: []byte(`
5844groups:
5845- name: foo
5846 rules:
5847 - record: colo:foo:sum
5848 expr: sum without (instance) ( rate(my_metric[2m]) * on (instance)
5849 group_left (hardware_generation, hms_scope, sliver) (instance:metadata{})
5850 )
5851`),
5852 strict: false,
5853 schema: parser.PrometheusSchema,
5854 output: parser.File{
5855 IsRelaxed: true,
5856 Groups: []parser.Group{
5857 {
5858 Name: parser.YamlNode{
5859 Value: "foo",
5860 Pos: diags.PositionRanges{
5861 {Line: 3, FirstColumn: 9, LastColumn: 11},
5862 },
5863 },
5864 Rules: []parser.Rule{
5865 {
5866 Lines: diags.LineRange{First: 5, Last: 8},
5867 RecordingRule: &parser.RecordingRule{
5868 Record: parser.YamlNode{
5869 Value: "colo:foo:sum",
5870 Pos: diags.PositionRanges{
5871 {Line: 5, FirstColumn: 13, LastColumn: 24},
5872 },
5873 },
5874 Expr: parser.PromQLExpr{
5875 Value: &parser.YamlNode{
5876 Value: "sum without (instance) ( rate(my_metric[2m]) * on (instance) group_left (hardware_generation, hms_scope, sliver) (instance:metadata{}) )",
5877 Pos: diags.PositionRanges{
5878 {Line: 6, FirstColumn: 11, LastColumn: 71},
5879 {Line: 7, FirstColumn: 7, LastColumn: 80},
5880 {Line: 8, FirstColumn: 7, LastColumn: 7},
5881 },
5882 },
5883 },
5884 },
5885 },
5886 },
5887 },
5888 },
5889 },
5890 },
5891 {
5892 input: []byte(`
5893groups:
5894- name: "{{ source }}"
5895 rules:
5896 # AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
5897 # BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
5898 - record: some_long_name
5899 labels:
5900 metricssource: receiver
5901 expr:
5902 clamp_max(
5903 sum(rate(my_metric_long_name_total{output="control:output:kafka:/:requests:http-b:http_requests_control_sample"}[5m])) /
5904 sum(rate(my_metric_long_name_total{output="control:output:kafka:/:requests:http-b:http_requests_control_sample"}[5m] offset 30m)),
5905 2
5906 )
5907`),
5908 strict: true,
5909 schema: parser.PrometheusSchema,
5910 output: parser.File{
5911 Groups: []parser.Group{
5912 {
5913 Name: parser.YamlNode{
5914 Value: "{{ source }}",
5915 Pos: diags.PositionRanges{
5916 {Line: 3, FirstColumn: 10, LastColumn: 21},
5917 },
5918 },
5919 Rules: []parser.Rule{
5920 {
5921 Lines: diags.LineRange{First: 7, Last: 15},
5922 RecordingRule: &parser.RecordingRule{
5923 Record: parser.YamlNode{
5924 Value: "some_long_name",
5925 Pos: diags.PositionRanges{
5926 {Line: 7, FirstColumn: 13, LastColumn: 26},
5927 },
5928 },
5929 Labels: &parser.YamlMap{
5930 Key: &parser.YamlNode{
5931 Value: "labels",
5932 Pos: diags.PositionRanges{
5933 {Line: 8, FirstColumn: 5, LastColumn: 10},
5934 },
5935 },
5936 Items: []*parser.YamlKeyValue{
5937 {
5938 Key: &parser.YamlNode{
5939 Value: "metricssource",
5940 Pos: diags.PositionRanges{
5941 {Line: 9, FirstColumn: 7, LastColumn: 19},
5942 },
5943 },
5944 Value: &parser.YamlNode{
5945 Value: "receiver",
5946 Pos: diags.PositionRanges{
5947 {Line: 9, FirstColumn: 22, LastColumn: 29},
5948 },
5949 },
5950 },
5951 },
5952 },
5953 Expr: parser.PromQLExpr{
5954 Value: &parser.YamlNode{
5955 Value: `clamp_max( sum(rate(my_metric_long_name_total{output="control:output:kafka:/:requests:http-b:http_requests_control_sample"}[5m])) / sum(rate(my_metric_long_name_total{output="control:output:kafka:/:requests:http-b:http_requests_control_sample"}[5m] offset 30m)), 2 )`,
5956 Pos: diags.PositionRanges{
5957 {Line: 11, FirstColumn: 7, LastColumn: 17},
5958 {Line: 12, FirstColumn: 9, LastColumn: 129},
5959 {Line: 13, FirstColumn: 9, LastColumn: 139},
5960 {Line: 14, FirstColumn: 9, LastColumn: 10},
5961 {Line: 15, FirstColumn: 7, LastColumn: 7},
5962 },
5963 },
5964 },
5965 },
5966 },
5967 },
5968 },
5969 },
5970 },
5971 },
5972 {
5973 input: []byte(`
5974groups:
5975- name: "{{ source }}"
5976 rules:
5977
5978 - alert: Director_Is_Not_Advertising_Any_Routes
5979 expr: |
5980 sum without (name) (
5981 bird_protocol_prefix_export_count{ip_version="4",name=~".*external.*",proto!="Kernel"}
5982 * on (instance) group_left (profile,cluster)
5983 cf_node_role{kubernetes_role="director",role="kubernetes"}
5984 ) <= 0
5985 for: 1m
5986`),
5987 strict: true,
5988 schema: parser.PrometheusSchema,
5989 output: parser.File{
5990 Groups: []parser.Group{
5991 {
5992 Name: parser.YamlNode{
5993 Value: "{{ source }}",
5994 Pos: diags.PositionRanges{
5995 {Line: 3, FirstColumn: 10, LastColumn: 21},
5996 },
5997 },
5998 Rules: []parser.Rule{
5999 {
6000 Lines: diags.LineRange{First: 6, Last: 13},
6001 AlertingRule: &parser.AlertingRule{
6002 Alert: parser.YamlNode{
6003 Value: "Director_Is_Not_Advertising_Any_Routes",
6004 Pos: diags.PositionRanges{
6005 {Line: 6, FirstColumn: 12, LastColumn: 49},
6006 },
6007 },
6008 Expr: parser.PromQLExpr{
6009 Value: &parser.YamlNode{
6010 Value: `sum without (name) (
6011 bird_protocol_prefix_export_count{ip_version="4",name=~".*external.*",proto!="Kernel"}
6012 * on (instance) group_left (profile,cluster)
6013 cf_node_role{kubernetes_role="director",role="kubernetes"}
6014) <= 0
6015`,
6016 Pos: diags.PositionRanges{
6017 {Line: 8, FirstColumn: 9, LastColumn: 29},
6018 {Line: 9, FirstColumn: 9, LastColumn: 99},
6019 {Line: 10, FirstColumn: 9, LastColumn: 55},
6020 {Line: 11, FirstColumn: 9, LastColumn: 71},
6021 {Line: 12, FirstColumn: 9, LastColumn: 14},
6022 },
6023 },
6024 },
6025 For: &parser.YamlDuration{
6026 Value: time.Minute,
6027 Raw: "1m",
6028 Pos: diags.PositionRanges{
6029 {Line: 13, FirstColumn: 10, LastColumn: 11},
6030 },
6031 },
6032 },
6033 },
6034 },
6035 },
6036 },
6037 },
6038 },
6039 {
6040 input: []byte(`
6041groups:
6042- name: xxx
6043 interval: 3m
6044 rules: []
6045`),
6046 strict: true,
6047 output: parser.File{
6048 Groups: []parser.Group{
6049 {
6050 Name: parser.YamlNode{
6051 Value: "xxx",
6052 Pos: diags.PositionRanges{
6053 {Line: 3, FirstColumn: 9, LastColumn: 11},
6054 },
6055 },
6056 Interval: &parser.YamlDuration{
6057 Value: time.Minute * 3,
6058 Raw: "3m",
6059 Pos: diags.PositionRanges{
6060 {Line: 4, FirstColumn: 13, LastColumn: 14},
6061 },
6062 },
6063 },
6064 },
6065 },
6066 },
6067 {
6068 input: []byte(`
6069groups:
6070- name: xxx
6071 interval: 3m
6072 rules: []
6073`),
6074 output: parser.File{
6075 IsRelaxed: true,
6076 Groups: []parser.Group{
6077 {
6078 Name: parser.YamlNode{
6079 Value: "xxx",
6080 Pos: diags.PositionRanges{
6081 {Line: 3, FirstColumn: 9, LastColumn: 11},
6082 },
6083 },
6084 Interval: &parser.YamlDuration{
6085 Value: time.Minute * 3,
6086 Raw: "3m",
6087 Pos: diags.PositionRanges{
6088 {Line: 4, FirstColumn: 13, LastColumn: 14},
6089 },
6090 },
6091 },
6092 },
6093 },
6094 },
6095 {
6096 input: []byte(`
6097groups:
6098- name: xxx
6099 interval: 3m
6100 rules: []
6101---
6102groups:
6103- name: yyy
6104 interval: 2m
6105 rules: []
6106`),
6107 output: parser.File{
6108 IsRelaxed: true,
6109 Groups: []parser.Group{
6110 {
6111 Name: parser.YamlNode{
6112 Value: "xxx",
6113 Pos: diags.PositionRanges{
6114 {Line: 3, FirstColumn: 9, LastColumn: 11},
6115 },
6116 },
6117 Interval: &parser.YamlDuration{
6118 Value: time.Minute * 3,
6119 Raw: "3m",
6120 Pos: diags.PositionRanges{
6121 {Line: 4, FirstColumn: 13, LastColumn: 14},
6122 },
6123 },
6124 },
6125 {
6126 Name: parser.YamlNode{
6127 Value: "yyy",
6128 Pos: diags.PositionRanges{
6129 {Line: 8, FirstColumn: 9, LastColumn: 11},
6130 },
6131 },
6132 Interval: &parser.YamlDuration{
6133 Value: time.Minute * 2,
6134 Raw: "2m",
6135 Pos: diags.PositionRanges{
6136 {Line: 9, FirstColumn: 13, LastColumn: 14},
6137 },
6138 },
6139 },
6140 },
6141 },
6142 },
6143 {
6144 input: []byte(`
6145groups:
6146- name: xxx
6147 interval: 3m
6148 labels:
6149 foo: bar
6150 rules: []
6151`),
6152 strict: true,
6153 output: parser.File{
6154 Groups: []parser.Group{
6155 {
6156 Name: parser.YamlNode{
6157 Value: "xxx",
6158 Pos: diags.PositionRanges{
6159 {Line: 3, FirstColumn: 9, LastColumn: 11},
6160 },
6161 },
6162 Interval: &parser.YamlDuration{
6163 Value: time.Minute * 3,
6164 Raw: "3m",
6165 Pos: diags.PositionRanges{
6166 {Line: 4, FirstColumn: 13, LastColumn: 14},
6167 },
6168 },
6169 Labels: &parser.YamlMap{
6170 Key: &parser.YamlNode{
6171 Value: "labels",
6172 Pos: diags.PositionRanges{
6173 {Line: 5, FirstColumn: 3, LastColumn: 8},
6174 },
6175 },
6176 Items: []*parser.YamlKeyValue{
6177 {
6178 Key: &parser.YamlNode{
6179 Value: "foo",
6180 Pos: diags.PositionRanges{
6181 {Line: 6, FirstColumn: 5, LastColumn: 7},
6182 },
6183 },
6184 Value: &parser.YamlNode{
6185 Value: "bar",
6186 Pos: diags.PositionRanges{
6187 {Line: 6, FirstColumn: 10, LastColumn: 12},
6188 },
6189 },
6190 },
6191 },
6192 },
6193 },
6194 },
6195 },
6196 },
6197 {
6198 input: []byte(`
6199groups:
6200- name: xxx
6201 labels:
6202 - foo: bar
6203 rules: []
6204`),
6205 strict: true,
6206 output: parser.File{
6207 Groups: []parser.Group{
6208 {
6209 Name: parser.YamlNode{
6210 Value: "xxx",
6211 Pos: diags.PositionRanges{
6212 {Line: 3, FirstColumn: 9, LastColumn: 11},
6213 },
6214 },
6215 Error: parser.ParseError{
6216 Err: errors.New("group labels must be a mapping, got list"),
6217 Line: 4,
6218 },
6219 },
6220 },
6221 },
6222 },
6223 {
6224 input: []byte(`
6225groups:
6226- name: xxx
6227 labels:
6228 foo: 1
6229 rules: []
6230`),
6231 strict: true,
6232 output: parser.File{
6233 Groups: []parser.Group{
6234 {
6235 Name: parser.YamlNode{
6236 Value: "xxx",
6237 Pos: diags.PositionRanges{
6238 {Line: 3, FirstColumn: 9, LastColumn: 11},
6239 },
6240 },
6241 Error: parser.ParseError{
6242 Err: errors.New("labels foo value must be a string, got integer instead"),
6243 Line: 5,
6244 },
6245 },
6246 },
6247 },
6248 },
6249 {
6250 input: []byte(`
6251groups:
6252- name: xxx
6253 labels:
6254 foo: bar
6255 bob: foo
6256 foo: bob
6257 rules: []
6258`),
6259 strict: true,
6260 output: parser.File{
6261 Groups: []parser.Group{
6262 {
6263 Name: parser.YamlNode{
6264 Value: "xxx",
6265 Pos: diags.PositionRanges{
6266 {Line: 3, FirstColumn: 9, LastColumn: 11},
6267 },
6268 },
6269 Error: parser.ParseError{
6270 Err: errors.New("duplicated labels key foo"),
6271 Line: 7,
6272 },
6273 },
6274 },
6275 },
6276 },
6277 {
6278 input: []byte(`
6279groups:
6280- name: xxx
6281 interval: 3m
6282 query_offset: 1s
6283 limit: 5
6284 labels:
6285 foo: bar
6286 rules: []
6287`),
6288 output: parser.File{
6289 IsRelaxed: true,
6290 Groups: []parser.Group{
6291 {
6292 Name: parser.YamlNode{
6293 Value: "xxx",
6294 Pos: diags.PositionRanges{
6295 {Line: 3, FirstColumn: 9, LastColumn: 11},
6296 },
6297 },
6298 Interval: &parser.YamlDuration{
6299 Value: time.Minute * 3,
6300 Raw: "3m",
6301 Pos: diags.PositionRanges{
6302 {Line: 4, FirstColumn: 13, LastColumn: 14},
6303 },
6304 },
6305 QueryOffset: &parser.YamlDuration{
6306 Value: time.Second,
6307 Raw: "1s",
6308 Pos: diags.PositionRanges{
6309 {Line: 5, FirstColumn: 17, LastColumn: 18},
6310 },
6311 },
6312 Limit: &parser.YamlInt{
6313 Value: 5,
6314 Raw: "5",
6315 Pos: diags.PositionRanges{
6316 {Line: 6, FirstColumn: 10, LastColumn: 10},
6317 },
6318 },
6319 Labels: &parser.YamlMap{
6320 Key: &parser.YamlNode{
6321 Value: "labels",
6322 Pos: diags.PositionRanges{
6323 {Line: 7, FirstColumn: 3, LastColumn: 8},
6324 },
6325 },
6326 Items: []*parser.YamlKeyValue{
6327 {
6328 Key: &parser.YamlNode{
6329 Value: "foo",
6330 Pos: diags.PositionRanges{
6331 {Line: 8, FirstColumn: 5, LastColumn: 7},
6332 },
6333 },
6334 Value: &parser.YamlNode{
6335 Value: "bar",
6336 Pos: diags.PositionRanges{
6337 {Line: 8, FirstColumn: 10, LastColumn: 12},
6338 },
6339 },
6340 },
6341 },
6342 },
6343 },
6344 },
6345 },
6346 },
6347 {
6348 input: []byte(`
6349groups:
6350- name: xxx
6351 labels:
6352 - foo: bar
6353 rules: []
6354`),
6355 output: parser.File{
6356 IsRelaxed: true,
6357 Groups: []parser.Group{
6358 {
6359 Name: parser.YamlNode{
6360 Value: "xxx",
6361 Pos: diags.PositionRanges{
6362 {Line: 3, FirstColumn: 9, LastColumn: 11},
6363 },
6364 },
6365 Labels: &parser.YamlMap{
6366 Key: &parser.YamlNode{
6367 Value: "labels",
6368 Pos: diags.PositionRanges{
6369 {Line: 4, FirstColumn: 3, LastColumn: 8},
6370 },
6371 },
6372 },
6373 },
6374 },
6375 },
6376 },
6377 {
6378 input: []byte(`
6379groups:
6380- name: xxx
6381 labels:
6382 foo: 1
6383 rules: []
6384`),
6385 output: parser.File{
6386 IsRelaxed: true,
6387 Groups: []parser.Group{
6388 {
6389 Name: parser.YamlNode{
6390 Value: "xxx",
6391 Pos: diags.PositionRanges{
6392 {Line: 3, FirstColumn: 9, LastColumn: 11},
6393 },
6394 },
6395 Labels: &parser.YamlMap{
6396 Key: &parser.YamlNode{
6397 Value: "labels",
6398 Pos: diags.PositionRanges{
6399 {Line: 4, FirstColumn: 3, LastColumn: 8},
6400 },
6401 },
6402 Items: []*parser.YamlKeyValue{
6403 {
6404 Key: &parser.YamlNode{
6405 Value: "foo",
6406 Pos: diags.PositionRanges{
6407 {Line: 5, FirstColumn: 5, LastColumn: 7},
6408 },
6409 },
6410 Value: &parser.YamlNode{
6411 Value: "1",
6412 Pos: diags.PositionRanges{
6413 {Line: 5, FirstColumn: 10, LastColumn: 10},
6414 },
6415 },
6416 },
6417 },
6418 },
6419 },
6420 },
6421 },
6422 },
6423 {
6424 input: []byte(`
6425groups:
6426- name: xxx
6427 labels:
6428 foo: bar
6429 bob: foo
6430 foo: bob
6431 rules: []
6432`),
6433 output: parser.File{
6434 IsRelaxed: true,
6435 Groups: []parser.Group{
6436 {
6437 Name: parser.YamlNode{
6438 Value: "xxx",
6439 Pos: diags.PositionRanges{
6440 {Line: 3, FirstColumn: 9, LastColumn: 11},
6441 },
6442 },
6443 Labels: &parser.YamlMap{
6444 Key: &parser.YamlNode{
6445 Value: "labels",
6446 Pos: diags.PositionRanges{
6447 {Line: 4, FirstColumn: 3, LastColumn: 8},
6448 },
6449 },
6450 Items: []*parser.YamlKeyValue{
6451 {
6452 Key: &parser.YamlNode{
6453 Value: "foo",
6454 Pos: diags.PositionRanges{
6455 {Line: 5, FirstColumn: 5, LastColumn: 7},
6456 },
6457 },
6458 Value: &parser.YamlNode{
6459 Value: "bar",
6460 Pos: diags.PositionRanges{
6461 {Line: 5, FirstColumn: 10, LastColumn: 12},
6462 },
6463 },
6464 },
6465 {
6466 Key: &parser.YamlNode{
6467 Value: "bob",
6468 Pos: diags.PositionRanges{
6469 {Line: 6, FirstColumn: 5, LastColumn: 7},
6470 },
6471 },
6472 Value: &parser.YamlNode{
6473 Value: "foo",
6474 Pos: diags.PositionRanges{
6475 {Line: 6, FirstColumn: 10, LastColumn: 12},
6476 },
6477 },
6478 },
6479 {
6480 Key: &parser.YamlNode{
6481 Value: "foo",
6482 Pos: diags.PositionRanges{
6483 {Line: 7, FirstColumn: 5, LastColumn: 7},
6484 },
6485 },
6486 Value: &parser.YamlNode{
6487 Value: "bob",
6488 Pos: diags.PositionRanges{
6489 {Line: 7, FirstColumn: 10, LastColumn: 12},
6490 },
6491 },
6492 },
6493 },
6494 },
6495 },
6496 },
6497 },
6498 },
6499 {
6500 input: []byte(`
6501- name: xxx
6502 interval: 3m
6503 query_offset: 1s
6504 limit: 5
6505 labels:
6506 foo: bar
6507 rules:
6508 - record: up:count
6509 expr: count(up)
6510`),
6511 output: parser.File{
6512 IsRelaxed: true,
6513 Groups: []parser.Group{
6514 {
6515 Name: parser.YamlNode{
6516 Value: "xxx",
6517 Pos: diags.PositionRanges{
6518 {Line: 2, FirstColumn: 9, LastColumn: 11},
6519 },
6520 },
6521 Interval: &parser.YamlDuration{
6522 Value: time.Minute * 3,
6523 Raw: "3m",
6524 Pos: diags.PositionRanges{
6525 {Line: 3, FirstColumn: 13, LastColumn: 14},
6526 },
6527 },
6528 QueryOffset: &parser.YamlDuration{
6529 Value: time.Second,
6530 Raw: "1s",
6531 Pos: diags.PositionRanges{
6532 {Line: 4, FirstColumn: 17, LastColumn: 18},
6533 },
6534 },
6535 Limit: &parser.YamlInt{
6536 Value: 5,
6537 Raw: "5",
6538 Pos: diags.PositionRanges{
6539 {Line: 5, FirstColumn: 10, LastColumn: 10},
6540 },
6541 },
6542 Labels: &parser.YamlMap{
6543 Key: &parser.YamlNode{
6544 Value: "labels",
6545 Pos: diags.PositionRanges{
6546 {Line: 6, FirstColumn: 3, LastColumn: 8},
6547 },
6548 },
6549 Items: []*parser.YamlKeyValue{
6550 {
6551 Key: &parser.YamlNode{
6552 Value: "foo",
6553 Pos: diags.PositionRanges{
6554 {Line: 7, FirstColumn: 5, LastColumn: 7},
6555 },
6556 },
6557 Value: &parser.YamlNode{
6558 Value: "bar",
6559 Pos: diags.PositionRanges{
6560 {Line: 7, FirstColumn: 10, LastColumn: 12},
6561 },
6562 },
6563 },
6564 },
6565 },
6566 Rules: []parser.Rule{
6567 {
6568 Lines: diags.LineRange{First: 9, Last: 10},
6569 RecordingRule: &parser.RecordingRule{
6570 Record: parser.YamlNode{
6571 Value: "up:count",
6572 Pos: diags.PositionRanges{
6573 {Line: 9, FirstColumn: 13, LastColumn: 20},
6574 },
6575 },
6576 Expr: parser.PromQLExpr{
6577 Value: &parser.YamlNode{
6578 Value: "count(up)",
6579 Pos: diags.PositionRanges{
6580 {Line: 10, FirstColumn: 11, LastColumn: 19},
6581 },
6582 },
6583 },
6584 },
6585 },
6586 },
6587 },
6588 },
6589 },
6590 },
6591 {
6592 input: []byte(`
6593
6594- interval: 3m
6595 query_offset: 1s
6596 limit: 5
6597 labels:
6598 foo: bar
6599 rules:
6600 - record: up:count
6601 expr: count(up)
6602`),
6603 output: parser.File{
6604 IsRelaxed: true,
6605 Groups: []parser.Group{
6606 {
6607 Name: parser.YamlNode{Value: ""},
6608 Rules: []parser.Rule{
6609 {
6610 Lines: diags.LineRange{First: 9, Last: 10},
6611 RecordingRule: &parser.RecordingRule{
6612 Record: parser.YamlNode{
6613 Value: "up:count",
6614 Pos: diags.PositionRanges{
6615 {Line: 9, FirstColumn: 13, LastColumn: 20},
6616 },
6617 },
6618 Expr: parser.PromQLExpr{
6619 Value: &parser.YamlNode{
6620 Value: "count(up)",
6621 Pos: diags.PositionRanges{
6622 {Line: 10, FirstColumn: 11, LastColumn: 19},
6623 },
6624 },
6625 },
6626 },
6627 },
6628 },
6629 },
6630 },
6631 },
6632 },
6633 {
6634 input: []byte(`
6635apiVersion: v1
6636kind: ConfigMap
6637metadata:
6638 labels:
6639 shard: "0"
6640 total-shards: "1"
6641 name: shard-jobs
6642 namespace: foo
6643data:
6644 jobs.yaml: |
6645 jobs:
6646 - name: foo
6647 interval: 1m
6648 queries:
6649 - name: xxx
6650 help: ''
6651 labels:
6652 - account_id
6653 - bucket
6654 - cache_status
6655 mtls_identity:
6656 cert_path: /etc/identity/tls.crt
6657 key_path: /etc/identity/tls.key
6658`),
6659 output: parser.File{
6660 Groups: nil,
6661 IsRelaxed: true,
6662 },
6663 },
6664 {
6665 // Test rule with line comment on the mapping node itself
6666 input: []byte(`- record: foo # pint disable line comment
6667 expr: bar
6668`),
6669 output: parser.File{
6670 IsRelaxed: true,
6671 Groups: []parser.Group{
6672 {
6673 Rules: []parser.Rule{
6674 {
6675 Lines: diags.LineRange{First: 1, Last: 2},
6676 Comments: []comments.Comment{
6677 {
6678 Type: comments.DisableType,
6679 Value: comments.Disable{Match: "line comment"},
6680 },
6681 },
6682 RecordingRule: &parser.RecordingRule{
6683 Record: parser.YamlNode{
6684 Value: "foo",
6685 Pos: diags.PositionRanges{
6686 {Line: 1, FirstColumn: 11, LastColumn: 13},
6687 },
6688 },
6689 Expr: parser.PromQLExpr{
6690 Value: &parser.YamlNode{
6691 Value: "bar",
6692 Pos: diags.PositionRanges{
6693 {Line: 2, FirstColumn: 9, LastColumn: 11},
6694 },
6695 },
6696 },
6697 },
6698 },
6699 },
6700 },
6701 },
6702 },
6703 },
6704 {
6705 // Test flow mapping with line comment (comment attached to parent node)
6706 input: []byte(`- {record: foo, expr: bar} # pint disable flow comment
6707`),
6708 output: parser.File{
6709 IsRelaxed: true,
6710 Groups: []parser.Group{
6711 {
6712 Rules: []parser.Rule{
6713 {
6714 Lines: diags.LineRange{First: 1, Last: 1},
6715 Comments: []comments.Comment{
6716 {
6717 Type: comments.DisableType,
6718 Value: comments.Disable{Match: "flow comment"},
6719 },
6720 },
6721 RecordingRule: &parser.RecordingRule{
6722 Record: parser.YamlNode{
6723 Value: "foo",
6724 Pos: diags.PositionRanges{
6725 {Line: 1, FirstColumn: 12, LastColumn: 14},
6726 },
6727 },
6728 Expr: parser.PromQLExpr{
6729 Value: &parser.YamlNode{
6730 Value: "bar",
6731 Pos: diags.PositionRanges{
6732 {Line: 1, FirstColumn: 23, LastColumn: 25},
6733 },
6734 },
6735 },
6736 },
6737 },
6738 },
6739 },
6740 },
6741 },
6742 },
6743 {
6744 // Test flow mapping with foot comment (comment attached to parent node's FootComment)
6745 input: []byte(`groups:
6746- name: test
6747 rules:
6748 - {record: foo, expr: bar}
6749 # pint disable foot comment
6750`),
6751 output: parser.File{
6752 IsRelaxed: true,
6753 Groups: []parser.Group{
6754 {
6755 Name: parser.YamlNode{
6756 Value: "test",
6757 Pos: diags.PositionRanges{
6758 {Line: 2, FirstColumn: 9, LastColumn: 12},
6759 },
6760 },
6761 Rules: []parser.Rule{
6762 {
6763 Lines: diags.LineRange{First: 4, Last: 4},
6764 Comments: []comments.Comment{
6765 {
6766 Type: comments.DisableType,
6767 Value: comments.Disable{Match: "foot comment"},
6768 },
6769 },
6770 RecordingRule: &parser.RecordingRule{
6771 Record: parser.YamlNode{
6772 Value: "foo",
6773 Pos: diags.PositionRanges{
6774 {Line: 4, FirstColumn: 14, LastColumn: 16},
6775 },
6776 },
6777 Expr: parser.PromQLExpr{
6778 Value: &parser.YamlNode{
6779 Value: "bar",
6780 Pos: diags.PositionRanges{
6781 {Line: 4, FirstColumn: 25, LastColumn: 27},
6782 },
6783 },
6784 },
6785 },
6786 },
6787 },
6788 },
6789 },
6790 },
6791 },
6792 }
6793
6794 alwaysEqual := cmp.Comparer(func(_, _ any) bool { return true })
6795 ignorePrometheusExpr := cmp.FilterValues(func(x, y any) bool {
6796 _, xe := x.(*parser.PromQLNode)
6797 _, ye := y.(*parser.PromQLNode)
6798 return xe || ye
6799 }, alwaysEqual)
6800
6801 cmpErrorText := cmp.Comparer(func(x, y any) bool {
6802 xe := x.(error)
6803 ye := y.(error)
6804 return xe.Error() == ye.Error()
6805 })
6806 sameErrorText := cmp.FilterValues(func(x, y any) bool {
6807 _, xe := x.(error)
6808 _, ye := y.(error)
6809 return xe && ye
6810 }, cmpErrorText)
6811
6812 for i, tc := range testCases {
6813 t.Run(strconv.Itoa(i+1), func(t *testing.T) {
6814 t.Logf("\n--- Content ---%s--- END ---", tc.input)
6815
6816 s := bufio.NewScanner(bytes.NewReader(tc.input))
6817 for s.Scan() {
6818 tc.output.TotalLines++
6819 }
6820
6821 p := parser.NewParser(parser.Options{IsStrict: tc.strict, Schema: tc.schema, Names: tc.names})
6822 file := p.Parse(bytes.NewReader(tc.input))
6823
6824 if diff := cmp.Diff(
6825 tc.output, file,
6826 ignorePrometheusExpr,
6827 sameErrorText,
6828 cmpopts.IgnoreUnexported(parser.PromQLExpr{}),
6829 ); diff != "" {
6830 t.Errorf("Parse() returned wrong output (-want +got):\n%s", diff)
6831 return
6832 }
6833 })
6834 }
6835}
6836
6837func TestPromQLExprSyntaxError(t *testing.T) {
6838 type testCaseT struct {
6839 name string
6840 expectedError string
6841 input []byte
6842 schema parser.Schema
6843 names model.ValidationScheme
6844 }
6845
6846 testCases := []testCaseT{
6847 {
6848 name: "invalid label matching operator",
6849 input: []byte(`
6850groups:
6851- name: foo
6852 rules:
6853 - record: foo
6854 expr: |
6855 {'up' == 1}
6856`),
6857 expectedError: `1:8: parse error: unexpected "=" in label matching, expected string`,
6858 schema: parser.PrometheusSchema,
6859 names: model.LegacyValidation,
6860 },
6861 {
6862 name: "unclosed parenthesis",
6863 input: []byte(`
6864groups:
6865- name: foo
6866 rules:
6867 - record: foo
6868 expr: sum(up
6869`),
6870 expectedError: `1:7: parse error: unclosed left parenthesis`,
6871 schema: parser.PrometheusSchema,
6872 names: model.LegacyValidation,
6873 },
6874 {
6875 name: "unclosed brace",
6876 input: []byte(`
6877groups:
6878- name: foo
6879 rules:
6880 - record: foo
6881 expr: up{job="test"
6882`),
6883 expectedError: `1:14: parse error: unexpected end of input inside braces`,
6884 schema: parser.PrometheusSchema,
6885 names: model.LegacyValidation,
6886 },
6887 {
6888 name: "invalid aggregation without grouping",
6889 input: []byte(`
6890groups:
6891- name: foo
6892 rules:
6893 - record: foo
6894 expr: sum without (up)
6895`),
6896 expectedError: `1:17: parse error: unexpected end of input in aggregation`,
6897 schema: parser.PrometheusSchema,
6898 names: model.LegacyValidation,
6899 },
6900 {
6901 name: "bad regex syntax",
6902 input: []byte(`
6903groups:
6904- name: foo
6905 rules:
6906 - record: foo
6907 expr: up{job=~"["}
6908`),
6909 expectedError: `1:4: parse error: error parsing regexp: missing closing ]: ` + "`[`",
6910 schema: parser.PrometheusSchema,
6911 names: model.LegacyValidation,
6912 },
6913 {
6914 name: "invalid binary operator position",
6915 input: []byte(`
6916groups:
6917- name: foo
6918 rules:
6919 - record: foo
6920 expr: up +
6921`),
6922 expectedError: `1:5: parse error: unexpected end of input`,
6923 schema: parser.PrometheusSchema,
6924 names: model.LegacyValidation,
6925 },
6926 {
6927 name: "multiple binary operators",
6928 input: []byte(`
6929groups:
6930- name: foo
6931 rules:
6932 - record: foo
6933 expr: up + * down
6934`),
6935 expectedError: `1:6: parse error: unexpected <op:*>`,
6936 schema: parser.PrometheusSchema,
6937 names: model.LegacyValidation,
6938 },
6939 {
6940 name: "invalid duration syntax",
6941 input: []byte(`
6942groups:
6943- name: foo
6944 rules:
6945 - record: foo
6946 expr: rate(up[5x])
6947`),
6948 expectedError: `1:9: parse error: bad number or duration syntax: "5"`,
6949 schema: parser.PrometheusSchema,
6950 names: model.LegacyValidation,
6951 },
6952 {
6953 name: "missing range selector",
6954 input: []byte(`
6955groups:
6956- name: foo
6957 rules:
6958 - record: foo
6959 expr: rate(up)
6960`),
6961 expectedError: `1:6: parse error: expected type range vector in call to function "rate", got instant vector`,
6962 schema: parser.PrometheusSchema,
6963 names: model.LegacyValidation,
6964 },
6965 {
6966 name: "invalid label name in matcher",
6967 input: []byte(`
6968groups:
6969- name: foo
6970 rules:
6971 - record: foo
6972 expr: up{123="test"}
6973`),
6974 expectedError: `1:4: parse error: unexpected character inside braces: '1'`,
6975 schema: parser.PrometheusSchema,
6976 names: model.LegacyValidation,
6977 },
6978 {
6979 name: "unclosed parenthesis with Thanos schema",
6980 input: []byte(`
6981groups:
6982- name: foo
6983 rules:
6984 - record: foo
6985 expr: sum(up
6986`),
6987 expectedError: `1:7: parse error: unclosed left parenthesis`,
6988 schema: parser.ThanosSchema,
6989 names: model.LegacyValidation,
6990 },
6991 {
6992 name: "invalid binary operator with UTF8 validation",
6993 input: []byte(`
6994groups:
6995- name: foo
6996 rules:
6997 - record: foo
6998 expr: up +
6999`),
7000 expectedError: `1:5: parse error: unexpected end of input`,
7001 schema: parser.PrometheusSchema,
7002 names: model.UTF8Validation,
7003 },
7004 {
7005 name: "bad regex with Thanos schema and UTF8 validation",
7006 input: []byte(`
7007groups:
7008- name: foo
7009 rules:
7010 - record: foo
7011 expr: up{job=~"["}
7012`),
7013 expectedError: `1:4: parse error: error parsing regexp: missing closing ]: ` + "`[`",
7014 schema: parser.ThanosSchema,
7015 names: model.UTF8Validation,
7016 },
7017 }
7018
7019 for _, tc := range testCases {
7020 t.Run(tc.name, func(t *testing.T) {
7021 p := parser.NewParser(parser.Options{Schema: tc.schema, Names: tc.names})
7022 r := bytes.NewReader(tc.input)
7023 file := p.Parse(r)
7024
7025 require.NoError(t, file.Error.Err)
7026 require.NotEmpty(t, file.Groups)
7027 for _, group := range file.Groups {
7028 for _, rule := range group.Rules {
7029 require.NoError(t, rule.Error.Err)
7030 expr := rule.Expr()
7031 err := expr.SyntaxError()
7032 require.Error(t, err)
7033 require.Equal(t, tc.expectedError, err.Error())
7034 }
7035 }
7036 })
7037 }
7038}
7039
7040func BenchmarkParse(b *testing.B) {
7041 data, err := os.ReadFile("testrules.yml")
7042 require.NoError(b, err)
7043
7044 p := parser.NewParser(parser.Options{IsStrict: true, Names: model.LegacyValidation})
7045 for b.Loop() {
7046 b.StopTimer()
7047 r := bytes.NewReader(data)
7048 b.StartTimer()
7049
7050 f := p.Parse(r)
7051
7052 b.StopTimer()
7053 require.Len(b, f.Groups, 90)
7054 require.NoError(b, f.Error.Err)
7055 require.Equal(b, 5501, f.TotalLines)
7056 b.StartTimer()
7057 }
7058}