blob: 3bcce2f8dcf7799746dea802c033e6571fba24cd [file] [log] [blame]
Colin Crossc64f2642018-09-20 21:48:44 -07001// Copyright 2018 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package pathtools
16
17import (
18 "os"
19 "path/filepath"
20 "reflect"
21 "syscall"
22 "testing"
23)
24
25func symlinkMockFs() *mockFs {
26 files := []string{
27 "a/a/a",
28 "a/a/f -> ../../f",
29 "b -> a",
30 "c -> a/a",
31 "d -> c",
32 "e -> a/a/a",
33 "dangling -> missing",
34 "f",
35 }
36
37 mockFiles := make(map[string][]byte)
38
39 for _, f := range files {
40 mockFiles[f] = nil
41 mockFiles[filepath.Join(pwd, "testdata", f)] = nil
42 }
43
44 return MockFs(mockFiles).(*mockFs)
45}
46
47func TestMockFs_followSymlinks(t *testing.T) {
48
49 testCases := []struct {
50 from, to string
51 }{
52 {".", "."},
53 {"/", "/"},
54
55 {"a", "a"},
56 {"a/a", "a/a"},
57 {"a/a/a", "a/a/a"},
58 {"a/a/f", "f"},
59
60 {"b", "a"},
61 {"b/a", "a/a"},
62 {"b/a/a", "a/a/a"},
63 {"b/a/f", "f"},
64
65 {"c/a", "a/a/a"},
66 {"c/f", "f"},
67
68 {"d/a", "a/a/a"},
Colin Crossdaf9de12018-09-26 16:49:14 -070069 {"d/f", "f"},
Colin Crossc64f2642018-09-20 21:48:44 -070070
71 {"e", "a/a/a"},
72
73 {"f", "f"},
74
75 {"dangling", "missing"},
76
77 {"a/missing", "a/missing"},
78 {"b/missing", "a/missing"},
79 {"c/missing", "a/a/missing"},
80 {"d/missing", "a/a/missing"},
81 {"e/missing", "a/a/a/missing"},
82 {"dangling/missing", "missing/missing"},
83
84 {"a/missing/missing", "a/missing/missing"},
85 {"b/missing/missing", "a/missing/missing"},
86 {"c/missing/missing", "a/a/missing/missing"},
87 {"d/missing/missing", "a/a/missing/missing"},
88 {"e/missing/missing", "a/a/a/missing/missing"},
89 {"dangling/missing/missing", "missing/missing/missing"},
90 }
91
92 mock := symlinkMockFs()
93
94 for _, test := range testCases {
95 t.Run(test.from, func(t *testing.T) {
96 got := mock.followSymlinks(test.from)
97 if got != test.to {
98 t.Errorf("want: %v, got %v", test.to, got)
99 }
100 })
101 }
102}
103
Colin Cross9e1ff742018-09-20 22:44:36 -0700104func TestFs_IsDir(t *testing.T) {
Colin Crossc64f2642018-09-20 21:48:44 -0700105 testCases := []struct {
106 name string
107 isDir bool
108 err error
109 }{
110 {"a", true, nil},
111 {"a/a", true, nil},
112 {"a/a/a", false, nil},
113 {"a/a/f", false, nil},
114
115 {"b", true, nil},
116 {"b/a", true, nil},
117 {"b/a/a", false, nil},
118 {"b/a/f", false, nil},
119
120 {"c", true, nil},
121 {"c/a", false, nil},
122 {"c/f", false, nil},
123
124 {"d", true, nil},
125 {"d/a", false, nil},
126 {"d/f", false, nil},
127
128 {"e", false, nil},
129
130 {"f", false, nil},
131
132 {"dangling", false, os.ErrNotExist},
133
134 {"a/missing", false, os.ErrNotExist},
135 {"b/missing", false, os.ErrNotExist},
136 {"c/missing", false, os.ErrNotExist},
137 {"d/missing", false, os.ErrNotExist},
Colin Crossd85b3c72018-09-26 16:52:26 -0700138 {"e/missing", false, syscall.ENOTDIR},
Colin Crossc64f2642018-09-20 21:48:44 -0700139 {"dangling/missing", false, os.ErrNotExist},
140
141 {"a/missing/missing", false, os.ErrNotExist},
142 {"b/missing/missing", false, os.ErrNotExist},
143 {"c/missing/missing", false, os.ErrNotExist},
144 {"d/missing/missing", false, os.ErrNotExist},
Colin Crossd85b3c72018-09-26 16:52:26 -0700145 {"e/missing/missing", false, syscall.ENOTDIR},
Colin Crossc64f2642018-09-20 21:48:44 -0700146 {"dangling/missing/missing", false, os.ErrNotExist},
147
Colin Crossd85b3c72018-09-26 16:52:26 -0700148 {"c/f/missing", false, syscall.ENOTDIR},
Colin Crossc64f2642018-09-20 21:48:44 -0700149 }
150
151 mock := symlinkMockFs()
Colin Cross9e1ff742018-09-20 22:44:36 -0700152 fsList := []FileSystem{mock, OsFs}
153 names := []string{"mock", "os"}
Colin Crossc64f2642018-09-20 21:48:44 -0700154
Colin Cross9e1ff742018-09-20 22:44:36 -0700155 os.Chdir("testdata/dangling")
156 defer os.Chdir("../..")
157
158 for i, fs := range fsList {
159 t.Run(names[i], func(t *testing.T) {
160 for _, test := range testCases {
161 t.Run(test.name, func(t *testing.T) {
162 got, err := fs.IsDir(test.name)
163 checkErr(t, test.err, err)
164 if got != test.isDir {
165 t.Errorf("want: %v, got %v", test.isDir, got)
166 }
167 })
Colin Crossc64f2642018-09-20 21:48:44 -0700168 }
Colin Cross9e1ff742018-09-20 22:44:36 -0700169 })
170 }
171}
172
Colin Crosse98d0822018-09-21 15:30:13 -0700173func TestFs_ListDirsRecursiveFollowSymlinks(t *testing.T) {
Colin Cross9e1ff742018-09-20 22:44:36 -0700174 testCases := []struct {
175 name string
176 dirs []string
177 err error
178 }{
179 {".", []string{".", "a", "a/a", "b", "b/a", "c", "d"}, nil},
180
181 {"a", []string{"a", "a/a"}, nil},
182 {"a/a", []string{"a/a"}, nil},
183 {"a/a/a", nil, nil},
184
185 {"b", []string{"b", "b/a"}, nil},
186 {"b/a", []string{"b/a"}, nil},
187 {"b/a/a", nil, nil},
188
189 {"c", []string{"c"}, nil},
190 {"c/a", nil, nil},
191
192 {"d", []string{"d"}, nil},
193 {"d/a", nil, nil},
194
195 {"e", nil, nil},
196
197 {"dangling", nil, os.ErrNotExist},
198
199 {"missing", nil, os.ErrNotExist},
200 }
201
202 mock := symlinkMockFs()
203 fsList := []FileSystem{mock, OsFs}
204 names := []string{"mock", "os"}
205
206 os.Chdir("testdata/dangling")
207 defer os.Chdir("../..")
208
209 for i, fs := range fsList {
210 t.Run(names[i], func(t *testing.T) {
211
212 for _, test := range testCases {
213 t.Run(test.name, func(t *testing.T) {
Colin Crosse98d0822018-09-21 15:30:13 -0700214 got, err := fs.ListDirsRecursive(test.name, FollowSymlinks)
215 checkErr(t, test.err, err)
216 if !reflect.DeepEqual(got, test.dirs) {
217 t.Errorf("want: %v, got %v", test.dirs, got)
218 }
219 })
220 }
221 })
222 }
223}
224
225func TestFs_ListDirsRecursiveDontFollowSymlinks(t *testing.T) {
226 testCases := []struct {
227 name string
228 dirs []string
229 err error
230 }{
231 {".", []string{".", "a", "a/a"}, nil},
232
233 {"a", []string{"a", "a/a"}, nil},
234 {"a/a", []string{"a/a"}, nil},
235 {"a/a/a", nil, nil},
236
237 {"b", []string{"b", "b/a"}, nil},
238 {"b/a", []string{"b/a"}, nil},
239 {"b/a/a", nil, nil},
240
241 {"c", []string{"c"}, nil},
242 {"c/a", nil, nil},
243
244 {"d", []string{"d"}, nil},
245 {"d/a", nil, nil},
246
247 {"e", nil, nil},
248
249 {"dangling", nil, os.ErrNotExist},
250
251 {"missing", nil, os.ErrNotExist},
252 }
253
254 mock := symlinkMockFs()
255 fsList := []FileSystem{mock, OsFs}
256 names := []string{"mock", "os"}
257
258 os.Chdir("testdata/dangling")
259 defer os.Chdir("../..")
260
261 for i, fs := range fsList {
262 t.Run(names[i], func(t *testing.T) {
263
264 for _, test := range testCases {
265 t.Run(test.name, func(t *testing.T) {
266 got, err := fs.ListDirsRecursive(test.name, DontFollowSymlinks)
Colin Cross9e1ff742018-09-20 22:44:36 -0700267 checkErr(t, test.err, err)
268 if !reflect.DeepEqual(got, test.dirs) {
269 t.Errorf("want: %v, got %v", test.dirs, got)
270 }
271 })
Colin Crossc64f2642018-09-20 21:48:44 -0700272 }
273 })
274 }
275}
276
Colin Crosse81b4322018-09-26 16:53:26 -0700277func TestFs_Readlink(t *testing.T) {
278 testCases := []struct {
279 from, to string
280 err error
281 }{
282 {".", "", syscall.EINVAL},
283 {"/", "", syscall.EINVAL},
284
285 {"a", "", syscall.EINVAL},
286 {"a/a", "", syscall.EINVAL},
287 {"a/a/a", "", syscall.EINVAL},
288 {"a/a/f", "../../f", nil},
289
290 {"b", "a", nil},
291 {"b/a", "", syscall.EINVAL},
292 {"b/a/a", "", syscall.EINVAL},
293 {"b/a/f", "../../f", nil},
294
295 {"c", "a/a", nil},
296 {"c/a", "", syscall.EINVAL},
297 {"c/f", "../../f", nil},
298
299 {"d/a", "", syscall.EINVAL},
300 {"d/f", "../../f", nil},
301
302 {"e", "a/a/a", nil},
303
304 {"f", "", syscall.EINVAL},
305
306 {"dangling", "missing", nil},
307
308 {"a/missing", "", os.ErrNotExist},
309 {"b/missing", "", os.ErrNotExist},
310 {"c/missing", "", os.ErrNotExist},
311 {"d/missing", "", os.ErrNotExist},
312 {"e/missing", "", os.ErrNotExist},
313 {"dangling/missing", "", os.ErrNotExist},
314
315 {"a/missing/missing", "", os.ErrNotExist},
316 {"b/missing/missing", "", os.ErrNotExist},
317 {"c/missing/missing", "", os.ErrNotExist},
318 {"d/missing/missing", "", os.ErrNotExist},
319 {"e/missing/missing", "", os.ErrNotExist},
320 {"dangling/missing/missing", "", os.ErrNotExist},
321 }
322
323 mock := symlinkMockFs()
324 fsList := []FileSystem{mock, OsFs}
325 names := []string{"mock", "os"}
326
327 os.Chdir("testdata/dangling")
328 defer os.Chdir("../..")
329
330 for i, fs := range fsList {
331 t.Run(names[i], func(t *testing.T) {
332
333 for _, test := range testCases {
334 t.Run(test.from, func(t *testing.T) {
335 got, err := fs.Readlink(test.from)
336 checkErr(t, test.err, err)
337 if got != test.to {
338 t.Errorf("fs.Readlink(%q) want: %q, got %q", test.from, test.to, got)
339 }
340 })
341 }
342 })
343 }
344}
345
Colin Cross15fbefb2018-09-27 15:46:30 -0700346func TestFs_Lstat(t *testing.T) {
347 testCases := []struct {
348 name string
349 mode os.FileMode
350 size int64
351 err error
352 }{
353 {".", os.ModeDir, 0, nil},
354 {"/", os.ModeDir, 0, nil},
355
356 {"a", os.ModeDir, 0, nil},
357 {"a/a", os.ModeDir, 0, nil},
358 {"a/a/a", 0, 0, nil},
359 {"a/a/f", os.ModeSymlink, 7, nil},
360
361 {"b", os.ModeSymlink, 1, nil},
362 {"b/a", os.ModeDir, 0, nil},
363 {"b/a/a", 0, 0, nil},
364 {"b/a/f", os.ModeSymlink, 7, nil},
365
366 {"c", os.ModeSymlink, 3, nil},
367 {"c/a", 0, 0, nil},
368 {"c/f", os.ModeSymlink, 7, nil},
369
370 {"d/a", 0, 0, nil},
371 {"d/f", os.ModeSymlink, 7, nil},
372
373 {"e", os.ModeSymlink, 5, nil},
374
375 {"f", 0, 0, nil},
376
377 {"dangling", os.ModeSymlink, 7, nil},
378
379 {"a/missing", 0, 0, os.ErrNotExist},
380 {"b/missing", 0, 0, os.ErrNotExist},
381 {"c/missing", 0, 0, os.ErrNotExist},
382 {"d/missing", 0, 0, os.ErrNotExist},
383 {"e/missing", 0, 0, os.ErrNotExist},
384 {"dangling/missing", 0, 0, os.ErrNotExist},
385
386 {"a/missing/missing", 0, 0, os.ErrNotExist},
387 {"b/missing/missing", 0, 0, os.ErrNotExist},
388 {"c/missing/missing", 0, 0, os.ErrNotExist},
389 {"d/missing/missing", 0, 0, os.ErrNotExist},
390 {"e/missing/missing", 0, 0, os.ErrNotExist},
391 {"dangling/missing/missing", 0, 0, os.ErrNotExist},
392 }
393
394 mock := symlinkMockFs()
395 fsList := []FileSystem{mock, OsFs}
396 names := []string{"mock", "os"}
397
398 os.Chdir("testdata/dangling")
399 defer os.Chdir("../..")
400
401 for i, fs := range fsList {
402 t.Run(names[i], func(t *testing.T) {
403
404 for _, test := range testCases {
405 t.Run(test.name, func(t *testing.T) {
406 got, err := fs.Lstat(test.name)
407 checkErr(t, test.err, err)
408 if err != nil {
409 return
410 }
411 if got.Mode()&os.ModeType != test.mode {
412 t.Errorf("fs.Readlink(%q).Mode()&os.ModeType want: %x, got %x",
413 test.name, test.mode, got.Mode()&os.ModeType)
414 }
415 if test.mode == 0 && got.Size() != test.size {
416 t.Errorf("fs.Readlink(%q).Size() want: %d, got %d", test.name, test.size, got.Size())
417 }
418 })
419 }
420 })
421 }
422}
423
Colin Crossc64f2642018-09-20 21:48:44 -0700424func TestMockFs_glob(t *testing.T) {
425 testCases := []struct {
426 pattern string
427 files []string
428 }{
429 {"*", []string{"a", "b", "c", "d", "dangling", "e", "f"}},
430 {"./*", []string{"a", "b", "c", "d", "dangling", "e", "f"}},
431 {"a", []string{"a"}},
432 {"a/a", []string{"a/a"}},
433 {"a/*", []string{"a/a"}},
434 {"a/a/a", []string{"a/a/a"}},
435 {"a/a/f", []string{"a/a/f"}},
436 {"a/a/*", []string{"a/a/a", "a/a/f"}},
437
438 {"b", []string{"b"}},
439 {"b/a", []string{"b/a"}},
440 {"b/*", []string{"b/a"}},
441 {"b/a/a", []string{"b/a/a"}},
442 {"b/a/f", []string{"b/a/f"}},
443 {"b/a/*", []string{"b/a/a", "b/a/f"}},
444
445 {"c", []string{"c"}},
446 {"c/a", []string{"c/a"}},
447 {"c/f", []string{"c/f"}},
448 {"c/*", []string{"c/a", "c/f"}},
449
450 {"d", []string{"d"}},
451 {"d/a", []string{"d/a"}},
452 {"d/f", []string{"d/f"}},
453 {"d/*", []string{"d/a", "d/f"}},
454
455 {"e", []string{"e"}},
456
457 {"dangling", []string{"dangling"}},
458
459 {"missing", nil},
460 }
461
462 mock := symlinkMockFs()
Colin Cross9e1ff742018-09-20 22:44:36 -0700463 fsList := []FileSystem{mock, OsFs}
464 names := []string{"mock", "os"}
Colin Crossc64f2642018-09-20 21:48:44 -0700465
Colin Cross9e1ff742018-09-20 22:44:36 -0700466 os.Chdir("testdata/dangling")
467 defer os.Chdir("../..")
468
469 for i, fs := range fsList {
470 t.Run(names[i], func(t *testing.T) {
471 for _, test := range testCases {
472 t.Run(test.pattern, func(t *testing.T) {
473 got, err := fs.glob(test.pattern)
474 if err != nil {
475 t.Fatal(err)
476 }
477 if !reflect.DeepEqual(got, test.files) {
478 t.Errorf("want: %v, got %v", test.files, got)
479 }
480 })
Colin Crossc64f2642018-09-20 21:48:44 -0700481 }
482 })
483 }
484}
Colin Cross9e1ff742018-09-20 22:44:36 -0700485
Colin Crossd85b3c72018-09-26 16:52:26 -0700486func syscallError(err error) error {
487 if serr, ok := err.(*os.SyscallError); ok {
488 return serr.Err.(syscall.Errno)
489 } else if serr, ok := err.(syscall.Errno); ok {
490 return serr
491 } else {
492 return nil
493 }
494}
495
Colin Cross9e1ff742018-09-20 22:44:36 -0700496func checkErr(t *testing.T, want, got error) {
497 t.Helper()
Colin Crossd85b3c72018-09-26 16:52:26 -0700498 if (got != nil) != (want != nil) {
499 t.Fatalf("want: %v, got %v", want, got)
Colin Cross9e1ff742018-09-20 22:44:36 -0700500 }
Colin Crossd85b3c72018-09-26 16:52:26 -0700501
502 if os.IsNotExist(got) == os.IsNotExist(want) {
503 return
504 }
505
506 if syscallError(got) == syscallError(want) {
507 return
508 }
509
510 t.Fatalf("want: %v, got %v", want, got)
Colin Cross9e1ff742018-09-20 22:44:36 -0700511}