cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.16.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/discovery/git_branch_test.go

336lines · 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
52func TestGitBranchFinder(t *testing.T) {
53 type testCaseT struct {
54 files map[string]string
55 finder discovery.GitBranchFinder
56 rules map[string][]string
57 err string
58 }
59
60 testRuleBody := `
61- record: first
62 expr: sum(foo)
63
64- alert: second
65 expr: foo > bar
66 labels:
67 cluster: dev
68
69- record: third
70 expr: count(foo)
71 labels:
72 cluster: dev
73`
74
75 testCases := []testCaseT{
76 {
77 files: map[string]string{},
78 finder: discovery.NewGitBranchFinder(
79 func(args ...string) ([]byte, error) {
80 return nil, fmt.Errorf("mock error")
81 },
82 nil,
83 "main",
84 0,
85 ),
86 err: "failed to get the list of commits to scan: mock error",
87 },
88 {
89 files: map[string]string{},
90 finder: discovery.NewGitBranchFinder(
91 func(args ...string) ([]byte, error) {
92 switch strings.Join(args, " ") {
93 case "log --format=%H --no-abbrev-commit --reverse main..HEAD":
94 return []byte("commit1\n"), nil
95 default:
96 return nil, fmt.Errorf("mock error")
97 }
98 },
99 nil,
100 "main",
101 0,
102 ),
103 err: "failed to get the list of modified files from git: mock error",
104 },
105 {
106 files: map[string]string{},
107 finder: discovery.NewGitBranchFinder(
108 func(args ...string) ([]byte, error) {
109 switch strings.Join(args, " ") {
110 case "log --format=%H --no-abbrev-commit --reverse main..HEAD":
111 return []byte("commit1\n"), nil
112 case "log --reverse --no-merges --pretty=format:%H --name-status commit1^..commit1":
113 return []byte("commit1\nM foo.yml\n"), nil
114 case "blame --line-porcelain -- foo.yml":
115 return nil, fmt.Errorf("mock error")
116 default:
117 t.Errorf("unknown args: %v", args)
118 t.FailNow()
119 return nil, nil
120 }
121 },
122 nil,
123 "main",
124 0,
125 ),
126 err: "failed to run git blame for foo.yml: mock error",
127 },
128 {
129 files: map[string]string{},
130 finder: discovery.NewGitBranchFinder(
131 func(args ...string) ([]byte, error) {
132 switch strings.Join(args, " ") {
133 case "log --format=%H --no-abbrev-commit --reverse main..HEAD":
134 return []byte("commit1\n"), nil
135 case "log --reverse --no-merges --pretty=format:%H --name-status commit1^..commit1":
136 return []byte("commit1\nM foo.yml\n"), nil
137 case "blame --line-porcelain -- foo.yml":
138 return blame(map[string][]blameRange{
139 "foo.yml": {
140 {sha: "commitX", lines: []int{1, 3, 4, 5, 6, 9, 10, 11, 12}},
141 {sha: "commit1", lines: []int{2, 7, 8}},
142 },
143 }), nil
144 default:
145 t.Errorf("unknown args: %v", args)
146 t.FailNow()
147 return nil, nil
148 }
149 },
150 nil,
151 "main",
152 0,
153 ),
154 err: "open foo.yml: no such file or directory",
155 },
156 {
157 files: map[string]string{
158 "foo.yml": testRuleBody,
159 },
160 finder: discovery.NewGitBranchFinder(
161 func(args ...string) ([]byte, error) {
162 switch strings.Join(args, " ") {
163 case "log --format=%H --no-abbrev-commit --reverse main..HEAD":
164 return []byte("commit1\n"), nil
165 case "log --reverse --no-merges --pretty=format:%H --name-status commit1^..commit1":
166 return []byte("commit1\nM foo.yml\n"), nil
167 case "blame --line-porcelain -- foo.yml":
168 return blame(map[string][]blameRange{
169 "foo.yml": {
170 {sha: "commitX", lines: []int{1, 3, 4, 5, 6, 9, 10, 11, 12}},
171 {sha: "commit1", lines: []int{2, 7, 8}},
172 },
173 }), nil
174 default:
175 t.Errorf("unknown args: %v", args)
176 t.FailNow()
177 return nil, nil
178 }
179 },
180 nil,
181 "main",
182 0,
183 ),
184 rules: map[string][]string{"foo.yml": {"first", "second"}},
185 },
186 {
187 files: map[string]string{
188 "foo/i1.yml": testRuleBody,
189 "i2.yml": testRuleBody,
190 "foo/c1a.yml": testRuleBody,
191 "foo/c1b.yml": testRuleBody,
192 "c2a.yml": testRuleBody,
193 "c2c.yml": testRuleBody,
194 "foo/c2b.yml": testRuleBody,
195 "bar/c3a.yml": testRuleBody,
196 "c3b.yml": testRuleBody,
197 "c3c.yml": testRuleBody,
198 "c3d.yml": testRuleBody,
199 "c3e.yml": testRuleBody,
200 },
201 finder: discovery.NewGitBranchFinder(
202 func(args ...string) ([]byte, error) {
203 switch strings.Join(args, " ") {
204 case "log --format=%H --no-abbrev-commit --reverse main..HEAD":
205 return []byte("commit1\ncommit2\ncommit3\n"), nil
206 case "log --reverse --no-merges --pretty=format:%H --name-status commit1^..commit3":
207 return []byte(`commit1
208M foo/c1a.yml
209M foo/c1b.yml
210commit2
211M c2a.yml
212M foo/c2b.yml
213A foo/c2c.yml
214commit3
215
216A bar/c3a.yml
217R053 src.txt c3b.yml
218R100 foo/c3c.txt c3c.yml
219M c2a.yml
220C50 foo/cp1.yml c3d.yml
221D foo/c2b.yml
222R090 foo/c2c.yml c2c.yml
223`), nil
224 case "blame --line-porcelain -- foo/c1a.yml":
225 return blame(map[string][]blameRange{
226 "foo/c1a.yml": {
227 {sha: "commit1", lines: []int{2, 12}}, // 1 & 3
228 {sha: "commitX", lines: []int{1, 3, 4, 5, 6, 7, 8, 9, 10, 11}},
229 },
230 }), nil
231 case "blame --line-porcelain -- foo/c1b.yml":
232 return blame(map[string][]blameRange{
233 "foo/c1b.yml": {
234 {sha: "commit1", lines: []int{11, 12}}, // 3
235 {sha: "commitX", lines: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}},
236 },
237 }), nil
238 case "blame --line-porcelain -- c2a.yml":
239 return blame(map[string][]blameRange{
240 "c2a.yml": {
241 {sha: "commitX", lines: []int{1, 2, 3, 4, 5, 6, 9, 11, 12}},
242 {sha: "commit2", lines: []int{7, 8, 10}}, // 2 & 3
243 {sha: "commit3", lines: []int{3}}, // 1
244 },
245 }), nil
246 case "blame --line-porcelain -- c2c.yml":
247 return blame(map[string][]blameRange{
248 "c2c.yml": {
249 {sha: "commit2", lines: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}, // 2 & 3
250 {sha: "commit3", lines: []int{3}}, // 1
251 },
252 }), nil
253 case "blame --line-porcelain -- c3b.yml":
254 return blame(map[string][]blameRange{
255 "c3b.yml": {
256 {sha: "commitX", lines: []int{1, 11, 12}},
257 {sha: "commit3", lines: []int{2, 3, 4, 5, 6, 7, 8, 9, 10}}, // 1 & 2 & 3
258 },
259 }), nil
260 case "blame --line-porcelain -- c3c.yml":
261 return blame(map[string][]blameRange{
262 "c3c.yml": {
263 {sha: "commit3", lines: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}}, // 1 & 2 & 3
264 },
265 }), nil
266 case "blame --line-porcelain -- c3d.yml":
267 return blame(map[string][]blameRange{
268 "c3d.yml": {
269 {sha: "commitX", lines: []int{1, 11, 12}},
270 {sha: "commit3", lines: []int{2, 3, 4, 5, 6, 7, 8, 9, 10}}, // 1 & 2 & 3
271 },
272 }), nil
273 default:
274 t.Errorf("unknown args: %v", args)
275 t.FailNow()
276 return nil, nil
277 }
278 },
279 []*regexp.Regexp{
280 regexp.MustCompile("^foo/.*"),
281 regexp.MustCompile("^c.*.yml$"),
282 },
283 "main",
284 0,
285 ),
286 rules: map[string][]string{
287 "foo/c1a.yml": {"first", "third"},
288 "foo/c1b.yml": {"third"},
289 "c2a.yml": {"first", "second", "third"},
290 "c2c.yml": {"first", "second", "third"},
291 "c3b.yml": {"first", "second", "third"},
292 "c3c.yml": {"first", "second", "third"},
293 "c3d.yml": {"first", "second", "third"},
294 },
295 },
296 }
297
298 for i, tc := range testCases {
299 t.Run(strconv.Itoa(i), func(t *testing.T) {
300 workdir := t.TempDir()
301 err := os.Chdir(workdir)
302 require.NoError(t, err)
303
304 for p, content := range tc.files {
305 if strings.Contains(p, "/") {
306 err = os.MkdirAll(path.Dir(p), 0o755)
307 require.NoError(t, err)
308 }
309 err = ioutil.WriteFile(p, []byte(content), 0o644)
310 require.NoError(t, err)
311 }
312
313 entries, err := tc.finder.Find()
314 if tc.err != "" {
315 require.EqualError(t, err, tc.err)
316 } else {
317 require.NoError(t, err)
318
319 m := map[string][]string{}
320 for _, e := range entries {
321 if _, ok := m[e.Path]; !ok {
322 m[e.Path] = []string{}
323 }
324 var name string
325 if e.Rule.AlertingRule != nil {
326 name = e.Rule.AlertingRule.Alert.Value.Value
327 } else {
328 name = e.Rule.RecordingRule.Record.Value.Value
329 }
330 m[e.Path] = append(m[e.Path], name)
331 }
332 require.Equal(t, tc.rules, m)
333 }
334 })
335 }
336}
337