cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.33.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser_test.go

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