cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.20.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/discovery/git_branch_test.go

490lines · modecode

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