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/discovery/git_branch_test.go

1427lines · modecode

1package discovery_test
2
3import (
4 "encoding/json"
5 "errors"
6 "fmt"
7 "os"
8 "regexp"
9 "strings"
10 "testing"
11
12 "github.com/google/go-cmp/cmp"
13 "github.com/stretchr/testify/require"
14
15 "github.com/cloudflare/pint/internal/diags"
16 "github.com/cloudflare/pint/internal/discovery"
17 "github.com/cloudflare/pint/internal/git"
18 "github.com/cloudflare/pint/internal/parser"
19)
20
21func gitCommit(t *testing.T, message string) {
22 t.Setenv("GIT_AUTHOR_NAME", "pint")
23 t.Setenv("GIT_AUTHOR_EMAIL", "pint@example.com")
24 t.Setenv("GIT_COMMITTER_NAME", "pint")
25 t.Setenv("GIT_COMMITTER_EMAIL", "pint")
26 _, err := git.RunGit("commit", "-am", "commit "+message)
27 require.NoError(t, err, "git commit %s", message)
28}
29
30func commitFile(t *testing.T, path, content, message string) {
31 err := os.WriteFile(path, []byte(content), 0o644)
32 require.NoError(t, err, "write %s", path)
33 _, err = git.RunGit("add", path)
34 require.NoError(t, err, "git add")
35 gitCommit(t, message)
36}
37
38func TestGitBranchFinder(t *testing.T) {
39 includeAll := []*regexp.Regexp{regexp.MustCompile(".*")}
40
41 mustParse := func(offset int, s string) parser.Rule {
42 p := parser.NewParser(parser.DefaultOptions)
43 file := p.Parse(strings.NewReader(strings.Repeat("\n", offset) + s))
44 if file.Error.Err != nil {
45 panic(fmt.Sprintf("failed to parse rule:\n---\n%s\n---\nerror: %s", s, file.Error))
46 }
47 if len(file.Groups) != 1 {
48 panic(fmt.Sprintf("wrong number of groups returned: %d\n---\n%s\n---", len(file.Groups), s))
49 }
50 if len(file.Groups[0].Rules) != 1 {
51 panic(fmt.Sprintf("wrong number of rules returned: %d\n---\n%s\n---", len(file.Groups[0].Rules), s))
52 }
53 return file.Groups[0].Rules[0]
54 }
55
56 type setupFn func(t *testing.T)
57
58 type testCaseT struct {
59 setup setupFn
60 title string
61 err string
62 entries []*discovery.Entry
63 finder discovery.GitBranchFinder
64 }
65
66 testCases := []testCaseT{
67 {
68 title: "git list PR commits error - main",
69 setup: func(_ *testing.T) {},
70 finder: discovery.NewGitBranchFinder(
71 func(args ...string) ([]byte, error) {
72 return nil, fmt.Errorf("mock git error: %v", args)
73 },
74 git.NewPathFilter(includeAll, nil, nil),
75 "main",
76 50,
77 parser.DefaultOptions,
78 nil,
79 ),
80 entries: nil,
81 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]",
82 },
83 {
84 title: "git list PR commits error - master",
85 setup: func(_ *testing.T) {},
86 finder: discovery.NewGitBranchFinder(
87 func(args ...string) ([]byte, error) {
88 return nil, fmt.Errorf("mock git error: %v", args)
89 },
90 git.NewPathFilter(includeAll, nil, nil),
91 "master",
92 50,
93 parser.DefaultOptions,
94 nil,
95 ),
96 entries: nil,
97 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]",
98 },
99 {
100 title: "too many commits",
101 setup: func(t *testing.T) {
102 commitFile(t, "rules.yml", "# v1\n", "v1")
103
104 _, err := git.RunGit("checkout", "-b", "v2")
105 require.NoError(t, err, "git checkout v2")
106
107 commitFile(t, "rules.yml", "# v2-1\n", "v2-1")
108 commitFile(t, "rules.yml", "# v2-2\n", "v2-2")
109 commitFile(t, "rules.yml", "# v2-3\n", "v2-3")
110 commitFile(t, "rules.yml", "# v2-4\n", "v2-4")
111 },
112 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 3, parser.DefaultOptions, nil),
113 entries: nil,
114 err: "number of commits to check (4) is higher than maxCommits (3), exiting",
115 },
116 {
117 title: "git list modified files error",
118 setup: func(_ *testing.T) {},
119 finder: discovery.NewGitBranchFinder(
120 func(args ...string) ([]byte, error) {
121 switch strings.Join(args, " ") {
122 case "log --format=%H --no-abbrev-commit --reverse main..HEAD":
123 return []byte("c1\nc2\nc3\nc4\n"), nil
124 default:
125 return nil, fmt.Errorf("mock git error: %v", args)
126 }
127 },
128 git.NewPathFilter(includeAll, nil, nil),
129 "main",
130 4,
131 parser.DefaultOptions,
132 nil,
133 ),
134 entries: nil,
135 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]",
136 },
137 {
138 title: "git get commit message error",
139 setup: func(_ *testing.T) {},
140 finder: discovery.NewGitBranchFinder(
141 func(args ...string) ([]byte, error) {
142 switch strings.Join(args, " ") {
143 case "log --reverse --no-merges --first-parent --format=%H --name-status main..HEAD":
144 return []byte("c1\nA\trules.yml\n"), nil
145 default:
146 return nil, fmt.Errorf("mock git error: %v", args)
147 }
148 },
149 git.NewPathFilter(includeAll, nil, nil),
150 "main",
151 4,
152 parser.DefaultOptions,
153 nil,
154 ),
155 entries: nil,
156 err: "failed to get commit message for c1: mock git error: [show -s --format=%B c1]",
157 },
158 {
159 title: "git blame error",
160 setup: func(_ *testing.T) {},
161 finder: discovery.NewGitBranchFinder(
162 func(args ...string) ([]byte, error) {
163 switch strings.Join(args, " ") {
164 case "log --reverse --no-merges --first-parent --format=%H --name-status main..HEAD":
165 return []byte("c1\nA\trules.yml\n"), nil
166 case "ls-tree c1^ rules.yml":
167 return []byte("100644 blob c0\trules.yml"), nil
168 case "ls-tree c1 rules.yml":
169 return []byte("100644 blob c1\trules.yml"), nil
170 case "show -s --format=%B c1":
171 return []byte(""), nil
172 default:
173 return nil, fmt.Errorf("mock git error: %v", args)
174 }
175 },
176 git.NewPathFilter(includeAll, nil, nil),
177 "main",
178 4,
179 parser.DefaultOptions,
180 nil,
181 ),
182 entries: nil,
183 err: "failed to run git blame for rules.yml: mock git error: [blame --line-porcelain c1 -- rules.yml]",
184 },
185 {
186 title: "no rules in file",
187 setup: func(t *testing.T) {
188 commitFile(t, "rules.yml", "# v1\n", "v1")
189
190 _, err := git.RunGit("checkout", "-b", "v2")
191 require.NoError(t, err, "git checkout v2")
192
193 commitFile(t, "rules.yml", "# v2\n", "v2")
194 },
195 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
196 entries: nil,
197 },
198 {
199 title: "no rule changes",
200 setup: func(t *testing.T) {
201 commitFile(t, "rules.yml", `
202groups:
203- name: v1
204 rules:
205 - record: up:count
206 expr: count(up == 1)
207`, "v1")
208
209 _, err := git.RunGit("checkout", "-b", "v2")
210 require.NoError(t, err, "git checkout v2")
211
212 commitFile(t, "rules.yml", `
213groups:
214- name: v2
215 rules:
216 - record: up:count
217 expr: count(up == 1)
218`, "v2")
219 },
220 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
221 entries: []*discovery.Entry{
222 {
223 State: discovery.Noop,
224 Path: discovery.Path{
225 Name: "rules.yml",
226 SymlinkTarget: "rules.yml",
227 },
228 ModifiedLines: []int{},
229 Rule: mustParse(4, " - record: up:count\n expr: count(up == 1)\n"),
230 },
231 },
232 },
233 {
234 title: "rule changed - strict",
235 setup: func(t *testing.T) {
236 commitFile(t, "rules.yml", `
237groups:
238- name: v1
239 rules:
240 - record: up:count
241 expr: count(up)
242`, "v1")
243
244 _, err := git.RunGit("checkout", "-b", "v2")
245 require.NoError(t, err, "git checkout v2")
246
247 commitFile(t, "rules.yml", `
248groups:
249- name: v2
250 rules:
251 - record: up:count
252 expr: count(up == 1)
253`, "v2")
254 },
255 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
256 entries: []*discovery.Entry{
257 {
258 State: discovery.Modified,
259 Path: discovery.Path{
260 Name: "rules.yml",
261 SymlinkTarget: "rules.yml",
262 },
263 ModifiedLines: []int{6},
264 Rule: mustParse(4, " - record: up:count\n expr: count(up == 1)\n"),
265 },
266 },
267 },
268 {
269 title: "rule changed - relaxed",
270 setup: func(t *testing.T) {
271 commitFile(t, "rules.yml", `
272- record: up:count
273 expr: count(up)
274`, "v1")
275
276 _, err := git.RunGit("checkout", "-b", "v2")
277 require.NoError(t, err, "git checkout v2")
278
279 commitFile(t, "rules.yml", `
280- record: up:count
281 expr: count(up == 1)
282`, "v2")
283 },
284 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
285 entries: []*discovery.Entry{
286 {
287 State: discovery.Modified,
288 Path: discovery.Path{
289 Name: "rules.yml",
290 SymlinkTarget: "rules.yml",
291 },
292 ModifiedLines: []int{3},
293 Rule: mustParse(1, "- record: up:count\n expr: count(up == 1)\n"),
294 },
295 },
296 },
297 {
298 title: "rule changed - empty include list",
299 setup: func(t *testing.T) {
300 commitFile(t, "rules.yml", `
301- record: up:count
302 expr: count(up)
303`, "v1")
304
305 _, err := git.RunGit("checkout", "-b", "v2")
306 require.NoError(t, err, "git checkout v2")
307
308 commitFile(t, "rules.yml", `
309- record: up:count
310 expr: count(up == 1)
311`, "v2")
312 },
313 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(nil, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
314 entries: []*discovery.Entry{
315 {
316 State: discovery.Modified,
317 Path: discovery.Path{
318 Name: "rules.yml",
319 SymlinkTarget: "rules.yml",
320 },
321 ModifiedLines: []int{3},
322 Rule: mustParse(1, "- record: up:count\n expr: count(up == 1)\n"),
323 },
324 },
325 },
326 {
327 title: "rule changed but file excluded",
328 setup: func(t *testing.T) {
329 commitFile(t, "rules.yml", `
330groups:
331- name: v1
332 rules:
333 - record: up:count
334 expr: count(up)
335`, "v1")
336
337 _, err := git.RunGit("checkout", "-b", "v2")
338 require.NoError(t, err, "git checkout v2")
339
340 commitFile(t, "rules.yml", `
341groups:
342- name: v2
343 rules:
344 - record: up:count
345 expr: count(up == 1)
346`, "v2")
347 },
348 finder: discovery.NewGitBranchFinder(
349 git.RunGit,
350 git.NewPathFilter([]*regexp.Regexp{regexp.MustCompile("^foo#")}, nil, nil),
351 "main",
352 4,
353 parser.DefaultOptions,
354 nil,
355 ),
356 entries: nil,
357 },
358 {
359 title: "rule changed - [skip ci]",
360 setup: func(t *testing.T) {
361 commitFile(t, "rules.yml", `
362groups:
363- name: v1
364 rules:
365 - record: up:count
366 expr: count(up)
367`, "v1")
368
369 _, err := git.RunGit("checkout", "-b", "v2")
370 require.NoError(t, err, "git checkout v2")
371
372 commitFile(t, "rules.yml", `
373groups:
374- name: v2
375 rules:
376 - record: up:count
377 expr: count(up == 1)
378`, "v2\nskip this commit\n[skip ci]\n")
379 },
380 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
381 entries: nil,
382 },
383 {
384 title: "rule changed - [no ci]",
385 setup: func(t *testing.T) {
386 commitFile(t, "rules.yml", `
387groups:
388- name: v1
389 rules:
390 - record: up:count
391 expr: count(up)
392`, "v1")
393
394 _, err := git.RunGit("checkout", "-b", "v2")
395 require.NoError(t, err, "git checkout v2")
396
397 commitFile(t, "rules.yml", `
398groups:
399- name: v2
400 rules:
401 - record: up:count
402 expr: count(up == 1)
403`, "v2\nskip this commit\n[no ci]\n")
404 },
405 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
406 entries: nil,
407 },
408 {
409 title: "rule symlinked",
410 setup: func(t *testing.T) {
411 commitFile(t, "rules.yml", `
412- record: up:count
413 expr: count(up)
414`, "v1")
415
416 _, err := git.RunGit("checkout", "-b", "v2")
417 require.NoError(t, err, "git checkout v2")
418
419 err = os.Symlink("rules.yml", "symlink.yml")
420 require.NoError(t, err, "symlink")
421 _, err = git.RunGit("add", "symlink.yml")
422 require.NoError(t, err, "git add")
423 gitCommit(t, "v2")
424 },
425 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
426 entries: []*discovery.Entry{
427 {
428 State: discovery.Added,
429 Path: discovery.Path{
430 Name: "symlink.yml",
431 SymlinkTarget: "rules.yml",
432 },
433 ModifiedLines: []int{2, 3},
434 Rule: mustParse(1, "- record: up:count\n expr: count(up)\n"),
435 },
436 },
437 },
438 {
439 title: "rule changed - multiple rules",
440 setup: func(t *testing.T) {
441 commitFile(t, "rules.yml", `
442groups:
443- name: v1
444 rules:
445 - record: up:count:1
446 expr: count(up)
447 - record: up:count:2
448 expr: count(up)
449 - record: up:count:3
450 expr: count(up)
451`, "v1")
452
453 _, err := git.RunGit("checkout", "-b", "v2")
454 require.NoError(t, err, "git checkout v2")
455
456 commitFile(t, "rules.yml", `
457groups:
458- name: v2
459 rules:
460 - record: up:count:1
461 expr: count(up == 1)
462 - record: up:count:2a
463 expr: count(up)
464 - record: up:count:3
465 expr: count(up)
466 - record: up:count:4
467 expr: count(up)
468`, "v2")
469 },
470 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
471 entries: []*discovery.Entry{
472 {
473 State: discovery.Modified,
474 Path: discovery.Path{
475 Name: "rules.yml",
476 SymlinkTarget: "rules.yml",
477 },
478 ModifiedLines: []int{6},
479 Rule: mustParse(4, " - record: up:count:1\n expr: count(up == 1)\n"),
480 },
481 {
482 State: discovery.Added,
483 Path: discovery.Path{
484 Name: "rules.yml",
485 SymlinkTarget: "rules.yml",
486 },
487 ModifiedLines: []int{7},
488 Rule: mustParse(6, " - record: up:count:2a\n expr: count(up)\n"),
489 },
490 {
491 State: discovery.Noop,
492 Path: discovery.Path{
493 Name: "rules.yml",
494 SymlinkTarget: "rules.yml",
495 },
496 ModifiedLines: []int{},
497 Rule: mustParse(8, " - record: up:count:3\n expr: count(up)\n"),
498 },
499 {
500 State: discovery.Added,
501 Path: discovery.Path{
502 Name: "rules.yml",
503 SymlinkTarget: "rules.yml",
504 },
505 ModifiedLines: []int{11, 12},
506 Rule: mustParse(10, " - record: up:count:4\n expr: count(up)\n"),
507 },
508 {
509 State: discovery.Removed,
510 Path: discovery.Path{
511 Name: "rules.yml",
512 SymlinkTarget: "rules.yml",
513 },
514 ModifiedLines: []int{7},
515 Rule: mustParse(6, " - record: up:count:2\n expr: count(up)\n"),
516 },
517 },
518 },
519 {
520 title: "rule changed - added extra line",
521 setup: func(t *testing.T) {
522 commitFile(t, "rules.yml", `
523- alert: rule1
524 expr: sum(foo) by(job)
525- alert: rule2
526 expr: sum(foo) by(job)
527 for: 0s
528`, "v1")
529
530 _, err := git.RunGit("checkout", "-b", "v2")
531 require.NoError(t, err, "git checkout v2")
532
533 commitFile(t, "rules.yml", `
534- alert: rule1
535 expr: sum(foo) by(job)
536 for: 0s
537- alert: rule2
538 expr: sum(foo) by(job)
539 for: 0s
540`, "v2")
541 },
542 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
543 entries: []*discovery.Entry{
544 {
545 State: discovery.Modified,
546 Path: discovery.Path{
547 Name: "rules.yml",
548 SymlinkTarget: "rules.yml",
549 },
550 ModifiedLines: []int{4},
551 Rule: mustParse(1, "- alert: rule1\n expr: sum(foo) by(job)\n for: 0s\n"),
552 },
553 {
554 State: discovery.Noop,
555 Path: discovery.Path{
556 Name: "rules.yml",
557 SymlinkTarget: "rules.yml",
558 },
559 ModifiedLines: []int{},
560 Rule: mustParse(4, "- alert: rule2\n expr: sum(foo) by(job)\n for: 0s\n"),
561 },
562 },
563 },
564 {
565 title: "rule removed - head",
566 setup: func(t *testing.T) {
567 commitFile(t, "rules.yml", `
568- alert: rule1
569 expr: sum(foo) by(job)
570- alert: rule2
571 expr: sum(foo) by(job)
572`, "v1")
573
574 _, err := git.RunGit("checkout", "-b", "v2")
575 require.NoError(t, err, "git checkout v2")
576
577 commitFile(t, "rules.yml", `
578- alert: rule2
579 expr: sum(foo) by(job)
580`, "v2")
581 },
582 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
583 entries: []*discovery.Entry{
584 {
585 State: discovery.Noop,
586 Path: discovery.Path{
587 Name: "rules.yml",
588 SymlinkTarget: "rules.yml",
589 },
590 ModifiedLines: []int{},
591 Rule: mustParse(1, "- alert: rule2\n expr: sum(foo) by(job)\n"),
592 },
593 {
594 State: discovery.Removed,
595 Path: discovery.Path{
596 Name: "rules.yml",
597 SymlinkTarget: "rules.yml",
598 },
599 ModifiedLines: []int{2, 3},
600 Rule: mustParse(1, "- alert: rule1\n expr: sum(foo) by(job)\n"),
601 },
602 },
603 },
604 {
605 title: "rule removed - tail",
606 setup: func(t *testing.T) {
607 commitFile(t, "rules.yml", `
608- alert: rule1
609 expr: sum(foo) by(job)
610- alert: rule2
611 expr: sum(foo) by(job)
612`, "v1")
613
614 _, err := git.RunGit("checkout", "-b", "v2")
615 require.NoError(t, err, "git checkout v2")
616
617 commitFile(t, "rules.yml", `
618- alert: rule1
619 expr: sum(foo) by(job)
620`, "v2")
621 },
622 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
623 entries: []*discovery.Entry{
624 {
625 State: discovery.Noop,
626 Path: discovery.Path{
627 Name: "rules.yml",
628 SymlinkTarget: "rules.yml",
629 },
630 ModifiedLines: []int{},
631 Rule: mustParse(1, "- alert: rule1\n expr: sum(foo) by(job)\n"),
632 },
633 {
634 State: discovery.Removed,
635 Path: discovery.Path{
636 Name: "rules.yml",
637 SymlinkTarget: "rules.yml",
638 },
639 ModifiedLines: []int{4, 5},
640 Rule: mustParse(3, "- alert: rule2\n expr: sum(foo) by(job)\n"),
641 },
642 },
643 },
644 {
645 title: "rule removed - middle",
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- alert: rule3
653 expr: sum(foo) by(job)
654`, "v1")
655
656 _, err := git.RunGit("checkout", "-b", "v2")
657 require.NoError(t, err, "git checkout v2")
658
659 commitFile(t, "rules.yml", `
660- alert: rule1
661 expr: sum(foo) by(job)
662- alert: rule3
663 expr: sum(foo) by(job)
664`, "v2")
665 },
666 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
667 entries: []*discovery.Entry{
668 {
669 State: discovery.Noop,
670 Path: discovery.Path{
671 Name: "rules.yml",
672 SymlinkTarget: "rules.yml",
673 },
674 ModifiedLines: []int{},
675 Rule: mustParse(1, "- alert: rule1\n expr: sum(foo) by(job)\n"),
676 },
677 {
678 State: discovery.Noop,
679 Path: discovery.Path{
680 Name: "rules.yml",
681 SymlinkTarget: "rules.yml",
682 },
683 ModifiedLines: []int{},
684 Rule: mustParse(3, "- alert: rule3\n expr: sum(foo) by(job)\n"),
685 },
686 {
687 State: discovery.Removed,
688 Path: discovery.Path{
689 Name: "rules.yml",
690 SymlinkTarget: "rules.yml",
691 },
692 ModifiedLines: []int{4, 5},
693 Rule: mustParse(3, "- alert: rule2\n expr: sum(foo) by(job)\n"),
694 },
695 },
696 },
697 {
698 title: "rule fixed",
699 setup: func(t *testing.T) {
700 commitFile(t, "rules.yml", `
701groups:
702- name: v1
703 rules:
704 - record: up:count
705 expr: count(up)
706 expr: sum(up)
707`, "v1")
708
709 _, err := git.RunGit("checkout", "-b", "v2")
710 require.NoError(t, err, "git checkout v2")
711
712 commitFile(t, "rules.yml", `
713groups:
714- name: v2
715 rules:
716 - record: up:count
717 expr: count(up)
718`, "v2")
719 },
720 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
721 entries: []*discovery.Entry{
722 {
723 State: discovery.Added,
724 Path: discovery.Path{
725 Name: "rules.yml",
726 SymlinkTarget: "rules.yml",
727 },
728 ModifiedLines: nil,
729 Rule: mustParse(4, " - record: up:count\n expr: count(up)\n"),
730 },
731 {
732 State: discovery.Removed,
733 Path: discovery.Path{
734 Name: "rules.yml",
735 SymlinkTarget: "rules.yml",
736 },
737 ModifiedLines: []int{5, 6, 7},
738 Rule: parser.Rule{
739 Lines: diags.LineRange{First: 5, Last: 7},
740 Error: parser.ParseError{
741 Line: 7,
742 Err: errors.New("duplicated expr key"),
743 },
744 },
745 },
746 },
747 },
748 {
749 title: "rules duplicated",
750 setup: func(t *testing.T) {
751 commitFile(t, "rules.yml", `
752- alert: rule1
753 expr: sum(foo) by(job)
754- alert: rule2
755 expr: sum(foo) by(job)
756`, "v1")
757
758 _, err := git.RunGit("checkout", "-b", "v2")
759 require.NoError(t, err, "git checkout v2")
760
761 commitFile(t, "rules.yml", `
762- alert: rule1
763 expr: sum(foo) by(job)
764- alert: rule2
765 expr: sum(foo) by(job)
766- alert: rule2
767 expr: sum(foo) by(job)
768- alert: rule1
769 expr: sum(foo) by(job)
770`, "v2")
771 },
772 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
773 entries: []*discovery.Entry{
774 {
775 State: discovery.Noop,
776 Path: discovery.Path{
777 Name: "rules.yml",
778 SymlinkTarget: "rules.yml",
779 },
780 ModifiedLines: []int{},
781 Rule: mustParse(1, "- alert: rule1\n expr: sum(foo) by(job)\n"),
782 },
783 {
784 State: discovery.Noop,
785 Path: discovery.Path{
786 Name: "rules.yml",
787 SymlinkTarget: "rules.yml",
788 },
789 ModifiedLines: []int{},
790 Rule: mustParse(3, "- alert: rule2\n expr: sum(foo) by(job)\n"),
791 },
792 {
793 State: discovery.Added,
794 Path: discovery.Path{
795 Name: "rules.yml",
796 SymlinkTarget: "rules.yml",
797 },
798 ModifiedLines: []int{6, 7},
799 Rule: mustParse(5, "- alert: rule2\n expr: sum(foo) by(job)\n"),
800 },
801 {
802 State: discovery.Added,
803 Path: discovery.Path{
804 Name: "rules.yml",
805 SymlinkTarget: "rules.yml",
806 },
807 ModifiedLines: []int{8, 9},
808 Rule: mustParse(7, "- alert: rule1\n expr: sum(foo) by(job)\n"),
809 },
810 },
811 },
812 {
813 title: "rules duplicated with different query",
814 setup: func(t *testing.T) {
815 commitFile(t, "rules.yml", `
816- alert: rule1
817 expr: sum(foo) by(job)
818`, "v1")
819
820 _, err := git.RunGit("checkout", "-b", "v2")
821 require.NoError(t, err, "git checkout v2")
822
823 commitFile(t, "rules.yml", `
824- alert: rule1
825 expr: up == 0
826- alert: rule1
827 expr: up == 1
828- alert: rule1
829 expr: up != 0
830- alert: rule2
831 expr: sum(foo) by(job)
832`, "v2")
833 },
834 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
835 entries: []*discovery.Entry{
836 {
837 State: discovery.Modified,
838 Path: discovery.Path{
839 Name: "rules.yml",
840 SymlinkTarget: "rules.yml",
841 },
842 ModifiedLines: []int{3},
843 Rule: mustParse(1, "- alert: rule1\n expr: up == 0\n"),
844 },
845 {
846 State: discovery.Added,
847 Path: discovery.Path{
848 Name: "rules.yml",
849 SymlinkTarget: "rules.yml",
850 },
851 ModifiedLines: []int{4, 5},
852 Rule: mustParse(3, "- alert: rule1\n expr: up == 1\n"),
853 },
854 {
855 State: discovery.Added,
856 Path: discovery.Path{
857 Name: "rules.yml",
858 SymlinkTarget: "rules.yml",
859 },
860 ModifiedLines: []int{6, 7},
861 Rule: mustParse(5, "- alert: rule1\n expr: up != 0\n"),
862 },
863 {
864 State: discovery.Added,
865 Path: discovery.Path{
866 Name: "rules.yml",
867 SymlinkTarget: "rules.yml",
868 },
869 ModifiedLines: []int{8},
870 Rule: mustParse(7, "- alert: rule2\n expr: sum(foo) by(job)\n"),
871 },
872 },
873 },
874 {
875 title: "rule changed - modified for and added extra lines",
876 setup: func(t *testing.T) {
877 commitFile(t, "rules.yml", `
878- alert: rule1
879 expr: sum(foo) by(job)
880 for: 1s
881- alert: rule2
882 expr: sum(foo) by(job)
883 for: 1s
884`, "v1")
885
886 _, err := git.RunGit("checkout", "-b", "v2")
887 require.NoError(t, err, "git checkout v2")
888
889 commitFile(t, "rules.yml", `
890- alert: rule1
891 expr: sum(foo) by(job)
892 for: 1s
893- alert: rule2
894 expr: sum(foo) by(job)
895 keep_firing_for: 5m
896 for: 0s
897 annotations:
898 foo: bar
899 labels:
900 foo: bar
901`, "v2")
902 },
903 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
904 entries: []*discovery.Entry{
905 {
906 State: discovery.Noop,
907 Path: discovery.Path{
908 Name: "rules.yml",
909 SymlinkTarget: "rules.yml",
910 },
911 ModifiedLines: []int{},
912 Rule: mustParse(1, "- alert: rule1\n expr: sum(foo) by(job)\n for: 1s\n"),
913 },
914 {
915 State: discovery.Modified,
916 Path: discovery.Path{
917 Name: "rules.yml",
918 SymlinkTarget: "rules.yml",
919 },
920 ModifiedLines: []int{7, 8, 9, 10, 11, 12},
921 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"),
922 },
923 },
924 },
925 {
926 title: "rule file moved",
927 setup: func(t *testing.T) {
928 commitFile(t, "a.yml", `
929- alert: rule
930 expr: up == 0
931`, "v1")
932
933 _, err := git.RunGit("checkout", "-b", "v2")
934 require.NoError(t, err, "git checkout v2")
935
936 _, err = git.RunGit("mv", "a.yml", "b.yml")
937 require.NoError(t, err, "git mv")
938
939 gitCommit(t, "v2")
940 },
941 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
942 entries: []*discovery.Entry{
943 {
944 State: discovery.Moved,
945 Path: discovery.Path{
946 Name: "b.yml",
947 SymlinkTarget: "b.yml",
948 },
949 ModifiedLines: []int{1, 2, 3},
950 Rule: mustParse(1, "- alert: rule\n expr: up == 0\n"),
951 },
952 },
953 },
954 {
955 title: "rule modified then the file renamed",
956 setup: func(t *testing.T) {
957 commitFile(t, "a.yml", `
958- alert: rule
959 # pint disable promql/series
960 expr: up == 0
961`, "v1")
962
963 _, err := git.RunGit("checkout", "-b", "v2")
964 require.NoError(t, err, "git checkout v2")
965
966 commitFile(t, "a.yml", `
967- alert: rule
968 expr: up == 0
969`, "v2")
970
971 _, err = git.RunGit("mv", "a.yml", "b.yml")
972 require.NoError(t, err, "git mv")
973
974 gitCommit(t, "v3")
975 },
976 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
977 entries: []*discovery.Entry{
978 {
979 State: discovery.Moved,
980 Path: discovery.Path{
981 Name: "b.yml",
982 SymlinkTarget: "b.yml",
983 },
984 ModifiedLines: []int{1, 2, 3},
985 Rule: mustParse(1, "- alert: rule\n expr: up == 0\n"),
986 },
987 },
988 },
989 {
990 title: "rule file with symlink moved",
991 setup: func(t *testing.T) {
992 commitFile(t, "rules.yml", `
993- alert: rule
994 expr: up == 0
995`, "v1")
996
997 err := os.Symlink("rules.yml", "symlink.yml")
998 require.NoError(t, err, "symlink")
999 _, err = git.RunGit("add", "symlink.yml")
1000 require.NoError(t, err, "git add")
1001 gitCommit(t, "v1")
1002
1003 _, err = git.RunGit("checkout", "-b", "v2")
1004 require.NoError(t, err, "git checkout v2")
1005
1006 _, err = git.RunGit("mv", "rules.yml", "new.yml")
1007 require.NoError(t, err, "git mv")
1008
1009 _, err = git.RunGit("rm", "-f", "symlink.yml")
1010 require.NoError(t, err, "git rm symlink")
1011
1012 gitCommit(t, "v2")
1013 },
1014 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
1015 entries: []*discovery.Entry{
1016 {
1017 State: discovery.Moved,
1018 Path: discovery.Path{
1019 Name: "new.yml",
1020 SymlinkTarget: "new.yml",
1021 },
1022 ModifiedLines: []int{1, 2, 3},
1023 Rule: mustParse(1, "- alert: rule\n expr: up == 0\n"),
1024 },
1025 {
1026 State: discovery.Removed,
1027 Path: discovery.Path{
1028 Name: "symlink.yml",
1029 SymlinkTarget: "rules.yml",
1030 },
1031 ModifiedLines: []int{2, 3},
1032 Rule: mustParse(1, "- alert: rule\n expr: up == 0\n"),
1033 },
1034 },
1035 },
1036 {
1037 title: "rule broken",
1038 setup: func(t *testing.T) {
1039 commitFile(t, "rules.yml", `
1040groups:
1041- name: v1
1042 rules:
1043 - record: rule1
1044 expr: sum(up)
1045 - record: rule2
1046 expr: sum(up)
1047 - record: rule3
1048 expr: sum(up)
1049 - record: rule4
1050 expr: sum(up)
1051`, "v1")
1052
1053 _, err := git.RunGit("checkout", "-b", "v2")
1054 require.NoError(t, err, "git checkout v2")
1055
1056 commitFile(t, "rules.yml", `
1057groups:
1058- name: v2
1059 rules:
1060 - record: rule1
1061 expr: sum(up)
1062 - record: rule2
1063 expr: sum(up)
1064 - record: rule3
1065 expr: sum(up)
1066 +
1067 sum(up)
1068 - record: rule4
1069 expr: sum(up)
1070 - record: rule5
1071 expr: sum(up)
1072`, "v2")
1073 },
1074 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
1075 entries: []*discovery.Entry{
1076 {
1077 State: discovery.Added,
1078 Path: discovery.Path{
1079 Name: "rules.yml",
1080 SymlinkTarget: "rules.yml",
1081 },
1082 ModifiedLines: []int{3, 11, 12, 15, 16},
1083 PathError: parser.ParseError{
1084 Line: 11,
1085 Err: errors.New("could not find expected ':'"),
1086 },
1087 },
1088 },
1089 },
1090 {
1091 title: "file/disable comment removed",
1092 setup: func(t *testing.T) {
1093 commitFile(t, "rules.yml", `
1094# pint file/disable promql/series
1095
1096- alert: rule1
1097 expr: sum(foo) by(job)
1098- alert: rule2
1099 expr: sum(foo) by(job)
1100- alert: rule3
1101 expr: sum(foo) by(job)
1102`, "v1")
1103
1104 _, err := git.RunGit("checkout", "-b", "v2")
1105 require.NoError(t, err, "git checkout v2")
1106
1107 commitFile(t, "rules.yml", `
1108
1109
1110- alert: rule1
1111 expr: sum(foo) by(job)
1112- alert: rule2
1113 expr: sum(foo) by(job)
1114- alert: rule3
1115 expr: sum(foo) by(job)
1116`, "v2")
1117 },
1118 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
1119 entries: []*discovery.Entry{
1120 {
1121 State: discovery.Modified,
1122 Path: discovery.Path{
1123 Name: "rules.yml",
1124 SymlinkTarget: "rules.yml",
1125 },
1126 Rule: mustParse(3, "- alert: rule1\n expr: sum(foo) by(job)\n"),
1127 },
1128 {
1129 State: discovery.Modified,
1130 Path: discovery.Path{
1131 Name: "rules.yml",
1132 SymlinkTarget: "rules.yml",
1133 },
1134 Rule: mustParse(5, "- alert: rule2\n expr: sum(foo) by(job)\n"),
1135 },
1136 {
1137 State: discovery.Modified,
1138 Path: discovery.Path{
1139 Name: "rules.yml",
1140 SymlinkTarget: "rules.yml",
1141 },
1142 Rule: mustParse(7, "- alert: rule3\n expr: sum(foo) by(job)\n"),
1143 },
1144 },
1145 },
1146 {
1147 title: "add two dups",
1148 setup: func(t *testing.T) {
1149 commitFile(t, "rules.yml", `
1150- alert: rule1
1151 expr: sum(foo) by(job)
1152- alert: rule2
1153 expr: sum(foo) by(job)
1154- alert: rule3
1155 expr: sum(foo) by(job)
1156`, "v1")
1157
1158 _, err := git.RunGit("checkout", "-b", "v2")
1159 require.NoError(t, err, "git checkout v2")
1160
1161 commitFile(t, "rules.yml", `
1162- alert: rule1
1163 expr: sum(foo) by(job)
1164- alert: rule2
1165 expr: sum(foo) by(job)
1166- alert: rule3
1167 expr: sum(foo) by(job)
1168- alert: rule1
1169 expr: sum(foo) by(job)
1170- alert: rule1
1171 expr: sum(foo) by(job)
1172`, "v2")
1173 },
1174 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
1175 entries: []*discovery.Entry{
1176 {
1177 State: discovery.Noop,
1178 Path: discovery.Path{
1179 Name: "rules.yml",
1180 SymlinkTarget: "rules.yml",
1181 },
1182 ModifiedLines: []int{},
1183 Rule: mustParse(1, "- alert: rule1\n expr: sum(foo) by(job)\n"),
1184 },
1185 {
1186 State: discovery.Noop,
1187 Path: discovery.Path{
1188 Name: "rules.yml",
1189 SymlinkTarget: "rules.yml",
1190 },
1191 ModifiedLines: []int{},
1192 Rule: mustParse(3, "- alert: rule2\n expr: sum(foo) by(job)\n"),
1193 },
1194 {
1195 State: discovery.Noop,
1196 Path: discovery.Path{
1197 Name: "rules.yml",
1198 SymlinkTarget: "rules.yml",
1199 },
1200 ModifiedLines: []int{},
1201 Rule: mustParse(5, "- alert: rule3\n expr: sum(foo) by(job)\n"),
1202 },
1203 {
1204 State: discovery.Added,
1205 Path: discovery.Path{
1206 Name: "rules.yml",
1207 SymlinkTarget: "rules.yml",
1208 },
1209 ModifiedLines: []int{8, 9},
1210 Rule: mustParse(7, "- alert: rule1\n expr: sum(foo) by(job)\n"),
1211 },
1212 {
1213 State: discovery.Added,
1214 Path: discovery.Path{
1215 Name: "rules.yml",
1216 SymlinkTarget: "rules.yml",
1217 },
1218 ModifiedLines: []int{10, 11},
1219 Rule: mustParse(9, "- alert: rule1\n expr: sum(foo) by(job)\n"),
1220 },
1221 },
1222 },
1223 {
1224 title: "rule comment modified",
1225 setup: func(t *testing.T) {
1226 commitFile(t, "rules.yml", `
1227groups:
1228- name: v1
1229 rules:
1230 - record: up:count
1231 pint disable promql/series(up)
1232 expr: sum(up)
1233`, "v1")
1234
1235 _, err := git.RunGit("checkout", "-b", "v2")
1236 require.NoError(t, err, "git checkout v2")
1237
1238 commitFile(t, "rules.yml", `
1239groups:
1240- name: v2
1241 rules:
1242 - record: up:count
1243 # pint disable promql/series(up)
1244 expr: sum(up)
1245`, "v2")
1246 },
1247 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
1248 entries: []*discovery.Entry{
1249 {
1250 State: discovery.Added,
1251 Path: discovery.Path{
1252 Name: "rules.yml",
1253 SymlinkTarget: "rules.yml",
1254 },
1255 ModifiedLines: []int{6},
1256 Rule: mustParse(4, " - record: up:count\n # pint disable promql/series(up)\n expr: sum(up)\n"),
1257 },
1258 {
1259 State: discovery.Removed,
1260 Path: discovery.Path{
1261 Name: "rules.yml",
1262 SymlinkTarget: "rules.yml",
1263 },
1264 PathError: parser.ParseError{
1265 Err: errors.New("xxx"),
1266 Line: 6,
1267 },
1268 ModifiedLines: []int{3, 6},
1269 },
1270 },
1271 },
1272 {
1273 title: "rule partially replaced",
1274 setup: func(t *testing.T) {
1275 commitFile(t, "rules.yml", `
1276groups:
1277- name: v1
1278 rules:
1279 - record: up:sum
1280 expr: |
1281 sum(
1282 up
1283 )
1284`, "v1")
1285
1286 _, err := git.RunGit("checkout", "-b", "v2")
1287 require.NoError(t, err, "git checkout v2")
1288
1289 commitFile(t, "rules.yml", `
1290groups:
1291- name: v1
1292 rules:
1293 - record: up:count
1294 expr: |
1295 count(
1296 up
1297 )
1298`, "v2")
1299 },
1300 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
1301 entries: []*discovery.Entry{
1302 {
1303 State: discovery.Added,
1304 Path: discovery.Path{
1305 Name: "rules.yml",
1306 SymlinkTarget: "rules.yml",
1307 },
1308 ModifiedLines: []int{5, 7},
1309 Rule: mustParse(4, " - record: up:count\n expr: |\n count(\n up\n )\n"),
1310 },
1311 {
1312 State: discovery.Removed,
1313 Path: discovery.Path{
1314 Name: "rules.yml",
1315 SymlinkTarget: "rules.yml",
1316 },
1317 ModifiedLines: []int{5, 7},
1318 Rule: mustParse(4, " - record: up:sum\n expr: |\n sum(\n up\n )\n"),
1319 },
1320 },
1321 },
1322 {
1323 title: "addSymlinkedEntries error",
1324 setup: func(t *testing.T) {
1325 commitFile(t, "rules.yml", `
1326groups:
1327- name: v1
1328 rules:
1329 - record: foo
1330 expr: bar
1331`, "v1")
1332
1333 _, err := git.RunGit("checkout", "-b", "v2")
1334 require.NoError(t, err, "git checkout v2")
1335
1336 commitFile(t, "rules.yml", `
1337groups:
1338- name: v1
1339 rules:
1340 - record: foo
1341 expr: sum(bar)
1342`, "v2")
1343
1344 require.NoError(t, os.Symlink("/nonexistent/path", "broken.yml"))
1345 },
1346 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, nil), "main", 4, parser.DefaultOptions, nil),
1347 entries: nil,
1348 err: "broken.yml is a symlink but target file cannot be evaluated: lstat /nonexistent: no such file or directory",
1349 },
1350 {
1351 title: "multiple rules with same name in before",
1352 setup: func(t *testing.T) {
1353 commitFile(t, "rules.yml", `
1354- alert: rule1
1355 expr: up == 0
1356- alert: rule1
1357 expr: up == 1
1358`, "v1")
1359
1360 _, err := git.RunGit("checkout", "-b", "v2")
1361 require.NoError(t, err, "git checkout v2")
1362
1363 commitFile(t, "rules.yml", `
1364- alert: rule1
1365 expr: up != 0
1366`, "v2")
1367 },
1368 finder: discovery.NewGitBranchFinder(git.RunGit, git.NewPathFilter(includeAll, nil, includeAll), "main", 4, parser.DefaultOptions, nil),
1369 entries: []*discovery.Entry{
1370 {
1371 State: discovery.Added,
1372 Path: discovery.Path{
1373 Name: "rules.yml",
1374 SymlinkTarget: "rules.yml",
1375 },
1376 ModifiedLines: []int{3},
1377 Rule: mustParse(1, "- alert: rule1\n expr: up != 0\n"),
1378 },
1379 {
1380 State: discovery.Removed,
1381 Path: discovery.Path{
1382 Name: "rules.yml",
1383 SymlinkTarget: "rules.yml",
1384 },
1385 ModifiedLines: []int{3},
1386 Rule: mustParse(1, "- alert: rule1\n expr: up == 0\n"),
1387 },
1388 {
1389 State: discovery.Removed,
1390 Path: discovery.Path{
1391 Name: "rules.yml",
1392 SymlinkTarget: "rules.yml",
1393 },
1394 ModifiedLines: []int{4, 5},
1395 Rule: mustParse(3, "- alert: rule1\n expr: up == 1\n"),
1396 },
1397 },
1398 },
1399 }
1400
1401 for _, tc := range testCases {
1402 t.Run(tc.title, func(t *testing.T) {
1403 dir := t.TempDir()
1404 t.Chdir(dir)
1405
1406 _, err := git.RunGit("init", "--initial-branch=main", ".")
1407 require.NoError(t, err, "git init")
1408
1409 tc.setup(t)
1410 entries, err := tc.finder.Find(nil)
1411 if tc.err != "" {
1412 require.EqualError(t, err, tc.err)
1413 } else {
1414 require.NoError(t, err, "tc.finder.Find()")
1415
1416 expected, err := json.MarshalIndent(tc.entries, "", " ")
1417 require.NoError(t, err, "json(expected)")
1418 got, err := json.MarshalIndent(entries, "", " ")
1419 require.NoError(t, err, "json(got)")
1420 if diff := cmp.Diff(string(expected), string(got)); diff != "" {
1421 t.Errorf("tc.finder.Find() returned wrong output (-want +got):\n%s", diff)
1422 return
1423 }
1424 }
1425 })
1426 }
1427}
1428