cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
baaaa2a8a4c537acd8ab3963accdf2ef0d39534e

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser_test.go

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