cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.26.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/discovery/git_branch_test.go

489lines · modecode

1package discovery_test
2
3import (
4 "fmt"
5 "os"
6 "path"
7 "regexp"
8 "strconv"
9 "strings"
10 "testing"
11
12 "github.com/stretchr/testify/require"
13
14 "github.com/cloudflare/pint/internal/discovery"
15)
16
17func blameLine(sha string, line int, filename string) string {
18 return fmt.Sprintf(`%s %d %d 1
19author Alice Mock
20author-mail <alice@example.com>
21author-time 1559927997
22author-tz 0000
23committer Alice Mock
24committer-mail <alice@example.com>
25committer-time 1559927997
26committer-tz 0000
27summary Mock commit title
28boundary
29filename %s
30 fake content
31`, sha, line, line, filename)
32}
33
34type blameRange struct {
35 sha string
36 lines []int
37}
38
39func blame(v map[string][]blameRange) []byte {
40 var out string
41 for path, brs := range v {
42 for _, br := range brs {
43 for _, l := range br.lines {
44 out += blameLine(br.sha, l, path)
45 }
46 }
47 }
48 return []byte(out)
49}
50
51type rule struct {
52 path string
53 name string
54 lines []int
55 modified []int
56}
57
58func TestGitBranchFinder(t *testing.T) {
59 type testCaseT struct {
60 files map[string]string
61 finder discovery.GitBranchFinder
62 rules []rule
63 err string
64 }
65
66 testRuleBody := `
67- record: first
68 expr: sum(foo)
69
70- alert: second
71 expr: foo > bar
72 labels:
73 cluster: dev
74
75- record: third
76 expr: count(foo)
77 labels:
78 cluster: dev
79`
80
81 testCases := []testCaseT{
82 {
83 files: map[string]string{},
84 finder: discovery.NewGitBranchFinder(
85 func(args ...string) ([]byte, error) {
86 return nil, fmt.Errorf("mock error")
87 },
88 nil,
89 "main",
90 0,
91 nil,
92 ),
93 err: "failed to get the list of commits to scan: mock error",
94 },
95 {
96 files: map[string]string{},
97 finder: discovery.NewGitBranchFinder(
98 func(args ...string) ([]byte, error) {
99 switch strings.Join(args, " ") {
100 case "log --format=%H --no-abbrev-commit --reverse main..HEAD":
101 return []byte("commit1\n"), nil
102 default:
103 return nil, fmt.Errorf("mock error")
104 }
105 },
106 nil,
107 "main",
108 0,
109 nil,
110 ),
111 err: "failed to get the list of modified files from git: mock error",
112 },
113 {
114 files: map[string]string{},
115 finder: discovery.NewGitBranchFinder(
116 func(args ...string) ([]byte, error) {
117 switch strings.Join(args, " ") {
118 case "log --format=%H --no-abbrev-commit --reverse main..HEAD":
119 return []byte("commit1\n"), nil
120 case "log --reverse --no-merges --pretty=format:%H --name-status commit1^..commit1":
121 return []byte("commit1\nM foo.yml\n"), nil
122 case "show -s --format=%B commit1":
123 return nil, fmt.Errorf("mock error")
124 default:
125 t.Errorf("unknown args: %v", args)
126 t.FailNow()
127 return nil, nil
128 }
129 },
130 nil,
131 "main",
132 0,
133 []*regexp.Regexp{regexp.MustCompile(".*")},
134 ),
135 err: "failed to get commit message for commit1: mock error",
136 },
137 {
138 files: map[string]string{},
139 finder: discovery.NewGitBranchFinder(
140 func(args ...string) ([]byte, error) {
141 switch strings.Join(args, " ") {
142 case "log --format=%H --no-abbrev-commit --reverse main..HEAD":
143 return []byte("commit1\n"), nil
144 case "log --reverse --no-merges --pretty=format:%H --name-status commit1^..commit1":
145 return []byte("commit1\nM foo.yml\n"), nil
146 case "show -s --format=%B commit1":
147 return []byte("foo"), nil
148 case "blame --line-porcelain -- foo.yml":
149 return nil, fmt.Errorf("mock error")
150 default:
151 t.Errorf("unknown args: %v", args)
152 t.FailNow()
153 return nil, nil
154 }
155 },
156 nil,
157 "main",
158 0,
159 []*regexp.Regexp{regexp.MustCompile(".*")},
160 ),
161 err: "failed to run git blame for foo.yml: mock error",
162 },
163 {
164 files: map[string]string{},
165 finder: discovery.NewGitBranchFinder(
166 func(args ...string) ([]byte, error) {
167 switch strings.Join(args, " ") {
168 case "log --format=%H --no-abbrev-commit --reverse main..HEAD":
169 return []byte("commit1\n"), nil
170 case "log --reverse --no-merges --pretty=format:%H --name-status commit1^..commit1":
171 return []byte("commit1\nM foo.yml\n"), nil
172 case "show -s --format=%B commit1":
173 return []byte("foo"), nil
174 case "blame --line-porcelain -- foo.yml":
175 return blame(map[string][]blameRange{
176 "foo.yml": {
177 {sha: "commitX", lines: []int{1, 3, 4, 5, 6, 9, 10, 11, 12}},
178 {sha: "commit1", lines: []int{2, 7, 8}},
179 },
180 }), nil
181 default:
182 t.Errorf("unknown args: %v", args)
183 t.FailNow()
184 return nil, nil
185 }
186 },
187 nil,
188 "main",
189 0,
190 []*regexp.Regexp{regexp.MustCompile(".*")},
191 ),
192 err: "open foo.yml: no such file or directory",
193 },
194 {
195 files: map[string]string{
196 "foo.yml": testRuleBody,
197 },
198 finder: discovery.NewGitBranchFinder(
199 func(args ...string) ([]byte, error) {
200 switch strings.Join(args, " ") {
201 case "log --format=%H --no-abbrev-commit --reverse main..HEAD":
202 return []byte("commit1\n"), nil
203 case "log --reverse --no-merges --pretty=format:%H --name-status commit1^..commit1":
204 return []byte("commit1\nM foo.yml\n"), nil
205 case "show -s --format=%B commit1":
206 return []byte("foo"), nil
207 case "blame --line-porcelain -- foo.yml":
208 return blame(map[string][]blameRange{
209 "foo.yml": {
210 {sha: "commitX", lines: []int{1, 3, 4, 5, 6, 9, 10, 11, 12}},
211 {sha: "commit1", lines: []int{2, 7, 8}},
212 },
213 }), nil
214 default:
215 t.Errorf("unknown args: %v", args)
216 t.FailNow()
217 return nil, nil
218 }
219 },
220 nil,
221 "main",
222 0,
223 []*regexp.Regexp{regexp.MustCompile(".*")},
224 ),
225 rules: []rule{
226 {path: "foo.yml", name: "first", lines: []int{2, 3}, modified: []int{2}},
227 {path: "foo.yml", name: "second", lines: []int{5, 6, 7, 8}, modified: []int{7, 8}},
228 },
229 },
230 {
231 files: map[string]string{
232 "foo/i1.yml": testRuleBody,
233 "i2.yml": testRuleBody,
234 "foo/c1a.yml": testRuleBody,
235 "foo/c1b.yml": testRuleBody,
236 "c2a.yml": testRuleBody,
237 "c2c.yml": testRuleBody,
238 "foo/c2b.yml": testRuleBody,
239 "bar/c3a.yml": testRuleBody,
240 "c3b.yml": testRuleBody,
241 "c3c.yml": testRuleBody,
242 "c3d.yml": testRuleBody,
243 "c3e.yml": testRuleBody,
244 },
245 finder: discovery.NewGitBranchFinder(
246 func(args ...string) ([]byte, error) {
247 switch strings.Join(args, " ") {
248 case "log --format=%H --no-abbrev-commit --reverse main..HEAD":
249 return []byte("commit1\ncommit2\ncommit3\n"), nil
250 case "log --reverse --no-merges --pretty=format:%H --name-status commit1^..commit3":
251 return []byte(`commit1
252M foo/c1a.yml
253M foo/c1b.yml
254commit2
255M c2a.yml
256M foo/c2b.yml
257A foo/c2c.yml
258commit3
259
260A bar/c3a.yml
261R053 src.txt c3b.yml
262R100 foo/c3c.txt c3c.yml
263M c2a.yml
264C50 foo/cp1.yml c3d.yml
265D foo/c2b.yml
266R090 foo/c2c.yml c2c.yml
267`), nil
268 case "show -s --format=%B commit1":
269 return []byte("foo"), nil
270 case "show -s --format=%B commit2":
271 return []byte("bar"), nil
272 case "show -s --format=%B commit3":
273 return []byte("foo"), nil
274 case "blame --line-porcelain -- foo/c1a.yml":
275 return blame(map[string][]blameRange{
276 "foo/c1a.yml": {
277 {sha: "commit1", lines: []int{2, 12}}, // 1 & 3
278 {sha: "commitX", lines: []int{1, 3, 4, 5, 6, 7, 8, 9, 10, 11}},
279 },
280 }), nil
281 case "blame --line-porcelain -- foo/c1b.yml":
282 return blame(map[string][]blameRange{
283 "foo/c1b.yml": {
284 {sha: "commit1", lines: []int{11, 12}}, // 3
285 {sha: "commitX", lines: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}},
286 },
287 }), nil
288 case "blame --line-porcelain -- c2a.yml":
289 return blame(map[string][]blameRange{
290 "c2a.yml": {
291 {sha: "commitX", lines: []int{1, 2, 3, 4, 5, 6, 9, 11, 12}},
292 {sha: "commit2", lines: []int{7, 8, 10}}, // 2 & 3
293 {sha: "commit3", lines: []int{3}}, // 1
294 },
295 }), nil
296 case "blame --line-porcelain -- c2c.yml":
297 return blame(map[string][]blameRange{
298 "c2c.yml": {
299 {sha: "commit2", lines: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}, // 2 & 3
300 {sha: "commit3", lines: []int{3}}, // 1
301 },
302 }), nil
303 case "blame --line-porcelain -- c3b.yml":
304 return blame(map[string][]blameRange{
305 "c3b.yml": {
306 {sha: "commitX", lines: []int{1, 11, 12}},
307 {sha: "commit3", lines: []int{2, 3, 4, 5, 6, 7, 8, 9, 10}}, // 1 & 2 & 3
308 },
309 }), nil
310 case "blame --line-porcelain -- c3c.yml":
311 return blame(map[string][]blameRange{
312 "c3c.yml": {
313 {sha: "commit3", lines: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}, // 1 & 2 & 3
314 },
315 }), nil
316 case "blame --line-porcelain -- c3d.yml":
317 return blame(map[string][]blameRange{
318 "c3d.yml": {
319 {sha: "commitX", lines: []int{1, 11, 12}},
320 {sha: "commit3", lines: []int{2, 3, 4, 5, 6, 7, 8, 9, 10}}, // 1 & 2 & 3
321 },
322 }), nil
323 default:
324 t.Errorf("unknown args: %v", args)
325 t.FailNow()
326 return nil, nil
327 }
328 },
329 []*regexp.Regexp{
330 regexp.MustCompile("^foo/.*"),
331 regexp.MustCompile("^c.*.yml$"),
332 },
333 "main",
334 0,
335 []*regexp.Regexp{regexp.MustCompile(".*")},
336 ),
337 rules: []rule{
338 {path: "c3b.yml", name: "first", lines: []int{2, 3}, modified: []int{2, 3}},
339 {path: "c3b.yml", name: "second", lines: []int{5, 6, 7, 8}, modified: []int{5, 6, 7, 8}},
340 {path: "c3b.yml", name: "third", lines: []int{10, 11, 12, 13}, modified: []int{10}},
341 {path: "c3c.yml", name: "first", lines: []int{2, 3}, modified: []int{2, 3}},
342 {path: "c3c.yml", name: "second", lines: []int{5, 6, 7, 8}, modified: []int{5, 6, 7, 8}},
343 {path: "c3c.yml", name: "third", lines: []int{10, 11, 12, 13}, modified: []int{10, 11, 12}},
344 {path: "c3d.yml", name: "first", lines: []int{2, 3}, modified: []int{2, 3}},
345 {path: "c3d.yml", name: "second", lines: []int{5, 6, 7, 8}, modified: []int{5, 6, 7, 8}},
346 {path: "c3d.yml", name: "third", lines: []int{10, 11, 12, 13}, modified: []int{10}},
347 {path: "foo/c1a.yml", name: "first", lines: []int{2, 3}, modified: []int{2}},
348 {path: "foo/c1a.yml", name: "third", lines: []int{10, 11, 12, 13}, modified: []int{12}},
349 {path: "foo/c1b.yml", name: "third", lines: []int{10, 11, 12, 13}, modified: []int{11, 12}},
350 {path: "c2a.yml", name: "first", lines: []int{2, 3}, modified: []int{3}},
351 {path: "c2a.yml", name: "second", lines: []int{5, 6, 7, 8}, modified: []int{7, 8}},
352 {path: "c2a.yml", name: "third", lines: []int{10, 11, 12, 13}, modified: []int{10}},
353 {path: "c2c.yml", name: "first", lines: []int{2, 3}, modified: []int{2, 3, 3}},
354 {path: "c2c.yml", name: "second", lines: []int{5, 6, 7, 8}, modified: []int{5, 6, 7, 8}},
355 {path: "c2c.yml", name: "third", lines: []int{10, 11, 12, 13}, modified: []int{10, 11, 12}},
356 },
357 },
358 {
359 files: map[string]string{
360 "foo.yml": testRuleBody,
361 },
362 finder: discovery.NewGitBranchFinder(
363 func(args ...string) ([]byte, error) {
364 switch strings.Join(args, " ") {
365 case "log --format=%H --no-abbrev-commit --reverse main..HEAD":
366 return []byte("commit1\n"), nil
367 case "log --reverse --no-merges --pretty=format:%H --name-status commit1^..commit1":
368 return []byte("commit1\nM foo.yml\n"), nil
369 case "show -s --format=%B commit1":
370 return []byte("foo"), nil
371 case "blame --line-porcelain -- foo.yml":
372 return blame(map[string][]blameRange{
373 "foo.yml": {
374 {sha: "commitX", lines: []int{1, 3, 4, 5, 6, 9, 10, 11, 12}},
375 {sha: "commit1", lines: []int{2, 7, 8}},
376 },
377 }), nil
378 default:
379 t.Errorf("unknown args: %v", args)
380 t.FailNow()
381 return nil, nil
382 }
383 },
384 nil,
385 "main",
386 0,
387 nil,
388 ),
389 rules: []rule{
390 {path: "foo.yml", modified: []int{2, 7, 8}},
391 },
392 },
393 {
394 files: map[string]string{
395 "foo.yml": testRuleBody,
396 },
397 finder: discovery.NewGitBranchFinder(
398 func(args ...string) ([]byte, error) {
399 switch strings.Join(args, " ") {
400 case "log --format=%H --no-abbrev-commit --reverse main..HEAD":
401 return []byte("commit1\n"), nil
402 case "log --reverse --no-merges --pretty=format:%H --name-status commit1^..commit1":
403 return []byte("commit1\nM foo.yml\n"), nil
404 case "show -s --format=%B commit1":
405 return []byte("foo [skip ci] bar"), nil
406 default:
407 t.Errorf("unknown args: %v", args)
408 t.FailNow()
409 return nil, nil
410 }
411 },
412 nil,
413 "main",
414 0,
415 []*regexp.Regexp{regexp.MustCompile(".*")},
416 ),
417 rules: nil,
418 },
419 {
420 files: map[string]string{
421 "foo.yml": testRuleBody,
422 },
423 finder: discovery.NewGitBranchFinder(
424 func(args ...string) ([]byte, error) {
425 switch strings.Join(args, " ") {
426 case "log --format=%H --no-abbrev-commit --reverse main..HEAD":
427 return []byte("commit1\n"), nil
428 case "log --reverse --no-merges --pretty=format:%H --name-status commit1^..commit1":
429 return []byte("commit1\nM foo.yml\n"), nil
430 case "show -s --format=%B commit1":
431 return []byte("foo [no ci] bar"), nil
432 default:
433 t.Errorf("unknown args: %v", args)
434 t.FailNow()
435 return nil, nil
436 }
437 },
438 nil,
439 "main",
440 0,
441 []*regexp.Regexp{regexp.MustCompile(".*")},
442 ),
443 rules: nil,
444 },
445 }
446
447 for i, tc := range testCases {
448 t.Run(strconv.Itoa(i), func(t *testing.T) {
449 workdir := t.TempDir()
450 err := os.Chdir(workdir)
451 require.NoError(t, err)
452
453 for p, content := range tc.files {
454 if strings.Contains(p, "/") {
455 err = os.MkdirAll(path.Dir(p), 0o755)
456 require.NoError(t, err)
457 }
458 err = os.WriteFile(p, []byte(content), 0o644)
459 require.NoError(t, err)
460 }
461
462 entries, err := tc.finder.Find()
463 if tc.err != "" {
464 require.EqualError(t, err, tc.err)
465 } else {
466 require.NoError(t, err)
467
468 rules := []rule{}
469 for _, e := range entries {
470 t.Logf("Entry: path=%s pathErr=%v lines=%v modified=%v", e.Path, e.PathError, e.Rule.Lines(), e.ModifiedLines)
471 var name string
472 if e.Rule.AlertingRule != nil {
473 name = e.Rule.AlertingRule.Alert.Value.Value
474 }
475 if e.Rule.RecordingRule != nil {
476 name = e.Rule.RecordingRule.Record.Value.Value
477 }
478 rules = append(rules, rule{
479 path: e.Path,
480 name: name,
481 lines: e.Rule.Lines(),
482 modified: e.ModifiedLines,
483 })
484 }
485 require.ElementsMatch(t, tc.rules, rules)
486 }
487 })
488 }
489}
490