|
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