cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
b6a8fe58ea35ad705affa786e4a7f33252cdeb5c

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser_test.go

1877lines · modecode

1package parser_test
2
3import (
4 "fmt"
5 "strconv"
6 "testing"
7
8 "github.com/stretchr/testify/require"
9
10 "github.com/cloudflare/pint/internal/comments"
11 "github.com/cloudflare/pint/internal/parser"
12
13 "github.com/google/go-cmp/cmp"
14 promparser "github.com/prometheus/prometheus/promql/parser"
15)
16
17func TestParse(t *testing.T) {
18 type testCaseT struct {
19 content []byte
20 output []parser.Rule
21 err string
22 }
23
24 testCases := []testCaseT{
25 {
26 content: nil,
27 output: nil,
28 },
29 {
30 content: []byte{},
31 output: nil,
32 },
33 {
34 content: []byte(string("! !00 \xf6")),
35 output: nil,
36 err: "yaml: incomplete UTF-8 octet sequence",
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 err: "yaml: block sequence entries are not allowed in this context",
95 },
96 {
97 content: []byte("- record: foo expr: sum(\n"),
98 err: "yaml: mapping values are not allowed in this context",
99 },
100 {
101 content: []byte("- record\n\texpr: foo\n"),
102 err: "yaml: line 2: found a tab character that violates indentation",
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 },
448 {
449 content: []byte(`
450groups:
451- name: custom_rules
452 rules:
453 - record: name
454 expr: sum(foo)
455 labels:
456 foo: bar
457 bob: alice
458`),
459 output: []parser.Rule{
460 {
461 Lines: parser.LineRange{First: 5, Last: 9},
462 RecordingRule: &parser.RecordingRule{
463 Record: parser.YamlNode{
464 Lines: parser.LineRange{First: 5, Last: 5},
465 Value: "name",
466 },
467 Expr: parser.PromQLExpr{
468 Value: &parser.YamlNode{
469 Lines: parser.LineRange{First: 6, Last: 6},
470 Value: "sum(foo)",
471 },
472 Query: &parser.PromQLNode{
473 Expr: "sum(foo)",
474 Children: []*parser.PromQLNode{
475 {Expr: "foo"},
476 },
477 },
478 },
479 Labels: &parser.YamlMap{
480 Lines: parser.LineRange{First: 7, Last: 9},
481 Key: &parser.YamlNode{
482 Lines: parser.LineRange{First: 7, Last: 7},
483 Value: "labels",
484 },
485 Items: []*parser.YamlKeyValue{
486 {
487 Key: &parser.YamlNode{
488 Lines: parser.LineRange{First: 8, Last: 8},
489 Value: "foo",
490 },
491 Value: &parser.YamlNode{
492 Lines: parser.LineRange{First: 8, Last: 8},
493 Value: "bar",
494 },
495 },
496 {
497 Key: &parser.YamlNode{
498 Lines: parser.LineRange{First: 9, Last: 9},
499 Value: "bob",
500 },
501 Value: &parser.YamlNode{
502 Lines: parser.LineRange{First: 9, Last: 9},
503 Value: "alice",
504 },
505 },
506 },
507 },
508 },
509 },
510 },
511 },
512 {
513 content: []byte(`- alert: Down
514 expr: |
515 up == 0
516 for: |+
517 11m
518 labels:
519 severity: critical
520 annotations:
521 uri: https://docs.example.com/down.html
522
523- record: foo
524 expr: |-
525 bar
526 /
527 baz > 1
528 labels: {}
529`),
530 output: []parser.Rule{
531 {
532 Lines: parser.LineRange{First: 1, Last: 9},
533 AlertingRule: &parser.AlertingRule{
534 Alert: parser.YamlNode{
535 Lines: parser.LineRange{First: 1, Last: 1},
536 Value: "Down",
537 },
538 Expr: parser.PromQLExpr{
539 Value: &parser.YamlNode{
540 Lines: parser.LineRange{First: 2, Last: 3},
541 Value: "up == 0\n",
542 },
543 Query: &parser.PromQLNode{
544 Expr: "up == 0\n",
545 Children: []*parser.PromQLNode{
546 {Expr: "up"},
547 {Expr: "0"},
548 },
549 },
550 },
551 For: &parser.YamlNode{
552 Lines: parser.LineRange{First: 4, Last: 5},
553 Value: "11m\n",
554 },
555 Labels: &parser.YamlMap{
556 Lines: parser.LineRange{First: 6, Last: 7},
557 Key: &parser.YamlNode{
558 Lines: parser.LineRange{First: 6, Last: 6},
559 Value: "labels",
560 },
561 Items: []*parser.YamlKeyValue{
562 {
563 Key: &parser.YamlNode{
564 Lines: parser.LineRange{First: 7, Last: 7},
565 Value: "severity",
566 },
567 Value: &parser.YamlNode{
568 Lines: parser.LineRange{First: 7, Last: 7},
569 Value: "critical",
570 },
571 },
572 },
573 },
574 Annotations: &parser.YamlMap{
575 Lines: parser.LineRange{First: 8, Last: 9},
576 Key: &parser.YamlNode{
577 Lines: parser.LineRange{First: 8, Last: 8},
578 Value: "annotations",
579 },
580 Items: []*parser.YamlKeyValue{
581 {
582 Key: &parser.YamlNode{
583 Lines: parser.LineRange{First: 9, Last: 9},
584 Value: "uri",
585 },
586 Value: &parser.YamlNode{
587 Lines: parser.LineRange{First: 9, Last: 9},
588 Value: "https://docs.example.com/down.html",
589 },
590 },
591 },
592 },
593 },
594 },
595 {
596 Lines: parser.LineRange{First: 11, Last: 16},
597 RecordingRule: &parser.RecordingRule{
598 Record: parser.YamlNode{
599 Lines: parser.LineRange{First: 11, Last: 11},
600 Value: "foo",
601 },
602 Expr: parser.PromQLExpr{
603 Value: &parser.YamlNode{
604 Lines: parser.LineRange{First: 12, Last: 15},
605 Value: "bar\n/\nbaz > 1",
606 },
607 Query: &parser.PromQLNode{
608 Expr: "bar\n/\nbaz > 1",
609 Children: []*parser.PromQLNode{
610 {
611 Expr: "bar / baz", Children: []*parser.PromQLNode{
612 {Expr: "bar"},
613 {Expr: "baz"},
614 },
615 },
616 {Expr: "1"},
617 },
618 },
619 },
620 Labels: &parser.YamlMap{
621 Lines: parser.LineRange{First: 16, Last: 16},
622 Key: &parser.YamlNode{
623 Lines: parser.LineRange{First: 16, Last: 16},
624 Value: "labels",
625 },
626 },
627 },
628 },
629 },
630 },
631 {
632 content: []byte(`- alert: Foo
633 expr:
634 (
635 xxx
636 -
637 yyy
638 ) * bar > 0
639 and on(instance, device) baz
640 for: 30m
641`),
642 output: []parser.Rule{
643 {
644 Lines: parser.LineRange{First: 1, Last: 9},
645 AlertingRule: &parser.AlertingRule{
646 Alert: parser.YamlNode{
647 Lines: parser.LineRange{First: 1, Last: 1},
648 Value: "Foo",
649 },
650 Expr: parser.PromQLExpr{
651 Value: &parser.YamlNode{
652 Lines: parser.LineRange{First: 2, Last: 8},
653 Value: "( xxx - yyy ) * bar > 0 and on(instance, device) baz",
654 },
655 Query: &parser.PromQLNode{
656 Expr: "( xxx - yyy ) * bar > 0 and on(instance, device) baz",
657 Children: []*parser.PromQLNode{
658 {
659 Expr: "(xxx - yyy) * bar > 0",
660 Children: []*parser.PromQLNode{
661 {
662 Expr: "(xxx - yyy) * bar",
663 Children: []*parser.PromQLNode{
664 {
665 Expr: "(xxx - yyy)",
666 Children: []*parser.PromQLNode{
667 {
668 Expr: "xxx - yyy",
669 Children: []*parser.PromQLNode{
670 {Expr: "xxx"},
671 {Expr: "yyy"},
672 },
673 },
674 },
675 },
676 {
677 Expr: "bar",
678 },
679 },
680 },
681 {
682 Expr: "0",
683 },
684 },
685 },
686 {Expr: "baz"},
687 },
688 },
689 },
690 For: &parser.YamlNode{
691 Lines: parser.LineRange{First: 9, Last: 9},
692 Value: "30m",
693 },
694 },
695 },
696 },
697 },
698 {
699 content: []byte(`---
700kind: ConfigMap
701apiVersion: v1
702metadata:
703 name: example-app-alerts
704 labels:
705 app: example-app
706data:
707 alerts: |
708 groups:
709 - name: example-app-alerts
710 rules:
711 - alert: Example_Is_Down
712 expr: kube_deployment_status_replicas_available{namespace="example-app"} < 1
713 for: 5m
714 labels:
715 priority: "2"
716 environment: production
717 annotations:
718 summary: "No replicas for Example have been running for 5 minutes"
719
720 - alert: Example_High_Restart_Rate
721 expr: sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )
722`),
723 output: []parser.Rule{
724 {
725 Lines: parser.LineRange{First: 13, Last: 20},
726 AlertingRule: &parser.AlertingRule{
727 Alert: parser.YamlNode{
728 Lines: parser.LineRange{First: 13, Last: 13},
729 Value: "Example_Is_Down",
730 },
731 Expr: parser.PromQLExpr{
732 Value: &parser.YamlNode{
733 Lines: parser.LineRange{First: 14, Last: 14},
734 Value: `kube_deployment_status_replicas_available{namespace="example-app"} < 1`,
735 },
736 Query: &parser.PromQLNode{
737 Expr: `kube_deployment_status_replicas_available{namespace="example-app"} < 1`,
738 Children: []*parser.PromQLNode{
739 {Expr: `kube_deployment_status_replicas_available{namespace="example-app"}`},
740 {Expr: "1"},
741 },
742 },
743 },
744 For: &parser.YamlNode{
745 Lines: parser.LineRange{First: 15, Last: 15},
746 Value: "5m",
747 },
748 Labels: &parser.YamlMap{
749 Lines: parser.LineRange{First: 16, Last: 18},
750 Key: &parser.YamlNode{
751 Lines: parser.LineRange{First: 16, Last: 16},
752 Value: "labels",
753 },
754 Items: []*parser.YamlKeyValue{
755 {
756 Key: &parser.YamlNode{
757 Lines: parser.LineRange{First: 17, Last: 17},
758 Value: "priority",
759 },
760 Value: &parser.YamlNode{
761 Lines: parser.LineRange{First: 17, Last: 17},
762 Value: "2",
763 },
764 },
765 {
766 Key: &parser.YamlNode{
767 Lines: parser.LineRange{First: 18, Last: 18},
768 Value: "environment",
769 },
770 Value: &parser.YamlNode{
771 Lines: parser.LineRange{First: 18, Last: 18},
772 Value: "production",
773 },
774 },
775 },
776 },
777 Annotations: &parser.YamlMap{
778 Lines: parser.LineRange{First: 19, Last: 20},
779 Key: &parser.YamlNode{
780 Lines: parser.LineRange{First: 19, Last: 19},
781 Value: "annotations",
782 },
783 Items: []*parser.YamlKeyValue{
784 {
785 Key: &parser.YamlNode{
786 Lines: parser.LineRange{First: 20, Last: 20},
787 Value: "summary",
788 },
789 Value: &parser.YamlNode{
790 Lines: parser.LineRange{First: 20, Last: 20},
791 Value: "No replicas for Example have been running for 5 minutes",
792 },
793 },
794 },
795 },
796 },
797 },
798 {
799 Lines: parser.LineRange{First: 22, Last: 23},
800 AlertingRule: &parser.AlertingRule{
801 Alert: parser.YamlNode{
802 Lines: parser.LineRange{First: 22, Last: 22},
803 Value: "Example_High_Restart_Rate",
804 },
805 Expr: parser.PromQLExpr{
806 Value: &parser.YamlNode{
807 Lines: parser.LineRange{First: 23, Last: 23},
808 Value: `sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )`,
809 },
810 Query: &parser.PromQLNode{
811 Expr: `sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )`,
812 Children: []*parser.PromQLNode{
813 {
814 Expr: `sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m]))`,
815 Children: []*parser.PromQLNode{
816 {
817 Expr: `rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])`,
818 Children: []*parser.PromQLNode{
819 {
820 Expr: `kube_pod_container_status_restarts_total{namespace="example-app"}[5m]`,
821 Children: []*parser.PromQLNode{
822 {Expr: `kube_pod_container_status_restarts_total{namespace="example-app"}`},
823 },
824 },
825 },
826 },
827 },
828 },
829 {
830 Expr: "(3 / 60)",
831 Children: []*parser.PromQLNode{
832 {
833 Expr: "3 / 60",
834 Children: []*parser.PromQLNode{
835 {Expr: "3"},
836 {Expr: "60"},
837 },
838 },
839 },
840 },
841 },
842 },
843 },
844 },
845 },
846 },
847 },
848 {
849 content: []byte(`groups:
850- name: "haproxy.api_server.rules"
851 rules:
852 - alert: HaproxyServerHealthcheckFailure
853 expr: increase(haproxy_server_check_failures_total[15m]) > 100
854 for: 5m
855 labels:
856 severity: 24x7
857 annotations:
858 summary: "HAProxy server healthcheck failure (instance {{ $labels.instance }})"
859 description: "Some server healthcheck are failing on {{ $labels.server }}\n VALUE = {{ $value }}\n LABELS: {{ $labels }}"
860`),
861 output: []parser.Rule{
862 {
863 Lines: parser.LineRange{First: 4, Last: 13},
864 AlertingRule: &parser.AlertingRule{
865 Alert: parser.YamlNode{
866 Lines: parser.LineRange{First: 4, Last: 4},
867 Value: "HaproxyServerHealthcheckFailure",
868 },
869 Expr: parser.PromQLExpr{
870 Value: &parser.YamlNode{
871 Lines: parser.LineRange{First: 5, Last: 5},
872 Value: "increase(haproxy_server_check_failures_total[15m]) > 100",
873 },
874 Query: &parser.PromQLNode{
875 Expr: "increase(haproxy_server_check_failures_total[15m]) > 100",
876 Children: []*parser.PromQLNode{
877 {
878 Expr: "increase(haproxy_server_check_failures_total[15m])",
879 Children: []*parser.PromQLNode{
880 {
881 Expr: "haproxy_server_check_failures_total[15m]",
882 Children: []*parser.PromQLNode{
883 {
884 Expr: "haproxy_server_check_failures_total",
885 },
886 },
887 },
888 },
889 },
890 {Expr: "100"},
891 },
892 },
893 },
894 For: &parser.YamlNode{
895 Lines: parser.LineRange{First: 6, Last: 6},
896 Value: "5m",
897 },
898 Labels: &parser.YamlMap{
899 Lines: parser.LineRange{First: 7, Last: 8},
900 Key: &parser.YamlNode{
901 Lines: parser.LineRange{First: 7, Last: 7},
902 Value: "labels",
903 },
904 Items: []*parser.YamlKeyValue{
905 {
906 Key: &parser.YamlNode{
907 Lines: parser.LineRange{First: 8, Last: 8},
908 Value: "severity",
909 },
910 Value: &parser.YamlNode{
911 Lines: parser.LineRange{First: 8, Last: 8},
912 Value: "24x7",
913 },
914 },
915 },
916 },
917 Annotations: &parser.YamlMap{
918 Lines: parser.LineRange{First: 9, Last: 13},
919 Key: &parser.YamlNode{
920 Lines: parser.LineRange{First: 9, Last: 9},
921 Value: "annotations",
922 },
923 Items: []*parser.YamlKeyValue{
924 {
925 Key: &parser.YamlNode{
926 Lines: parser.LineRange{First: 10, Last: 10},
927 Value: "summary",
928 },
929 Value: &parser.YamlNode{
930 Lines: parser.LineRange{First: 10, Last: 10},
931 Value: "HAProxy server healthcheck failure (instance {{ $labels.instance }})",
932 },
933 },
934 {
935 Key: &parser.YamlNode{
936 Lines: parser.LineRange{First: 11, Last: 11},
937 Value: "description",
938 },
939 Value: &parser.YamlNode{
940 // FIXME https://github.com/cloudflare/pint/issues/20
941 // Should be Lines: [11]
942 Lines: parser.LineRange{First: 11, Last: 13},
943 // Should be `Some ...` since \n should be escaped
944 Value: "Some server healthcheck are failing on {{ $labels.server }}\n VALUE = {{ $value }}\n LABELS: {{ $labels }}",
945 },
946 },
947 },
948 },
949 },
950 },
951 },
952 },
953 {
954 content: []byte(`groups:
955- name: certmanager
956 rules:
957 # pint disable before recordAnchor
958 - &recordAnchor # pint disable recordAnchor
959 record: name1 # pint disable name1
960 expr: expr1 # pint disable expr1
961 # pint disable after expr1
962 - <<: *recordAnchor
963 expr: expr2
964 - <<: *recordAnchor
965`),
966 output: []parser.Rule{
967 {
968 Lines: parser.LineRange{First: 6, Last: 7},
969 Comments: []comments.Comment{
970 {
971 Type: comments.DisableType,
972 Value: comments.Disable{Match: "before recordAnchor"},
973 },
974 {
975 Type: comments.DisableType,
976 Value: comments.Disable{Match: "recordAnchor"},
977 },
978 {
979 Type: comments.DisableType,
980 Value: comments.Disable{Match: "name1"},
981 },
982 {
983 Type: comments.DisableType,
984 Value: comments.Disable{Match: "after expr1"},
985 },
986 {
987 Type: comments.DisableType,
988 Value: comments.Disable{Match: "expr1"},
989 },
990 },
991 RecordingRule: &parser.RecordingRule{
992 Record: parser.YamlNode{
993 Lines: parser.LineRange{First: 6, Last: 6},
994 Value: "name1",
995 },
996 Expr: parser.PromQLExpr{
997 Value: &parser.YamlNode{
998 Lines: parser.LineRange{First: 7, Last: 7},
999 Value: "expr1",
1000 },
1001 Query: &parser.PromQLNode{Expr: "expr1"},
1002 },
1003 },
1004 },
1005 {
1006 Comments: []comments.Comment{
1007 {
1008 Type: comments.DisableType,
1009 Value: comments.Disable{Match: "before recordAnchor"},
1010 },
1011 {
1012 Type: comments.DisableType,
1013 Value: comments.Disable{Match: "recordAnchor"},
1014 },
1015 {
1016 Type: comments.DisableType,
1017 Value: comments.Disable{Match: "name1"},
1018 },
1019 },
1020 Lines: parser.LineRange{First: 6, Last: 10},
1021 RecordingRule: &parser.RecordingRule{
1022 Record: parser.YamlNode{
1023 Lines: parser.LineRange{First: 6, Last: 6},
1024 Value: "name1",
1025 },
1026 Expr: parser.PromQLExpr{
1027 Value: &parser.YamlNode{
1028 Lines: parser.LineRange{First: 10, Last: 10},
1029 Value: "expr2",
1030 },
1031 Query: &parser.PromQLNode{Expr: "expr2"},
1032 },
1033 },
1034 },
1035 {
1036 Comments: []comments.Comment{
1037 {
1038 Type: comments.DisableType,
1039 Value: comments.Disable{Match: "before recordAnchor"},
1040 },
1041 {
1042 Type: comments.DisableType,
1043 Value: comments.Disable{Match: "recordAnchor"},
1044 },
1045 {
1046 Type: comments.DisableType,
1047 Value: comments.Disable{Match: "name1"},
1048 },
1049 {
1050 Type: comments.DisableType,
1051 Value: comments.Disable{Match: "after expr1"},
1052 },
1053 {
1054 Type: comments.DisableType,
1055 Value: comments.Disable{Match: "expr1"},
1056 },
1057 },
1058 Lines: parser.LineRange{First: 6, Last: 7},
1059 RecordingRule: &parser.RecordingRule{
1060 Record: parser.YamlNode{
1061 Lines: parser.LineRange{First: 6, Last: 6},
1062 Value: "name1",
1063 },
1064 Expr: parser.PromQLExpr{
1065 Value: &parser.YamlNode{
1066 Lines: parser.LineRange{First: 7, Last: 7},
1067 Value: "expr1",
1068 },
1069 Query: &parser.PromQLNode{Expr: "expr1"},
1070 },
1071 },
1072 },
1073 },
1074 },
1075 {
1076 content: []byte(`groups:
1077- name: certmanager
1078 rules:
1079 - record: name1
1080 expr: expr1
1081 labels: &labelsAnchor
1082 label1: val1
1083 label2: val2
1084 - record: name2
1085 expr: expr2
1086 labels: *labelsAnchor
1087 # pint disable foot comment
1088`),
1089 output: []parser.Rule{
1090 {
1091 Lines: parser.LineRange{First: 4, Last: 8},
1092 RecordingRule: &parser.RecordingRule{
1093 Record: parser.YamlNode{
1094 Lines: parser.LineRange{First: 4, Last: 4},
1095 Value: "name1",
1096 },
1097 Expr: parser.PromQLExpr{
1098 Value: &parser.YamlNode{
1099 Lines: parser.LineRange{First: 5, Last: 5},
1100 Value: "expr1",
1101 },
1102 Query: &parser.PromQLNode{Expr: "expr1"},
1103 },
1104 Labels: &parser.YamlMap{
1105 Lines: parser.LineRange{First: 6, Last: 8},
1106 Key: &parser.YamlNode{
1107 Lines: parser.LineRange{First: 6, Last: 6},
1108 Value: "labels",
1109 },
1110 Items: []*parser.YamlKeyValue{
1111 {
1112 Key: &parser.YamlNode{
1113 Lines: parser.LineRange{First: 7, Last: 7},
1114 Value: "label1",
1115 },
1116 Value: &parser.YamlNode{
1117 Lines: parser.LineRange{First: 7, Last: 7},
1118 Value: "val1",
1119 },
1120 },
1121 {
1122 Key: &parser.YamlNode{
1123 Lines: parser.LineRange{First: 8, Last: 8},
1124 Value: "label2",
1125 },
1126 Value: &parser.YamlNode{
1127 Lines: parser.LineRange{First: 8, Last: 8},
1128 Value: "val2",
1129 },
1130 },
1131 },
1132 },
1133 },
1134 },
1135 {
1136 Comments: []comments.Comment{
1137 {
1138 Type: comments.DisableType,
1139 Value: comments.Disable{Match: "foot comment"},
1140 },
1141 },
1142 Lines: parser.LineRange{First: 9, Last: 11},
1143 RecordingRule: &parser.RecordingRule{
1144 Record: parser.YamlNode{
1145 Lines: parser.LineRange{First: 9, Last: 9},
1146 Value: "name2",
1147 },
1148 Expr: parser.PromQLExpr{
1149 Value: &parser.YamlNode{
1150 Lines: parser.LineRange{First: 10, Last: 10},
1151 Value: "expr2",
1152 },
1153 Query: &parser.PromQLNode{Expr: "expr2"},
1154 },
1155 Labels: &parser.YamlMap{
1156 Lines: parser.LineRange{First: 11, Last: 11},
1157 Key: &parser.YamlNode{
1158 Lines: parser.LineRange{First: 11, Last: 11},
1159 Value: "labels",
1160 },
1161 Items: []*parser.YamlKeyValue{
1162 {
1163 Key: &parser.YamlNode{
1164 Lines: parser.LineRange{First: 7, Last: 7},
1165 Value: "label1",
1166 },
1167 Value: &parser.YamlNode{
1168 Lines: parser.LineRange{First: 7, Last: 7},
1169 Value: "val1",
1170 },
1171 },
1172 {
1173 Key: &parser.YamlNode{
1174 Lines: parser.LineRange{First: 8, Last: 8},
1175 Value: "label2",
1176 },
1177 Value: &parser.YamlNode{
1178 Lines: parser.LineRange{First: 8, Last: 8},
1179 Value: "val2",
1180 },
1181 },
1182 },
1183 },
1184 },
1185 },
1186 },
1187 },
1188 {
1189 content: []byte("- alert:\n expr: vector(1)\n"),
1190 output: []parser.Rule{
1191 {
1192 Lines: parser.LineRange{First: 1, Last: 2},
1193 Error: parser.ParseError{Err: fmt.Errorf("alert value cannot be empty"), Line: 1},
1194 },
1195 },
1196 },
1197 {
1198 content: []byte("- alert: foo\n expr:\n"),
1199 output: []parser.Rule{
1200 {
1201 Lines: parser.LineRange{First: 1, Last: 2},
1202 Error: parser.ParseError{Err: fmt.Errorf("expr value cannot be empty"), Line: 2},
1203 },
1204 },
1205 },
1206 {
1207 content: []byte("- alert: foo\n"),
1208 output: []parser.Rule{
1209 {
1210 Lines: parser.LineRange{First: 1, Last: 1},
1211 Error: parser.ParseError{Err: fmt.Errorf("missing expr key"), Line: 1},
1212 },
1213 },
1214 },
1215 {
1216 content: []byte("- record:\n expr:\n"),
1217 output: []parser.Rule{
1218 {
1219 Lines: parser.LineRange{First: 1, Last: 2},
1220 Error: parser.ParseError{Err: fmt.Errorf("record value cannot be empty"), Line: 1},
1221 },
1222 },
1223 },
1224 {
1225 content: []byte("- record: foo\n expr:\n"),
1226 output: []parser.Rule{
1227 {
1228 Lines: parser.LineRange{First: 1, Last: 2},
1229 Error: parser.ParseError{Err: fmt.Errorf("expr value cannot be empty"), Line: 2},
1230 },
1231 },
1232 },
1233 {
1234 content: []byte("- record: foo\n"),
1235 output: []parser.Rule{
1236 {
1237 Lines: parser.LineRange{First: 1, Last: 1},
1238 Error: parser.ParseError{Err: fmt.Errorf("missing expr key"), Line: 1},
1239 },
1240 },
1241 },
1242 {
1243 content: []byte(string(`
1244# pint file/owner bob
1245# pint ignore/begin
1246# pint ignore/end
1247# pint disable up
1248
1249- record: foo
1250 expr: up
1251
1252# pint file/owner alice
1253
1254- record: foo
1255 expr: up
1256
1257# pint ignore/next-line
1258`)),
1259 output: []parser.Rule{
1260 {
1261 Lines: parser.LineRange{First: 7, Last: 8},
1262 RecordingRule: &parser.RecordingRule{
1263 Record: parser.YamlNode{
1264 Lines: parser.LineRange{First: 7, Last: 7},
1265 Value: "foo",
1266 },
1267 Expr: parser.PromQLExpr{
1268 Value: &parser.YamlNode{
1269 Lines: parser.LineRange{First: 8, Last: 8},
1270 Value: "up",
1271 },
1272 Query: &parser.PromQLNode{Expr: "up"},
1273 },
1274 },
1275 },
1276 {
1277 Lines: parser.LineRange{First: 12, Last: 13},
1278 RecordingRule: &parser.RecordingRule{
1279 Record: parser.YamlNode{
1280 Lines: parser.LineRange{First: 12, Last: 12},
1281 Value: "foo",
1282 },
1283 Expr: parser.PromQLExpr{
1284 Value: &parser.YamlNode{
1285 Lines: parser.LineRange{First: 13, Last: 13},
1286 Value: "up",
1287 },
1288 Query: &parser.PromQLNode{Expr: "up"},
1289 },
1290 },
1291 },
1292 },
1293 },
1294 {
1295 content: []byte(string(`
1296- alert: Template
1297 expr: &expr up == 0
1298 labels:
1299 notify: &maybe_escalate_notify chat-alerts
1300- alert: Service Down
1301 expr: *expr
1302 labels:
1303 notify: *maybe_escalate_notify
1304 summary: foo
1305`)),
1306 output: []parser.Rule{
1307 {
1308 Lines: parser.LineRange{First: 2, Last: 5},
1309 AlertingRule: &parser.AlertingRule{
1310 Alert: parser.YamlNode{
1311 Lines: parser.LineRange{First: 2, Last: 2},
1312 Value: "Template",
1313 },
1314 Expr: parser.PromQLExpr{
1315 Value: &parser.YamlNode{
1316 Lines: parser.LineRange{First: 3, Last: 3},
1317 Value: "up == 0",
1318 },
1319 Query: &parser.PromQLNode{
1320 Expr: "up == 0",
1321 Children: []*parser.PromQLNode{
1322 {Expr: "up"},
1323 {Expr: "0"},
1324 },
1325 },
1326 },
1327 Labels: &parser.YamlMap{
1328 Lines: parser.LineRange{First: 4, Last: 5},
1329 Key: &parser.YamlNode{
1330 Lines: parser.LineRange{First: 4, Last: 4},
1331 Value: "labels",
1332 },
1333 Items: []*parser.YamlKeyValue{
1334 {
1335 Key: &parser.YamlNode{
1336 Lines: parser.LineRange{First: 5, Last: 5},
1337 Value: "notify",
1338 },
1339 Value: &parser.YamlNode{
1340 Lines: parser.LineRange{First: 5, Last: 5},
1341 Value: "chat-alerts",
1342 },
1343 },
1344 },
1345 },
1346 },
1347 },
1348 {
1349 Lines: parser.LineRange{First: 6, Last: 10},
1350 AlertingRule: &parser.AlertingRule{
1351 Alert: parser.YamlNode{
1352 Lines: parser.LineRange{First: 6, Last: 6},
1353 Value: "Service Down",
1354 },
1355 Expr: parser.PromQLExpr{
1356 Value: &parser.YamlNode{
1357 Lines: parser.LineRange{First: 7, Last: 7},
1358 Value: "up == 0",
1359 },
1360 Query: &parser.PromQLNode{
1361 Expr: "up == 0",
1362 Children: []*parser.PromQLNode{
1363 {Expr: "up"},
1364 {Expr: "0"},
1365 },
1366 },
1367 },
1368 Labels: &parser.YamlMap{
1369 Lines: parser.LineRange{First: 8, Last: 10},
1370 Key: &parser.YamlNode{
1371 Lines: parser.LineRange{First: 8, Last: 8},
1372 Value: "labels",
1373 },
1374 Items: []*parser.YamlKeyValue{
1375 {
1376 Key: &parser.YamlNode{
1377 Lines: parser.LineRange{First: 9, Last: 9},
1378 Value: "notify",
1379 },
1380 Value: &parser.YamlNode{
1381 Lines: parser.LineRange{First: 9, Last: 9},
1382 Value: "chat-alerts",
1383 },
1384 },
1385 {
1386 Key: &parser.YamlNode{
1387 Lines: parser.LineRange{First: 10, Last: 10},
1388 Value: "summary",
1389 },
1390 Value: &parser.YamlNode{
1391 Lines: parser.LineRange{First: 10, Last: 10},
1392 Value: "foo",
1393 },
1394 },
1395 },
1396 },
1397 },
1398 },
1399 },
1400 },
1401 {
1402 content: []byte(`
1403- record: invalid metric name
1404 expr: bar
1405`),
1406 output: []parser.Rule{
1407 {
1408 Lines: parser.LineRange{First: 2, Last: 3},
1409 Error: parser.ParseError{Err: fmt.Errorf("invalid recording rule name: invalid metric name"), Line: 2},
1410 },
1411 },
1412 },
1413 {
1414 content: []byte(`
1415- record: foo
1416 expr: bar
1417 labels:
1418 "foo bar": yes
1419`),
1420 output: []parser.Rule{
1421 {
1422 Lines: parser.LineRange{First: 2, Last: 5},
1423 Error: parser.ParseError{Err: fmt.Errorf("invalid label name: foo bar"), Line: 5},
1424 },
1425 },
1426 },
1427 {
1428 content: []byte(`
1429- alert: foo
1430 expr: bar
1431 labels:
1432 "foo bar": yes
1433`),
1434 output: []parser.Rule{
1435 {
1436 Lines: parser.LineRange{First: 2, Last: 5},
1437 Error: parser.ParseError{Err: fmt.Errorf("invalid label name: foo bar"), Line: 5},
1438 },
1439 },
1440 },
1441 {
1442 content: []byte(`
1443- alert: foo
1444 expr: bar
1445 labels:
1446 "{{ $value }}": yes
1447`),
1448 output: []parser.Rule{
1449 {
1450 Lines: parser.LineRange{First: 2, Last: 5},
1451 Error: parser.ParseError{Err: fmt.Errorf("invalid label name: {{ $value }}"), Line: 5},
1452 },
1453 },
1454 },
1455 {
1456 content: []byte(`
1457- alert: foo
1458 expr: bar
1459 annotations:
1460 "foo bar": yes
1461`),
1462 output: []parser.Rule{
1463 {
1464 Lines: parser.LineRange{First: 2, Last: 5},
1465 Error: parser.ParseError{Err: fmt.Errorf("invalid annotation name: foo bar"), Line: 5},
1466 },
1467 },
1468 },
1469 {
1470 content: []byte(`
1471- alert: foo
1472 expr: bar
1473 labels:
1474 foo: ` + string("\xed\xbf\xbf")),
1475 // Label values are invalid only if they aren't valid UTF-8 strings
1476 // which also makes them unparsable by YAML.
1477 err: "yaml: invalid Unicode character",
1478 },
1479 {
1480 content: []byte(`
1481- alert: foo
1482 expr: bar
1483 annotations:
1484 "{{ $value }}": yes
1485`),
1486 output: []parser.Rule{
1487 {
1488 Lines: parser.LineRange{First: 2, Last: 5},
1489 Error: parser.ParseError{Err: fmt.Errorf("invalid annotation name: {{ $value }}"), Line: 5},
1490 },
1491 },
1492 },
1493 {
1494 content: []byte(`
1495- record: foo
1496 expr: bar
1497 keep_firing_for: 5m
1498`),
1499 output: []parser.Rule{
1500 {
1501 Lines: parser.LineRange{First: 2, Last: 4},
1502 Error: parser.ParseError{Err: fmt.Errorf("invalid field 'keep_firing_for' in recording rule"), Line: 4},
1503 },
1504 },
1505 },
1506 {
1507 content: []byte(`
1508- record: foo
1509 expr: bar
1510 for: 5m
1511`),
1512 output: []parser.Rule{
1513 {
1514 Lines: parser.LineRange{First: 2, Last: 4},
1515 Error: parser.ParseError{Err: fmt.Errorf("invalid field 'for' in recording rule"), Line: 4},
1516 },
1517 },
1518 },
1519 {
1520 content: []byte(`
1521- record: foo
1522 expr: bar
1523 annotations:
1524 foo: bar
1525`),
1526 output: []parser.Rule{
1527 {
1528 Lines: parser.LineRange{First: 2, Last: 5},
1529 Error: parser.ParseError{Err: fmt.Errorf("invalid field 'annotations' in recording rule"), Line: 4},
1530 },
1531 },
1532 },
1533 // Tag tests
1534 {
1535 content: []byte(`
1536- record: 5
1537 expr: bar
1538`),
1539 output: []parser.Rule{
1540 {
1541 Lines: parser.LineRange{First: 2, Last: 3},
1542 Error: parser.ParseError{Err: fmt.Errorf("record value must be a YAML string, got integer instead"), Line: 2},
1543 },
1544 },
1545 },
1546 {
1547 content: []byte(`
1548- alert: 5
1549 expr: bar
1550`),
1551 output: []parser.Rule{
1552 {
1553 Lines: parser.LineRange{First: 2, Last: 3},
1554 Error: parser.ParseError{Err: fmt.Errorf("alert value must be a YAML string, got integer instead"), Line: 2},
1555 },
1556 },
1557 },
1558 {
1559 content: []byte(`
1560- record: foo
1561 expr: 5
1562`),
1563 output: []parser.Rule{
1564 {
1565 Lines: parser.LineRange{First: 2, Last: 3},
1566 Error: parser.ParseError{Err: fmt.Errorf("expr value must be a YAML string, got integer instead"), Line: 3},
1567 },
1568 },
1569 },
1570 {
1571 content: []byte(`
1572- alert: foo
1573 expr: bar
1574 for: 5
1575`),
1576 output: []parser.Rule{
1577 {
1578 Lines: parser.LineRange{First: 2, Last: 4},
1579 Error: parser.ParseError{Err: fmt.Errorf("for value must be a YAML string, got integer instead"), Line: 4},
1580 },
1581 },
1582 },
1583 {
1584 content: []byte(`
1585- alert: foo
1586 expr: bar
1587 keep_firing_for: 5
1588`),
1589 output: []parser.Rule{
1590 {
1591 Lines: parser.LineRange{First: 2, Last: 4},
1592 Error: parser.ParseError{Err: fmt.Errorf("keep_firing_for value must be a YAML string, got integer instead"), Line: 4},
1593 },
1594 },
1595 },
1596 {
1597 content: []byte(`
1598- record: foo
1599 expr: bar
1600 labels: []
1601`),
1602 output: []parser.Rule{
1603 {
1604 Lines: parser.LineRange{First: 2, Last: 4},
1605 Error: parser.ParseError{Err: fmt.Errorf("labels value must be a YAML mapping, got list instead"), Line: 4},
1606 },
1607 },
1608 },
1609 {
1610 content: []byte(`
1611- alert: foo
1612 expr: bar
1613 labels: {}
1614 annotations: []
1615`),
1616 output: []parser.Rule{
1617 {
1618 Lines: parser.LineRange{First: 2, Last: 5},
1619 Error: parser.ParseError{Err: fmt.Errorf("annotations value must be a YAML mapping, got list instead"), Line: 5},
1620 },
1621 },
1622 },
1623 {
1624 content: []byte(`
1625- alert: foo
1626 expr: bar
1627 labels:
1628 foo: 3
1629 annotations:
1630 bar: "5"
1631`),
1632 output: []parser.Rule{
1633 {
1634 Lines: parser.LineRange{First: 2, Last: 7},
1635 Error: parser.ParseError{Err: fmt.Errorf("labels foo value must be a YAML string, got integer instead"), Line: 5},
1636 },
1637 },
1638 },
1639 {
1640 content: []byte(`
1641- alert: foo
1642 expr: bar
1643 labels: {}
1644 annotations:
1645 foo: "3"
1646 bar: 5
1647`),
1648 output: []parser.Rule{
1649 {
1650 Lines: parser.LineRange{First: 2, Last: 7},
1651 Error: parser.ParseError{Err: fmt.Errorf("annotations bar value must be a YAML string, got integer instead"), Line: 7},
1652 },
1653 },
1654 },
1655 {
1656 content: []byte(`
1657- record: foo
1658 expr: bar
1659 labels: 4
1660`),
1661 output: []parser.Rule{
1662 {
1663 Lines: parser.LineRange{First: 2, Last: 4},
1664 Error: parser.ParseError{Err: fmt.Errorf("labels value must be a YAML mapping, got integer instead"), Line: 4},
1665 },
1666 },
1667 },
1668 {
1669 content: []byte(`
1670- record: foo
1671 expr: bar
1672 labels: true
1673`),
1674 output: []parser.Rule{
1675 {
1676 Lines: parser.LineRange{First: 2, Last: 4},
1677 Error: parser.ParseError{Err: fmt.Errorf("labels value must be a YAML mapping, got bool instead"), Line: 4},
1678 },
1679 },
1680 },
1681 {
1682 content: []byte(`
1683- record: foo
1684 expr: bar
1685 labels: null
1686`),
1687 output: []parser.Rule{
1688 {
1689 Lines: parser.LineRange{First: 2, Last: 4},
1690 RecordingRule: &parser.RecordingRule{
1691 Record: parser.YamlNode{
1692 Lines: parser.LineRange{First: 2, Last: 2},
1693 Value: "foo",
1694 },
1695 Expr: parser.PromQLExpr{
1696 Value: &parser.YamlNode{
1697 Lines: parser.LineRange{First: 3, Last: 3},
1698 Value: "bar",
1699 },
1700 Query: &parser.PromQLNode{Expr: "bar"},
1701 },
1702 Labels: &parser.YamlMap{
1703 Lines: parser.LineRange{First: 4, Last: 4},
1704 Key: &parser.YamlNode{
1705 Lines: parser.LineRange{First: 4, Last: 4},
1706 Value: "labels",
1707 },
1708 },
1709 },
1710 },
1711 },
1712 },
1713 {
1714 content: []byte(`
1715- record: true
1716 expr: bar
1717`),
1718 output: []parser.Rule{
1719 {
1720 Lines: parser.LineRange{First: 2, Last: 3},
1721 Error: parser.ParseError{Err: fmt.Errorf("record value must be a YAML string, got bool instead"), Line: 2},
1722 },
1723 },
1724 },
1725 {
1726 content: []byte(`
1727- record:
1728 query: foo
1729 expr: bar
1730`),
1731 output: []parser.Rule{
1732 {
1733 Lines: parser.LineRange{First: 2, Last: 4},
1734 Error: parser.ParseError{Err: fmt.Errorf("record value must be a YAML string, got mapping instead"), Line: 3},
1735 },
1736 },
1737 },
1738 {
1739 content: []byte(`
1740- record: foo
1741 expr: bar
1742 labels: some
1743`),
1744 output: []parser.Rule{
1745 {
1746 Lines: parser.LineRange{First: 2, Last: 4},
1747 Error: parser.ParseError{Err: fmt.Errorf("labels value must be a YAML mapping, got string instead"), Line: 4},
1748 },
1749 },
1750 },
1751 {
1752 content: []byte(`
1753- record: foo
1754 expr: bar
1755 labels: !!binary "SGVsbG8sIFdvcmxkIQ=="
1756`),
1757 output: []parser.Rule{
1758 {
1759 Lines: parser.LineRange{First: 2, Last: 4},
1760 Error: parser.ParseError{Err: fmt.Errorf("labels value must be a YAML mapping, got binary data instead"), Line: 4},
1761 },
1762 },
1763 },
1764 {
1765 content: []byte(`
1766- alert: foo
1767 expr: bar
1768 for: 1.23
1769`),
1770 output: []parser.Rule{
1771 {
1772 Lines: parser.LineRange{First: 2, Last: 4},
1773 Error: parser.ParseError{Err: fmt.Errorf("for value must be a YAML string, got float instead"), Line: 4},
1774 },
1775 },
1776 },
1777 {
1778 content: []byte(`
1779- record: foo
1780 expr: bar
1781 labels: !!garbage "SGVsbG8sIFdvcmxkIQ=="
1782`),
1783 output: []parser.Rule{
1784 {
1785 Lines: parser.LineRange{First: 2, Last: 4},
1786 Error: parser.ParseError{Err: fmt.Errorf("labels value must be a YAML mapping, got garbage instead"), Line: 4},
1787 },
1788 },
1789 },
1790 {
1791 content: []byte(`
1792- record: foo
1793 expr: bar
1794 labels: !! "SGVsbG8sIFdvcmxkIQ=="
1795`),
1796 err: "yaml: line 4: did not find expected tag URI",
1797 },
1798 {
1799 content: []byte(`
1800- record: &foo foo
1801 expr: bar
1802 labels: *foo
1803`),
1804 output: []parser.Rule{
1805 {
1806 Lines: parser.LineRange{First: 2, Last: 4},
1807 Error: parser.ParseError{Err: fmt.Errorf("labels value must be a YAML mapping, got string instead"), Line: 4},
1808 },
1809 },
1810 },
1811 // Multi-document tests
1812 {
1813 content: []byte("---\n- expr: foo\n record: foo\n---\n- expr: bar\n"),
1814 output: []parser.Rule{
1815 {
1816 Lines: parser.LineRange{First: 2, Last: 3},
1817 RecordingRule: &parser.RecordingRule{
1818 Record: parser.YamlNode{
1819 Lines: parser.LineRange{First: 3, Last: 3},
1820 Value: "foo",
1821 },
1822 Expr: parser.PromQLExpr{
1823 Value: &parser.YamlNode{
1824 Lines: parser.LineRange{First: 2, Last: 2},
1825 Value: "foo",
1826 },
1827 Query: &parser.PromQLNode{Expr: "foo"},
1828 },
1829 },
1830 },
1831 // FIXME
1832 // {
1833 // Lines: parser.LineRange{First: 2, Last: 2},
1834 // Error: parser.ParseError{Err: fmt.Errorf("incomplete rule, no alert or record key"), Line: 2},
1835 // },
1836 },
1837 },
1838 }
1839
1840 alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
1841 ignorePrometheusExpr := cmp.FilterValues(func(x, y interface{}) bool {
1842 _, xe := x.(promparser.Expr)
1843 _, ye := y.(promparser.Expr)
1844 return xe || ye
1845 }, alwaysEqual)
1846
1847 cmpErrorText := cmp.Comparer(func(x, y interface{}) bool {
1848 xe := x.(error)
1849 ye := y.(error)
1850 return xe.Error() == ye.Error()
1851 })
1852 sameErrorText := cmp.FilterValues(func(x, y interface{}) bool {
1853 _, xe := x.(error)
1854 _, ye := y.(error)
1855 return xe && ye
1856 }, cmpErrorText)
1857
1858 for i, tc := range testCases {
1859 t.Run(strconv.Itoa(i+1), func(t *testing.T) {
1860 t.Logf("--- Content ---%s--- END ---", tc.content)
1861
1862 p := parser.NewParser()
1863 output, err := p.Parse(tc.content)
1864
1865 if tc.err != "" {
1866 require.EqualError(t, err, tc.err)
1867 } else {
1868 require.NoError(t, err)
1869 }
1870
1871 if diff := cmp.Diff(tc.output, output, ignorePrometheusExpr, sameErrorText); diff != "" {
1872 t.Errorf("Parse() returned wrong output (-want +got):\n%s", diff)
1873 return
1874 }
1875 })
1876 }
1877}
1878