cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.81.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/discovery/git_branch_test.go

1919lines · modecode

1package discovery_test
2
3import (
4 "encoding/json"
5 "errors"
6 "fmt"
7 "io/fs"
8 "os"
9 "regexp"
10 "strings"
11 "syscall"
12 "testing"
13
14 "github.com/google/go-cmp/cmp"
15 "github.com/stretchr/testify/require"
16
17 "github.com/cloudflare/pint/internal/diags"
18 "github.com/cloudflare/pint/internal/discovery"
19 "github.com/cloudflare/pint/internal/git"
20 "github.com/cloudflare/pint/internal/parser"
21)
22
23func gitCommit(t *testing.T, message string) {
24 t.Setenv("GIT_AUTHOR_NAME", "pint")
25 t.Setenv("GIT_AUTHOR_EMAIL", "pint@example.com")
26 t.Setenv("GIT_COMMITTER_NAME", "pint")
27 t.Setenv("GIT_COMMITTER_EMAIL", "pint")
28 _, err := git.RunGit("commit", "-am", "commit "+message)
29 require.NoError(t, err, "git commit %s", message)
30}
31
32func commitFile(t *testing.T, path, content, message string) {
33 err := os.WriteFile(path, []byte(content), 0o644)
34 require.NoError(t, err, "write %s", path)
35 _, err = git.RunGit("add", path)
36 require.NoError(t, err, "git add")
37 gitCommit(t, message)
38}
39
40func TestGitBranchFinder(t *testing.T) {
41 includeAll := []*regexp.Regexp{regexp.MustCompile(".*")}
42
43 mustParse := func(offset int, s string) parser.Rule {
44 p := parser.NewParser(parser.DefaultOptions)
45 file := p.Parse(strings.NewReader(strings.Repeat("\n", offset) + s))
46 if file.Error.Err != nil {
47 panic(fmt.Sprintf("failed to parse rule:\n---\n%s\n---\nerror: %s", s, file.Error))
48 }
49 if len(file.Groups) != 1 {
50 panic(fmt.Sprintf("wrong number of groups returned: %d\n---\n%s\n---", len(file.Groups), s))
51 }
52 if len(file.Groups[0].Rules) != 1 {
53 panic(fmt.Sprintf("wrong number of rules returned: %d\n---\n%s\n---", len(file.Groups[0].Rules), s))
54 }
55 return file.Groups[0].Rules[0]
56 }
57
58 type setupFn func(t *testing.T)
59
60 type testCaseT struct {
61 setup setupFn
62 title string
63 err string
64 entries []*discovery.Entry
65 allEntries []*discovery.Entry
66 finder discovery.GitBranchFinder
67 }
68
69 testCases := []testCaseT{
70 {
71 title: "git list PR commits error - main",
72 setup: func(_ *testing.T) {},
73 finder: discovery.NewGitBranchFinder(
74 func(args ...string) ([]byte, error) {
75 return nil, fmt.Errorf("mock git error: %v", args)
76 },
77 git.NewPathFilter(includeAll, nil, nil),
78 "main",
79 50,
80 parser.DefaultOptions,
81 nil,
82 ),
83 entries: nil,
84 err: "failed to get the list of modified files from git: mock git error: [log --reverse --no-merges --first-parent --format=%H --name-status main..HEAD]",
85 },
86 {
87 title: "git list PR commits error - master",
88 setup: func(_ *testing.T) {},
89 finder: discovery.NewGitBranchFinder(
90 func(args ...string) ([]byte, error) {
91 return nil, fmt.Errorf("mock git error: %v", args)
92 },
93 git.NewPathFilter(includeAll, nil, nil),
94 "master",
95 50,
96 parser.DefaultOptions,
97 nil,
98 ),
99 entries: nil,
100 err: "failed to get the list of modified files from git: mock git error: [log --reverse --no-merges --first-parent --format=%H --name-status master..HEAD]",
101 },
102 {
103 title: "too many commits",
104 setup: func(t *testing.T) {
105 commitFile(t, "rules.yml", "# v1\n", "v1")
106
107 _, err := git.RunGit("checkout", "-b", "v2")
108 require.NoError(t, err, "git checkout v2")
109
110 commitFile(t, "rules.yml", "# v2-1\n", "v2-1")
111 commitFile(t, "rules.yml", "# v2-2\n", "v2-2")
112 commitFile(t, "rules.yml", "# v2-3\n", "v2-3")
113 commitFile(t, "rules.yml", "# v2-4\n", "v2-4")
114 },
115 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 3, parser.DefaultOptions, nil),
116 entries: nil,
117 err: "number of commits to check (4) is higher than maxCommits (3), exiting",
118 },
119 {
120 title: "git list modified files error",
121 setup: func(_ *testing.T) {},
122 finder: discovery.NewGitBranchFinder(
123 func(args ...string) ([]byte, error) {
124 switch strings.Join(args, " ") {
125 case "log --format=%H --no-abbrev-commit --reverse main..HEAD":
126 return []byte("c1\nc2\nc3\nc4\n"), nil
127 default:
128 return nil, fmt.Errorf("mock git error: %v", args)
129 }
130 },
131 git.NewPathFilter(includeAll, nil, nil),
132 "main",
133 4,
134 parser.DefaultOptions,
135 nil,
136 ),
137 entries: nil,
138 err: "failed to get the list of modified files from git: mock git error: [log --reverse --no-merges --first-parent --format=%H --name-status main..HEAD]",
139 },
140 {
141 title: "git get commit message error",
142 setup: func(_ *testing.T) {},
143 finder: discovery.NewGitBranchFinder(
144 func(args ...string) ([]byte, error) {
145 switch strings.Join(args, " ") {
146 case "log --reverse --no-merges --first-parent --format=%H --name-status main..HEAD":
147 return []byte("c1\nA\trules.yml\n"), nil
148 default:
149 return nil, fmt.Errorf("mock git error: %v", args)
150 }
151 },
152 git.NewPathFilter(includeAll, nil, nil),
153 "main",
154 4,
155 parser.DefaultOptions,
156 nil,
157 ),
158 entries: nil,
159 err: "failed to get commit message for c1: mock git error: [show -s --format=%B c1]",
160 },
161 {
162 title: "git diff error",
163 setup: func(_ *testing.T) {},
164 finder: discovery.NewGitBranchFinder(
165 func(args ...string) ([]byte, error) {
166 switch strings.Join(args, " ") {
167 case "log --reverse --no-merges --first-parent --format=%H --name-status main..HEAD":
168 return []byte("c1\nA\trules.yml\n"), nil
169 case "ls-tree c1^ rules.yml":
170 return []byte("100644 blob c0\trules.yml"), nil
171 case "ls-tree c1 rules.yml":
172 return []byte("100644 blob c1\trules.yml"), nil
173 case "show -s --format=%B c1":
174 return []byte(""), nil
175 case "cat-file blob c0":
176 return []byte("# old\n"), nil
177 case "cat-file blob c1":
178 return []byte("# new\n"), nil
179 default:
180 return nil, fmt.Errorf("mock git error: %v", args)
181 }
182 },
183 git.NewPathFilter(includeAll, nil, nil),
184 "main",
185 4,
186 parser.DefaultOptions,
187 nil,
188 ),
189 entries: nil,
190 err: "failed to run git diff for rules.yml: git diff for rules.yml: mock git error: [diff -M c1^..c1 -- rules.yml rules.yml]",
191 },
192 {
193 title: "no rules in file",
194 setup: func(t *testing.T) {
195 commitFile(t, "rules.yml", "# v1\n", "v1")
196
197 _, err := git.RunGit("checkout", "-b", "v2")
198 require.NoError(t, err, "git checkout v2")
199
200 commitFile(t, "rules.yml", "# v2\n", "v2")
201 },
202 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
203 entries: nil,
204 },
205 {
206 title: "no rule changes",
207 setup: func(t *testing.T) {
208 commitFile(t, "rules.yml", `
209groups:
210- name: v1
211 rules:
212 - record: up:count
213 expr: count(up == 1)
214`, "v1")
215
216 _, err := git.RunGit("checkout", "-b", "v2")
217 require.NoError(t, err, "git checkout v2")
218
219 commitFile(t, "rules.yml", `
220groups:
221- name: v2
222 rules:
223 - record: up:count
224 expr: count(up == 1)
225`, "v2")
226 },
227 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
228 entries: []*discovery.Entry{
229 {
230 State: discovery.Noop,
231 Path: discovery.Path{
232 Name: "rules.yml",
233 SymlinkTarget: "rules.yml",
234 },
235 Changes: &discovery.Changes{
236 Lines: git.LineNumbers{
237 {Before: 3, After: 3, Modified: true},
238 },
239 },
240 Rule: mustParse(4, " - record: up:count\n expr: count(up == 1)\n"),
241 },
242 },
243 },
244 {
245 title: "rule changed - strict",
246 setup: func(t *testing.T) {
247 commitFile(t, "rules.yml", `
248groups:
249- name: v1
250 rules:
251 - record: up:count
252 expr: count(up)
253`, "v1")
254
255 _, err := git.RunGit("checkout", "-b", "v2")
256 require.NoError(t, err, "git checkout v2")
257
258 commitFile(t, "rules.yml", `
259groups:
260- name: v2
261 rules:
262 - record: up:count
263 expr: count(up == 1)
264`, "v2")
265 },
266 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
267 entries: []*discovery.Entry{
268 {
269 State: discovery.Modified,
270 Path: discovery.Path{
271 Name: "rules.yml",
272 SymlinkTarget: "rules.yml",
273 },
274 Changes: &discovery.Changes{
275 Lines: []git.LineNumber{
276 {Before: 3, After: 3, Modified: true},
277 {Before: 6, After: 6, Modified: true},
278 },
279 },
280 Rule: mustParse(4, " - record: up:count\n expr: count(up == 1)\n"),
281 },
282 },
283 },
284 {
285 title: "rule changed - relaxed",
286 setup: func(t *testing.T) {
287 commitFile(t, "rules.yml", `
288- record: up:count
289 expr: count(up)
290`, "v1")
291
292 _, err := git.RunGit("checkout", "-b", "v2")
293 require.NoError(t, err, "git checkout v2")
294
295 commitFile(t, "rules.yml", `
296- record: up:count
297 expr: count(up == 1)
298`, "v2")
299 },
300 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
301 entries: []*discovery.Entry{
302 {
303 State: discovery.Modified,
304 Path: discovery.Path{
305 Name: "rules.yml",
306 SymlinkTarget: "rules.yml",
307 },
308 Changes: &discovery.Changes{
309 Lines: []git.LineNumber{
310 {Before: 3, After: 3, Modified: true},
311 },
312 },
313 Rule: mustParse(1, "- record: up:count\n expr: count(up == 1)\n"),
314 },
315 },
316 },
317 {
318 title: "rule changed - empty include list",
319 setup: func(t *testing.T) {
320 commitFile(t, "rules.yml", `
321- record: up:count
322 expr: count(up)
323`, "v1")
324
325 _, err := git.RunGit("checkout", "-b", "v2")
326 require.NoError(t, err, "git checkout v2")
327
328 commitFile(t, "rules.yml", `
329- record: up:count
330 expr: count(up == 1)
331`, "v2")
332 },
333 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(nil, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
334 entries: []*discovery.Entry{
335 {
336 State: discovery.Modified,
337 Path: discovery.Path{
338 Name: "rules.yml",
339 SymlinkTarget: "rules.yml",
340 },
341 Changes: &discovery.Changes{
342 Lines: []git.LineNumber{
343 {Before: 3, After: 3, Modified: true},
344 },
345 },
346 Rule: mustParse(1, "- record: up:count\n expr: count(up == 1)\n"),
347 },
348 },
349 },
350 {
351 title: "rule changed but file excluded",
352 setup: func(t *testing.T) {
353 commitFile(t, "rules.yml", `
354groups:
355- name: v1
356 rules:
357 - record: up:count
358 expr: count(up)
359`, "v1")
360
361 _, err := git.RunGit("checkout", "-b", "v2")
362 require.NoError(t, err, "git checkout v2")
363
364 commitFile(t, "rules.yml", `
365groups:
366- name: v2
367 rules:
368 - record: up:count
369 expr: count(up == 1)
370`, "v2")
371 },
372 finder: discovery.NewGitBranchFinder(
373 git.RunGit,
374 git.NewPathFilter([]*regexp.Regexp{regexp.MustCompile("^foo#")}, nil, nil),
375 "main",
376 4,
377 parser.DefaultOptions,
378 nil,
379 ),
380 entries: nil,
381 },
382 {
383 title: "rule changed - [skip ci]",
384 setup: func(t *testing.T) {
385 commitFile(t, "rules.yml", `
386groups:
387- name: v1
388 rules:
389 - record: up:count
390 expr: count(up)
391`, "v1")
392
393 _, err := git.RunGit("checkout", "-b", "v2")
394 require.NoError(t, err, "git checkout v2")
395
396 commitFile(t, "rules.yml", `
397groups:
398- name: v2
399 rules:
400 - record: up:count
401 expr: count(up == 1)
402`, "v2\nskip this commit\n[skip ci]\n")
403 },
404 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
405 entries: nil,
406 },
407 {
408 title: "rule changed - [no ci]",
409 setup: func(t *testing.T) {
410 commitFile(t, "rules.yml", `
411groups:
412- name: v1
413 rules:
414 - record: up:count
415 expr: count(up)
416`, "v1")
417
418 _, err := git.RunGit("checkout", "-b", "v2")
419 require.NoError(t, err, "git checkout v2")
420
421 commitFile(t, "rules.yml", `
422groups:
423- name: v2
424 rules:
425 - record: up:count
426 expr: count(up == 1)
427`, "v2\nskip this commit\n[no ci]\n")
428 },
429 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
430 entries: nil,
431 },
432 {
433 title: "rule symlinked",
434 setup: func(t *testing.T) {
435 commitFile(t, "rules.yml", `
436- record: up:count
437 expr: count(up)
438`, "v1")
439
440 _, err := git.RunGit("checkout", "-b", "v2")
441 require.NoError(t, err, "git checkout v2")
442
443 err = os.Symlink("rules.yml", "symlink.yml")
444 require.NoError(t, err, "symlink")
445 _, err = git.RunGit("add", "symlink.yml")
446 require.NoError(t, err, "git add")
447 gitCommit(t, "v2")
448 },
449 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
450 entries: []*discovery.Entry{
451 {
452 State: discovery.Added,
453 Path: discovery.Path{
454 Name: "symlink.yml",
455 SymlinkTarget: "rules.yml",
456 },
457 Changes: &discovery.Changes{
458 Lines: git.MakeLineRangeFromTo(1, 3, git.LinesAfter),
459 },
460 Rule: mustParse(1, "- record: up:count\n expr: count(up)\n"),
461 },
462 },
463 },
464 {
465 title: "rule changed - multiple rules",
466 setup: func(t *testing.T) {
467 commitFile(t, "rules.yml", `
468groups:
469- name: v1
470 rules:
471 - record: up:count:1
472 expr: count(up)
473 - record: up:count:2
474 expr: count(up)
475 - record: up:count:3
476 expr: count(up)
477`, "v1")
478
479 _, err := git.RunGit("checkout", "-b", "v2")
480 require.NoError(t, err, "git checkout v2")
481
482 commitFile(t, "rules.yml", `
483groups:
484- name: v2
485 rules:
486 - record: up:count:1
487 expr: count(up == 1)
488 - record: up:count:2a
489 expr: count(up)
490 - record: up:count:3
491 expr: count(up)
492 - record: up:count:4
493 expr: count(up)
494`, "v2")
495 },
496 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
497 entries: []*discovery.Entry{
498 {
499 State: discovery.Modified,
500 Path: discovery.Path{
501 Name: "rules.yml",
502 SymlinkTarget: "rules.yml",
503 },
504 Changes: &discovery.Changes{
505 Lines: []git.LineNumber{
506 {Before: 3, After: 3, Modified: true},
507 {Before: 6, After: 6, Modified: true},
508 {Before: 7, After: 7, Modified: true},
509 {Before: 0, After: 11, Modified: true},
510 {Before: 0, After: 12, Modified: true},
511 },
512 },
513 Rule: mustParse(4, " - record: up:count:1\n expr: count(up == 1)\n"),
514 },
515 {
516 State: discovery.Added,
517 Path: discovery.Path{
518 Name: "rules.yml",
519 SymlinkTarget: "rules.yml",
520 },
521 Changes: &discovery.Changes{
522 Lines: []git.LineNumber{
523 {Before: 3, After: 3, Modified: true},
524 {Before: 6, After: 6, Modified: true},
525 {Before: 7, After: 7, Modified: true},
526 {Before: 0, After: 11, Modified: true},
527 {Before: 0, After: 12, Modified: true},
528 },
529 },
530 Rule: mustParse(6, " - record: up:count:2a\n expr: count(up)\n"),
531 },
532 {
533 State: discovery.Noop,
534 Path: discovery.Path{
535 Name: "rules.yml",
536 SymlinkTarget: "rules.yml",
537 },
538 Changes: &discovery.Changes{
539 Lines: []git.LineNumber{
540 {Before: 3, After: 3, Modified: true},
541 {Before: 6, After: 6, Modified: true},
542 {Before: 7, After: 7, Modified: true},
543 {Before: 0, After: 11, Modified: true},
544 {Before: 0, After: 12, Modified: true},
545 },
546 },
547 Rule: mustParse(8, " - record: up:count:3\n expr: count(up)\n"),
548 },
549 {
550 State: discovery.Added,
551 Path: discovery.Path{
552 Name: "rules.yml",
553 SymlinkTarget: "rules.yml",
554 },
555 Changes: &discovery.Changes{
556 Lines: []git.LineNumber{
557 {Before: 3, After: 3, Modified: true},
558 {Before: 6, After: 6, Modified: true},
559 {Before: 7, After: 7, Modified: true},
560 {Before: 0, After: 11, Modified: true},
561 {Before: 0, After: 12, Modified: true},
562 },
563 },
564 Rule: mustParse(10, " - record: up:count:4\n expr: count(up)\n"),
565 },
566 {
567 State: discovery.Removed,
568 Path: discovery.Path{
569 Name: "rules.yml",
570 SymlinkTarget: "rules.yml",
571 },
572 Changes: &discovery.Changes{
573 Lines: []git.LineNumber{
574 {Before: 3, After: 3, Modified: true},
575 {Before: 6, After: 6, Modified: true},
576 {Before: 7, After: 7, Modified: true},
577 {Before: 0, After: 11, Modified: true},
578 {Before: 0, After: 12, Modified: true},
579 },
580 },
581 Rule: mustParse(6, " - record: up:count:2\n expr: count(up)\n"),
582 },
583 },
584 },
585 {
586 title: "rule changed - added extra line",
587 setup: func(t *testing.T) {
588 commitFile(t, "rules.yml", `
589- alert: rule1
590 expr: sum(foo) by(job)
591- alert: rule2
592 expr: sum(foo) by(job)
593 for: 0s
594`, "v1")
595
596 _, err := git.RunGit("checkout", "-b", "v2")
597 require.NoError(t, err, "git checkout v2")
598
599 commitFile(t, "rules.yml", `
600- alert: rule1
601 expr: sum(foo) by(job)
602 for: 0s
603- alert: rule2
604 expr: sum(foo) by(job)
605 for: 0s
606`, "v2")
607 },
608 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
609 entries: []*discovery.Entry{
610 {
611 State: discovery.Modified,
612 Path: discovery.Path{
613 Name: "rules.yml",
614 SymlinkTarget: "rules.yml",
615 },
616 Changes: &discovery.Changes{
617 Lines: []git.LineNumber{
618 {Before: 0, After: 4, Modified: true},
619 {Before: 4, After: 5, Modified: false},
620 {Before: 5, After: 6, Modified: false},
621 {Before: 6, After: 7, Modified: false},
622 },
623 },
624 Rule: mustParse(1, "- alert: rule1\n expr: sum(foo) by(job)\n for: 0s\n"),
625 },
626 {
627 State: discovery.Noop,
628 Path: discovery.Path{
629 Name: "rules.yml",
630 SymlinkTarget: "rules.yml",
631 },
632 Changes: &discovery.Changes{
633 Lines: []git.LineNumber{
634 {Before: 0, After: 4, Modified: true},
635 {Before: 4, After: 5, Modified: false},
636 {Before: 5, After: 6, Modified: false},
637 {Before: 6, After: 7, Modified: false},
638 },
639 },
640 Rule: mustParse(4, "- alert: rule2\n expr: sum(foo) by(job)\n for: 0s\n"),
641 },
642 },
643 },
644 {
645 title: "rule removed - head",
646 setup: func(t *testing.T) {
647 commitFile(t, "rules.yml", `
648- alert: rule1
649 expr: sum(foo) by(job)
650- alert: rule2
651 expr: sum(foo) by(job)
652`, "v1")
653
654 _, err := git.RunGit("checkout", "-b", "v2")
655 require.NoError(t, err, "git checkout v2")
656
657 commitFile(t, "rules.yml", `
658- alert: rule2
659 expr: sum(foo) by(job)
660`, "v2")
661 },
662 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
663 entries: []*discovery.Entry{
664 {
665 State: discovery.Noop,
666 Path: discovery.Path{
667 Name: "rules.yml",
668 SymlinkTarget: "rules.yml",
669 },
670 Changes: &discovery.Changes{
671 Lines: []git.LineNumber{
672 {Before: 2, After: 0},
673 {Before: 3, After: 0},
674 {Before: 4, After: 2},
675 {Before: 5, After: 3},
676 },
677 },
678 Rule: mustParse(1, "- alert: rule2\n expr: sum(foo) by(job)\n"),
679 },
680 {
681 State: discovery.Removed,
682 Path: discovery.Path{
683 Name: "rules.yml",
684 SymlinkTarget: "rules.yml",
685 },
686 Changes: &discovery.Changes{
687 Lines: []git.LineNumber{
688 {Before: 2, After: 0},
689 {Before: 3, After: 0},
690 {Before: 4, After: 2},
691 {Before: 5, After: 3},
692 },
693 },
694 Rule: mustParse(1, "- alert: rule1\n expr: sum(foo) by(job)\n"),
695 },
696 },
697 },
698 {
699 title: "rule removed - tail",
700 setup: func(t *testing.T) {
701 commitFile(t, "rules.yml", `
702- alert: rule1
703 expr: sum(foo) by(job)
704- alert: rule2
705 expr: sum(foo) by(job)
706`, "v1")
707
708 _, err := git.RunGit("checkout", "-b", "v2")
709 require.NoError(t, err, "git checkout v2")
710
711 commitFile(t, "rules.yml", `
712- alert: rule1
713 expr: sum(foo) by(job)
714`, "v2")
715 },
716 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
717 entries: []*discovery.Entry{
718 {
719 State: discovery.Noop,
720 Path: discovery.Path{
721 Name: "rules.yml",
722 SymlinkTarget: "rules.yml",
723 },
724 Changes: &discovery.Changes{
725 Lines: git.MakeLineRangeFromTo(4, 5, git.LinesBefore),
726 },
727 Rule: mustParse(1, "- alert: rule1\n expr: sum(foo) by(job)\n"),
728 },
729 {
730 State: discovery.Removed,
731 Path: discovery.Path{
732 Name: "rules.yml",
733 SymlinkTarget: "rules.yml",
734 },
735 Changes: &discovery.Changes{
736 Lines: git.MakeLineRangeFromTo(4, 5, git.LinesBefore),
737 },
738 Rule: mustParse(3, "- alert: rule2\n expr: sum(foo) by(job)\n"),
739 },
740 },
741 },
742 {
743 title: "rule removed - middle",
744 setup: func(t *testing.T) {
745 commitFile(t, "rules.yml", `
746- alert: rule1
747 expr: sum(foo) by(job)
748- alert: rule2
749 expr: sum(foo) by(job)
750- alert: rule3
751 expr: sum(foo) by(job)
752`, "v1")
753
754 _, err := git.RunGit("checkout", "-b", "v2")
755 require.NoError(t, err, "git checkout v2")
756
757 commitFile(t, "rules.yml", `
758- alert: rule1
759 expr: sum(foo) by(job)
760- alert: rule3
761 expr: sum(foo) by(job)
762`, "v2")
763 },
764 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
765 entries: []*discovery.Entry{
766 {
767 State: discovery.Noop,
768 Path: discovery.Path{
769 Name: "rules.yml",
770 SymlinkTarget: "rules.yml",
771 },
772 Changes: &discovery.Changes{
773 Lines: []git.LineNumber{
774 {Before: 4, After: 0},
775 {Before: 5, After: 0},
776 {Before: 6, After: 4},
777 {Before: 7, After: 5},
778 },
779 },
780 Rule: mustParse(1, "- alert: rule1\n expr: sum(foo) by(job)\n"),
781 },
782 {
783 State: discovery.Noop,
784 Path: discovery.Path{
785 Name: "rules.yml",
786 SymlinkTarget: "rules.yml",
787 },
788 Changes: &discovery.Changes{
789 Lines: []git.LineNumber{
790 {Before: 4, After: 0},
791 {Before: 5, After: 0},
792 {Before: 6, After: 4},
793 {Before: 7, After: 5},
794 },
795 },
796 Rule: mustParse(3, "- alert: rule3\n expr: sum(foo) by(job)\n"),
797 },
798 {
799 State: discovery.Removed,
800 Path: discovery.Path{
801 Name: "rules.yml",
802 SymlinkTarget: "rules.yml",
803 },
804 Changes: &discovery.Changes{
805 Lines: []git.LineNumber{
806 {Before: 4, After: 0},
807 {Before: 5, After: 0},
808 {Before: 6, After: 4},
809 {Before: 7, After: 5},
810 },
811 },
812 Rule: mustParse(3, "- alert: rule2\n expr: sum(foo) by(job)\n"),
813 },
814 },
815 },
816 {
817 title: "rule fixed",
818 setup: func(t *testing.T) {
819 commitFile(t, "rules.yml", `
820groups:
821- name: v1
822 rules:
823 - record: up:count
824 expr: count(up)
825 expr: sum(up)
826`, "v1")
827
828 _, err := git.RunGit("checkout", "-b", "v2")
829 require.NoError(t, err, "git checkout v2")
830
831 commitFile(t, "rules.yml", `
832groups:
833- name: v2
834 rules:
835 - record: up:count
836 expr: count(up)
837`, "v2")
838 },
839 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
840 entries: []*discovery.Entry{
841 {
842 State: discovery.Added,
843 Path: discovery.Path{
844 Name: "rules.yml",
845 SymlinkTarget: "rules.yml",
846 },
847
848 Changes: &discovery.Changes{
849 Lines: []git.LineNumber{
850 {Before: 3, After: 3, Modified: true},
851 {Before: 7, After: 0},
852 },
853 },
854 Rule: mustParse(4, " - record: up:count\n expr: count(up)\n"),
855 },
856 {
857 State: discovery.Removed,
858 Path: discovery.Path{
859 Name: "rules.yml",
860 SymlinkTarget: "rules.yml",
861 },
862 Changes: &discovery.Changes{
863 Lines: []git.LineNumber{
864 {Before: 3, After: 3, Modified: true},
865 {Before: 7, After: 0},
866 },
867 },
868 Rule: parser.Rule{
869 Lines: diags.LineRange{First: 5, Last: 7},
870 Error: parser.ParseError{
871 Line: 7,
872 Err: errors.New("duplicated expr key"),
873 },
874 },
875 },
876 },
877 },
878 {
879 title: "rules duplicated",
880 setup: func(t *testing.T) {
881 commitFile(t, "rules.yml", `
882- alert: rule1
883 expr: sum(foo) by(job)
884- alert: rule2
885 expr: sum(foo) by(job)
886`, "v1")
887
888 _, err := git.RunGit("checkout", "-b", "v2")
889 require.NoError(t, err, "git checkout v2")
890
891 commitFile(t, "rules.yml", `
892- alert: rule1
893 expr: sum(foo) by(job)
894- alert: rule2
895 expr: sum(foo) by(job)
896- alert: rule2
897 expr: sum(foo) by(job)
898- alert: rule1
899 expr: sum(foo) by(job)
900`, "v2")
901 },
902 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
903 entries: []*discovery.Entry{
904 {
905 State: discovery.Noop,
906 Path: discovery.Path{
907 Name: "rules.yml",
908 SymlinkTarget: "rules.yml",
909 },
910 Changes: &discovery.Changes{
911 Lines: []git.LineNumber{
912 {Before: 0, After: 6, Modified: true},
913 {Before: 0, After: 7, Modified: true},
914 {Before: 0, After: 8, Modified: true},
915 {Before: 0, After: 9, Modified: true},
916 },
917 },
918 Rule: mustParse(1, "- alert: rule1\n expr: sum(foo) by(job)\n"),
919 },
920 {
921 State: discovery.Noop,
922 Path: discovery.Path{
923 Name: "rules.yml",
924 SymlinkTarget: "rules.yml",
925 },
926 Changes: &discovery.Changes{
927 Lines: []git.LineNumber{
928 {Before: 0, After: 6, Modified: true},
929 {Before: 0, After: 7, Modified: true},
930 {Before: 0, After: 8, Modified: true},
931 {Before: 0, After: 9, Modified: true},
932 },
933 },
934 Rule: mustParse(3, "- alert: rule2\n expr: sum(foo) by(job)\n"),
935 },
936 {
937 State: discovery.Added,
938 Path: discovery.Path{
939 Name: "rules.yml",
940 SymlinkTarget: "rules.yml",
941 },
942 Changes: &discovery.Changes{
943 Lines: []git.LineNumber{
944 {Before: 0, After: 6, Modified: true},
945 {Before: 0, After: 7, Modified: true},
946 {Before: 0, After: 8, Modified: true},
947 {Before: 0, After: 9, Modified: true},
948 },
949 },
950 Rule: mustParse(5, "- alert: rule2\n expr: sum(foo) by(job)\n"),
951 },
952 {
953 State: discovery.Added,
954 Path: discovery.Path{
955 Name: "rules.yml",
956 SymlinkTarget: "rules.yml",
957 },
958 Changes: &discovery.Changes{
959 Lines: []git.LineNumber{
960 {Before: 0, After: 6, Modified: true},
961 {Before: 0, After: 7, Modified: true},
962 {Before: 0, After: 8, Modified: true},
963 {Before: 0, After: 9, Modified: true},
964 },
965 },
966 Rule: mustParse(7, "- alert: rule1\n expr: sum(foo) by(job)\n"),
967 },
968 },
969 },
970 {
971 title: "rules duplicated with different query",
972 setup: func(t *testing.T) {
973 commitFile(t, "rules.yml", `
974- alert: rule1
975 expr: sum(foo) by(job)
976`, "v1")
977
978 _, err := git.RunGit("checkout", "-b", "v2")
979 require.NoError(t, err, "git checkout v2")
980
981 commitFile(t, "rules.yml", `
982- alert: rule1
983 expr: up == 0
984- alert: rule1
985 expr: up == 1
986- alert: rule1
987 expr: up != 0
988- alert: rule2
989 expr: sum(foo) by(job)
990`, "v2")
991 },
992 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
993 entries: []*discovery.Entry{
994 {
995 State: discovery.Modified,
996 Path: discovery.Path{
997 Name: "rules.yml",
998 SymlinkTarget: "rules.yml",
999 },
1000 Changes: &discovery.Changes{
1001 Lines: []git.LineNumber{
1002 {Before: 0, After: 3, Modified: true},
1003 {Before: 0, After: 4, Modified: true},
1004 {Before: 0, After: 5, Modified: true},
1005 {Before: 0, After: 6, Modified: true},
1006 {Before: 0, After: 7, Modified: true},
1007 {Before: 0, After: 8, Modified: true},
1008 {Before: 3, After: 9},
1009 },
1010 },
1011 Rule: mustParse(1, "- alert: rule1\n expr: up == 0\n"),
1012 },
1013 {
1014 State: discovery.Added,
1015 Path: discovery.Path{
1016 Name: "rules.yml",
1017 SymlinkTarget: "rules.yml",
1018 },
1019 Changes: &discovery.Changes{
1020 Lines: []git.LineNumber{
1021 {Before: 0, After: 3, Modified: true},
1022 {Before: 0, After: 4, Modified: true},
1023 {Before: 0, After: 5, Modified: true},
1024 {Before: 0, After: 6, Modified: true},
1025 {Before: 0, After: 7, Modified: true},
1026 {Before: 0, After: 8, Modified: true},
1027 {Before: 3, After: 9},
1028 },
1029 },
1030 Rule: mustParse(3, "- alert: rule1\n expr: up == 1\n"),
1031 },
1032 {
1033 State: discovery.Added,
1034 Path: discovery.Path{
1035 Name: "rules.yml",
1036 SymlinkTarget: "rules.yml",
1037 },
1038 Changes: &discovery.Changes{
1039 Lines: []git.LineNumber{
1040 {Before: 0, After: 3, Modified: true},
1041 {Before: 0, After: 4, Modified: true},
1042 {Before: 0, After: 5, Modified: true},
1043 {Before: 0, After: 6, Modified: true},
1044 {Before: 0, After: 7, Modified: true},
1045 {Before: 0, After: 8, Modified: true},
1046 {Before: 3, After: 9},
1047 },
1048 },
1049 Rule: mustParse(5, "- alert: rule1\n expr: up != 0\n"),
1050 },
1051 {
1052 State: discovery.Added,
1053 Path: discovery.Path{
1054 Name: "rules.yml",
1055 SymlinkTarget: "rules.yml",
1056 },
1057 Changes: &discovery.Changes{
1058 Lines: []git.LineNumber{
1059 {Before: 0, After: 3, Modified: true},
1060 {Before: 0, After: 4, Modified: true},
1061 {Before: 0, After: 5, Modified: true},
1062 {Before: 0, After: 6, Modified: true},
1063 {Before: 0, After: 7, Modified: true},
1064 {Before: 0, After: 8, Modified: true},
1065 {Before: 3, After: 9},
1066 },
1067 },
1068 Rule: mustParse(7, "- alert: rule2\n expr: sum(foo) by(job)\n"),
1069 },
1070 },
1071 },
1072 {
1073 title: "rule changed - modified for and added extra lines",
1074 setup: func(t *testing.T) {
1075 commitFile(t, "rules.yml", `
1076- alert: rule1
1077 expr: sum(foo) by(job)
1078 for: 1s
1079- alert: rule2
1080 expr: sum(foo) by(job)
1081 for: 1s
1082`, "v1")
1083
1084 _, err := git.RunGit("checkout", "-b", "v2")
1085 require.NoError(t, err, "git checkout v2")
1086
1087 commitFile(t, "rules.yml", `
1088- alert: rule1
1089 expr: sum(foo) by(job)
1090 for: 1s
1091- alert: rule2
1092 expr: sum(foo) by(job)
1093 keep_firing_for: 5m
1094 for: 0s
1095 annotations:
1096 foo: bar
1097 labels:
1098 foo: bar
1099`, "v2")
1100 },
1101 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
1102 entries: []*discovery.Entry{
1103 {
1104 State: discovery.Noop,
1105 Path: discovery.Path{
1106 Name: "rules.yml",
1107 SymlinkTarget: "rules.yml",
1108 },
1109 Changes: &discovery.Changes{
1110 Lines: []git.LineNumber{
1111 {Before: 7, After: 7, Modified: true},
1112 {Before: 0, After: 8, Modified: true},
1113 {Before: 0, After: 9, Modified: true},
1114 {Before: 0, After: 10, Modified: true},
1115 {Before: 0, After: 11, Modified: true},
1116 {Before: 0, After: 12, Modified: true},
1117 },
1118 },
1119 Rule: mustParse(1, "- alert: rule1\n expr: sum(foo) by(job)\n for: 1s\n"),
1120 },
1121 {
1122 State: discovery.Modified,
1123 Path: discovery.Path{
1124 Name: "rules.yml",
1125 SymlinkTarget: "rules.yml",
1126 },
1127 Changes: &discovery.Changes{
1128 Lines: []git.LineNumber{
1129 {Before: 7, After: 7, Modified: true},
1130 {Before: 0, After: 8, Modified: true},
1131 {Before: 0, After: 9, Modified: true},
1132 {Before: 0, After: 10, Modified: true},
1133 {Before: 0, After: 11, Modified: true},
1134 {Before: 0, After: 12, Modified: true},
1135 },
1136 },
1137 Rule: mustParse(4, "- alert: rule2\n expr: sum(foo) by(job)\n keep_firing_for: 5m\n for: 0s\n annotations:\n foo: bar\n labels:\n foo: bar\n"),
1138 },
1139 },
1140 },
1141 {
1142 title: "rule file moved",
1143 setup: func(t *testing.T) {
1144 commitFile(t, "a.yml", `
1145- alert: rule
1146 expr: up == 0
1147`, "v1")
1148
1149 _, err := git.RunGit("checkout", "-b", "v2")
1150 require.NoError(t, err, "git checkout v2")
1151
1152 _, err = git.RunGit("mv", "a.yml", "b.yml")
1153 require.NoError(t, err, "git mv")
1154
1155 gitCommit(t, "v2")
1156 },
1157 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
1158 entries: []*discovery.Entry{
1159 {
1160 State: discovery.Moved,
1161 Path: discovery.Path{
1162 Name: "b.yml",
1163 SymlinkTarget: "b.yml",
1164 },
1165 Changes: &discovery.Changes{
1166 OldPath: "a.yml",
1167 // Renamed without content changes.
1168 Lines: git.LineNumbers{},
1169 },
1170 Rule: mustParse(1, "- alert: rule\n expr: up == 0\n"),
1171 },
1172 },
1173 },
1174 {
1175 title: "rule modified then the file renamed",
1176 setup: func(t *testing.T) {
1177 commitFile(t, "a.yml", `
1178- alert: rule
1179 # pint disable promql/series
1180 expr: up == 0
1181`, "v1")
1182
1183 _, err := git.RunGit("checkout", "-b", "v2")
1184 require.NoError(t, err, "git checkout v2")
1185
1186 commitFile(t, "a.yml", `
1187- alert: rule
1188 expr: up == 0
1189`, "v2")
1190
1191 _, err = git.RunGit("mv", "a.yml", "b.yml")
1192 require.NoError(t, err, "git mv")
1193
1194 gitCommit(t, "v3")
1195 },
1196 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
1197 entries: []*discovery.Entry{
1198 {
1199 State: discovery.Moved,
1200 Path: discovery.Path{
1201 Name: "b.yml",
1202 SymlinkTarget: "b.yml",
1203 },
1204 Changes: &discovery.Changes{
1205 OldPath: "a.yml",
1206 Lines: []git.LineNumber{
1207 {Before: 3, After: 0},
1208 {Before: 4, After: 3},
1209 },
1210 },
1211 Rule: mustParse(1, "- alert: rule\n expr: up == 0\n"),
1212 },
1213 },
1214 },
1215 {
1216 title: "rule file with symlink moved",
1217 setup: func(t *testing.T) {
1218 commitFile(t, "rules.yml", `
1219- alert: rule
1220 expr: up == 0
1221`, "v1")
1222
1223 err := os.Symlink("rules.yml", "symlink.yml")
1224 require.NoError(t, err, "symlink")
1225 _, err = git.RunGit("add", "symlink.yml")
1226 require.NoError(t, err, "git add")
1227 gitCommit(t, "v1")
1228
1229 _, err = git.RunGit("checkout", "-b", "v2")
1230 require.NoError(t, err, "git checkout v2")
1231
1232 _, err = git.RunGit("mv", "rules.yml", "new.yml")
1233 require.NoError(t, err, "git mv")
1234
1235 _, err = git.RunGit("rm", "-f", "symlink.yml")
1236 require.NoError(t, err, "git rm symlink")
1237
1238 gitCommit(t, "v2")
1239 },
1240 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
1241 entries: []*discovery.Entry{
1242 {
1243 State: discovery.Moved,
1244 Path: discovery.Path{
1245 Name: "new.yml",
1246 SymlinkTarget: "new.yml",
1247 },
1248 Changes: &discovery.Changes{
1249 OldPath: "rules.yml",
1250 // Renamed without content changes.
1251 Lines: git.LineNumbers{},
1252 },
1253 Rule: mustParse(1, "- alert: rule\n expr: up == 0\n"),
1254 },
1255 {
1256 State: discovery.Removed,
1257 Path: discovery.Path{
1258 Name: "symlink.yml",
1259 SymlinkTarget: "rules.yml",
1260 },
1261 Changes: &discovery.Changes{
1262 Lines: git.MakeLineRangeFromTo(1, 3, git.LinesBefore),
1263 },
1264 Rule: mustParse(1, "- alert: rule\n expr: up == 0\n"),
1265 },
1266 },
1267 },
1268 {
1269 title: "rule broken",
1270 setup: func(t *testing.T) {
1271 commitFile(t, "rules.yml", `
1272groups:
1273- name: v1
1274 rules:
1275 - record: rule1
1276 expr: sum(up)
1277 - record: rule2
1278 expr: sum(up)
1279 - record: rule3
1280 expr: sum(up)
1281 - record: rule4
1282 expr: sum(up)
1283`, "v1")
1284
1285 _, err := git.RunGit("checkout", "-b", "v2")
1286 require.NoError(t, err, "git checkout v2")
1287
1288 commitFile(t, "rules.yml", `
1289groups:
1290- name: v2
1291 rules:
1292 - record: rule1
1293 expr: sum(up)
1294 - record: rule2
1295 expr: sum(up)
1296 - record: rule3
1297 expr: sum(up)
1298 +
1299 sum(up)
1300 - record: rule4
1301 expr: sum(up)
1302 - record: rule5
1303 expr: sum(up)
1304`, "v2")
1305 },
1306 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
1307 entries: []*discovery.Entry{
1308 {
1309 State: discovery.Added,
1310 Path: discovery.Path{
1311 Name: "rules.yml",
1312 SymlinkTarget: "rules.yml",
1313 },
1314 Changes: &discovery.Changes{
1315 Lines: []git.LineNumber{
1316 {Before: 3, After: 3, Modified: true},
1317 {Before: 0, After: 11, Modified: true},
1318 {Before: 0, After: 12, Modified: true},
1319 {Before: 11, After: 13},
1320 {Before: 12, After: 14},
1321 {Before: 0, After: 15, Modified: true},
1322 {Before: 0, After: 16, Modified: true},
1323 },
1324 },
1325 PathError: parser.ParseError{
1326 Line: 11,
1327 Err: errors.New("could not find expected ':'"),
1328 },
1329 },
1330 },
1331 },
1332 {
1333 title: "file/disable comment removed",
1334 setup: func(t *testing.T) {
1335 commitFile(t, "rules.yml", `
1336# pint file/disable promql/series
1337
1338- alert: rule1
1339 expr: sum(foo) by(job)
1340- alert: rule2
1341 expr: sum(foo) by(job)
1342- alert: rule3
1343 expr: sum(foo) by(job)
1344`, "v1")
1345
1346 _, err := git.RunGit("checkout", "-b", "v2")
1347 require.NoError(t, err, "git checkout v2")
1348
1349 commitFile(t, "rules.yml", `
1350
1351
1352- alert: rule1
1353 expr: sum(foo) by(job)
1354- alert: rule2
1355 expr: sum(foo) by(job)
1356- alert: rule3
1357 expr: sum(foo) by(job)
1358`, "v2")
1359 },
1360 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
1361 entries: []*discovery.Entry{
1362 {
1363 State: discovery.Modified,
1364 Path: discovery.Path{
1365 Name: "rules.yml",
1366 SymlinkTarget: "rules.yml",
1367 },
1368 Changes: &discovery.Changes{
1369 Lines: []git.LineNumber{
1370 {Before: 2, After: 2, Modified: true},
1371 },
1372 },
1373 Rule: mustParse(3, "- alert: rule1\n expr: sum(foo) by(job)\n"),
1374 },
1375 {
1376 State: discovery.Modified,
1377 Path: discovery.Path{
1378 Name: "rules.yml",
1379 SymlinkTarget: "rules.yml",
1380 },
1381 Changes: &discovery.Changes{
1382 Lines: []git.LineNumber{
1383 {Before: 2, After: 2, Modified: true},
1384 },
1385 },
1386 Rule: mustParse(5, "- alert: rule2\n expr: sum(foo) by(job)\n"),
1387 },
1388 {
1389 State: discovery.Modified,
1390 Path: discovery.Path{
1391 Name: "rules.yml",
1392 SymlinkTarget: "rules.yml",
1393 },
1394 Changes: &discovery.Changes{
1395 Lines: []git.LineNumber{
1396 {Before: 2, After: 2, Modified: true},
1397 },
1398 },
1399 Rule: mustParse(7, "- alert: rule3\n expr: sum(foo) by(job)\n"),
1400 },
1401 },
1402 },
1403 {
1404 title: "add two dups",
1405 setup: func(t *testing.T) {
1406 commitFile(t, "rules.yml", `
1407- alert: rule1
1408 expr: sum(foo) by(job)
1409- alert: rule2
1410 expr: sum(foo) by(job)
1411- alert: rule3
1412 expr: sum(foo) by(job)
1413`, "v1")
1414
1415 _, err := git.RunGit("checkout", "-b", "v2")
1416 require.NoError(t, err, "git checkout v2")
1417
1418 commitFile(t, "rules.yml", `
1419- alert: rule1
1420 expr: sum(foo) by(job)
1421- alert: rule2
1422 expr: sum(foo) by(job)
1423- alert: rule3
1424 expr: sum(foo) by(job)
1425- alert: rule1
1426 expr: sum(foo) by(job)
1427- alert: rule1
1428 expr: sum(foo) by(job)
1429`, "v2")
1430 },
1431 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
1432 entries: []*discovery.Entry{
1433 {
1434 State: discovery.Noop,
1435 Path: discovery.Path{
1436 Name: "rules.yml",
1437 SymlinkTarget: "rules.yml",
1438 },
1439 Changes: &discovery.Changes{
1440 Lines: []git.LineNumber{
1441 {Before: 0, After: 8, Modified: true},
1442 {Before: 0, After: 9, Modified: true},
1443 {Before: 0, After: 10, Modified: true},
1444 {Before: 0, After: 11, Modified: true},
1445 },
1446 },
1447 Rule: mustParse(1, "- alert: rule1\n expr: sum(foo) by(job)\n"),
1448 },
1449 {
1450 State: discovery.Noop,
1451 Path: discovery.Path{
1452 Name: "rules.yml",
1453 SymlinkTarget: "rules.yml",
1454 },
1455 Changes: &discovery.Changes{
1456 Lines: []git.LineNumber{
1457 {Before: 0, After: 8, Modified: true},
1458 {Before: 0, After: 9, Modified: true},
1459 {Before: 0, After: 10, Modified: true},
1460 {Before: 0, After: 11, Modified: true},
1461 },
1462 },
1463 Rule: mustParse(3, "- alert: rule2\n expr: sum(foo) by(job)\n"),
1464 },
1465 {
1466 State: discovery.Noop,
1467 Path: discovery.Path{
1468 Name: "rules.yml",
1469 SymlinkTarget: "rules.yml",
1470 },
1471 Changes: &discovery.Changes{
1472 Lines: []git.LineNumber{
1473 {Before: 0, After: 8, Modified: true},
1474 {Before: 0, After: 9, Modified: true},
1475 {Before: 0, After: 10, Modified: true},
1476 {Before: 0, After: 11, Modified: true},
1477 },
1478 },
1479 Rule: mustParse(5, "- alert: rule3\n expr: sum(foo) by(job)\n"),
1480 },
1481 {
1482 State: discovery.Added,
1483 Path: discovery.Path{
1484 Name: "rules.yml",
1485 SymlinkTarget: "rules.yml",
1486 },
1487 Changes: &discovery.Changes{
1488 Lines: []git.LineNumber{
1489 {Before: 0, After: 8, Modified: true},
1490 {Before: 0, After: 9, Modified: true},
1491 {Before: 0, After: 10, Modified: true},
1492 {Before: 0, After: 11, Modified: true},
1493 },
1494 },
1495 Rule: mustParse(7, "- alert: rule1\n expr: sum(foo) by(job)\n"),
1496 },
1497 {
1498 State: discovery.Added,
1499 Path: discovery.Path{
1500 Name: "rules.yml",
1501 SymlinkTarget: "rules.yml",
1502 },
1503 Changes: &discovery.Changes{
1504 Lines: []git.LineNumber{
1505 {Before: 0, After: 8, Modified: true},
1506 {Before: 0, After: 9, Modified: true},
1507 {Before: 0, After: 10, Modified: true},
1508 {Before: 0, After: 11, Modified: true},
1509 },
1510 },
1511 Rule: mustParse(9, "- alert: rule1\n expr: sum(foo) by(job)\n"),
1512 },
1513 },
1514 },
1515 {
1516 title: "rule comment modified",
1517 setup: func(t *testing.T) {
1518 commitFile(t, "rules.yml", `
1519groups:
1520- name: v1
1521 rules:
1522 - record: up:count
1523 pint disable promql/series(up)
1524 expr: sum(up)
1525`, "v1")
1526
1527 _, err := git.RunGit("checkout", "-b", "v2")
1528 require.NoError(t, err, "git checkout v2")
1529
1530 commitFile(t, "rules.yml", `
1531groups:
1532- name: v2
1533 rules:
1534 - record: up:count
1535 # pint disable promql/series(up)
1536 expr: sum(up)
1537`, "v2")
1538 },
1539 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
1540 entries: []*discovery.Entry{
1541 {
1542 State: discovery.Added,
1543 Path: discovery.Path{
1544 Name: "rules.yml",
1545 SymlinkTarget: "rules.yml",
1546 },
1547 Changes: &discovery.Changes{
1548 Lines: []git.LineNumber{
1549 {Before: 3, After: 3, Modified: true},
1550 {Before: 6, After: 6, Modified: true},
1551 },
1552 },
1553 Rule: mustParse(4, " - record: up:count\n # pint disable promql/series(up)\n expr: sum(up)\n"),
1554 },
1555 {
1556 State: discovery.Removed,
1557 Path: discovery.Path{
1558 Name: "rules.yml",
1559 SymlinkTarget: "rules.yml",
1560 },
1561 Changes: &discovery.Changes{
1562 Lines: []git.LineNumber{
1563 {Before: 3, After: 3, Modified: true},
1564 {Before: 6, After: 6, Modified: true},
1565 },
1566 },
1567 PathError: parser.ParseError{
1568 Err: errors.New("xxx"),
1569 Line: 6,
1570 },
1571 },
1572 },
1573 },
1574 {
1575 title: "rule partially replaced",
1576 setup: func(t *testing.T) {
1577 commitFile(t, "rules.yml", `
1578groups:
1579- name: v1
1580 rules:
1581 - record: up:sum
1582 expr: |
1583 sum(
1584 up
1585 )
1586`, "v1")
1587
1588 _, err := git.RunGit("checkout", "-b", "v2")
1589 require.NoError(t, err, "git checkout v2")
1590
1591 commitFile(t, "rules.yml", `
1592groups:
1593- name: v1
1594 rules:
1595 - record: up:count
1596 expr: |
1597 count(
1598 up
1599 )
1600`, "v2")
1601 },
1602 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
1603 entries: []*discovery.Entry{
1604 {
1605 State: discovery.Added,
1606 Path: discovery.Path{
1607 Name: "rules.yml",
1608 SymlinkTarget: "rules.yml",
1609 },
1610 Changes: &discovery.Changes{
1611 Lines: []git.LineNumber{
1612 {Before: 5, After: 5, Modified: true},
1613 {Before: 7, After: 7, Modified: true},
1614 },
1615 },
1616 Rule: mustParse(4, " - record: up:count\n expr: |\n count(\n up\n )\n"),
1617 },
1618 {
1619 State: discovery.Removed,
1620 Path: discovery.Path{
1621 Name: "rules.yml",
1622 SymlinkTarget: "rules.yml",
1623 },
1624 Changes: &discovery.Changes{
1625 Lines: []git.LineNumber{
1626 {Before: 5, After: 5, Modified: true},
1627 {Before: 7, After: 7, Modified: true},
1628 },
1629 },
1630 Rule: mustParse(4, " - record: up:sum\n expr: |\n sum(\n up\n )\n"),
1631 },
1632 },
1633 },
1634 {
1635 title: "broken symlink does not prevent rule discovery",
1636 setup: func(t *testing.T) {
1637 commitFile(t, "rules.yml", `
1638groups:
1639- name: v1
1640 rules:
1641 - record: foo
1642 expr: bar
1643`, "v1")
1644
1645 _, err := git.RunGit("checkout", "-b", "v2")
1646 require.NoError(t, err, "git checkout v2")
1647
1648 commitFile(t, "rules.yml", `
1649groups:
1650- name: v1
1651 rules:
1652 - record: foo
1653 expr: sum(bar)
1654`, "v2")
1655
1656 require.NoError(t, os.Symlink("/nonexistent/path", "broken.yml"))
1657 },
1658 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
1659 entries: []*discovery.Entry{
1660 {
1661 State: discovery.Modified,
1662 Path: discovery.Path{
1663 Name: "rules.yml",
1664 SymlinkTarget: "rules.yml",
1665 },
1666 Changes: &discovery.Changes{
1667 Lines: []git.LineNumber{
1668 {Before: 6, After: 6, Modified: true},
1669 },
1670 },
1671 Rule: mustParse(4, " - record: foo\n expr: sum(bar)\n"),
1672 },
1673 {
1674 State: discovery.Modified,
1675 Path: discovery.Path{
1676 Name: "broken.yml",
1677 SymlinkTarget: "broken.yml",
1678 },
1679 PathError: fmt.Errorf(
1680 "this is a symlink but target file cannot be evaluated: %w",
1681 &fs.PathError{
1682 Op: "lstat",
1683 Path: "/nonexistent",
1684 Err: syscall.ENOENT,
1685 },
1686 ),
1687 },
1688 },
1689 },
1690 {
1691 title: "multiple rules with same name in before",
1692 setup: func(t *testing.T) {
1693 commitFile(t, "rules.yml", `
1694- alert: rule1
1695 expr: up == 0
1696- alert: rule1
1697 expr: up == 1
1698`, "v1")
1699
1700 _, err := git.RunGit("checkout", "-b", "v2")
1701 require.NoError(t, err, "git checkout v2")
1702
1703 commitFile(t, "rules.yml", `
1704- alert: rule1
1705 expr: up != 0
1706`, "v2")
1707 },
1708 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
1709 entries: []*discovery.Entry{
1710 {
1711 State: discovery.Added,
1712 Path: discovery.Path{
1713 Name: "rules.yml",
1714 SymlinkTarget: "rules.yml",
1715 },
1716 Changes: &discovery.Changes{
1717 Lines: []git.LineNumber{
1718 {Before: 3, After: 3, Modified: true},
1719 {Before: 4, After: 0},
1720 {Before: 5, After: 0},
1721 },
1722 },
1723 Rule: mustParse(1, "- alert: rule1\n expr: up != 0\n"),
1724 },
1725 {
1726 State: discovery.Removed,
1727 Path: discovery.Path{
1728 Name: "rules.yml",
1729 SymlinkTarget: "rules.yml",
1730 },
1731 Changes: &discovery.Changes{
1732 Lines: []git.LineNumber{
1733 {Before: 3, After: 3, Modified: true},
1734 {Before: 4, After: 0},
1735 {Before: 5, After: 0},
1736 },
1737 },
1738 Rule: mustParse(1, "- alert: rule1\n expr: up == 0\n"),
1739 },
1740 {
1741 State: discovery.Removed,
1742 Path: discovery.Path{
1743 Name: "rules.yml",
1744 SymlinkTarget: "rules.yml",
1745 },
1746 Changes: &discovery.Changes{
1747 Lines: []git.LineNumber{
1748 {Before: 3, After: 3, Modified: true},
1749 {Before: 4, After: 0},
1750 {Before: 5, After: 0},
1751 },
1752 },
1753 Rule: mustParse(3, "- alert: rule1\n expr: up == 1\n"),
1754 },
1755 },
1756 },
1757 {
1758 title: "allEntries matching updates existing entry",
1759 setup: func(t *testing.T) {
1760 commitFile(t, "rules.yml", `
1761- record: foo
1762 expr: bar
1763`, "v1")
1764
1765 _, err := git.RunGit("checkout", "-b", "v2")
1766 require.NoError(t, err, "git checkout v2")
1767
1768 commitFile(t, "rules.yml", `
1769- record: foo
1770 expr: sum(bar)
1771`, "v2")
1772 },
1773 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
1774 allEntries: []*discovery.Entry{
1775 {
1776 State: discovery.Noop,
1777 Path: discovery.Path{
1778 Name: "rules.yml",
1779 SymlinkTarget: "rules.yml",
1780 },
1781 Rule: mustParse(1, "- record: foo\n expr: sum(bar)\n"),
1782 },
1783 },
1784 entries: []*discovery.Entry{
1785 {
1786 State: discovery.Modified,
1787 Path: discovery.Path{
1788 Name: "rules.yml",
1789 SymlinkTarget: "rules.yml",
1790 },
1791 Changes: &discovery.Changes{
1792 Lines: []git.LineNumber{
1793 {Before: 3, After: 3, Modified: true},
1794 },
1795 },
1796 Rule: mustParse(1, "- record: foo\n expr: sum(bar)\n"),
1797 },
1798 },
1799 },
1800 {
1801 title: "symlink passes path filter",
1802 setup: func(t *testing.T) {
1803 commitFile(t, "rules.yml", `
1804- record: foo
1805 expr: bar
1806`, "v1")
1807
1808 _, err := git.RunGit("checkout", "-b", "v2")
1809 require.NoError(t, err, "git checkout v2")
1810
1811 commitFile(t, "rules.yml", `
1812- record: foo
1813 expr: sum(bar)
1814`, "v2")
1815
1816 require.NoError(t, os.Symlink("rules.yml", "link.yml"))
1817 },
1818 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
1819 entries: []*discovery.Entry{
1820 {
1821 State: discovery.Modified,
1822 Path: discovery.Path{
1823 Name: "rules.yml",
1824 SymlinkTarget: "rules.yml",
1825 },
1826 Changes: &discovery.Changes{
1827 Lines: []git.LineNumber{
1828 {Before: 3, After: 3, Modified: true},
1829 },
1830 },
1831 Rule: mustParse(1, "- record: foo\n expr: sum(bar)\n"),
1832 },
1833 {
1834 State: discovery.Modified,
1835 Path: discovery.Path{
1836 Name: "link.yml",
1837 SymlinkTarget: "rules.yml",
1838 },
1839 Changes: &discovery.Changes{
1840 Lines: []git.LineNumber{
1841 {Before: 3, After: 3, Modified: true},
1842 },
1843 },
1844 Rule: mustParse(1, "- record: foo\n expr: sum(bar)\n"),
1845 },
1846 },
1847 },
1848 {
1849 title: "symlink filtered by path filter",
1850 setup: func(t *testing.T) {
1851 commitFile(t, "rules.yml", `
1852- record: foo
1853 expr: bar
1854`, "v1")
1855
1856 _, err := git.RunGit("checkout", "-b", "v2")
1857 require.NoError(t, err, "git checkout v2")
1858
1859 commitFile(t, "rules.yml", `
1860- record: foo
1861 expr: sum(bar)
1862`, "v2")
1863
1864 require.NoError(t, os.Symlink("rules.yml", "link.yml"))
1865 },
1866 finder: discovery.NewGitBranchFinder(
1867 git.RunGit,
1868 git.NewPathFilter(
1869 []*regexp.Regexp{regexp.MustCompile(`^rules\.yml$`)},
1870 nil,
1871 includeAll,
1872 ),
1873 "main", 4, parser.DefaultOptions, nil,
1874 ),
1875 entries: []*discovery.Entry{
1876 {
1877 State: discovery.Modified,
1878 Path: discovery.Path{
1879 Name: "rules.yml",
1880 SymlinkTarget: "rules.yml",
1881 },
1882 Changes: &discovery.Changes{
1883 Lines: []git.LineNumber{
1884 {Before: 3, After: 3, Modified: true},
1885 },
1886 },
1887 Rule: mustParse(1, "- record: foo\n expr: sum(bar)\n"),
1888 },
1889 },
1890 },
1891 }
1892
1893 for _, tc := range testCases {
1894 t.Run(tc.title, func(t *testing.T) {
1895 dir := t.TempDir()
1896 t.Chdir(dir)
1897
1898 _, err := git.RunGit("init", "--initial-branch=main", ".")
1899 require.NoError(t, err, "git init")
1900
1901 tc.setup(t)
1902 entries, err := tc.finder.Find(tc.allEntries)
1903 if tc.err != "" {
1904 require.EqualError(t, err, tc.err)
1905 } else {
1906 require.NoError(t, err, "tc.finder.Find()")
1907
1908 expected, err := json.MarshalIndent(tc.entries, "", " ")
1909 require.NoError(t, err, "json(expected)")
1910 got, err := json.MarshalIndent(entries, "", " ")
1911 require.NoError(t, err, "json(got)")
1912 if diff := cmp.Diff(string(expected), string(got)); diff != "" {
1913 t.Errorf("tc.finder.Find() returned wrong output (-want +got):\n%s", diff)
1914 return
1915 }
1916 }
1917 })
1918 }
1919}
1920