cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.50.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser_test.go

1456lines · modecode

1package parser_test
2
3import (
4 "fmt"
5 "strconv"
6 "testing"
7
8 "github.com/cloudflare/pint/internal/comments"
9 "github.com/cloudflare/pint/internal/parser"
10
11 "github.com/google/go-cmp/cmp"
12 promparser "github.com/prometheus/prometheus/promql/parser"
13)
14
15func TestParse(t *testing.T) {
16 type testCaseT struct {
17 content []byte
18 output []parser.Rule
19 shouldError bool
20 }
21
22 testCases := []testCaseT{
23 {
24 content: nil,
25 output: nil,
26 shouldError: false,
27 },
28 {
29 content: []byte{},
30 output: nil,
31 shouldError: false,
32 },
33 {
34 content: []byte(string("! !00 \xf6")),
35 output: nil,
36 shouldError: true,
37 },
38 {
39 content: []byte("- 0: 0\n 00000000: 000000\n 000000:00000000000: 00000000\n 00000000000:000000: 0000000000000000000000000000000000\n 000000: 0000000\n expr: |"),
40 output: []parser.Rule{
41 {Error: parser.ParseError{Err: fmt.Errorf("incomplete rule, no alert or record key"), Line: 6}},
42 },
43 },
44 {
45 content: []byte("- record: |\n multiline\n"),
46 output: []parser.Rule{
47 {Error: parser.ParseError{Err: fmt.Errorf("missing expr key"), Line: 2}},
48 },
49 },
50 {
51 content: []byte("- expr: foo\n"),
52 output: []parser.Rule{
53 {Error: parser.ParseError{Err: fmt.Errorf("incomplete rule, no alert or record key"), Line: 1}},
54 },
55 },
56 {
57 content: []byte("- alert: foo\n"),
58 output: []parser.Rule{
59 {Error: parser.ParseError{Err: fmt.Errorf("missing expr key"), Line: 1}},
60 },
61 },
62 {
63 content: []byte("- alert: foo\n record: foo\n"),
64 output: []parser.Rule{
65 {Error: parser.ParseError{Err: fmt.Errorf("got both record and alert keys in a single rule"), Line: 1}},
66 },
67 },
68 {
69 content: []byte("- record: foo\n labels:\n foo: bar\n"),
70 output: []parser.Rule{
71 {Error: parser.ParseError{Err: fmt.Errorf("missing expr key"), Line: 1}},
72 },
73 },
74 {
75 content: []byte("- record: - foo\n"),
76 shouldError: true,
77 },
78 {
79 content: []byte("- record: foo expr: sum(\n"),
80 shouldError: true,
81 },
82 {
83 content: []byte("- record\n\texpr: foo\n"),
84 shouldError: true,
85 },
86 {
87 content: []byte(`
88- record: foo
89 expr: bar
90 expr: bar
91`),
92 output: []parser.Rule{
93 {Error: parser.ParseError{Err: fmt.Errorf("duplicated expr key"), Line: 4}},
94 },
95 },
96 {
97 content: []byte(`
98- record: foo
99 expr: bar
100 record: bar
101`),
102 output: []parser.Rule{
103 {Error: parser.ParseError{Err: fmt.Errorf("duplicated record key"), Line: 4}},
104 },
105 },
106 {
107 content: []byte(`
108- alert: foo
109 alert: bar
110 expr: bar
111`),
112 output: []parser.Rule{
113 {Error: parser.ParseError{Err: fmt.Errorf("duplicated alert key"), Line: 3}},
114 },
115 },
116 {
117 content: []byte(`
118- alert: foo
119 for: 5m
120 expr: bar
121 for: 1m
122`),
123 output: []parser.Rule{
124 {Error: parser.ParseError{Err: fmt.Errorf("duplicated for key"), Line: 5}},
125 },
126 },
127 {
128 content: []byte(`
129- alert: foo
130 keep_firing_for: 5m
131 expr: bar
132 keep_firing_for: 1m
133`),
134 output: []parser.Rule{
135 {Error: parser.ParseError{Err: fmt.Errorf("duplicated keep_firing_for key"), Line: 5}},
136 },
137 },
138 {
139 content: []byte(`
140- alert: foo
141 labels: {}
142 expr: bar
143 labels: {}
144`),
145 output: []parser.Rule{
146 {Error: parser.ParseError{Err: fmt.Errorf("duplicated labels key"), Line: 5}},
147 },
148 },
149 {
150 content: []byte(`
151- record: foo
152 labels: {}
153 expr: bar
154 labels: {}
155`),
156 output: []parser.Rule{
157 {Error: parser.ParseError{Err: fmt.Errorf("duplicated labels key"), Line: 5}},
158 },
159 },
160 {
161 content: []byte(`
162- alert: foo
163 annotations: {}
164 expr: bar
165 annotations: {}
166`),
167 output: []parser.Rule{
168 {Error: parser.ParseError{Err: fmt.Errorf("duplicated annotations key"), Line: 5}},
169 },
170 },
171 {
172 content: []byte("- record: foo\n expr: foo\n extra: true\n"),
173 output: []parser.Rule{
174 {Error: parser.ParseError{Err: fmt.Errorf("invalid key(s) found: extra"), Line: 3}},
175 },
176 },
177 {
178 content: []byte("- record: foo\n expr: foo offset 10m\n"),
179 output: []parser.Rule{
180 {
181 RecordingRule: &parser.RecordingRule{
182 Record: parser.YamlKeyValue{
183 Key: &parser.YamlNode{
184 Position: parser.FilePosition{Lines: []int{1}},
185 Value: "record",
186 },
187 Value: &parser.YamlNode{
188 Position: parser.FilePosition{Lines: []int{1}},
189 Value: "foo",
190 },
191 },
192 Expr: parser.PromQLExpr{
193 Key: &parser.YamlNode{
194 Position: parser.FilePosition{Lines: []int{2}},
195 Value: "expr",
196 },
197 Value: &parser.YamlNode{
198 Position: parser.FilePosition{Lines: []int{2}},
199 Value: "foo offset 10m",
200 },
201 Query: &parser.PromQLNode{
202 Expr: "foo offset 10m",
203 },
204 },
205 },
206 },
207 },
208 },
209 {
210 content: []byte("- record: foo\n expr: foo offset -10m\n"),
211 output: []parser.Rule{
212 {
213 RecordingRule: &parser.RecordingRule{
214 Record: parser.YamlKeyValue{
215 Key: &parser.YamlNode{
216 Position: parser.FilePosition{Lines: []int{1}},
217 Value: "record",
218 },
219 Value: &parser.YamlNode{
220 Position: parser.FilePosition{Lines: []int{1}},
221 Value: "foo",
222 },
223 },
224 Expr: parser.PromQLExpr{
225 Key: &parser.YamlNode{
226 Position: parser.FilePosition{Lines: []int{2}},
227 Value: "expr",
228 },
229 Value: &parser.YamlNode{
230 Position: parser.FilePosition{Lines: []int{2}},
231 Value: "foo offset -10m",
232 },
233 Query: &parser.PromQLNode{
234 Expr: "foo offset -10m",
235 },
236 },
237 },
238 },
239 },
240 },
241 {
242 content: []byte(`
243# pint disable head comment
244- record: foo # pint disable record comment
245 expr: foo offset 10m # pint disable expr comment
246 # pint disable pre-labels comment
247 labels:
248 # pint disable pre-foo comment
249 foo: bar
250 # pint disable post-foo comment
251 bob: alice
252 # pint disable foot comment
253`),
254 output: []parser.Rule{
255 {
256 Comments: []comments.Comment{
257 {
258 Type: comments.DisableType,
259 Value: comments.Disable{Match: "head comment"},
260 },
261 {
262 Type: comments.DisableType,
263 Value: comments.Disable{Match: "record comment"},
264 },
265 {
266 Type: comments.DisableType,
267 Value: comments.Disable{Match: "expr comment"},
268 },
269 {
270 Type: comments.DisableType,
271 Value: comments.Disable{Match: "pre-labels comment"},
272 },
273 {
274 Type: comments.DisableType,
275 Value: comments.Disable{Match: "foot comment"},
276 },
277 {
278 Type: comments.DisableType,
279 Value: comments.Disable{Match: "pre-foo comment"},
280 },
281 {
282 Type: comments.DisableType,
283 Value: comments.Disable{Match: "post-foo comment"},
284 },
285 },
286 RecordingRule: &parser.RecordingRule{
287 Record: parser.YamlKeyValue{
288 Key: &parser.YamlNode{
289 Position: parser.FilePosition{Lines: []int{3}},
290 Value: "record",
291 },
292 Value: &parser.YamlNode{
293 Position: parser.FilePosition{Lines: []int{3}},
294 Value: "foo",
295 },
296 },
297 Expr: parser.PromQLExpr{
298 Key: &parser.YamlNode{
299 Position: parser.FilePosition{Lines: []int{4}},
300 Value: "expr",
301 },
302 Value: &parser.YamlNode{
303 Position: parser.FilePosition{Lines: []int{4}},
304 Value: "foo offset 10m",
305 },
306 Query: &parser.PromQLNode{
307 Expr: "foo offset 10m",
308 },
309 },
310 Labels: &parser.YamlMap{
311 Key: &parser.YamlNode{
312 Position: parser.FilePosition{Lines: []int{6}},
313 Value: "labels",
314 },
315 Items: []*parser.YamlKeyValue{
316 {
317 Key: &parser.YamlNode{
318 Position: parser.FilePosition{Lines: []int{8}},
319 Value: "foo",
320 },
321 Value: &parser.YamlNode{
322 Position: parser.FilePosition{Lines: []int{8}},
323 Value: "bar",
324 },
325 },
326 {
327 Key: &parser.YamlNode{
328 Position: parser.FilePosition{Lines: []int{10}},
329 Value: "bob",
330 },
331 Value: &parser.YamlNode{
332 Position: parser.FilePosition{Lines: []int{10}},
333 Value: "alice",
334 },
335 },
336 },
337 },
338 },
339 },
340 },
341 },
342 {
343 content: []byte("- record: foo\n expr: foo[5m] offset 10m\n"),
344 output: []parser.Rule{
345 {
346 RecordingRule: &parser.RecordingRule{
347 Record: parser.YamlKeyValue{
348 Key: &parser.YamlNode{
349 Position: parser.FilePosition{Lines: []int{1}},
350 Value: "record",
351 },
352 Value: &parser.YamlNode{
353 Position: parser.FilePosition{Lines: []int{1}},
354 Value: "foo",
355 },
356 },
357 Expr: parser.PromQLExpr{
358 Key: &parser.YamlNode{
359 Position: parser.FilePosition{Lines: []int{2}},
360 Value: "expr",
361 },
362 Value: &parser.YamlNode{
363 Position: parser.FilePosition{Lines: []int{2}},
364 Value: "foo[5m] offset 10m",
365 },
366 Query: &parser.PromQLNode{
367 Expr: "foo[5m] offset 10m",
368 Children: []*parser.PromQLNode{
369 {Expr: "foo offset 10m"},
370 },
371 },
372 },
373 },
374 },
375 },
376 },
377 {
378 content: []byte(`
379- record: name
380 expr: sum(foo)
381 labels:
382 foo: bar
383 bob: alice
384`),
385 output: []parser.Rule{
386 {
387 RecordingRule: &parser.RecordingRule{
388 Record: parser.YamlKeyValue{
389 Key: &parser.YamlNode{
390 Position: parser.FilePosition{Lines: []int{2}},
391 Value: "record",
392 },
393 Value: &parser.YamlNode{
394 Position: parser.FilePosition{Lines: []int{2}},
395 Value: "name",
396 },
397 },
398 Expr: parser.PromQLExpr{
399 Key: &parser.YamlNode{
400 Position: parser.FilePosition{Lines: []int{3}},
401 Value: "expr",
402 },
403 Value: &parser.YamlNode{
404 Position: parser.FilePosition{Lines: []int{3}},
405 Value: "sum(foo)",
406 },
407 Query: &parser.PromQLNode{
408 Expr: "sum(foo)",
409 Children: []*parser.PromQLNode{
410 {Expr: "foo"},
411 },
412 },
413 },
414 Labels: &parser.YamlMap{
415 Key: &parser.YamlNode{
416 Position: parser.FilePosition{Lines: []int{4}},
417 Value: "labels",
418 },
419 Items: []*parser.YamlKeyValue{
420 {
421 Key: &parser.YamlNode{
422 Position: parser.FilePosition{Lines: []int{5}},
423 Value: "foo",
424 },
425 Value: &parser.YamlNode{
426 Position: parser.FilePosition{Lines: []int{5}},
427 Value: "bar",
428 },
429 },
430 {
431 Key: &parser.YamlNode{
432 Position: parser.FilePosition{Lines: []int{6}},
433 Value: "bob",
434 },
435 Value: &parser.YamlNode{
436 Position: parser.FilePosition{Lines: []int{6}},
437 Value: "alice",
438 },
439 },
440 },
441 },
442 },
443 },
444 },
445 shouldError: false,
446 },
447 {
448 content: []byte(`
449groups:
450- name: custom_rules
451 rules:
452 - record: name
453 expr: sum(foo)
454 labels:
455 foo: bar
456 bob: alice
457`),
458 output: []parser.Rule{
459 {
460 RecordingRule: &parser.RecordingRule{
461 Record: parser.YamlKeyValue{
462 Key: &parser.YamlNode{
463 Position: parser.FilePosition{Lines: []int{5}},
464 Value: "record",
465 },
466 Value: &parser.YamlNode{
467 Position: parser.FilePosition{Lines: []int{5}},
468 Value: "name",
469 },
470 },
471 Expr: parser.PromQLExpr{
472 Key: &parser.YamlNode{
473 Position: parser.FilePosition{Lines: []int{6}},
474 Value: "expr",
475 },
476 Value: &parser.YamlNode{
477 Position: parser.FilePosition{Lines: []int{6}},
478 Value: "sum(foo)",
479 },
480 Query: &parser.PromQLNode{
481 Expr: "sum(foo)",
482 Children: []*parser.PromQLNode{
483 {Expr: "foo"},
484 },
485 },
486 },
487 Labels: &parser.YamlMap{
488 Key: &parser.YamlNode{
489 Position: parser.FilePosition{Lines: []int{7}},
490 Value: "labels",
491 },
492 Items: []*parser.YamlKeyValue{
493 {
494 Key: &parser.YamlNode{
495 Position: parser.FilePosition{Lines: []int{8}},
496 Value: "foo",
497 },
498 Value: &parser.YamlNode{
499 Position: parser.FilePosition{Lines: []int{8}},
500 Value: "bar",
501 },
502 },
503 {
504 Key: &parser.YamlNode{
505 Position: parser.FilePosition{Lines: []int{9}},
506 Value: "bob",
507 },
508 Value: &parser.YamlNode{
509 Position: parser.FilePosition{Lines: []int{9}},
510 Value: "alice",
511 },
512 },
513 },
514 },
515 },
516 },
517 },
518 shouldError: false,
519 },
520 {
521 content: []byte(`- alert: Down
522 expr: |
523 up == 0
524 for: |+
525 11m
526 labels:
527 severity: critical
528 annotations:
529 uri: https://docs.example.com/down.html
530
531- record: >
532 foo
533 expr: |-
534 bar
535 /
536 baz > 1
537 labels: {}
538`),
539 output: []parser.Rule{
540 {
541 AlertingRule: &parser.AlertingRule{
542 Alert: parser.YamlKeyValue{
543 Key: &parser.YamlNode{
544 Position: parser.FilePosition{Lines: []int{1}},
545 Value: "alert",
546 },
547 Value: &parser.YamlNode{
548 Position: parser.FilePosition{Lines: []int{1}},
549 Value: "Down",
550 },
551 },
552 Expr: parser.PromQLExpr{
553 Key: &parser.YamlNode{
554 Position: parser.FilePosition{Lines: []int{2}},
555 Value: "expr",
556 },
557 Value: &parser.YamlNode{
558 Position: parser.FilePosition{Lines: []int{3}},
559 Value: "up == 0\n",
560 },
561 Query: &parser.PromQLNode{
562 Expr: "up == 0\n",
563 Children: []*parser.PromQLNode{
564 {Expr: "up"},
565 {Expr: "0"},
566 },
567 },
568 },
569 For: &parser.YamlKeyValue{
570 Key: &parser.YamlNode{
571 Position: parser.FilePosition{Lines: []int{4}},
572 Value: "for",
573 },
574 Value: &parser.YamlNode{
575 Position: parser.FilePosition{Lines: []int{5}},
576 Value: "11m\n",
577 },
578 },
579 Labels: &parser.YamlMap{
580 Key: &parser.YamlNode{
581 Position: parser.FilePosition{Lines: []int{6}},
582 Value: "labels",
583 },
584 Items: []*parser.YamlKeyValue{
585 {
586 Key: &parser.YamlNode{
587 Position: parser.FilePosition{Lines: []int{7}},
588 Value: "severity",
589 },
590 Value: &parser.YamlNode{
591 Position: parser.FilePosition{Lines: []int{7}},
592 Value: "critical",
593 },
594 },
595 },
596 },
597 Annotations: &parser.YamlMap{
598 Key: &parser.YamlNode{
599 Position: parser.FilePosition{Lines: []int{8}},
600 Value: "annotations",
601 },
602 Items: []*parser.YamlKeyValue{
603 {
604 Key: &parser.YamlNode{
605 Position: parser.FilePosition{Lines: []int{9}},
606 Value: "uri",
607 },
608 Value: &parser.YamlNode{
609 Position: parser.FilePosition{Lines: []int{9}},
610 Value: "https://docs.example.com/down.html",
611 },
612 },
613 },
614 },
615 },
616 },
617 {
618 RecordingRule: &parser.RecordingRule{
619 Record: parser.YamlKeyValue{
620 Key: &parser.YamlNode{
621 Position: parser.FilePosition{Lines: []int{11}},
622 Value: "record",
623 },
624 Value: &parser.YamlNode{
625 Position: parser.FilePosition{Lines: []int{12}},
626 Value: "foo\n",
627 },
628 },
629 Expr: parser.PromQLExpr{
630 Key: &parser.YamlNode{
631 Position: parser.FilePosition{Lines: []int{13}},
632 Value: "expr",
633 },
634 Value: &parser.YamlNode{
635 Position: parser.FilePosition{Lines: []int{14, 15, 16}},
636 Value: "bar\n/\nbaz > 1",
637 },
638 Query: &parser.PromQLNode{
639 Expr: "bar\n/\nbaz > 1",
640 Children: []*parser.PromQLNode{
641 {
642 Expr: "bar / baz", Children: []*parser.PromQLNode{
643 {Expr: "bar"},
644 {Expr: "baz"},
645 },
646 },
647 {Expr: "1"},
648 },
649 },
650 },
651 Labels: &parser.YamlMap{
652 Key: &parser.YamlNode{
653 Position: parser.FilePosition{Lines: []int{17}},
654 Value: "labels",
655 },
656 },
657 },
658 },
659 },
660 shouldError: false,
661 },
662 {
663 content: []byte(`- alert: Foo
664 expr:
665 (
666 xxx
667 -
668 yyy
669 ) * bar > 0
670 and on(instance, device) baz
671 for: 30m
672`),
673 output: []parser.Rule{
674 {
675 AlertingRule: &parser.AlertingRule{
676 Alert: parser.YamlKeyValue{
677 Key: &parser.YamlNode{
678 Position: parser.FilePosition{Lines: []int{1}},
679 Value: "alert",
680 },
681 Value: &parser.YamlNode{
682 Position: parser.FilePosition{Lines: []int{1}},
683 Value: "Foo",
684 },
685 },
686 Expr: parser.PromQLExpr{
687 Key: &parser.YamlNode{
688 Position: parser.FilePosition{Lines: []int{2}},
689 Value: "expr",
690 },
691 Value: &parser.YamlNode{
692 Position: parser.FilePosition{Lines: []int{3, 4, 5, 6, 7, 8}},
693 Value: "( xxx - yyy ) * bar > 0 and on(instance, device) baz",
694 },
695 Query: &parser.PromQLNode{
696 Expr: "( xxx - yyy ) * bar > 0 and on(instance, device) baz",
697 Children: []*parser.PromQLNode{
698 {
699 Expr: "(xxx - yyy) * bar > 0",
700 Children: []*parser.PromQLNode{
701 {
702 Expr: "(xxx - yyy) * bar",
703 Children: []*parser.PromQLNode{
704 {
705 Expr: "(xxx - yyy)",
706 Children: []*parser.PromQLNode{
707 {
708 Expr: "xxx - yyy",
709 Children: []*parser.PromQLNode{
710 {Expr: "xxx"},
711 {Expr: "yyy"},
712 },
713 },
714 },
715 },
716 {
717 Expr: "bar",
718 },
719 },
720 },
721 {
722 Expr: "0",
723 },
724 },
725 },
726 {Expr: "baz"},
727 },
728 },
729 },
730 For: &parser.YamlKeyValue{
731 Key: &parser.YamlNode{
732 Position: parser.FilePosition{Lines: []int{9}},
733 Value: "for",
734 },
735 Value: &parser.YamlNode{
736 Position: parser.FilePosition{Lines: []int{9}},
737 Value: "30m",
738 },
739 },
740 },
741 },
742 },
743 },
744 {
745 content: []byte(`---
746kind: ConfigMap
747apiVersion: v1
748metadata:
749 name: example-app-alerts
750 labels:
751 app: example-app
752data:
753 alerts: |
754 groups:
755 - name: example-app-alerts
756 rules:
757 - alert: Example_Is_Down
758 expr: kube_deployment_status_replicas_available{namespace="example-app"} < 1
759 for: 5m
760 labels:
761 priority: "2"
762 environment: production
763 annotations:
764 summary: "No replicas for Example have been running for 5 minutes"
765
766 - alert: Example_High_Restart_Rate
767 expr: sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )
768`),
769 output: []parser.Rule{
770 {
771 AlertingRule: &parser.AlertingRule{
772 Alert: parser.YamlKeyValue{
773 Key: &parser.YamlNode{
774 Position: parser.FilePosition{Lines: []int{13}},
775 Value: "alert",
776 },
777 Value: &parser.YamlNode{
778 Position: parser.FilePosition{Lines: []int{13}},
779 Value: "Example_Is_Down",
780 },
781 },
782 Expr: parser.PromQLExpr{
783 Key: &parser.YamlNode{
784 Position: parser.FilePosition{Lines: []int{14}},
785 Value: "expr",
786 },
787 Value: &parser.YamlNode{
788 Position: parser.FilePosition{Lines: []int{14}},
789 Value: `kube_deployment_status_replicas_available{namespace="example-app"} < 1`,
790 },
791 Query: &parser.PromQLNode{
792 Expr: `kube_deployment_status_replicas_available{namespace="example-app"} < 1`,
793 Children: []*parser.PromQLNode{
794 {Expr: `kube_deployment_status_replicas_available{namespace="example-app"}`},
795 {Expr: "1"},
796 },
797 },
798 },
799 For: &parser.YamlKeyValue{
800 Key: &parser.YamlNode{
801 Position: parser.FilePosition{Lines: []int{15}},
802 Value: "for",
803 },
804 Value: &parser.YamlNode{
805 Position: parser.FilePosition{Lines: []int{15}},
806 Value: "5m",
807 },
808 },
809 Labels: &parser.YamlMap{
810 Key: &parser.YamlNode{
811 Position: parser.FilePosition{Lines: []int{16}},
812 Value: "labels",
813 },
814 Items: []*parser.YamlKeyValue{
815 {
816 Key: &parser.YamlNode{
817 Position: parser.FilePosition{Lines: []int{17}},
818 Value: "priority",
819 },
820 Value: &parser.YamlNode{
821 Position: parser.FilePosition{Lines: []int{17}},
822 Value: "2",
823 },
824 },
825 {
826 Key: &parser.YamlNode{
827 Position: parser.FilePosition{Lines: []int{18}},
828 Value: "environment",
829 },
830 Value: &parser.YamlNode{
831 Position: parser.FilePosition{Lines: []int{18}},
832 Value: "production",
833 },
834 },
835 },
836 },
837 Annotations: &parser.YamlMap{
838 Key: &parser.YamlNode{
839 Position: parser.FilePosition{Lines: []int{19}},
840 Value: "annotations",
841 },
842 Items: []*parser.YamlKeyValue{
843 {
844 Key: &parser.YamlNode{
845 Position: parser.FilePosition{Lines: []int{20}},
846 Value: "summary",
847 },
848 Value: &parser.YamlNode{
849 Position: parser.FilePosition{Lines: []int{20}},
850 Value: "No replicas for Example have been running for 5 minutes",
851 },
852 },
853 },
854 },
855 },
856 },
857 {
858 AlertingRule: &parser.AlertingRule{
859 Alert: parser.YamlKeyValue{
860 Key: &parser.YamlNode{
861 Position: parser.FilePosition{Lines: []int{22}},
862 Value: "alert",
863 },
864 Value: &parser.YamlNode{
865 Position: parser.FilePosition{Lines: []int{22}},
866 Value: "Example_High_Restart_Rate",
867 },
868 },
869 Expr: parser.PromQLExpr{
870 Key: &parser.YamlNode{
871 Position: parser.FilePosition{Lines: []int{23}},
872 Value: "expr",
873 },
874 Value: &parser.YamlNode{
875 Position: parser.FilePosition{Lines: []int{23}},
876 Value: `sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )`,
877 },
878 Query: &parser.PromQLNode{
879 Expr: `sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )`,
880 Children: []*parser.PromQLNode{
881 {
882 Expr: `sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m]))`,
883 Children: []*parser.PromQLNode{
884 {
885 Expr: `rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])`,
886 Children: []*parser.PromQLNode{
887 {
888 Expr: `kube_pod_container_status_restarts_total{namespace="example-app"}[5m]`,
889 Children: []*parser.PromQLNode{
890 {Expr: `kube_pod_container_status_restarts_total{namespace="example-app"}`},
891 },
892 },
893 },
894 },
895 },
896 },
897 {
898 Expr: "(3 / 60)",
899 Children: []*parser.PromQLNode{
900 {
901 Expr: "3 / 60",
902 Children: []*parser.PromQLNode{
903 {Expr: "3"},
904 {Expr: "60"},
905 },
906 },
907 },
908 },
909 },
910 },
911 },
912 },
913 },
914 },
915 },
916 {
917 content: []byte(`groups:
918- name: "haproxy.api_server.rules"
919 rules:
920 - alert: HaproxyServerHealthcheckFailure
921 expr: increase(haproxy_server_check_failures_total[15m]) > 100
922 for: 5m
923 labels:
924 severity: 24x7
925 annotations:
926 summary: "HAProxy server healthcheck failure (instance {{ $labels.instance }})"
927 description: "Some server healthcheck are failing on {{ $labels.server }}\n VALUE = {{ $value }}\n LABELS: {{ $labels }}"
928`),
929 output: []parser.Rule{
930 {
931 AlertingRule: &parser.AlertingRule{
932 Alert: parser.YamlKeyValue{
933 Key: &parser.YamlNode{
934 Position: parser.FilePosition{Lines: []int{4}},
935 Value: "alert",
936 },
937 Value: &parser.YamlNode{
938 Position: parser.FilePosition{Lines: []int{4}},
939 Value: "HaproxyServerHealthcheckFailure",
940 },
941 },
942 Expr: parser.PromQLExpr{
943 Key: &parser.YamlNode{
944 Position: parser.FilePosition{Lines: []int{5}},
945 Value: "expr",
946 },
947 Value: &parser.YamlNode{
948 Position: parser.FilePosition{Lines: []int{5}},
949 Value: "increase(haproxy_server_check_failures_total[15m]) > 100",
950 },
951 Query: &parser.PromQLNode{
952 Expr: "increase(haproxy_server_check_failures_total[15m]) > 100",
953 Children: []*parser.PromQLNode{
954 {
955 Expr: "increase(haproxy_server_check_failures_total[15m])",
956 Children: []*parser.PromQLNode{
957 {
958 Expr: "haproxy_server_check_failures_total[15m]",
959 Children: []*parser.PromQLNode{
960 {
961 Expr: "haproxy_server_check_failures_total",
962 },
963 },
964 },
965 },
966 },
967 {Expr: "100"},
968 },
969 },
970 },
971 For: &parser.YamlKeyValue{
972 Key: &parser.YamlNode{
973 Position: parser.FilePosition{Lines: []int{6}},
974 Value: "for",
975 },
976 Value: &parser.YamlNode{
977 Position: parser.FilePosition{Lines: []int{6}},
978 Value: "5m",
979 },
980 },
981 Labels: &parser.YamlMap{
982 Key: &parser.YamlNode{
983 Position: parser.FilePosition{Lines: []int{7}},
984 Value: "labels",
985 },
986 Items: []*parser.YamlKeyValue{
987 {
988 Key: &parser.YamlNode{
989 Position: parser.FilePosition{Lines: []int{8}},
990 Value: "severity",
991 },
992 Value: &parser.YamlNode{
993 Position: parser.FilePosition{Lines: []int{8}},
994 Value: "24x7",
995 },
996 },
997 },
998 },
999 Annotations: &parser.YamlMap{
1000 Key: &parser.YamlNode{
1001 Position: parser.FilePosition{Lines: []int{9}},
1002 Value: "annotations",
1003 },
1004 Items: []*parser.YamlKeyValue{
1005 {
1006 Key: &parser.YamlNode{
1007 Position: parser.FilePosition{Lines: []int{10}},
1008 Value: "summary",
1009 },
1010 Value: &parser.YamlNode{
1011 Position: parser.FilePosition{Lines: []int{10}},
1012 Value: "HAProxy server healthcheck failure (instance {{ $labels.instance }})",
1013 },
1014 },
1015 {
1016 Key: &parser.YamlNode{
1017 Position: parser.FilePosition{Lines: []int{11}},
1018 Value: "description",
1019 },
1020 Value: &parser.YamlNode{
1021 // FIXME https://github.com/cloudflare/pint/issues/20
1022 // Should be Lines: [11]
1023 Position: parser.FilePosition{Lines: []int{11, 12, 13}},
1024 // Should be `Some ...` since \n should be escaped
1025 Value: "Some server healthcheck are failing on {{ $labels.server }}\n VALUE = {{ $value }}\n LABELS: {{ $labels }}",
1026 },
1027 },
1028 },
1029 },
1030 },
1031 },
1032 },
1033 },
1034 {
1035 content: []byte(`groups:
1036- name: certmanager
1037 rules:
1038 # pint disable before recordAnchor
1039 - &recordAnchor # pint disable recordAnchor
1040 record: name1 # pint disable name1
1041 expr: expr1 # pint disable expr1
1042 # pint disable after expr1
1043 - <<: *recordAnchor
1044 expr: expr2
1045 - <<: *recordAnchor
1046`),
1047 output: []parser.Rule{
1048 {
1049 Comments: []comments.Comment{
1050 {
1051 Type: comments.DisableType,
1052 Value: comments.Disable{Match: "before recordAnchor"},
1053 },
1054 {
1055 Type: comments.DisableType,
1056 Value: comments.Disable{Match: "recordAnchor"},
1057 },
1058 {
1059 Type: comments.DisableType,
1060 Value: comments.Disable{Match: "name1"},
1061 },
1062 {
1063 Type: comments.DisableType,
1064 Value: comments.Disable{Match: "after expr1"},
1065 },
1066 {
1067 Type: comments.DisableType,
1068 Value: comments.Disable{Match: "expr1"},
1069 },
1070 },
1071 RecordingRule: &parser.RecordingRule{
1072 Record: parser.YamlKeyValue{
1073 Key: &parser.YamlNode{
1074 Position: parser.FilePosition{Lines: []int{6}},
1075 Value: "record",
1076 },
1077 Value: &parser.YamlNode{
1078 Position: parser.FilePosition{Lines: []int{6}},
1079 Value: "name1",
1080 },
1081 },
1082 Expr: parser.PromQLExpr{
1083 Key: &parser.YamlNode{
1084 Position: parser.FilePosition{Lines: []int{7}},
1085 Value: "expr",
1086 },
1087 Value: &parser.YamlNode{
1088 Position: parser.FilePosition{Lines: []int{7}},
1089 Value: "expr1",
1090 },
1091 Query: &parser.PromQLNode{Expr: "expr1"},
1092 },
1093 },
1094 },
1095 {
1096 Comments: []comments.Comment{
1097 {
1098 Type: comments.DisableType,
1099 Value: comments.Disable{Match: "before recordAnchor"},
1100 },
1101 {
1102 Type: comments.DisableType,
1103 Value: comments.Disable{Match: "recordAnchor"},
1104 },
1105 {
1106 Type: comments.DisableType,
1107 Value: comments.Disable{Match: "name1"},
1108 },
1109 },
1110 RecordingRule: &parser.RecordingRule{
1111 Record: parser.YamlKeyValue{
1112 Key: &parser.YamlNode{
1113 Position: parser.FilePosition{Lines: []int{6}},
1114 Value: "record",
1115 },
1116 Value: &parser.YamlNode{
1117 Position: parser.FilePosition{Lines: []int{6}},
1118 Value: "name1",
1119 },
1120 },
1121 Expr: parser.PromQLExpr{
1122 Key: &parser.YamlNode{
1123 Position: parser.FilePosition{Lines: []int{10}},
1124 Value: "expr",
1125 },
1126 Value: &parser.YamlNode{
1127 Position: parser.FilePosition{Lines: []int{10}},
1128 Value: "expr2",
1129 },
1130 Query: &parser.PromQLNode{Expr: "expr2"},
1131 },
1132 },
1133 },
1134 {
1135 Comments: []comments.Comment{
1136 {
1137 Type: comments.DisableType,
1138 Value: comments.Disable{Match: "before recordAnchor"},
1139 },
1140 {
1141 Type: comments.DisableType,
1142 Value: comments.Disable{Match: "recordAnchor"},
1143 },
1144 {
1145 Type: comments.DisableType,
1146 Value: comments.Disable{Match: "name1"},
1147 },
1148 {
1149 Type: comments.DisableType,
1150 Value: comments.Disable{Match: "after expr1"},
1151 },
1152 {
1153 Type: comments.DisableType,
1154 Value: comments.Disable{Match: "expr1"},
1155 },
1156 },
1157 RecordingRule: &parser.RecordingRule{
1158 Record: parser.YamlKeyValue{
1159 Key: &parser.YamlNode{
1160 Position: parser.FilePosition{Lines: []int{6}},
1161 Value: "record",
1162 },
1163 Value: &parser.YamlNode{
1164 Position: parser.FilePosition{Lines: []int{6}},
1165 Value: "name1",
1166 },
1167 },
1168 Expr: parser.PromQLExpr{
1169 Key: &parser.YamlNode{
1170 Position: parser.FilePosition{Lines: []int{7}},
1171 Value: "expr",
1172 },
1173 Value: &parser.YamlNode{
1174 Position: parser.FilePosition{Lines: []int{7}},
1175 Value: "expr1",
1176 },
1177 Query: &parser.PromQLNode{Expr: "expr1"},
1178 },
1179 },
1180 },
1181 },
1182 },
1183 {
1184 content: []byte(`groups:
1185- name: certmanager
1186 rules:
1187 - record: name1
1188 expr: expr1
1189 labels: &labelsAnchor
1190 label1: val1
1191 label2: val2
1192 - record: name2
1193 expr: expr2
1194 labels: *labelsAnchor
1195 # pint disable foot comment
1196`),
1197 output: []parser.Rule{
1198 {
1199 RecordingRule: &parser.RecordingRule{
1200 Record: parser.YamlKeyValue{
1201 Key: &parser.YamlNode{
1202 Position: parser.FilePosition{Lines: []int{4}},
1203 Value: "record",
1204 },
1205 Value: &parser.YamlNode{
1206 Position: parser.FilePosition{Lines: []int{4}},
1207 Value: "name1",
1208 },
1209 },
1210 Expr: parser.PromQLExpr{
1211 Key: &parser.YamlNode{
1212 Position: parser.FilePosition{Lines: []int{5}},
1213 Value: "expr",
1214 },
1215 Value: &parser.YamlNode{
1216 Position: parser.FilePosition{Lines: []int{5}},
1217 Value: "expr1",
1218 },
1219 Query: &parser.PromQLNode{Expr: "expr1"},
1220 },
1221 Labels: &parser.YamlMap{
1222 Key: &parser.YamlNode{
1223 Position: parser.FilePosition{Lines: []int{6}},
1224 Value: "labels",
1225 },
1226 Items: []*parser.YamlKeyValue{
1227 {
1228 Key: &parser.YamlNode{
1229 Position: parser.FilePosition{Lines: []int{7}},
1230 Value: "label1",
1231 },
1232 Value: &parser.YamlNode{
1233 Position: parser.FilePosition{Lines: []int{7}},
1234 Value: "val1",
1235 },
1236 },
1237 {
1238 Key: &parser.YamlNode{
1239 Position: parser.FilePosition{Lines: []int{8}},
1240 Value: "label2",
1241 },
1242 Value: &parser.YamlNode{
1243 Position: parser.FilePosition{Lines: []int{8}},
1244 Value: "val2",
1245 },
1246 },
1247 },
1248 },
1249 },
1250 },
1251 {
1252 Comments: []comments.Comment{
1253 {
1254 Type: comments.DisableType,
1255 Value: comments.Disable{Match: "foot comment"},
1256 },
1257 },
1258 RecordingRule: &parser.RecordingRule{
1259 Record: parser.YamlKeyValue{
1260 Key: &parser.YamlNode{
1261 Position: parser.FilePosition{Lines: []int{9}},
1262 Value: "record",
1263 },
1264 Value: &parser.YamlNode{
1265 Position: parser.FilePosition{Lines: []int{9}},
1266 Value: "name2",
1267 },
1268 },
1269 Expr: parser.PromQLExpr{
1270 Key: &parser.YamlNode{
1271 Position: parser.FilePosition{Lines: []int{10}},
1272 Value: "expr",
1273 },
1274 Value: &parser.YamlNode{
1275 Position: parser.FilePosition{Lines: []int{10}},
1276 Value: "expr2",
1277 },
1278 Query: &parser.PromQLNode{Expr: "expr2"},
1279 },
1280 Labels: &parser.YamlMap{
1281 Key: &parser.YamlNode{
1282 Position: parser.FilePosition{Lines: []int{11}},
1283 Value: "labels",
1284 },
1285 Items: []*parser.YamlKeyValue{
1286 {
1287 Key: &parser.YamlNode{
1288 Position: parser.FilePosition{Lines: []int{7}},
1289 Value: "label1",
1290 },
1291 Value: &parser.YamlNode{
1292 Position: parser.FilePosition{Lines: []int{7}},
1293 Value: "val1",
1294 },
1295 },
1296 {
1297 Key: &parser.YamlNode{
1298 Position: parser.FilePosition{Lines: []int{8}},
1299 Value: "label2",
1300 },
1301 Value: &parser.YamlNode{
1302 Position: parser.FilePosition{Lines: []int{8}},
1303 Value: "val2",
1304 },
1305 },
1306 },
1307 },
1308 },
1309 },
1310 },
1311 },
1312 {
1313 content: []byte("- alert:\n expr: vector(1)\n"),
1314 output: []parser.Rule{
1315 {Error: parser.ParseError{Err: fmt.Errorf("alert value cannot be empty"), Line: 1}},
1316 },
1317 },
1318 {
1319 content: []byte("- alert: foo\n expr:\n"),
1320 output: []parser.Rule{
1321 {Error: parser.ParseError{Err: fmt.Errorf("expr value cannot be empty"), Line: 2}},
1322 },
1323 },
1324 {
1325 content: []byte("- alert: foo\n"),
1326 output: []parser.Rule{
1327 {Error: parser.ParseError{Err: fmt.Errorf("missing expr key"), Line: 1}},
1328 },
1329 },
1330 {
1331 content: []byte("- record:\n expr:\n"),
1332 output: []parser.Rule{
1333 {Error: parser.ParseError{Err: fmt.Errorf("record value cannot be empty"), Line: 1}},
1334 },
1335 },
1336 {
1337 content: []byte("- record: foo\n expr:\n"),
1338 output: []parser.Rule{
1339 {Error: parser.ParseError{Err: fmt.Errorf("expr value cannot be empty"), Line: 2}},
1340 },
1341 },
1342 {
1343 content: []byte("- record: foo\n"),
1344 output: []parser.Rule{
1345 {Error: parser.ParseError{Err: fmt.Errorf("missing expr key"), Line: 1}},
1346 },
1347 },
1348 {
1349 content: []byte(string(`
1350# pint file/owner bob
1351# pint ignore/begin
1352# pint ignore/end
1353# pint disable up
1354
1355- record: foo
1356 expr: up
1357
1358# pint file/owner alice
1359
1360- record: foo
1361 expr: up
1362
1363# pint ignore/next-line
1364`)),
1365 output: []parser.Rule{
1366 {
1367 RecordingRule: &parser.RecordingRule{
1368 Record: parser.YamlKeyValue{
1369 Key: &parser.YamlNode{
1370 Position: parser.FilePosition{Lines: []int{7}},
1371 Value: "record",
1372 },
1373 Value: &parser.YamlNode{
1374 Position: parser.FilePosition{Lines: []int{7}},
1375 Value: "foo",
1376 },
1377 },
1378 Expr: parser.PromQLExpr{
1379 Key: &parser.YamlNode{
1380 Position: parser.FilePosition{Lines: []int{8}},
1381 Value: "expr",
1382 },
1383 Value: &parser.YamlNode{
1384 Position: parser.FilePosition{Lines: []int{8}},
1385 Value: "up",
1386 },
1387 Query: &parser.PromQLNode{Expr: "up"},
1388 },
1389 },
1390 },
1391 {
1392 RecordingRule: &parser.RecordingRule{
1393 Record: parser.YamlKeyValue{
1394 Key: &parser.YamlNode{
1395 Position: parser.FilePosition{Lines: []int{12}},
1396 Value: "record",
1397 },
1398 Value: &parser.YamlNode{
1399 Position: parser.FilePosition{Lines: []int{12}},
1400 Value: "foo",
1401 },
1402 },
1403 Expr: parser.PromQLExpr{
1404 Key: &parser.YamlNode{
1405 Position: parser.FilePosition{Lines: []int{13}},
1406 Value: "expr",
1407 },
1408 Value: &parser.YamlNode{
1409 Position: parser.FilePosition{Lines: []int{13}},
1410 Value: "up",
1411 },
1412 Query: &parser.PromQLNode{Expr: "up"},
1413 },
1414 },
1415 },
1416 },
1417 shouldError: false,
1418 },
1419 }
1420
1421 alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
1422 ignorePrometheusExpr := cmp.FilterValues(func(x, y interface{}) bool {
1423 _, xe := x.(promparser.Expr)
1424 _, ye := y.(promparser.Expr)
1425 return xe || ye
1426 }, alwaysEqual)
1427
1428 cmpErrorText := cmp.Comparer(func(x, y interface{}) bool {
1429 xe := x.(error)
1430 ye := y.(error)
1431 return xe.Error() == ye.Error()
1432 })
1433 sameErrorText := cmp.FilterValues(func(x, y interface{}) bool {
1434 _, xe := x.(error)
1435 _, ye := y.(error)
1436 return xe && ye
1437 }, cmpErrorText)
1438
1439 for i, tc := range testCases {
1440 t.Run(strconv.Itoa(i+1), func(t *testing.T) {
1441 p := parser.NewParser()
1442 output, err := p.Parse(tc.content)
1443
1444 hadError := err != nil
1445 if hadError != tc.shouldError {
1446 t.Errorf("Parse() returned err=%v, expected=%v", err, tc.shouldError)
1447 return
1448 }
1449
1450 if diff := cmp.Diff(tc.output, output, ignorePrometheusExpr, sameErrorText); diff != "" {
1451 t.Errorf("Parse() returned wrong output (-want +got):\n%s", diff)
1452 return
1453 }
1454 })
1455 }
1456}
1457