blob: aac693d85310ef147a445e25ba4fe7873085ddc7 [file] [log] [blame]
Brian Sheedy34355d02025-03-06 11:34:36 -08001// Copyright 2025 Google LLC
2//
3// Redistribution and use in source and binary forms, with or without
4// modification, are permitted provided that the following conditions are met:
5//
6// 1. Redistributions of source code must retain the above copyright notice, this
7// list of conditions and the following disclaimer.
8//
9// 2. Redistributions in binary form must reproduce the above copyright notice,
10// this list of conditions and the following disclaimer in the documentation
11// and/or other materials provided with the distribution.
12//
13// 3. Neither the name of the copyright holder nor the names of its
14// contributors may be used to endorse or promote products derived from
15// this software without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28package glob
29
30import (
31 "fmt"
32 "strings"
33 "testing"
34
35 "dawn.googlesource.com/dawn/tools/src/oswrapper"
36 "github.com/stretchr/testify/require"
37)
38
39func TestGlob(t *testing.T) {
40 tests := []struct {
41 name string
42 input string
43 want []string
44 wantErr bool
45 wantErrMsg string
46 }{
47 // We can't really test the error condition from the filepath.Abs() call, as
48 // it basically only fails if the given path is not absolute and the cwd
49 // does not exist.
50 {
51 name: "Match no wildcard",
52 input: "/a/1/file_1.txt",
53 want: []string{"/a/1/file_1.txt"},
54 },
55 {
56 name: "No match no wildcard is directory",
57 input: "/a/1",
58 want: []string{},
59 },
60 {
61 name: "Match star wildcard files",
62 input: "/a/1/*",
63 want: []string{"/a/1/file_1.txt", "/a/1/file_2.txt"},
64 },
65 {
66 name: "Match star wildcard child directory",
67 input: "/a/*/file_1.txt",
68 want: []string{"/a/1/file_1.txt", "/a/2/file_1.txt", "/a/3/file_1.txt"},
69 },
70 {
Brian Sheedyb13d15c2025-11-20 06:56:17 -080071 name: "Match star wildcard parent directory",
72 input: "/*/1/file_1.txt",
73 want: []string{"/a/1/file_1.txt", "/b/1/file_1.txt", "/c/1/file_1.txt"},
Brian Sheedy34355d02025-03-06 11:34:36 -080074 },
75 {
76 name: "Match question wildcard files",
77 input: "/a/1/file_?.txt",
78 want: []string{"/a/1/file_1.txt", "/a/1/file_2.txt"},
79 },
80 {
81 name: "No match question wildcard files",
82 input: "/a/1/?file_1.txt",
83 want: []string{},
84 },
85 {
86 name: "Match question filecard child directory",
87 input: "/a/?/file_1.txt",
88 want: []string{"/a/1/file_1.txt", "/a/2/file_1.txt", "/a/3/file_1.txt"},
89 },
90 {
Brian Sheedyb13d15c2025-11-20 06:56:17 -080091 name: "Match question filecard parent directory",
92 input: "/?/1/file_1.txt",
93 want: []string{"/a/1/file_1.txt", "/b/1/file_1.txt", "/c/1/file_1.txt"},
Brian Sheedy34355d02025-03-06 11:34:36 -080094 },
95 {
96 name: "No match no wildcard non existent file",
97 input: "/a/1/file_3.txt",
98 want: []string{},
99 },
100 {
101 name: "No match no wildcard non existent directory",
102 input: "/a/5/file_1.txt",
103 want: []string{},
104 },
105 {
106 name: "No match star wildcard non existent file",
107 input: "/a/1/foo*_1.txt",
108 want: []string{},
109 },
110 {
111 name: "No match star wildcard non existent directory",
112 input: "/a/5*/file_1.txt",
113 want: []string{},
114 },
115 {
116 name: "No match question wildcard non existent file",
117 input: "/a/1/file?_1.txt",
118 want: []string{},
119 },
120 {
121 name: "No match question wildcard non existent directory",
122 input: "/a/5?/file_1.txt",
123 want: []string{},
124 },
Brian Sheedyb13d15c2025-11-20 06:56:17 -0800125 {
126 name: "No match root dir",
127 input: "/",
128 want: []string{},
129 },
Brian Sheedy34355d02025-03-06 11:34:36 -0800130 }
131
132 for _, testCase := range tests {
133 t.Run(testCase.name, func(t *testing.T) {
Brian Sheedyb13d15c2025-11-20 06:56:17 -0800134 wrapper := oswrapper.CreateFSTestOSWrapper()
Brian Sheedy34355d02025-03-06 11:34:36 -0800135 parentDirs := []string{"a", "b", "c"}
136 childDirs := []string{"1", "2", "3"}
137 fileNames := []string{"file_1.txt", "file_2.txt"}
138 for _, pd := range parentDirs {
139 for _, cd := range childDirs {
140 wrapper.MkdirAll(fmt.Sprintf("/%s/%s", pd, cd), 0o700)
141 for _, fn := range fileNames {
142 wrapper.Create(fmt.Sprintf("/%s/%s/%s", pd, cd, fn))
143 }
144 }
145 }
146
147 matches, err := Glob(testCase.input, wrapper)
148 require.Equal(t, testCase.want, matches)
149 if testCase.wantErr {
150 require.ErrorContains(t, err, testCase.wantErrMsg)
151 } else {
152 require.NoErrorf(t, err, "Failed to glob: %v", err)
153 }
154 })
155 }
156}
157
158func TestScan(t *testing.T) {
159 tests := []struct {
160 name string
161 root string
162 condition rule
163 want []string
164 wantErr bool
165 wantErrMsg string
166 }{
167 {
168 name: "Scan everything",
169 root: "/",
170 condition: func(path string, cond bool) bool { return true },
171 want: []string{
172 ".other/1/file_1.txt",
173 ".other/1/file_2.txt",
174 ".other/2/file_1.txt",
175 ".other/2/file_2.txt",
176 "a/1/file_1.txt",
177 "a/1/file_2.txt",
178 "a/2/file_1.txt",
179 "a/2/file_2.txt",
180 "b/1/file_1.txt",
181 "b/1/file_2.txt",
182 "b/2/file_1.txt",
183 "b/2/file_2.txt",
184 },
185 },
186 {
187 name: "Scan nothing",
188 root: "/",
189 condition: func(path string, cond bool) bool { return false },
190 want: []string{},
191 },
192 {
193 name: "Scan everything with subdir root",
194 root: "/a",
195 condition: func(path string, cond bool) bool { return true },
196 want: []string{
197 "1/file_1.txt",
198 "1/file_2.txt",
199 "2/file_1.txt",
200 "2/file_2.txt",
201 },
202 },
203 {
204 name: "Scan with rule",
205 root: "/",
206 condition: func(path string, cond bool) bool { return strings.Contains(path, "/2/") },
207 want: []string{
208 ".other/2/file_1.txt",
209 ".other/2/file_2.txt",
210 "a/2/file_1.txt",
211 "a/2/file_2.txt",
212 "b/2/file_1.txt",
213 "b/2/file_2.txt",
214 },
215 },
216 {
Brian Sheedyb13d15c2025-11-20 06:56:17 -0800217 // Treated as CWD.
218 // TODO(crbug.com/436025865): Update this to actually switch directories
219 // once FSTestOSWrapper is CWD-aware.
220 name: "Empty root",
221 root: "",
222 condition: func(path string, cond bool) bool { return true },
223 want: []string{
224 ".other/1/file_1.txt",
225 ".other/1/file_2.txt",
226 ".other/2/file_1.txt",
227 ".other/2/file_2.txt",
228 "a/1/file_1.txt",
229 "a/1/file_2.txt",
230 "a/2/file_1.txt",
231 "a/2/file_2.txt",
232 "b/1/file_1.txt",
233 "b/1/file_2.txt",
234 "b/2/file_1.txt",
235 "b/2/file_2.txt",
236 },
Brian Sheedy34355d02025-03-06 11:34:36 -0800237 },
238 {
Brian Sheedyb13d15c2025-11-20 06:56:17 -0800239 // TODO(crbug.com/436025865): Update error message when FSTestOSWrapper
240 // properly handles leading / in errors.
Brian Sheedy34355d02025-03-06 11:34:36 -0800241 name: "Non-existent root",
242 root: "/asdf",
243 condition: func(path string, cond bool) bool { return true },
244 want: nil,
245 wantErr: true,
Brian Sheedyb13d15c2025-11-20 06:56:17 -0800246 wantErrMsg: "open asdf: file does not exist",
Brian Sheedy34355d02025-03-06 11:34:36 -0800247 },
248 }
249
250 for _, testCase := range tests {
251 t.Run(testCase.name, func(t *testing.T) {
Brian Sheedyb13d15c2025-11-20 06:56:17 -0800252 wrapper := oswrapper.CreateFSTestOSWrapper()
Brian Sheedy34355d02025-03-06 11:34:36 -0800253 parentDirs := []string{"a", "b", ".git", ".other"}
254 childDirs := []string{"1", "2"}
255 fileNames := []string{"file_1.txt", "file_2.txt"}
256 for _, pd := range parentDirs {
257 for _, cd := range childDirs {
258 wrapper.MkdirAll(fmt.Sprintf("/%s/%s", pd, cd), 0o700)
259 for _, fn := range fileNames {
260 wrapper.Create(fmt.Sprintf("/%s/%s/%s", pd, cd, fn))
261 }
262 }
263 }
264
265 matches, err := Scan(testCase.root, Config{Paths: searchRules{testCase.condition}}, wrapper)
266 require.Equal(t, testCase.want, matches)
267 if testCase.wantErr {
268 require.ErrorContains(t, err, testCase.wantErrMsg)
269 } else {
270 require.NoErrorf(t, err, "Failed to scan: %v", err)
271 }
272 })
273 }
274}
275
276func TestLoadConfig(t *testing.T) {
277 tests := []struct {
278 name string
279 cfgPath string
280 cfgJson string
281 wantNumRules int
282 wantErr bool
283 wantErrMsg string
284 }{
285 {
286 name: "Success",
287 cfgPath: "/config.cfg",
288 // This is what was in tools/src/cmd/trim-includes/config.cfg at the time of writing.
289 cfgJson: `{
290 "paths": [
291 { "include": [ "src/**.h", "src/**.cc" ] },
292 { "exclude": [ "src/**_windows.*", "src/**_other.*" ] }
293 ]
294}`,
295 wantNumRules: 2,
296 },
297 {
298 name: "Both include and exclude",
299 cfgPath: "/config.cfg",
300 cfgJson: `{
301 "paths": [
302 { "include": [ "src/**.h", "src/**.cc" ], "exclude": [ "src/**.h" ] }
303 ]
304}`,
305 wantErr: true,
306 wantErrMsg: "Rule cannot contain both include and exclude",
307 },
308 {
309 name: "Invalid path",
310 cfgPath: "/asdf.cfg",
311 cfgJson: "{}",
312 wantErr: true,
313 wantErrMsg: "open /asdf.cfg: file does not exist",
314 },
315 }
316
317 for _, testCase := range tests {
318 t.Run(testCase.name, func(t *testing.T) {
Brian Sheedyb13d15c2025-11-20 06:56:17 -0800319 wrapper := oswrapper.CreateFSTestOSWrapper()
Brian Sheedy34355d02025-03-06 11:34:36 -0800320 wrapper.WriteFile("/config.cfg", []byte(testCase.cfgJson), 0o700)
321
322 cfg, err := LoadConfig(testCase.cfgPath, wrapper)
323 // We can't check equality since the loaded Config contains function
324 // pointers to anonymous functions. So, just check that the number of
325 // rules matches.
Ryan Harrison5f85f4f2025-08-18 14:04:16 -0700326 require.Len(t, cfg.Paths, testCase.wantNumRules)
Brian Sheedy34355d02025-03-06 11:34:36 -0800327 if testCase.wantErr {
328 require.ErrorContains(t, err, testCase.wantErrMsg)
329 } else {
330 require.NoErrorf(t, err, "Failed to load config: %v", err)
331 }
332 })
333 }
334}