|
1 | 1 | package codeowners
|
2 | 2 |
|
3 | 3 | import (
|
| 4 | + "encoding/json" |
4 | 5 | "io/ioutil"
|
5 |
| - "os" |
6 |
| - "os/exec" |
7 |
| - "path" |
8 | 6 | "testing"
|
9 | 7 |
|
10 | 8 | "github.com/stretchr/testify/assert"
|
11 | 9 | "github.com/stretchr/testify/require"
|
12 | 10 | )
|
13 | 11 |
|
| 12 | +type patternTest struct { |
| 13 | + Name string `json:"name"` |
| 14 | + Pattern string `json:"pattern"` |
| 15 | + Paths map[string]bool `json:"paths"` |
| 16 | + Focus bool `json:"focus"` |
| 17 | +} |
| 18 | + |
14 | 19 | func TestMatch(t *testing.T) {
|
15 |
| - examples := []struct { |
16 |
| - name string |
17 |
| - pattern string |
18 |
| - paths map[string]bool |
19 |
| - }{ |
20 |
| - { |
21 |
| - name: "single-segment pattern", |
22 |
| - pattern: "foo", |
23 |
| - paths: map[string]bool{ |
24 |
| - "foo": true, |
25 |
| - "foo/": true, |
26 |
| - "foo/bar": true, |
27 |
| - "bar/foo": true, |
28 |
| - "bar/foo/baz": true, |
29 |
| - "bar/baz": false, |
30 |
| - }, |
31 |
| - }, |
32 |
| - { |
33 |
| - name: "single-segment pattern with leading slash", |
34 |
| - pattern: "/foo", |
35 |
| - paths: map[string]bool{ |
36 |
| - "foo": true, |
37 |
| - "fool": false, |
38 |
| - "foo/": true, |
39 |
| - "foo/bar": true, |
40 |
| - "bar/foo": false, |
41 |
| - "bar/foo/baz": false, |
42 |
| - "bar/baz": false, |
43 |
| - }, |
44 |
| - }, |
45 |
| - { |
46 |
| - name: "single-segment pattern with trailing slash", |
47 |
| - pattern: "foo/", |
48 |
| - paths: map[string]bool{ |
49 |
| - "foo": false, |
50 |
| - "foo/": true, |
51 |
| - "foo/bar": true, |
52 |
| - "bar/foo": false, |
53 |
| - "bar/foo/baz": true, |
54 |
| - "bar/baz": false, |
55 |
| - }, |
56 |
| - }, |
57 |
| - { |
58 |
| - name: "single-segment pattern with leading and trailing slash", |
59 |
| - pattern: "/foo/", |
60 |
| - paths: map[string]bool{ |
61 |
| - "foo": false, |
62 |
| - "foo/": true, |
63 |
| - "foo/bar": true, |
64 |
| - "bar/foo": false, |
65 |
| - "bar/foo/baz": false, |
66 |
| - "bar/baz": false, |
67 |
| - }, |
68 |
| - }, |
69 |
| - { |
70 |
| - name: "multi-segment pattern", |
71 |
| - pattern: "foo/bar", |
72 |
| - paths: map[string]bool{ |
73 |
| - "foo/bar": true, |
74 |
| - "foo/bart": false, |
75 |
| - "foo/bar/baz": true, |
76 |
| - "baz/foo/bar": false, |
77 |
| - "baz/foo/bar/qux": false, |
78 |
| - }, |
79 |
| - }, |
80 |
| - { |
81 |
| - name: "multi-segment pattern with leading slash", |
82 |
| - pattern: "/foo/bar", |
83 |
| - paths: map[string]bool{ |
84 |
| - "foo/bar": true, |
85 |
| - "foo/bar/baz": true, |
86 |
| - "baz/foo/bar": false, |
87 |
| - "baz/foo/bar/qux": false, |
88 |
| - }, |
89 |
| - }, |
90 |
| - { |
91 |
| - name: "multi-segment pattern with trailing slash", |
92 |
| - pattern: "foo/bar/", |
93 |
| - paths: map[string]bool{ |
94 |
| - "foo/bar": false, |
95 |
| - "foo/bar/baz": true, |
96 |
| - "baz/foo/bar": false, |
97 |
| - "baz/foo/bar/qux": false, |
98 |
| - }, |
99 |
| - }, |
100 |
| - { |
101 |
| - name: "multi-segment pattern with leading and trailing slash", |
102 |
| - pattern: "/foo/bar/", |
103 |
| - paths: map[string]bool{ |
104 |
| - "foo/bar": false, |
105 |
| - "foo/bar/baz": true, |
106 |
| - "baz/foo/bar": false, |
107 |
| - "baz/foo/bar/qux": false, |
108 |
| - }, |
109 |
| - }, |
110 |
| - { |
111 |
| - name: "single segment pattern with wildcard", |
112 |
| - pattern: "f*", |
113 |
| - paths: map[string]bool{ |
114 |
| - "foo": true, |
115 |
| - "foo/": true, |
116 |
| - "foo/bar": true, |
117 |
| - "bar/foo": true, |
118 |
| - "bar/foo/baz": true, |
119 |
| - "bar/baz": false, |
120 |
| - "xfoo": false, |
121 |
| - }, |
122 |
| - }, |
123 |
| - { |
124 |
| - name: "single segment pattern with leading slash and wildcard", |
125 |
| - pattern: "/f*", |
126 |
| - paths: map[string]bool{ |
127 |
| - "foo": true, |
128 |
| - "foo/": true, |
129 |
| - "foo/bar": true, |
130 |
| - "bar/foo": false, |
131 |
| - "bar/foo/baz": false, |
132 |
| - "bar/baz": false, |
133 |
| - "xfoo": false, |
134 |
| - }, |
135 |
| - }, |
136 |
| - { |
137 |
| - name: "single segment pattern with trailing slash and wildcard", |
138 |
| - pattern: "f*/", |
139 |
| - paths: map[string]bool{ |
140 |
| - "foo": false, |
141 |
| - "foo/": true, |
142 |
| - "foo/bar": true, |
143 |
| - "bar/foo": false, |
144 |
| - "bar/foo/baz": true, |
145 |
| - "bar/baz": false, |
146 |
| - "xfoo": false, |
147 |
| - }, |
148 |
| - }, |
149 |
| - { |
150 |
| - name: "single segment pattern with leading and trailing slash and wildcard", |
151 |
| - pattern: "/f*/", |
152 |
| - paths: map[string]bool{ |
153 |
| - "foo": false, |
154 |
| - "foo/": true, |
155 |
| - "foo/bar": true, |
156 |
| - "bar/foo": false, |
157 |
| - "bar/foo/baz": false, |
158 |
| - "bar/baz": false, |
159 |
| - "xfoo": false, |
160 |
| - }, |
161 |
| - }, |
162 |
| - { |
163 |
| - name: "single segment pattern with escaped wildcard", |
164 |
| - pattern: "f\\*o", |
165 |
| - paths: map[string]bool{ |
166 |
| - "foo": false, |
167 |
| - "f*o": true, |
168 |
| - }, |
169 |
| - }, |
170 |
| - { |
171 |
| - name: "multi-segment pattern with wildcard", |
172 |
| - pattern: "foo/*.txt", |
173 |
| - paths: map[string]bool{ |
174 |
| - "foo": false, |
175 |
| - "foo/": false, |
176 |
| - "foo/bar.txt": true, |
177 |
| - "foo/bar/baz.txt": false, |
178 |
| - "qux/foo/bar.txt": false, |
179 |
| - "qux/foo/bar/baz.txt": false, |
180 |
| - }, |
181 |
| - }, |
182 |
| - { |
183 |
| - name: "single segment pattern with single-character wildcard", |
184 |
| - pattern: "f?o", |
185 |
| - paths: map[string]bool{ |
186 |
| - "foo": true, |
187 |
| - "fo": false, |
188 |
| - "fooo": false, |
189 |
| - }, |
190 |
| - }, |
191 |
| - { |
192 |
| - name: "single segment pattern with escaped single-character wildcard", |
193 |
| - pattern: "f\\?o", |
194 |
| - paths: map[string]bool{ |
195 |
| - "foo": false, |
196 |
| - "f?o": true, |
197 |
| - }, |
198 |
| - }, |
199 |
| - { |
200 |
| - name: "single segment pattern with character range", |
201 |
| - pattern: "[Ffb]oo", |
202 |
| - paths: map[string]bool{ |
203 |
| - "foo": true, |
204 |
| - "Foo": true, |
205 |
| - "boo": true, |
206 |
| - "too": false, |
207 |
| - }, |
208 |
| - }, |
209 |
| - { |
210 |
| - name: "single segment pattern with escaped character range", |
211 |
| - pattern: "[\\]f]o\\[o\\]", |
212 |
| - paths: map[string]bool{ |
213 |
| - "fo[o]": true, |
214 |
| - "]o[o]": true, |
215 |
| - "foo": false, |
216 |
| - }, |
217 |
| - }, |
218 |
| - { |
219 |
| - name: "leading double-asterisk wildcard", |
220 |
| - pattern: "**/foo/bar", |
221 |
| - paths: map[string]bool{ |
222 |
| - "foo/bar": true, |
223 |
| - "qux/foo/bar": true, |
224 |
| - "qux/foo/bar/baz": true, |
225 |
| - "foo/baz/bar": false, |
226 |
| - "qux/foo/baz/bar": false, |
227 |
| - }, |
228 |
| - }, |
229 |
| - { |
230 |
| - name: "leading double-asterisk wildcard with regular wildcard", |
231 |
| - pattern: "**/*bar*", |
232 |
| - paths: map[string]bool{ |
233 |
| - "bar": true, |
234 |
| - "foo/bar": true, |
235 |
| - "foo/rebar": true, |
236 |
| - "foo/barrio": true, |
237 |
| - "foo/qux/bar": true, |
238 |
| - }, |
239 |
| - }, |
240 |
| - { |
241 |
| - name: "trailing double-asterisk wildcard", |
242 |
| - pattern: "foo/bar/**", |
243 |
| - paths: map[string]bool{ |
244 |
| - "foo/bar": false, |
245 |
| - "foo/bar/baz": true, |
246 |
| - "foo/bar/baz/qux": true, |
247 |
| - "qux/foo/bar": false, |
248 |
| - "qux/foo/bar/baz": false, |
249 |
| - }, |
250 |
| - }, |
251 |
| - { |
252 |
| - name: "middle double-asterisk wildcard", |
253 |
| - pattern: "foo/**/bar", |
254 |
| - paths: map[string]bool{ |
255 |
| - "foo/bar": true, |
256 |
| - "foo/bar/baz": true, |
257 |
| - "foo/qux/bar/baz": true, |
258 |
| - "foo/qux/quux/bar/baz": true, |
259 |
| - "foo/bar/baz/qux": true, |
260 |
| - "qux/foo/bar": false, |
261 |
| - "qux/foo/bar/baz": false, |
262 |
| - }, |
263 |
| - }, |
264 |
| - { |
265 |
| - name: "middle double-asterisk wildcard with trailing slash", |
266 |
| - pattern: "foo/**/", |
267 |
| - paths: map[string]bool{ |
268 |
| - "foo/bar": false, |
269 |
| - "foo/bar/": true, |
270 |
| - "foo/bar/baz": true, |
271 |
| - }, |
272 |
| - }, |
273 |
| - } |
| 20 | + data, err := ioutil.ReadFile("testdata/patterns.json") |
| 21 | + require.NoError(t, err) |
274 | 22 |
|
275 |
| - tmpRepoPath, cleanup := createGitRepo(t) |
276 |
| - defer cleanup() |
| 23 | + var tests []patternTest |
| 24 | + err = json.Unmarshal(data, &tests) |
| 25 | + require.NoError(t, err) |
277 | 26 |
|
278 |
| - for _, e := range examples { |
279 |
| - ioutil.WriteFile(path.Join(tmpRepoPath, ".gitignore"), []byte(e.pattern+"\n"), 0644) |
| 27 | + focus := false |
| 28 | + for _, test := range tests { |
| 29 | + if test.Focus { |
| 30 | + focus = true |
| 31 | + } |
| 32 | + } |
280 | 33 |
|
281 |
| - t.Run(e.name, func(t *testing.T) { |
282 |
| - for path, shouldMatch := range e.paths { |
283 |
| - gitMatch := gitCheckIgnore(t, tmpRepoPath, path) |
284 |
| - require.Equal(t, gitMatch, shouldMatch, "bad test! pattern=%s path=%s git-match=%v expectation=%v", e.pattern, path, gitMatch, shouldMatch) |
| 34 | + for _, test := range tests { |
| 35 | + if test.Focus != focus { |
| 36 | + continue |
| 37 | + } |
285 | 38 |
|
286 |
| - pattern, err := newPattern(e.pattern) |
| 39 | + t.Run(test.Name, func(t *testing.T) { |
| 40 | + for path, shouldMatch := range test.Paths { |
| 41 | + pattern, err := newPattern(test.Pattern) |
287 | 42 | require.NoError(t, err)
|
288 | 43 |
|
| 44 | + // Debugging tips: |
| 45 | + // - Print the generated regex: `fmt.Println(pattern.regex.String())` |
| 46 | + // - Only run a single case by adding `"focus" : true` to the test in the JSON file |
| 47 | + |
289 | 48 | actual, err := pattern.match(path)
|
290 |
| - assert.NoError(t, err) |
| 49 | + require.NoError(t, err) |
| 50 | + |
291 | 51 | if shouldMatch {
|
292 |
| - assert.True(t, actual, "expected pattern %s to match path %s", e.pattern, path) |
| 52 | + assert.True(t, actual, "expected pattern %s to match path %s", test.Pattern, path) |
293 | 53 | } else {
|
294 |
| - assert.False(t, actual, "expected pattern %s to not match path %s", e.pattern, path) |
| 54 | + assert.False(t, actual, "expected pattern %s to not match path %s", test.Pattern, path) |
295 | 55 | }
|
296 | 56 | }
|
297 | 57 | })
|
298 | 58 | }
|
299 | 59 | }
|
300 |
| - |
301 |
| -func createGitRepo(t *testing.T) (string, func()) { |
302 |
| - dir, err := ioutil.TempDir("", "codeowners-test-") |
303 |
| - if err != nil { |
304 |
| - t.Fatalf("creating git repo tempdir: %v", err) |
305 |
| - } |
306 |
| - |
307 |
| - cmd := exec.Command("git", "init") |
308 |
| - cmd.Dir = dir |
309 |
| - if err = cmd.Run(); err != nil { |
310 |
| - t.Fatalf("initializing git repo: %v", err) |
311 |
| - } |
312 |
| - |
313 |
| - cleanup := func() { |
314 |
| - os.RemoveAll(dir) // clean up |
315 |
| - } |
316 |
| - |
317 |
| - return dir, cleanup |
318 |
| -} |
319 |
| - |
320 |
| -func gitCheckIgnore(t *testing.T, dir, path string) bool { |
321 |
| - cmd := exec.Command("git", "check-ignore", path) |
322 |
| - cmd.Dir = dir |
323 |
| - if err := cmd.Run(); err != nil { |
324 |
| - if _, isExitError := err.(*exec.ExitError); isExitError { |
325 |
| - return false |
326 |
| - } |
327 |
| - t.Fatalf("running git check-ignore: %v", err) |
328 |
| - } |
329 |
| - return true |
330 |
| -} |
0 commit comments