cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.10.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser_test.go

805lines · 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("- record: |\n multiline\n"),
34 output: []parser.Rule{
35 {Error: parser.ParseError{Err: fmt.Errorf("missing expr key"), Line: 1}},
36 },
37 },
38 {
39 content: []byte("- expr: foo\n"),
40 output: []parser.Rule{
41 {Error: parser.ParseError{Err: fmt.Errorf("incomplete rule, no alert or record key"), Line: 1}},
42 },
43 },
44 {
45 content: []byte("- alert: foo\n"),
46 output: []parser.Rule{
47 {Error: parser.ParseError{Err: fmt.Errorf("missing expr key"), Line: 1}},
48 },
49 },
50 {
51 content: []byte("- alert: foo\n record: foo\n"),
52 output: []parser.Rule{
53 {Error: parser.ParseError{Err: fmt.Errorf("got both record and alert keys in a single rule"), Line: 1}},
54 },
55 },
56 {
57 content: []byte("- record: foo\n labels:\n foo: bar\n"),
58 output: []parser.Rule{
59 {Error: parser.ParseError{Err: fmt.Errorf("missing expr key"), Line: 1}},
60 },
61 },
62 {
63 content: []byte("- record: - foo\n"),
64 shouldError: true,
65 },
66 {
67 content: []byte("- record: foo expr: sum(\n"),
68 shouldError: true,
69 },
70 {
71 content: []byte("- record\n\texpr: foo\n"),
72 output: nil,
73 shouldError: true,
74 },
75 {
76 content: []byte("- record: foo\n expr: foo\n extra: true\n"),
77 output: []parser.Rule{
78 {Error: parser.ParseError{Err: fmt.Errorf("invalid key(s) found: extra"), Line: 3}},
79 },
80 },
81 {
82 content: []byte("- record: foo\n expr: foo offset 10m\n"),
83 output: []parser.Rule{
84 {
85 RecordingRule: &parser.RecordingRule{
86 Record: parser.YamlKeyValue{
87 Key: &parser.YamlNode{
88 Position: parser.FilePosition{Lines: []int{1}},
89 Value: "record",
90 },
91 Value: &parser.YamlNode{
92 Position: parser.FilePosition{Lines: []int{1}},
93 Value: "foo",
94 },
95 },
96 Expr: parser.PromQLExpr{
97 Key: &parser.YamlNode{
98 Position: parser.FilePosition{Lines: []int{2}},
99 Value: "expr",
100 },
101 Value: &parser.YamlNode{
102 Position: parser.FilePosition{Lines: []int{2}},
103 Value: "foo offset 10m",
104 },
105 Query: &parser.PromQLNode{
106 Expr: "foo offset 10m",
107 },
108 },
109 },
110 },
111 },
112 },
113 {
114 content: []byte("- record: foo\n expr: foo offset -10m\n"),
115 output: []parser.Rule{
116 {
117 RecordingRule: &parser.RecordingRule{
118 Record: parser.YamlKeyValue{
119 Key: &parser.YamlNode{
120 Position: parser.FilePosition{Lines: []int{1}},
121 Value: "record",
122 },
123 Value: &parser.YamlNode{
124 Position: parser.FilePosition{Lines: []int{1}},
125 Value: "foo",
126 },
127 },
128 Expr: parser.PromQLExpr{
129 Key: &parser.YamlNode{
130 Position: parser.FilePosition{Lines: []int{2}},
131 Value: "expr",
132 },
133 Value: &parser.YamlNode{
134 Position: parser.FilePosition{Lines: []int{2}},
135 Value: "foo offset -10m",
136 },
137 Query: &parser.PromQLNode{
138 Expr: "foo offset -10m",
139 },
140 },
141 },
142 },
143 },
144 },
145 {
146 content: []byte(`
147# head comment
148- record: foo # record comment
149 expr: foo offset 10m # expr comment
150 # pre-labels comment
151 labels:
152 # pre-foo comment
153 foo: bar
154 # post-foo comment
155 bob: alice
156# foot comment
157`),
158 output: []parser.Rule{
159 {
160 RecordingRule: &parser.RecordingRule{
161 Record: parser.YamlKeyValue{
162 Key: &parser.YamlNode{
163 Position: parser.FilePosition{Lines: []int{3}},
164 Value: "record",
165 Comments: []string{"# head comment"},
166 },
167 Value: &parser.YamlNode{
168 Position: parser.FilePosition{Lines: []int{3}},
169 Value: "foo",
170 Comments: []string{"# record comment"},
171 },
172 },
173 Expr: parser.PromQLExpr{
174 Key: &parser.YamlNode{
175 Position: parser.FilePosition{Lines: []int{4}},
176 Value: "expr",
177 },
178 Value: &parser.YamlNode{
179 Position: parser.FilePosition{Lines: []int{4}},
180 Value: "foo offset 10m",
181 Comments: []string{"# expr comment"},
182 },
183 Query: &parser.PromQLNode{
184 Expr: "foo offset 10m",
185 },
186 },
187 Labels: &parser.YamlMap{
188 Key: &parser.YamlNode{
189 Position: parser.FilePosition{Lines: []int{6}},
190 Value: "labels",
191 Comments: []string{"# pre-labels comment"},
192 },
193 Items: []*parser.YamlKeyValue{
194 {
195 Key: &parser.YamlNode{
196 Position: parser.FilePosition{Lines: []int{8}},
197 Value: "foo",
198 Comments: []string{"# pre-foo comment"},
199 },
200 Value: &parser.YamlNode{
201 Position: parser.FilePosition{Lines: []int{8}},
202 Value: "bar",
203 },
204 },
205 {
206 Key: &parser.YamlNode{
207 Position: parser.FilePosition{Lines: []int{10}},
208 Value: "bob",
209 Comments: []string{"# post-foo comment"},
210 },
211 Value: &parser.YamlNode{
212 Position: parser.FilePosition{Lines: []int{10}},
213 Value: "alice",
214 },
215 },
216 },
217 },
218 },
219 },
220 },
221 },
222 {
223 content: []byte("- record: foo\n expr: foo[5m] offset 10m\n"),
224 output: []parser.Rule{
225 {
226 RecordingRule: &parser.RecordingRule{
227 Record: parser.YamlKeyValue{
228 Key: &parser.YamlNode{
229 Position: parser.FilePosition{Lines: []int{1}},
230 Value: "record",
231 },
232 Value: &parser.YamlNode{
233 Position: parser.FilePosition{Lines: []int{1}},
234 Value: "foo",
235 },
236 },
237 Expr: parser.PromQLExpr{
238 Key: &parser.YamlNode{
239 Position: parser.FilePosition{Lines: []int{2}},
240 Value: "expr",
241 },
242 Value: &parser.YamlNode{
243 Position: parser.FilePosition{Lines: []int{2}},
244 Value: "foo[5m] offset 10m",
245 },
246 Query: &parser.PromQLNode{
247 Expr: "foo[5m] offset 10m",
248 Children: []*parser.PromQLNode{
249 {Expr: "foo offset 10m"},
250 },
251 },
252 },
253 },
254 },
255 },
256 },
257 {
258 content: []byte(`
259- record: name
260 expr: sum(foo)
261 labels:
262 foo: bar
263 bob: alice
264`),
265 output: []parser.Rule{
266 {
267 RecordingRule: &parser.RecordingRule{
268 Record: parser.YamlKeyValue{
269 Key: &parser.YamlNode{
270 Position: parser.FilePosition{Lines: []int{2}},
271 Value: "record",
272 },
273 Value: &parser.YamlNode{
274 Position: parser.FilePosition{Lines: []int{2}},
275 Value: "name",
276 },
277 },
278 Expr: parser.PromQLExpr{
279 Key: &parser.YamlNode{
280 Position: parser.FilePosition{Lines: []int{3}},
281 Value: "expr",
282 },
283 Value: &parser.YamlNode{
284 Position: parser.FilePosition{Lines: []int{3}},
285 Value: "sum(foo)",
286 },
287 Query: &parser.PromQLNode{
288 Expr: "sum(foo)",
289 Children: []*parser.PromQLNode{
290 {Expr: "foo"},
291 },
292 },
293 },
294 Labels: &parser.YamlMap{
295 Key: &parser.YamlNode{
296 Position: parser.FilePosition{Lines: []int{4}},
297 Value: "labels",
298 },
299 Items: []*parser.YamlKeyValue{
300 {
301 Key: &parser.YamlNode{
302 Position: parser.FilePosition{Lines: []int{5}},
303 Value: "foo",
304 },
305 Value: &parser.YamlNode{
306 Position: parser.FilePosition{Lines: []int{5}},
307 Value: "bar",
308 },
309 },
310 {
311 Key: &parser.YamlNode{
312 Position: parser.FilePosition{Lines: []int{6}},
313 Value: "bob",
314 },
315 Value: &parser.YamlNode{
316 Position: parser.FilePosition{Lines: []int{6}},
317 Value: "alice",
318 },
319 },
320 },
321 },
322 },
323 },
324 },
325 shouldError: false,
326 },
327 {
328 content: []byte(`
329groups:
330- name: custom_rules
331 rules:
332 - record: name
333 expr: sum(foo)
334 labels:
335 foo: bar
336 bob: alice
337`),
338 output: []parser.Rule{
339 {
340 RecordingRule: &parser.RecordingRule{
341 Record: parser.YamlKeyValue{
342 Key: &parser.YamlNode{
343 Position: parser.FilePosition{Lines: []int{5}},
344 Value: "record",
345 },
346 Value: &parser.YamlNode{
347 Position: parser.FilePosition{Lines: []int{5}},
348 Value: "name",
349 },
350 },
351 Expr: parser.PromQLExpr{
352 Key: &parser.YamlNode{
353 Position: parser.FilePosition{Lines: []int{6}},
354 Value: "expr",
355 },
356 Value: &parser.YamlNode{
357 Position: parser.FilePosition{Lines: []int{6}},
358 Value: "sum(foo)",
359 },
360 Query: &parser.PromQLNode{
361 Expr: "sum(foo)",
362 Children: []*parser.PromQLNode{
363 {Expr: "foo"},
364 },
365 },
366 },
367 Labels: &parser.YamlMap{
368 Key: &parser.YamlNode{
369 Position: parser.FilePosition{Lines: []int{7}},
370 Value: "labels",
371 },
372 Items: []*parser.YamlKeyValue{
373 {
374 Key: &parser.YamlNode{
375 Position: parser.FilePosition{Lines: []int{8}},
376 Value: "foo",
377 },
378 Value: &parser.YamlNode{
379 Position: parser.FilePosition{Lines: []int{8}},
380 Value: "bar",
381 },
382 },
383 {
384 Key: &parser.YamlNode{
385 Position: parser.FilePosition{Lines: []int{9}},
386 Value: "bob",
387 },
388 Value: &parser.YamlNode{
389 Position: parser.FilePosition{Lines: []int{9}},
390 Value: "alice",
391 },
392 },
393 },
394 },
395 },
396 },
397 },
398 shouldError: false,
399 },
400 {
401 content: []byte(`- alert: Down
402 expr: |
403 up == 0
404 for: |+
405 11m
406 labels:
407 severity: critical
408 annotations:
409 uri: https://docs.example.com/down.html
410
411- record: >
412 foo
413 expr: |-
414 bar
415 /
416 baz > 1
417 labels: {}
418`),
419 output: []parser.Rule{
420 {
421 AlertingRule: &parser.AlertingRule{
422 Alert: parser.YamlKeyValue{
423 Key: &parser.YamlNode{
424 Position: parser.FilePosition{Lines: []int{1}},
425 Value: "alert",
426 },
427 Value: &parser.YamlNode{
428 Position: parser.FilePosition{Lines: []int{1}},
429 Value: "Down",
430 },
431 },
432 Expr: parser.PromQLExpr{
433 Key: &parser.YamlNode{
434 Position: parser.FilePosition{Lines: []int{2}},
435 Value: "expr",
436 },
437 Value: &parser.YamlNode{
438 Position: parser.FilePosition{Lines: []int{3}},
439 Value: "up == 0\n",
440 },
441 Query: &parser.PromQLNode{
442 Expr: "up == 0\n",
443 Children: []*parser.PromQLNode{
444 {Expr: "up"},
445 {Expr: "0"},
446 },
447 },
448 },
449 For: &parser.YamlKeyValue{
450 Key: &parser.YamlNode{
451 Position: parser.FilePosition{Lines: []int{4}},
452 Value: "for",
453 },
454 Value: &parser.YamlNode{
455 Position: parser.FilePosition{Lines: []int{5}},
456 Value: "11m\n",
457 },
458 },
459 Labels: &parser.YamlMap{
460 Key: &parser.YamlNode{
461 Position: parser.FilePosition{Lines: []int{6}},
462 Value: "labels",
463 },
464 Items: []*parser.YamlKeyValue{
465 {
466 Key: &parser.YamlNode{
467 Position: parser.FilePosition{Lines: []int{7}},
468 Value: "severity",
469 },
470 Value: &parser.YamlNode{
471 Position: parser.FilePosition{Lines: []int{7}},
472 Value: "critical",
473 },
474 },
475 },
476 },
477 Annotations: &parser.YamlMap{
478 Key: &parser.YamlNode{
479 Position: parser.FilePosition{Lines: []int{8}},
480 Value: "annotations",
481 },
482 Items: []*parser.YamlKeyValue{
483 {
484 Key: &parser.YamlNode{
485 Position: parser.FilePosition{Lines: []int{9}},
486 Value: "uri",
487 },
488 Value: &parser.YamlNode{
489 Position: parser.FilePosition{Lines: []int{9}},
490 Value: "https://docs.example.com/down.html",
491 },
492 },
493 },
494 },
495 },
496 },
497 {
498 RecordingRule: &parser.RecordingRule{
499 Record: parser.YamlKeyValue{
500 Key: &parser.YamlNode{
501 Position: parser.FilePosition{Lines: []int{11}},
502 Value: "record",
503 },
504 Value: &parser.YamlNode{
505 Position: parser.FilePosition{Lines: []int{12}},
506 Value: "foo\n",
507 },
508 },
509 Expr: parser.PromQLExpr{
510 Key: &parser.YamlNode{
511 Position: parser.FilePosition{Lines: []int{13}},
512 Value: "expr",
513 },
514 Value: &parser.YamlNode{
515 Position: parser.FilePosition{Lines: []int{14, 15, 16}},
516 Value: "bar\n/\nbaz > 1",
517 },
518 Query: &parser.PromQLNode{
519 Expr: "bar\n/\nbaz > 1",
520 Children: []*parser.PromQLNode{
521 {
522 Expr: "bar / baz", Children: []*parser.PromQLNode{
523 {Expr: "bar"},
524 {Expr: "baz"},
525 },
526 },
527 {Expr: "1"},
528 },
529 },
530 },
531 Labels: &parser.YamlMap{
532 Key: &parser.YamlNode{
533 Position: parser.FilePosition{Lines: []int{17}},
534 Value: "labels",
535 },
536 },
537 },
538 },
539 },
540 shouldError: false,
541 },
542 {
543 content: []byte(`- alert: Foo
544 expr:
545 (
546 xxx
547 -
548 yyy
549 ) * bar > 0
550 and on(instance, device) baz
551 for: 30m
552`),
553 output: []parser.Rule{
554 {
555 AlertingRule: &parser.AlertingRule{
556 Alert: parser.YamlKeyValue{
557 Key: &parser.YamlNode{
558 Position: parser.FilePosition{Lines: []int{1}},
559 Value: "alert",
560 },
561 Value: &parser.YamlNode{
562 Position: parser.FilePosition{Lines: []int{1}},
563 Value: "Foo",
564 },
565 },
566 Expr: parser.PromQLExpr{
567 Key: &parser.YamlNode{
568 Position: parser.FilePosition{Lines: []int{2}},
569 Value: "expr",
570 },
571 Value: &parser.YamlNode{
572 Position: parser.FilePosition{Lines: []int{3, 4, 5, 6, 7, 8}},
573 Value: "( xxx - yyy ) * bar > 0 and on(instance, device) baz",
574 },
575 Query: &parser.PromQLNode{
576 Expr: "( xxx - yyy ) * bar > 0 and on(instance, device) baz",
577 Children: []*parser.PromQLNode{
578 {
579 Expr: "(xxx - yyy) * bar > 0",
580 Children: []*parser.PromQLNode{
581 {
582 Expr: "(xxx - yyy) * bar",
583 Children: []*parser.PromQLNode{
584 {
585 Expr: "(xxx - yyy)",
586 Children: []*parser.PromQLNode{
587 {
588 Expr: "xxx - yyy",
589 Children: []*parser.PromQLNode{
590 {Expr: "xxx"},
591 {Expr: "yyy"},
592 },
593 },
594 },
595 },
596 {
597 Expr: "bar",
598 },
599 },
600 },
601 {
602 Expr: "0",
603 },
604 },
605 },
606 {Expr: "baz"},
607 },
608 },
609 },
610 For: &parser.YamlKeyValue{
611 Key: &parser.YamlNode{
612 Position: parser.FilePosition{Lines: []int{9}},
613 Value: "for",
614 },
615 Value: &parser.YamlNode{
616 Position: parser.FilePosition{Lines: []int{9}},
617 Value: "30m",
618 },
619 },
620 },
621 },
622 },
623 },
624 {
625 content: []byte(`---
626kind: ConfigMap
627apiVersion: v1
628metadata:
629 name: example-app-alerts
630 labels:
631 app: example-app
632data:
633 alerts: |
634 groups:
635 - name: example-app-alerts
636 rules:
637 - alert: Example_Is_Down
638 expr: kube_deployment_status_replicas_available{namespace="example-app"} < 1
639 for: 5m
640 labels:
641 priority: "2"
642 environment: production
643 annotations:
644 summary: "No replicas for Example have been running for 5 minutes"
645
646 - alert: Example_High_Restart_Rate
647 expr: sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )
648`),
649 output: nil,
650 },
651 /*
652 FIXME https://github.com/cloudflare/pint/issues/20
653 {
654 content: []byte(`groups:
655 - name: "haproxy.api_server.rules"
656 rules:
657 - alert: HaproxyServerHealthcheckFailure
658 expr: increase(haproxy_server_check_failures_total[15m]) > 100
659 for: 5m
660 labels:
661 severity: 24x7
662 annotations:
663 summary: "HAProxy server healthcheck failure (instance {{ $labels.instance }})"
664 description: "Some server healthcheck are failing on {{ $labels.server }}\n VALUE = {{ $value }}\n LABELS: {{ $labels }}"
665 `),
666 output: []parser.Rule{
667 {
668 AlertingRule: &parser.AlertingRule{
669 Alert: parser.YamlKeyValue{
670 Key: &parser.YamlNode{
671 Position: parser.FilePosition{Lines: []int{4}},
672 Value: "alert",
673 },
674 Value: &parser.YamlNode{
675 Position: parser.FilePosition{Lines: []int{4}},
676 Value: "HaproxyServerHealthcheckFailure",
677 },
678 },
679 Expr: parser.PromQLExpr{
680 Key: &parser.YamlNode{
681 Position: parser.FilePosition{Lines: []int{5}},
682 Value: "expr",
683 },
684 Value: &parser.YamlNode{
685 Position: parser.FilePosition{Lines: []int{5}},
686 Value: "increase(haproxy_server_check_failures_total[15m]) > 100",
687 },
688 Query: &parser.PromQLNode{
689 Expr: "increase(haproxy_server_check_failures_total[15m]) > 100",
690 Children: []*parser.PromQLNode{
691 {
692 Expr: "increase(haproxy_server_check_failures_total[15m])",
693 Children: []*parser.PromQLNode{
694 {
695 Expr: "haproxy_server_check_failures_total[15m]",
696 Children: []*parser.PromQLNode{
697 {
698 Expr: "haproxy_server_check_failures_total",
699 },
700 },
701 },
702 },
703 },
704 {Expr: "100"},
705 },
706 },
707 },
708 For: &parser.YamlKeyValue{
709 Key: &parser.YamlNode{
710 Position: parser.FilePosition{Lines: []int{6}},
711 Value: "for",
712 },
713 Value: &parser.YamlNode{Position: parser.FilePosition{Lines: []int{6}},
714 Value: "5m",
715 },
716 },
717 Labels: &parser.YamlMap{
718 Key: &parser.YamlNode{
719 Position: parser.FilePosition{Lines: []int{7}},
720 Value: "labels",
721 },
722 Items: []*parser.YamlKeyValue{
723 {
724 Key: &parser.YamlNode{
725 Position: parser.FilePosition{Lines: []int{8}},
726 Value: "severity",
727 },
728 Value: &parser.YamlNode{
729 Position: parser.FilePosition{Lines: []int{8}},
730 Value: "24x7",
731 },
732 },
733 },
734 },
735 Annotations: &parser.YamlMap{
736 Key: &parser.YamlNode{
737 Position: parser.FilePosition{Lines: []int{9}},
738 Value: "annotations",
739 },
740 Items: []*parser.YamlKeyValue{
741 {
742 Key: &parser.YamlNode{
743 Position: parser.FilePosition{Lines: []int{10}},
744 Value: "summary",
745 },
746 Value: &parser.YamlNode{
747 Position: parser.FilePosition{Lines: []int{10}},
748 Value: "HAProxy server healthcheck failure (instance {{ $labels.instance }})",
749 },
750 },
751 {
752 Key: &parser.YamlNode{
753 Position: parser.FilePosition{Lines: []int{11}},
754 Value: "description",
755 },
756 Value: &parser.YamlNode{
757 Position: parser.FilePosition{Lines: []int{11}},
758 Value: `Some server healthcheck are failing on {{ $labels.server }}\n VALUE = {{ $value }}\n LABELS: {{ $labels }}`,
759 },
760 },
761 },
762 },
763 },
764 },
765 },
766 },
767 */
768 }
769
770 alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
771 ignorePrometheusExpr := cmp.FilterValues(func(x, y interface{}) bool {
772 _, xe := x.(promparser.Expr)
773 _, ye := y.(promparser.Expr)
774 return xe || ye
775 }, alwaysEqual)
776
777 cmpErrorText := cmp.Comparer(func(x, y interface{}) bool {
778 xe := x.(error)
779 ye := y.(error)
780 return xe.Error() == ye.Error()
781 })
782 sameErrorText := cmp.FilterValues(func(x, y interface{}) bool {
783 _, xe := x.(error)
784 _, ye := y.(error)
785 return xe && ye
786 }, cmpErrorText)
787
788 for i, tc := range testCases {
789 t.Run(strconv.Itoa(i+1), func(t *testing.T) {
790 p := parser.NewParser()
791 output, err := p.Parse(tc.content)
792
793 hadError := err != nil
794 if hadError != tc.shouldError {
795 t.Errorf("Parse() returned err=%v, expected=%v", err, tc.shouldError)
796 return
797 }
798
799 if diff := cmp.Diff(tc.output, output, ignorePrometheusExpr, sameErrorText); diff != "" {
800 t.Errorf("Parse() returned wrong output (-want +got):\n%s", diff)
801 return
802 }
803 })
804 }
805}
806