cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
c67f7ef2f0487df2b5230674d21c3c886ff1fa8e

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser_test.go

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