cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.3.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser_test.go

770lines · 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{Position: parser.FilePosition{Lines: []int{5}},
423 Value: "11m\n",
424 },
425 },
426 Labels: &parser.YamlMap{
427 Key: &parser.YamlNode{
428 Position: parser.FilePosition{Lines: []int{6}},
429 Value: "labels",
430 },
431 Items: []*parser.YamlKeyValue{
432 {
433 Key: &parser.YamlNode{
434 Position: parser.FilePosition{Lines: []int{7}},
435 Value: "severity",
436 },
437 Value: &parser.YamlNode{
438 Position: parser.FilePosition{Lines: []int{7}},
439 Value: "critical",
440 },
441 },
442 },
443 },
444 Annotations: &parser.YamlMap{
445 Key: &parser.YamlNode{
446 Position: parser.FilePosition{Lines: []int{8}},
447 Value: "annotations",
448 },
449 Items: []*parser.YamlKeyValue{
450 {
451 Key: &parser.YamlNode{
452 Position: parser.FilePosition{Lines: []int{9}},
453 Value: "uri",
454 },
455 Value: &parser.YamlNode{
456 Position: parser.FilePosition{Lines: []int{9}},
457 Value: "https://docs.example.com/down.html",
458 },
459 },
460 },
461 },
462 },
463 },
464 {
465 RecordingRule: &parser.RecordingRule{
466 Record: parser.YamlKeyValue{
467 Key: &parser.YamlNode{
468 Position: parser.FilePosition{Lines: []int{11}},
469 Value: "record",
470 },
471 Value: &parser.YamlNode{
472 Position: parser.FilePosition{Lines: []int{12}},
473 Value: "foo\n",
474 },
475 },
476 Expr: parser.PromQLExpr{
477 Key: &parser.YamlNode{
478 Position: parser.FilePosition{Lines: []int{13}},
479 Value: "expr",
480 },
481 Value: &parser.YamlNode{
482 Position: parser.FilePosition{Lines: []int{14, 15, 16}},
483 Value: "bar\n/\nbaz > 1",
484 },
485 Query: &parser.PromQLNode{
486 Expr: "bar\n/\nbaz > 1",
487 Children: []*parser.PromQLNode{
488 {Expr: "bar / baz", Children: []*parser.PromQLNode{
489 {Expr: "bar"},
490 {Expr: "baz"},
491 },
492 },
493 {Expr: "1"},
494 },
495 },
496 },
497 Labels: &parser.YamlMap{
498 Key: &parser.YamlNode{
499 Position: parser.FilePosition{Lines: []int{17}},
500 Value: "labels",
501 },
502 },
503 },
504 },
505 },
506 shouldError: false,
507 },
508 {
509 content: []byte(`- alert: Foo
510 expr:
511 (
512 xxx
513 -
514 yyy
515 ) * bar > 0
516 and on(instance, device) baz
517 for: 30m
518`),
519 output: []parser.Rule{
520 {
521 AlertingRule: &parser.AlertingRule{
522 Alert: parser.YamlKeyValue{
523 Key: &parser.YamlNode{
524 Position: parser.FilePosition{Lines: []int{1}},
525 Value: "alert",
526 },
527 Value: &parser.YamlNode{
528 Position: parser.FilePosition{Lines: []int{1}},
529 Value: "Foo",
530 },
531 },
532 Expr: parser.PromQLExpr{
533 Key: &parser.YamlNode{
534 Position: parser.FilePosition{Lines: []int{2}},
535 Value: "expr",
536 },
537 Value: &parser.YamlNode{
538 Position: parser.FilePosition{Lines: []int{3, 4, 5, 6, 7, 8}},
539 Value: "( xxx - yyy ) * bar > 0 and on(instance, device) baz",
540 },
541 Query: &parser.PromQLNode{
542 Expr: "( xxx - yyy ) * bar > 0 and on(instance, device) baz",
543 Children: []*parser.PromQLNode{
544 {
545 Expr: "(xxx - yyy) * bar > 0",
546 Children: []*parser.PromQLNode{
547 {
548 Expr: "(xxx - yyy) * bar",
549 Children: []*parser.PromQLNode{
550 {
551 Expr: "(xxx - yyy)",
552 Children: []*parser.PromQLNode{
553 {
554 Expr: "xxx - yyy",
555 Children: []*parser.PromQLNode{
556 {Expr: "xxx"},
557 {Expr: "yyy"},
558 },
559 },
560 },
561 },
562 {
563 Expr: "bar",
564 },
565 },
566 },
567 {
568 Expr: "0",
569 },
570 },
571 },
572 {Expr: "baz"},
573 },
574 },
575 },
576 For: &parser.YamlKeyValue{
577 Key: &parser.YamlNode{
578 Position: parser.FilePosition{Lines: []int{9}},
579 Value: "for",
580 },
581 Value: &parser.YamlNode{Position: parser.FilePosition{Lines: []int{9}},
582 Value: "30m",
583 },
584 },
585 },
586 },
587 },
588 },
589 {
590 content: []byte(`---
591kind: ConfigMap
592apiVersion: v1
593metadata:
594 name: example-app-alerts
595 labels:
596 app: example-app
597data:
598 alerts: |
599 groups:
600 - name: example-app-alerts
601 rules:
602 - alert: Example_Is_Down
603 expr: kube_deployment_status_replicas_available{namespace="example-app"} < 1
604 for: 5m
605 labels:
606 priority: "2"
607 environment: production
608 annotations:
609 summary: "No replicas for Example have been running for 5 minutes"
610
611 - alert: Example_High_Restart_Rate
612 expr: sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )
613`),
614 output: nil,
615 },
616 /*
617 FIXME https://github.com/cloudflare/pint/issues/20
618 {
619 content: []byte(`groups:
620 - name: "haproxy.api_server.rules"
621 rules:
622 - alert: HaproxyServerHealthcheckFailure
623 expr: increase(haproxy_server_check_failures_total[15m]) > 100
624 for: 5m
625 labels:
626 severity: 24x7
627 annotations:
628 summary: "HAProxy server healthcheck failure (instance {{ $labels.instance }})"
629 description: "Some server healthcheck are failing on {{ $labels.server }}\n VALUE = {{ $value }}\n LABELS: {{ $labels }}"
630 `),
631 output: []parser.Rule{
632 {
633 AlertingRule: &parser.AlertingRule{
634 Alert: parser.YamlKeyValue{
635 Key: &parser.YamlNode{
636 Position: parser.FilePosition{Lines: []int{4}},
637 Value: "alert",
638 },
639 Value: &parser.YamlNode{
640 Position: parser.FilePosition{Lines: []int{4}},
641 Value: "HaproxyServerHealthcheckFailure",
642 },
643 },
644 Expr: parser.PromQLExpr{
645 Key: &parser.YamlNode{
646 Position: parser.FilePosition{Lines: []int{5}},
647 Value: "expr",
648 },
649 Value: &parser.YamlNode{
650 Position: parser.FilePosition{Lines: []int{5}},
651 Value: "increase(haproxy_server_check_failures_total[15m]) > 100",
652 },
653 Query: &parser.PromQLNode{
654 Expr: "increase(haproxy_server_check_failures_total[15m]) > 100",
655 Children: []*parser.PromQLNode{
656 {
657 Expr: "increase(haproxy_server_check_failures_total[15m])",
658 Children: []*parser.PromQLNode{
659 {
660 Expr: "haproxy_server_check_failures_total[15m]",
661 Children: []*parser.PromQLNode{
662 {
663 Expr: "haproxy_server_check_failures_total",
664 },
665 },
666 },
667 },
668 },
669 {Expr: "100"},
670 },
671 },
672 },
673 For: &parser.YamlKeyValue{
674 Key: &parser.YamlNode{
675 Position: parser.FilePosition{Lines: []int{6}},
676 Value: "for",
677 },
678 Value: &parser.YamlNode{Position: parser.FilePosition{Lines: []int{6}},
679 Value: "5m",
680 },
681 },
682 Labels: &parser.YamlMap{
683 Key: &parser.YamlNode{
684 Position: parser.FilePosition{Lines: []int{7}},
685 Value: "labels",
686 },
687 Items: []*parser.YamlKeyValue{
688 {
689 Key: &parser.YamlNode{
690 Position: parser.FilePosition{Lines: []int{8}},
691 Value: "severity",
692 },
693 Value: &parser.YamlNode{
694 Position: parser.FilePosition{Lines: []int{8}},
695 Value: "24x7",
696 },
697 },
698 },
699 },
700 Annotations: &parser.YamlMap{
701 Key: &parser.YamlNode{
702 Position: parser.FilePosition{Lines: []int{9}},
703 Value: "annotations",
704 },
705 Items: []*parser.YamlKeyValue{
706 {
707 Key: &parser.YamlNode{
708 Position: parser.FilePosition{Lines: []int{10}},
709 Value: "summary",
710 },
711 Value: &parser.YamlNode{
712 Position: parser.FilePosition{Lines: []int{10}},
713 Value: "HAProxy server healthcheck failure (instance {{ $labels.instance }})",
714 },
715 },
716 {
717 Key: &parser.YamlNode{
718 Position: parser.FilePosition{Lines: []int{11}},
719 Value: "description",
720 },
721 Value: &parser.YamlNode{
722 Position: parser.FilePosition{Lines: []int{11}},
723 Value: `Some server healthcheck are failing on {{ $labels.server }}\n VALUE = {{ $value }}\n LABELS: {{ $labels }}`,
724 },
725 },
726 },
727 },
728 },
729 },
730 },
731 },
732 */
733 }
734
735 alwaysEqual := cmp.Comparer(func(_, _ interface{}) bool { return true })
736 ignorePrometheusExpr := cmp.FilterValues(func(x, y interface{}) bool {
737 _, xe := x.(promparser.Expr)
738 _, ye := y.(promparser.Expr)
739 return xe || ye
740 }, alwaysEqual)
741
742 cmpErrorText := cmp.Comparer(func(x, y interface{}) bool {
743 xe := x.(error)
744 ye := y.(error)
745 return xe.Error() == ye.Error()
746 })
747 sameErrorText := cmp.FilterValues(func(x, y interface{}) bool {
748 _, xe := x.(error)
749 _, ye := y.(error)
750 return xe && ye
751 }, cmpErrorText)
752
753 for i, tc := range testCases {
754 t.Run(strconv.Itoa(i+1), func(t *testing.T) {
755 p := parser.NewParser()
756 output, err := p.Parse(tc.content)
757
758 hadError := err != nil
759 if hadError != tc.shouldError {
760 t.Errorf("Parse() returned err=%v, expected=%v", err, tc.shouldError)
761 return
762 }
763
764 if diff := cmp.Diff(tc.output, output, ignorePrometheusExpr, sameErrorText); diff != "" {
765 t.Errorf("Parse() returned wrong output (-want +got):\n%s", diff)
766 return
767 }
768 })
769 }
770}
771