• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// 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"},
69		{"d/f", "f"},
70
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
104func TestFs_IsDir(t *testing.T) {
105	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},
138		{"e/missing", false, syscall.ENOTDIR},
139		{"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},
145		{"e/missing/missing", false, syscall.ENOTDIR},
146		{"dangling/missing/missing", false, os.ErrNotExist},
147
148		{"c/f/missing", false, syscall.ENOTDIR},
149	}
150
151	mock := symlinkMockFs()
152	fsList := []FileSystem{mock, OsFs}
153	names := []string{"mock", "os"}
154
155	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				})
168			}
169		})
170	}
171}
172
173func TestFs_ListDirsRecursiveFollowSymlinks(t *testing.T) {
174	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) {
214					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)
267					checkErr(t, test.err, err)
268					if !reflect.DeepEqual(got, test.dirs) {
269						t.Errorf("want: %v, got %v", test.dirs, got)
270					}
271				})
272			}
273		})
274	}
275}
276
277func 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
346func 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.Lstat(%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.Lstat(%q).Size() want: %d, got %d", test.name, test.size, got.Size())
417					}
418				})
419			}
420		})
421	}
422}
423
424func TestFs_Stat(t *testing.T) {
425	testCases := []struct {
426		name string
427		mode os.FileMode
428		size int64
429		err  error
430	}{
431		{".", os.ModeDir, 0, nil},
432		{"/", os.ModeDir, 0, nil},
433
434		{"a", os.ModeDir, 0, nil},
435		{"a/a", os.ModeDir, 0, nil},
436		{"a/a/a", 0, 0, nil},
437		{"a/a/f", 0, 0, nil},
438
439		{"b", os.ModeDir, 0, nil},
440		{"b/a", os.ModeDir, 0, nil},
441		{"b/a/a", 0, 0, nil},
442		{"b/a/f", 0, 0, nil},
443
444		{"c", os.ModeDir, 0, nil},
445		{"c/a", 0, 0, nil},
446		{"c/f", 0, 0, nil},
447
448		{"d/a", 0, 0, nil},
449		{"d/f", 0, 0, nil},
450
451		{"e", 0, 0, nil},
452
453		{"f", 0, 0, nil},
454
455		{"dangling", 0, 0, os.ErrNotExist},
456
457		{"a/missing", 0, 0, os.ErrNotExist},
458		{"b/missing", 0, 0, os.ErrNotExist},
459		{"c/missing", 0, 0, os.ErrNotExist},
460		{"d/missing", 0, 0, os.ErrNotExist},
461		{"e/missing", 0, 0, os.ErrNotExist},
462		{"dangling/missing", 0, 0, os.ErrNotExist},
463
464		{"a/missing/missing", 0, 0, os.ErrNotExist},
465		{"b/missing/missing", 0, 0, os.ErrNotExist},
466		{"c/missing/missing", 0, 0, os.ErrNotExist},
467		{"d/missing/missing", 0, 0, os.ErrNotExist},
468		{"e/missing/missing", 0, 0, os.ErrNotExist},
469		{"dangling/missing/missing", 0, 0, os.ErrNotExist},
470	}
471
472	mock := symlinkMockFs()
473	fsList := []FileSystem{mock, OsFs}
474	names := []string{"mock", "os"}
475
476	os.Chdir("testdata/dangling")
477	defer os.Chdir("../..")
478
479	for i, fs := range fsList {
480		t.Run(names[i], func(t *testing.T) {
481
482			for _, test := range testCases {
483				t.Run(test.name, func(t *testing.T) {
484					got, err := fs.Stat(test.name)
485					checkErr(t, test.err, err)
486					if err != nil {
487						return
488					}
489					if got.Mode()&os.ModeType != test.mode {
490						t.Errorf("fs.Stat(%q).Mode()&os.ModeType want: %x, got %x",
491							test.name, test.mode, got.Mode()&os.ModeType)
492					}
493					if test.mode == 0 && got.Size() != test.size {
494						t.Errorf("fs.Stat(%q).Size() want: %d, got %d", test.name, test.size, got.Size())
495					}
496				})
497			}
498		})
499	}
500}
501
502func TestMockFs_glob(t *testing.T) {
503	testCases := []struct {
504		pattern string
505		files   []string
506	}{
507		{"*", []string{"a", "b", "c", "d", "dangling", "e", "f"}},
508		{"./*", []string{"a", "b", "c", "d", "dangling", "e", "f"}},
509		{"a", []string{"a"}},
510		{"a/a", []string{"a/a"}},
511		{"a/*", []string{"a/a"}},
512		{"a/a/a", []string{"a/a/a"}},
513		{"a/a/f", []string{"a/a/f"}},
514		{"a/a/*", []string{"a/a/a", "a/a/f"}},
515
516		{"b", []string{"b"}},
517		{"b/a", []string{"b/a"}},
518		{"b/*", []string{"b/a"}},
519		{"b/a/a", []string{"b/a/a"}},
520		{"b/a/f", []string{"b/a/f"}},
521		{"b/a/*", []string{"b/a/a", "b/a/f"}},
522
523		{"c", []string{"c"}},
524		{"c/a", []string{"c/a"}},
525		{"c/f", []string{"c/f"}},
526		{"c/*", []string{"c/a", "c/f"}},
527
528		{"d", []string{"d"}},
529		{"d/a", []string{"d/a"}},
530		{"d/f", []string{"d/f"}},
531		{"d/*", []string{"d/a", "d/f"}},
532
533		{"e", []string{"e"}},
534
535		{"dangling", []string{"dangling"}},
536
537		{"missing", nil},
538	}
539
540	mock := symlinkMockFs()
541	fsList := []FileSystem{mock, OsFs}
542	names := []string{"mock", "os"}
543
544	os.Chdir("testdata/dangling")
545	defer os.Chdir("../..")
546
547	for i, fs := range fsList {
548		t.Run(names[i], func(t *testing.T) {
549			for _, test := range testCases {
550				t.Run(test.pattern, func(t *testing.T) {
551					got, err := fs.glob(test.pattern)
552					if err != nil {
553						t.Fatal(err)
554					}
555					if !reflect.DeepEqual(got, test.files) {
556						t.Errorf("want: %v, got %v", test.files, got)
557					}
558				})
559			}
560		})
561	}
562}
563
564func syscallError(err error) error {
565	if serr, ok := err.(*os.SyscallError); ok {
566		return serr.Err.(syscall.Errno)
567	} else if serr, ok := err.(syscall.Errno); ok {
568		return serr
569	} else {
570		return nil
571	}
572}
573
574func checkErr(t *testing.T, want, got error) {
575	t.Helper()
576	if (got != nil) != (want != nil) {
577		t.Fatalf("want: %v, got %v", want, got)
578	}
579
580	if os.IsNotExist(got) == os.IsNotExist(want) {
581		return
582	}
583
584	if syscallError(got) == syscallError(want) {
585		return
586	}
587
588	t.Fatalf("want: %v, got %v", want, got)
589}
590