blob: 3b4d4d054d187d9541c1271c4bcc273a73d1cdb3 [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
Colin Crossc5fa50e2019-12-17 13:12:35 -080025const testdataDir = "testdata/dangling"
26
Colin Crossc64f2642018-09-20 21:48:44 -070027func symlinkMockFs() *mockFs {
28 files := []string{
29 "a/a/a",
30 "a/a/f -> ../../f",
31 "b -> a",
32 "c -> a/a",
33 "d -> c",
34 "e -> a/a/a",
35 "dangling -> missing",
36 "f",
37 }
38
39 mockFiles := make(map[string][]byte)
40
41 for _, f := range files {
42 mockFiles[f] = nil
43 mockFiles[filepath.Join(pwd, "testdata", f)] = nil
44 }
45
46 return MockFs(mockFiles).(*mockFs)
47}
48
49func TestMockFs_followSymlinks(t *testing.T) {
50
51 testCases := []struct {
52 from, to string
53 }{
54 {".", "."},
55 {"/", "/"},
56
57 {"a", "a"},
58 {"a/a", "a/a"},
59 {"a/a/a", "a/a/a"},
60 {"a/a/f", "f"},
61
62 {"b", "a"},
63 {"b/a", "a/a"},
64 {"b/a/a", "a/a/a"},
65 {"b/a/f", "f"},
66
67 {"c/a", "a/a/a"},
68 {"c/f", "f"},
69
70 {"d/a", "a/a/a"},
Colin Crossdaf9de12018-09-26 16:49:14 -070071 {"d/f", "f"},
Colin Crossc64f2642018-09-20 21:48:44 -070072
73 {"e", "a/a/a"},
74
75 {"f", "f"},
76
77 {"dangling", "missing"},
78
79 {"a/missing", "a/missing"},
80 {"b/missing", "a/missing"},
81 {"c/missing", "a/a/missing"},
82 {"d/missing", "a/a/missing"},
83 {"e/missing", "a/a/a/missing"},
84 {"dangling/missing", "missing/missing"},
85
86 {"a/missing/missing", "a/missing/missing"},
87 {"b/missing/missing", "a/missing/missing"},
88 {"c/missing/missing", "a/a/missing/missing"},
89 {"d/missing/missing", "a/a/missing/missing"},
90 {"e/missing/missing", "a/a/a/missing/missing"},
91 {"dangling/missing/missing", "missing/missing/missing"},
92 }
93
94 mock := symlinkMockFs()
95
96 for _, test := range testCases {
97 t.Run(test.from, func(t *testing.T) {
98 got := mock.followSymlinks(test.from)
99 if got != test.to {
100 t.Errorf("want: %v, got %v", test.to, got)
101 }
102 })
103 }
104}
105
Colin Cross2f95ec72020-01-10 13:47:35 -0800106func runTestFs(t *testing.T, f func(t *testing.T, fs FileSystem, dir string)) {
107 mock := symlinkMockFs()
108 wd, _ := os.Getwd()
109 absTestDataDir := filepath.Join(wd, testdataDir)
110
111 run := func(t *testing.T, fs FileSystem) {
112 t.Run("relpath", func(t *testing.T) {
113 f(t, fs, "")
114 })
115 t.Run("abspath", func(t *testing.T) {
116 f(t, fs, absTestDataDir)
117 })
118 }
119
120 t.Run("mock", func(t *testing.T) {
121 f(t, mock, "")
122 })
123
124 t.Run("os", func(t *testing.T) {
125 os.Chdir(absTestDataDir)
126 defer os.Chdir(wd)
127 run(t, OsFs)
128 })
129
130 t.Run("os relative srcDir", func(t *testing.T) {
131 run(t, NewOsFs(testdataDir))
132 })
133
134 t.Run("os absolute srcDir", func(t *testing.T) {
135 os.Chdir("/")
136 defer os.Chdir(wd)
137 run(t, NewOsFs(filepath.Join(wd, testdataDir)))
138 })
139
140}
141
Colin Cross9e1ff742018-09-20 22:44:36 -0700142func TestFs_IsDir(t *testing.T) {
Colin Crossc64f2642018-09-20 21:48:44 -0700143 testCases := []struct {
144 name string
145 isDir bool
146 err error
147 }{
148 {"a", true, nil},
149 {"a/a", true, nil},
150 {"a/a/a", false, nil},
151 {"a/a/f", false, nil},
152
153 {"b", true, nil},
154 {"b/a", true, nil},
155 {"b/a/a", false, nil},
156 {"b/a/f", false, nil},
157
158 {"c", true, nil},
159 {"c/a", false, nil},
160 {"c/f", false, nil},
161
162 {"d", true, nil},
163 {"d/a", false, nil},
164 {"d/f", false, nil},
165
166 {"e", false, nil},
167
168 {"f", false, nil},
169
170 {"dangling", false, os.ErrNotExist},
171
172 {"a/missing", false, os.ErrNotExist},
173 {"b/missing", false, os.ErrNotExist},
174 {"c/missing", false, os.ErrNotExist},
175 {"d/missing", false, os.ErrNotExist},
Colin Crossd85b3c72018-09-26 16:52:26 -0700176 {"e/missing", false, syscall.ENOTDIR},
Colin Crossc64f2642018-09-20 21:48:44 -0700177 {"dangling/missing", false, os.ErrNotExist},
178
179 {"a/missing/missing", false, os.ErrNotExist},
180 {"b/missing/missing", false, os.ErrNotExist},
181 {"c/missing/missing", false, os.ErrNotExist},
182 {"d/missing/missing", false, os.ErrNotExist},
Colin Crossd85b3c72018-09-26 16:52:26 -0700183 {"e/missing/missing", false, syscall.ENOTDIR},
Colin Crossc64f2642018-09-20 21:48:44 -0700184 {"dangling/missing/missing", false, os.ErrNotExist},
185
Colin Crossd85b3c72018-09-26 16:52:26 -0700186 {"c/f/missing", false, syscall.ENOTDIR},
Colin Crossc64f2642018-09-20 21:48:44 -0700187 }
188
Colin Cross2f95ec72020-01-10 13:47:35 -0800189 runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
Colin Crossc5fa50e2019-12-17 13:12:35 -0800190 for _, test := range testCases {
191 t.Run(test.name, func(t *testing.T) {
Colin Cross2f95ec72020-01-10 13:47:35 -0800192 got, err := fs.IsDir(filepath.Join(dir, test.name))
Colin Crossc5fa50e2019-12-17 13:12:35 -0800193 checkErr(t, test.err, err)
194 if got != test.isDir {
195 t.Errorf("want: %v, got %v", test.isDir, got)
196 }
197 })
198 }
Colin Crossc5fa50e2019-12-17 13:12:35 -0800199 })
Colin Cross9e1ff742018-09-20 22:44:36 -0700200}
201
Colin Crosse98d0822018-09-21 15:30:13 -0700202func TestFs_ListDirsRecursiveFollowSymlinks(t *testing.T) {
Colin Cross9e1ff742018-09-20 22:44:36 -0700203 testCases := []struct {
204 name string
205 dirs []string
206 err error
207 }{
208 {".", []string{".", "a", "a/a", "b", "b/a", "c", "d"}, nil},
209
210 {"a", []string{"a", "a/a"}, nil},
211 {"a/a", []string{"a/a"}, nil},
212 {"a/a/a", nil, nil},
213
214 {"b", []string{"b", "b/a"}, nil},
215 {"b/a", []string{"b/a"}, nil},
216 {"b/a/a", nil, nil},
217
218 {"c", []string{"c"}, nil},
219 {"c/a", nil, nil},
220
221 {"d", []string{"d"}, nil},
222 {"d/a", nil, nil},
223
224 {"e", nil, nil},
225
226 {"dangling", nil, os.ErrNotExist},
227
228 {"missing", nil, os.ErrNotExist},
229 }
230
Colin Cross2f95ec72020-01-10 13:47:35 -0800231 runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
Colin Crossc5fa50e2019-12-17 13:12:35 -0800232 for _, test := range testCases {
233 t.Run(test.name, func(t *testing.T) {
Colin Cross2f95ec72020-01-10 13:47:35 -0800234 got, err := fs.ListDirsRecursive(filepath.Join(dir, test.name), FollowSymlinks)
Colin Crossc5fa50e2019-12-17 13:12:35 -0800235 checkErr(t, test.err, err)
Colin Cross2f95ec72020-01-10 13:47:35 -0800236 want := append([]string(nil), test.dirs...)
237 for i := range want {
238 want[i] = filepath.Join(dir, want[i])
239 }
240 if !reflect.DeepEqual(got, want) {
241 t.Errorf("want: %v, got %v", want, got)
Colin Crossc5fa50e2019-12-17 13:12:35 -0800242 }
243 })
244 }
Colin Crossc5fa50e2019-12-17 13:12:35 -0800245 })
Colin Crosse98d0822018-09-21 15:30:13 -0700246}
247
248func TestFs_ListDirsRecursiveDontFollowSymlinks(t *testing.T) {
249 testCases := []struct {
250 name string
251 dirs []string
252 err error
253 }{
254 {".", []string{".", "a", "a/a"}, nil},
255
256 {"a", []string{"a", "a/a"}, nil},
257 {"a/a", []string{"a/a"}, nil},
258 {"a/a/a", nil, nil},
259
260 {"b", []string{"b", "b/a"}, nil},
261 {"b/a", []string{"b/a"}, nil},
262 {"b/a/a", nil, nil},
263
264 {"c", []string{"c"}, nil},
265 {"c/a", nil, nil},
266
267 {"d", []string{"d"}, nil},
268 {"d/a", nil, nil},
269
270 {"e", nil, nil},
271
272 {"dangling", nil, os.ErrNotExist},
273
274 {"missing", nil, os.ErrNotExist},
275 }
276
Colin Cross2f95ec72020-01-10 13:47:35 -0800277 runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
Colin Crossc5fa50e2019-12-17 13:12:35 -0800278 for _, test := range testCases {
279 t.Run(test.name, func(t *testing.T) {
Colin Cross2f95ec72020-01-10 13:47:35 -0800280 got, err := fs.ListDirsRecursive(filepath.Join(dir, test.name), DontFollowSymlinks)
Colin Crossc5fa50e2019-12-17 13:12:35 -0800281 checkErr(t, test.err, err)
Colin Cross2f95ec72020-01-10 13:47:35 -0800282 want := append([]string(nil), test.dirs...)
283 for i := range want {
284 want[i] = filepath.Join(dir, want[i])
285 }
286 if !reflect.DeepEqual(got, want) {
287 t.Errorf("want: %v, got %v", want, got)
Colin Crossc5fa50e2019-12-17 13:12:35 -0800288 }
289 })
290 }
Colin Crossc5fa50e2019-12-17 13:12:35 -0800291 })
Colin Crossc64f2642018-09-20 21:48:44 -0700292}
293
Colin Crosse81b4322018-09-26 16:53:26 -0700294func TestFs_Readlink(t *testing.T) {
295 testCases := []struct {
296 from, to string
297 err error
298 }{
299 {".", "", syscall.EINVAL},
300 {"/", "", syscall.EINVAL},
301
302 {"a", "", syscall.EINVAL},
303 {"a/a", "", syscall.EINVAL},
304 {"a/a/a", "", syscall.EINVAL},
305 {"a/a/f", "../../f", nil},
306
307 {"b", "a", nil},
308 {"b/a", "", syscall.EINVAL},
309 {"b/a/a", "", syscall.EINVAL},
310 {"b/a/f", "../../f", nil},
311
312 {"c", "a/a", nil},
313 {"c/a", "", syscall.EINVAL},
314 {"c/f", "../../f", nil},
315
316 {"d/a", "", syscall.EINVAL},
317 {"d/f", "../../f", nil},
318
319 {"e", "a/a/a", nil},
320
321 {"f", "", syscall.EINVAL},
322
323 {"dangling", "missing", nil},
324
325 {"a/missing", "", os.ErrNotExist},
326 {"b/missing", "", os.ErrNotExist},
327 {"c/missing", "", os.ErrNotExist},
328 {"d/missing", "", os.ErrNotExist},
329 {"e/missing", "", os.ErrNotExist},
330 {"dangling/missing", "", os.ErrNotExist},
331
332 {"a/missing/missing", "", os.ErrNotExist},
333 {"b/missing/missing", "", os.ErrNotExist},
334 {"c/missing/missing", "", os.ErrNotExist},
335 {"d/missing/missing", "", os.ErrNotExist},
336 {"e/missing/missing", "", os.ErrNotExist},
337 {"dangling/missing/missing", "", os.ErrNotExist},
338 }
339
Colin Cross2f95ec72020-01-10 13:47:35 -0800340 runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
Colin Crossc5fa50e2019-12-17 13:12:35 -0800341 for _, test := range testCases {
342 t.Run(test.from, func(t *testing.T) {
343 got, err := fs.Readlink(test.from)
344 checkErr(t, test.err, err)
345 if got != test.to {
346 t.Errorf("fs.Readlink(%q) want: %q, got %q", test.from, test.to, got)
347 }
348 })
349 }
Colin Crossc5fa50e2019-12-17 13:12:35 -0800350 })
Colin Crosse81b4322018-09-26 16:53:26 -0700351}
352
Colin Cross15fbefb2018-09-27 15:46:30 -0700353func TestFs_Lstat(t *testing.T) {
354 testCases := []struct {
355 name string
356 mode os.FileMode
357 size int64
358 err error
359 }{
360 {".", os.ModeDir, 0, nil},
361 {"/", os.ModeDir, 0, nil},
362
363 {"a", os.ModeDir, 0, nil},
364 {"a/a", os.ModeDir, 0, nil},
365 {"a/a/a", 0, 0, nil},
366 {"a/a/f", os.ModeSymlink, 7, nil},
367
368 {"b", os.ModeSymlink, 1, nil},
369 {"b/a", os.ModeDir, 0, nil},
370 {"b/a/a", 0, 0, nil},
371 {"b/a/f", os.ModeSymlink, 7, nil},
372
373 {"c", os.ModeSymlink, 3, nil},
374 {"c/a", 0, 0, nil},
375 {"c/f", os.ModeSymlink, 7, nil},
376
377 {"d/a", 0, 0, nil},
378 {"d/f", os.ModeSymlink, 7, nil},
379
380 {"e", os.ModeSymlink, 5, nil},
381
382 {"f", 0, 0, nil},
383
384 {"dangling", os.ModeSymlink, 7, nil},
385
386 {"a/missing", 0, 0, os.ErrNotExist},
387 {"b/missing", 0, 0, os.ErrNotExist},
388 {"c/missing", 0, 0, os.ErrNotExist},
389 {"d/missing", 0, 0, os.ErrNotExist},
390 {"e/missing", 0, 0, os.ErrNotExist},
391 {"dangling/missing", 0, 0, os.ErrNotExist},
392
393 {"a/missing/missing", 0, 0, os.ErrNotExist},
394 {"b/missing/missing", 0, 0, os.ErrNotExist},
395 {"c/missing/missing", 0, 0, os.ErrNotExist},
396 {"d/missing/missing", 0, 0, os.ErrNotExist},
397 {"e/missing/missing", 0, 0, os.ErrNotExist},
398 {"dangling/missing/missing", 0, 0, os.ErrNotExist},
399 }
400
Colin Cross2f95ec72020-01-10 13:47:35 -0800401 runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
Colin Crossc5fa50e2019-12-17 13:12:35 -0800402 for _, test := range testCases {
403 t.Run(test.name, func(t *testing.T) {
Colin Cross2f95ec72020-01-10 13:47:35 -0800404 got, err := fs.Lstat(filepath.Join(dir, test.name))
Colin Crossc5fa50e2019-12-17 13:12:35 -0800405 checkErr(t, test.err, err)
406 if err != nil {
407 return
408 }
409 if got.Mode()&os.ModeType != test.mode {
410 t.Errorf("fs.Lstat(%q).Mode()&os.ModeType want: %x, got %x",
411 test.name, test.mode, got.Mode()&os.ModeType)
412 }
413 if test.mode == 0 && got.Size() != test.size {
414 t.Errorf("fs.Lstat(%q).Size() want: %d, got %d", test.name, test.size, got.Size())
415 }
416 })
417 }
Colin Crossc5fa50e2019-12-17 13:12:35 -0800418 })
Colin Cross3316a5e2018-09-27 15:49:45 -0700419}
420
421func TestFs_Stat(t *testing.T) {
422 testCases := []struct {
423 name string
424 mode os.FileMode
425 size int64
426 err error
427 }{
428 {".", os.ModeDir, 0, nil},
429 {"/", os.ModeDir, 0, nil},
430
431 {"a", os.ModeDir, 0, nil},
432 {"a/a", os.ModeDir, 0, nil},
433 {"a/a/a", 0, 0, nil},
434 {"a/a/f", 0, 0, nil},
435
436 {"b", os.ModeDir, 0, nil},
437 {"b/a", os.ModeDir, 0, nil},
438 {"b/a/a", 0, 0, nil},
439 {"b/a/f", 0, 0, nil},
440
441 {"c", os.ModeDir, 0, nil},
442 {"c/a", 0, 0, nil},
443 {"c/f", 0, 0, nil},
444
445 {"d/a", 0, 0, nil},
446 {"d/f", 0, 0, nil},
447
448 {"e", 0, 0, nil},
449
450 {"f", 0, 0, nil},
451
452 {"dangling", 0, 0, os.ErrNotExist},
453
454 {"a/missing", 0, 0, os.ErrNotExist},
455 {"b/missing", 0, 0, os.ErrNotExist},
456 {"c/missing", 0, 0, os.ErrNotExist},
457 {"d/missing", 0, 0, os.ErrNotExist},
458 {"e/missing", 0, 0, os.ErrNotExist},
459 {"dangling/missing", 0, 0, os.ErrNotExist},
460
461 {"a/missing/missing", 0, 0, os.ErrNotExist},
462 {"b/missing/missing", 0, 0, os.ErrNotExist},
463 {"c/missing/missing", 0, 0, os.ErrNotExist},
464 {"d/missing/missing", 0, 0, os.ErrNotExist},
465 {"e/missing/missing", 0, 0, os.ErrNotExist},
466 {"dangling/missing/missing", 0, 0, os.ErrNotExist},
467 }
468
Colin Cross2f95ec72020-01-10 13:47:35 -0800469 runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
Colin Crossc5fa50e2019-12-17 13:12:35 -0800470 for _, test := range testCases {
471 t.Run(test.name, func(t *testing.T) {
Colin Cross2f95ec72020-01-10 13:47:35 -0800472 got, err := fs.Stat(filepath.Join(dir, test.name))
Colin Crossc5fa50e2019-12-17 13:12:35 -0800473 checkErr(t, test.err, err)
474 if err != nil {
475 return
476 }
477 if got.Mode()&os.ModeType != test.mode {
478 t.Errorf("fs.Stat(%q).Mode()&os.ModeType want: %x, got %x",
479 test.name, test.mode, got.Mode()&os.ModeType)
480 }
481 if test.mode == 0 && got.Size() != test.size {
482 t.Errorf("fs.Stat(%q).Size() want: %d, got %d", test.name, test.size, got.Size())
483 }
484 })
485 }
Colin Crossc5fa50e2019-12-17 13:12:35 -0800486 })
Colin Cross15fbefb2018-09-27 15:46:30 -0700487}
488
Colin Crossc64f2642018-09-20 21:48:44 -0700489func TestMockFs_glob(t *testing.T) {
490 testCases := []struct {
491 pattern string
492 files []string
493 }{
494 {"*", []string{"a", "b", "c", "d", "dangling", "e", "f"}},
495 {"./*", []string{"a", "b", "c", "d", "dangling", "e", "f"}},
496 {"a", []string{"a"}},
497 {"a/a", []string{"a/a"}},
498 {"a/*", []string{"a/a"}},
499 {"a/a/a", []string{"a/a/a"}},
500 {"a/a/f", []string{"a/a/f"}},
501 {"a/a/*", []string{"a/a/a", "a/a/f"}},
502
503 {"b", []string{"b"}},
504 {"b/a", []string{"b/a"}},
505 {"b/*", []string{"b/a"}},
506 {"b/a/a", []string{"b/a/a"}},
507 {"b/a/f", []string{"b/a/f"}},
508 {"b/a/*", []string{"b/a/a", "b/a/f"}},
509
510 {"c", []string{"c"}},
511 {"c/a", []string{"c/a"}},
512 {"c/f", []string{"c/f"}},
513 {"c/*", []string{"c/a", "c/f"}},
514
515 {"d", []string{"d"}},
516 {"d/a", []string{"d/a"}},
517 {"d/f", []string{"d/f"}},
518 {"d/*", []string{"d/a", "d/f"}},
519
520 {"e", []string{"e"}},
521
522 {"dangling", []string{"dangling"}},
523
524 {"missing", nil},
525 }
526
Colin Cross2f95ec72020-01-10 13:47:35 -0800527 runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
Colin Crossc5fa50e2019-12-17 13:12:35 -0800528 for _, test := range testCases {
529 t.Run(test.pattern, func(t *testing.T) {
530 got, err := fs.glob(test.pattern)
531 if err != nil {
532 t.Fatal(err)
533 }
534 if !reflect.DeepEqual(got, test.files) {
535 t.Errorf("want: %v, got %v", test.files, got)
536 }
537 })
538 }
Colin Crossc5fa50e2019-12-17 13:12:35 -0800539 })
Colin Crossc64f2642018-09-20 21:48:44 -0700540}
Colin Cross9e1ff742018-09-20 22:44:36 -0700541
Colin Crossd85b3c72018-09-26 16:52:26 -0700542func syscallError(err error) error {
543 if serr, ok := err.(*os.SyscallError); ok {
544 return serr.Err.(syscall.Errno)
545 } else if serr, ok := err.(syscall.Errno); ok {
546 return serr
547 } else {
548 return nil
549 }
550}
551
Colin Cross9e1ff742018-09-20 22:44:36 -0700552func checkErr(t *testing.T, want, got error) {
553 t.Helper()
Colin Crossd85b3c72018-09-26 16:52:26 -0700554 if (got != nil) != (want != nil) {
555 t.Fatalf("want: %v, got %v", want, got)
Colin Cross9e1ff742018-09-20 22:44:36 -0700556 }
Colin Crossd85b3c72018-09-26 16:52:26 -0700557
558 if os.IsNotExist(got) == os.IsNotExist(want) {
559 return
560 }
561
562 if syscallError(got) == syscallError(want) {
563 return
564 }
565
566 t.Fatalf("want: %v, got %v", want, got)
Colin Cross9e1ff742018-09-20 22:44:36 -0700567}