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
25const testdataDir = "testdata/dangling"
26
27func 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"},
71		{"d/f", "f"},
72
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
106func 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
142func TestFs_IsDir(t *testing.T) {
143	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},
176		{"e/missing", false, syscall.ENOTDIR},
177		{"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},
183		{"e/missing/missing", false, syscall.ENOTDIR},
184		{"dangling/missing/missing", false, os.ErrNotExist},
185
186		{"c/f/missing", false, syscall.ENOTDIR},
187	}
188
189	runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
190		for _, test := range testCases {
191			t.Run(test.name, func(t *testing.T) {
192				got, err := fs.IsDir(filepath.Join(dir, test.name))
193				checkErr(t, test.err, err)
194				if got != test.isDir {
195					t.Errorf("want: %v, got %v", test.isDir, got)
196				}
197			})
198		}
199	})
200}
201
202func TestFs_ListDirsRecursiveFollowSymlinks(t *testing.T) {
203	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
231	runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
232		for _, test := range testCases {
233			t.Run(test.name, func(t *testing.T) {
234				got, err := fs.ListDirsRecursive(filepath.Join(dir, test.name), FollowSymlinks)
235				checkErr(t, test.err, err)
236				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)
242				}
243			})
244		}
245	})
246}
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
277	runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
278		for _, test := range testCases {
279			t.Run(test.name, func(t *testing.T) {
280				got, err := fs.ListDirsRecursive(filepath.Join(dir, test.name), DontFollowSymlinks)
281				checkErr(t, test.err, err)
282				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)
288				}
289			})
290		}
291	})
292}
293
294func 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
340	runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
341		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		}
350	})
351}
352
353func 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
401	runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
402		for _, test := range testCases {
403			t.Run(test.name, func(t *testing.T) {
404				got, err := fs.Lstat(filepath.Join(dir, test.name))
405				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		}
418	})
419}
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
469	runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
470		for _, test := range testCases {
471			t.Run(test.name, func(t *testing.T) {
472				got, err := fs.Stat(filepath.Join(dir, test.name))
473				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		}
486	})
487}
488
489func 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
527	runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
528		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		}
539	})
540}
541
542func 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
552func checkErr(t *testing.T, want, got error) {
553	t.Helper()
554	if (got != nil) != (want != nil) {
555		t.Fatalf("want: %v, got %v", want, got)
556	}
557
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)
567}
568