cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.49.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser_test.go

1295lines · modecode

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