cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.80.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/models_test.go

512lines · modecode

1package parser_test
2
3import (
4 "strconv"
5 "strings"
6 "testing"
7
8 "github.com/stretchr/testify/require"
9
10 "github.com/cloudflare/pint/internal/diags"
11 "github.com/cloudflare/pint/internal/parser"
12)
13
14func newMustRule(content string) parser.Rule {
15 p := parser.NewParser(parser.DefaultOptions)
16 file := p.Parse(strings.NewReader(content))
17 if file.Error.Err != nil {
18 panic(file.Error.Err)
19 }
20 for _, group := range file.Groups {
21 for _, rule := range group.Rules {
22 return rule
23 }
24 }
25 return parser.Rule{}
26}
27
28func TestRuleIsIdentical(t *testing.T) {
29 type testCaseT struct {
30 a parser.Rule
31 b parser.Rule
32 equal bool
33 }
34
35 testCases := []testCaseT{
36 {
37 a: parser.Rule{},
38 b: parser.Rule{},
39 equal: true,
40 },
41 {
42 a: newMustRule("- record: foo\n expr: bob\n"),
43 b: newMustRule("- record: foo\n expr: bob\n"),
44 equal: true,
45 },
46 {
47 a: newMustRule("- record: bob\n expr: bob\n"),
48 b: newMustRule("- record: foo\n expr: bob\n"),
49 equal: false,
50 },
51 {
52 a: newMustRule("- record: foo\n expr: bob\n"),
53 b: newMustRule("- record: foo\n expr: bob\n labels: {}\n"),
54 equal: true,
55 },
56 {
57 a: newMustRule("- record: foo\n expr: bob\n labels: {}\n"),
58 b: newMustRule("- record: foo\n expr: bob\n"),
59 equal: true,
60 },
61 {
62 a: newMustRule("- record: foo\n expr: bob\n"),
63 b: newMustRule("- expr: bob\n record: foo\n"),
64 equal: true,
65 },
66 {
67 a: newMustRule("- record: foo\n expr: bob\n labels:\n foo: bar\n"),
68 b: newMustRule("- record: foo\n expr: bob\n"),
69 equal: false,
70 },
71 {
72 a: newMustRule("- record: foo\n expr: bob\n"),
73 b: newMustRule("- record: foo\n expr: bob\n labels:\n foo: bar\n"),
74 equal: false,
75 },
76 {
77 a: newMustRule("- record: foo\n expr: bob\n labels:\n bar: bar\n"),
78 b: newMustRule("- record: foo\n expr: bob\n labels:\n foo: bar\n"),
79 equal: false,
80 },
81 {
82 a: newMustRule("- record: foo\n expr: bob\n labels:\n foo: bar\n extra: foo\n"),
83 b: newMustRule("- record: foo\n expr: bob\n labels:\n foo: bar\n"),
84 equal: false,
85 },
86 {
87 a: newMustRule("- record: foo\n expr: bob\n labels:\n foo: bar\n extra: foo\n"),
88 b: newMustRule("- record: foo\n expr: bob\n labels:\n foo: bar\n extra: bar\n"),
89 equal: false,
90 },
91 {
92 a: newMustRule("- record: foo\n expr: bob\n labels:\n foo: bar\n abc: foo\n"),
93 b: newMustRule("- record: foo\n expr: bob\n labels:\n foo: bar\n cba: foo\n"),
94 equal: false,
95 },
96 {
97 a: newMustRule("- record: foo\n expr: bob\n labels:\n foo: bar\n"),
98 b: newMustRule("- record: foo\n expr: bob\n labels:\n foo: bar\n extra: foo\n"),
99 equal: false,
100 },
101 {
102 a: newMustRule("- record: foo\n expr: bob\n labels:\n foo: bar\n extra: foo\n"),
103 b: newMustRule("- record: foo\n expr: bob\n labels:\n foo: bar\n extra: bar\n"),
104 equal: false,
105 },
106 {
107 a: newMustRule("- record: foo\n expr: bob\n"),
108 b: newMustRule("- alert: foo\n expr: bob\n"),
109 equal: false,
110 },
111 {
112 a: newMustRule("- record: foo\n expr: bob\n"),
113 b: newMustRule("- record: foo\n xxx: bob\n"),
114 equal: false,
115 },
116 {
117 a: newMustRule("- record: foo\n expr: bob\n"),
118 b: newMustRule("- record: foo\n expr: bob == 0\n"),
119 equal: false,
120 },
121 {
122 a: newMustRule("- alert: foo\n expr: bob\n"),
123 b: newMustRule("- alert: foo\n expr: bob\n for: 4m\n"),
124 equal: false,
125 },
126 {
127 a: newMustRule("- alert: foo\n expr: bob\n"),
128 b: newMustRule("- record: foo\n expr: bob\n"),
129 equal: false,
130 },
131 {
132 a: newMustRule("- alert: foo\n expr: bob\n"),
133 b: newMustRule("- alert: foo\n expr: bob\n"),
134 equal: true,
135 },
136 {
137 a: newMustRule("- alert: foo\n expr: bob\n"),
138 b: newMustRule("- alert: foo\n expr: bob(\n"),
139 equal: false,
140 },
141 {
142 a: newMustRule("- alert: foo\n expr: bob\n"),
143 b: newMustRule("- alert: foo1\n expr: bob\n"),
144 equal: false,
145 },
146 {
147 a: newMustRule("- alert: foo\n expr: bob\n"),
148 b: newMustRule("- alert: foo\n expr: bob1\n"),
149 equal: false,
150 },
151 {
152 a: newMustRule("- alert: foo\n expr: bob\n for: 4m\n"),
153 b: newMustRule("- alert: foo\n expr: bob\n for: 5m\n"),
154 equal: false,
155 },
156 {
157 a: newMustRule("- alert: foo\n expr: bob\n for: 4m\n"),
158 b: newMustRule("- alert: foo\n expr: bob\n keep_firing_for: 4m\n"),
159 equal: false,
160 },
161 {
162 a: newMustRule("- alert: foo\n expr: bob\n keep_firing_for: 4m\n"),
163 b: newMustRule("- alert: foo\n expr: bob\n for: 4m\n"),
164 equal: false,
165 },
166 {
167 a: newMustRule("- alert: foo\n expr: bob\n keep_firing_for: 3m\n"),
168 b: newMustRule("- alert: foo\n expr: bob\n keep_firing_for: 4m\n"),
169 equal: false,
170 },
171 {
172 a: newMustRule("- alert: foo\n expr: bob\n"),
173 b: newMustRule("- alert: foo\n expr: bob\n labels:\n foo: bar\n"),
174 equal: false,
175 },
176 {
177 a: newMustRule("- alert: foo\n expr: bob\n labels:\n bar: bar\n"),
178 b: newMustRule("- alert: foo\n expr: bob\n labels:\n foo: bar\n"),
179 equal: false,
180 },
181 {
182 a: newMustRule("- alert: foo\n expr: bob\n"),
183 b: newMustRule("- alert: foo\n expr: bob\n annotations:\n foo: bar\n"),
184 equal: false,
185 },
186 {
187 a: newMustRule("- alert: foo\n expr: bob\n annotations:\n bar: bar\n"),
188 b: newMustRule("- alert: foo\n expr: bob\n annotations:\n foo: bar\n"),
189 equal: false,
190 },
191 {
192 a: newMustRule("- alert: foo\n # pint disable promql/series\n expr: bob\n"),
193 b: newMustRule("- alert: foo\n expr: bob\n"),
194 equal: false,
195 },
196 {
197 a: newMustRule("- alert: foo\n expr: bob\n"),
198 b: newMustRule("- alert: foo\n # pint disable promql/series\n expr: bob\n"),
199 equal: false,
200 },
201 {
202 a: newMustRule("- record: colo:labels:equal\n expr: sum(foo) without(job)\n labels:\n same: yes\n"),
203 b: newMustRule("- record: colo:labels:equal\n expr: sum(foo) without(job)\n labels:\n same: yes\n"),
204 equal: true,
205 },
206 {
207 a: newMustRule("- record: foo\n expr: sum(foo)\n labels:\n same: yes\n a: b\n d: c\n"),
208 b: newMustRule("- record: foo\n expr: sum(foo)\n labels:\n same: yes\n a: b\n c: d\n"),
209 equal: false,
210 },
211 }
212
213 for i, tc := range testCases {
214 t.Run(strconv.Itoa(i+1), func(t *testing.T) {
215 equal := tc.a.IsIdentical(tc.b)
216 require.Equal(t, tc.equal, equal, tc.a)
217 })
218 }
219}
220
221func TestRuleIsSame(t *testing.T) {
222 type testCaseT struct {
223 a parser.Rule
224 b parser.Rule
225 same bool
226 }
227
228 testCases := []testCaseT{
229 // Both empty rules
230 {
231 a: parser.Rule{},
232 b: parser.Rule{},
233 same: true,
234 },
235 // Different rule types (alerting vs recording)
236 {
237 a: newMustRule("- alert: foo\n expr: bob\n"),
238 b: newMustRule("- record: foo\n expr: bob\n"),
239 same: false,
240 },
241 // Different rule types (recording vs alerting)
242 {
243 a: newMustRule("- record: foo\n expr: bob\n"),
244 b: newMustRule("- alert: foo\n expr: bob\n"),
245 same: false,
246 },
247 // One has RecordingRule, other has neither (AlertingRule matches as nil)
248 {
249 a: parser.Rule{
250 RecordingRule: &parser.RecordingRule{},
251 },
252 b: parser.Rule{},
253 same: false,
254 },
255 // Same alerting rules
256 {
257 a: newMustRule("- alert: foo\n expr: bob\n"),
258 b: newMustRule("- alert: foo\n expr: bob\n"),
259 same: true,
260 },
261 // Same recording rules
262 {
263 a: newMustRule("- record: foo\n expr: bob\n"),
264 b: newMustRule("- record: foo\n expr: bob\n"),
265 same: true,
266 },
267 // Different line ranges - First line differs
268 {
269 a: parser.Rule{
270 Lines: diags.LineRange{First: 1, Last: 3},
271 },
272 b: parser.Rule{
273 Lines: diags.LineRange{First: 2, Last: 3},
274 },
275 same: false,
276 },
277 // Different line ranges - Last line differs
278 {
279 a: parser.Rule{
280 Lines: diags.LineRange{First: 1, Last: 3},
281 },
282 b: parser.Rule{
283 Lines: diags.LineRange{First: 1, Last: 4},
284 },
285 same: false,
286 },
287 // Different errors
288 {
289 a: parser.Rule{
290 Error: parser.ParseError{Line: 1, Err: nil},
291 },
292 b: parser.Rule{
293 Error: parser.ParseError{Line: 2, Err: nil},
294 },
295 same: false,
296 },
297 }
298
299 for i, tc := range testCases {
300 t.Run(strconv.Itoa(i+1), func(t *testing.T) {
301 same := tc.a.IsSame(tc.b)
302 require.Equal(t, tc.same, same)
303 })
304 }
305}
306
307func TestRuleLastKey(t *testing.T) {
308 type testCaseT struct {
309 expectedKey string
310 rule parser.Rule
311 expectedLine int
312 }
313
314 testCases := []testCaseT{
315 // Recording rule - record is last key
316 {
317 rule: newMustRule("- record: foo\n expr: bob\n"),
318 expectedKey: "bob",
319 expectedLine: 2,
320 },
321 // Recording rule - expr is last (comes after record in YAML)
322 {
323 rule: newMustRule("- expr: bob\n record: foo\n"),
324 expectedKey: "foo",
325 expectedLine: 2,
326 },
327 // Recording rule with labels - label key is last
328 {
329 rule: newMustRule("- record: foo\n expr: bob\n labels:\n severity: critical\n"),
330 expectedKey: "severity",
331 expectedLine: 4,
332 },
333 // Alerting rule - alert is last key
334 {
335 rule: newMustRule("- alert: foo\n expr: bob\n"),
336 expectedKey: "bob",
337 expectedLine: 2,
338 },
339 // Alerting rule with for
340 {
341 rule: newMustRule("- alert: foo\n expr: bob\n for: 5m\n"),
342 expectedKey: "5m",
343 expectedLine: 3,
344 },
345 // Alerting rule with keep_firing_for
346 {
347 rule: newMustRule("- alert: foo\n expr: bob\n keep_firing_for: 10m\n"),
348 expectedKey: "10m",
349 expectedLine: 3,
350 },
351 // Alerting rule with labels
352 {
353 rule: newMustRule("- alert: foo\n expr: bob\n labels:\n severity: critical\n"),
354 expectedKey: "severity",
355 expectedLine: 4,
356 },
357 // Alerting rule with annotations
358 {
359 rule: newMustRule("- alert: foo\n expr: bob\n annotations:\n summary: test\n"),
360 expectedKey: "summary",
361 expectedLine: 4,
362 },
363 // Alerting rule with both labels and annotations - annotations last
364 {
365 rule: newMustRule("- alert: foo\n expr: bob\n labels:\n severity: critical\n annotations:\n summary: test\n"),
366 expectedKey: "summary",
367 expectedLine: 6,
368 },
369 // Alerting rule with for, keep_firing_for, labels, annotations - annotations last
370 {
371 rule: newMustRule("- alert: foo\n expr: bob\n for: 5m\n keep_firing_for: 10m\n labels:\n severity: critical\n annotations:\n summary: test\n"),
372 expectedKey: "summary",
373 expectedLine: 8,
374 },
375 }
376
377 for i, tc := range testCases {
378 t.Run(strconv.Itoa(i+1), func(t *testing.T) {
379 lastKey := tc.rule.LastKey()
380 require.NotNil(t, lastKey)
381 require.Equal(t, tc.expectedKey, lastKey.Value)
382 require.Equal(t, tc.expectedLine, lastKey.Pos.Lines().Last)
383 })
384 }
385}
386
387func TestAlertingRuleIsIdenticalNilCases(t *testing.T) {
388 // Both nil
389 var nilA *parser.AlertingRule
390 var nilB *parser.AlertingRule
391 require.True(t, nilA.IsIdentical(nilB))
392
393 // One nil, one not
394 rule := newMustRule("- alert: foo\n expr: bob\n")
395 require.False(t, nilA.IsIdentical(rule.AlertingRule))
396 require.False(t, rule.AlertingRule.IsIdentical(nilB))
397}
398
399func TestRecordingRuleIsIdenticalNilCases(t *testing.T) {
400 // Both nil
401 var nilA *parser.RecordingRule
402 var nilB *parser.RecordingRule
403 require.True(t, nilA.IsIdentical(nilB))
404
405 // One nil, one not
406 rule := newMustRule("- record: foo\n expr: bob\n")
407 require.False(t, nilA.IsIdentical(rule.RecordingRule))
408 require.False(t, rule.RecordingRule.IsIdentical(nilB))
409}
410
411func TestYamlNodeIsIdentical(t *testing.T) {
412 type testCaseT struct {
413 a *parser.YamlNode
414 b *parser.YamlNode
415 equal bool
416 }
417
418 testCases := []testCaseT{
419 // Both nil
420 {a: nil, b: nil, equal: true},
421 // One nil, one not
422 {a: nil, b: &parser.YamlNode{Value: "foo"}, equal: false},
423 {a: &parser.YamlNode{Value: "foo"}, b: nil, equal: false},
424 // Both non-nil, same value
425 {a: &parser.YamlNode{Value: "foo"}, b: &parser.YamlNode{Value: "foo"}, equal: true},
426 // Both non-nil, different value
427 {a: &parser.YamlNode{Value: "foo"}, b: &parser.YamlNode{Value: "bar"}, equal: false},
428 }
429
430 for i, tc := range testCases {
431 t.Run(strconv.Itoa(i+1), func(t *testing.T) {
432 equal := tc.a.IsIdentical(tc.b)
433 require.Equal(t, tc.equal, equal)
434 })
435 }
436}
437
438func TestMergeMaps(t *testing.T) {
439 type testCaseT struct {
440 a *parser.YamlMap
441 b *parser.YamlMap
442 output *parser.YamlMap
443 }
444
445 testCases := []testCaseT{
446 {
447 a: nil,
448 b: nil,
449 output: nil,
450 },
451 {
452 a: &parser.YamlMap{},
453 b: nil,
454 output: &parser.YamlMap{},
455 },
456 {
457 a: nil,
458 b: &parser.YamlMap{},
459 output: &parser.YamlMap{},
460 },
461 {
462 a: &parser.YamlMap{},
463 b: &parser.YamlMap{},
464 output: &parser.YamlMap{},
465 },
466 {
467 a: &parser.YamlMap{
468 Key: &parser.YamlNode{Value: "a"},
469 Items: []*parser.YamlKeyValue{
470 {
471 Key: &parser.YamlNode{Value: "1"},
472 },
473 {
474 Key: &parser.YamlNode{Value: "2"},
475 },
476 },
477 },
478 b: &parser.YamlMap{
479 Key: &parser.YamlNode{Value: "b"},
480 Items: []*parser.YamlKeyValue{
481 {
482 Key: &parser.YamlNode{Value: "2"},
483 },
484 {
485 Key: &parser.YamlNode{Value: "3"},
486 },
487 },
488 },
489 output: &parser.YamlMap{
490 Key: &parser.YamlNode{Value: "a"},
491 Items: []*parser.YamlKeyValue{
492 {
493 Key: &parser.YamlNode{Value: "1"},
494 },
495 {
496 Key: &parser.YamlNode{Value: "2"},
497 },
498 {
499 Key: &parser.YamlNode{Value: "3"},
500 },
501 },
502 },
503 },
504 }
505
506 for i, tc := range testCases {
507 t.Run(strconv.Itoa(i+1), func(t *testing.T) {
508 output := parser.MergeMaps(tc.a, tc.b)
509 require.Equal(t, tc.output, output)
510 })
511 }
512}
513