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	"slices"
22	"syscall"
23	"testing"
24)
25
26const testdataDir = "testdata/dangling"
27
28func symlinkMockFs() *mockFs {
29	files := []string{
30		"a/a/a",
31		"a/a/f -> ../../f",
32		"b -> a",
33		"c -> a/a",
34		"d -> c",
35		"e -> a/a/a",
36		"dangling -> missing",
37		"f",
38	}
39
40	mockFiles := make(map[string][]byte)
41
42	for _, f := range files {
43		mockFiles[f] = nil
44		mockFiles[filepath.Join(pwd, "testdata", f)] = nil
45	}
46
47	return MockFs(mockFiles).(*mockFs)
48}
49
50func TestMockFs_followSymlinks(t *testing.T) {
51
52	testCases := []struct {
53		from, to string
54	}{
55		{".", "."},
56		{"/", "/"},
57
58		{"a", "a"},
59		{"a/a", "a/a"},
60		{"a/a/a", "a/a/a"},
61		{"a/a/f", "f"},
62
63		{"b", "a"},
64		{"b/a", "a/a"},
65		{"b/a/a", "a/a/a"},
66		{"b/a/f", "f"},
67
68		{"c/a", "a/a/a"},
69		{"c/f", "f"},
70
71		{"d/a", "a/a/a"},
72		{"d/f", "f"},
73
74		{"e", "a/a/a"},
75
76		{"f", "f"},
77
78		{"dangling", "missing"},
79
80		{"a/missing", "a/missing"},
81		{"b/missing", "a/missing"},
82		{"c/missing", "a/a/missing"},
83		{"d/missing", "a/a/missing"},
84		{"e/missing", "a/a/a/missing"},
85		{"dangling/missing", "missing/missing"},
86
87		{"a/missing/missing", "a/missing/missing"},
88		{"b/missing/missing", "a/missing/missing"},
89		{"c/missing/missing", "a/a/missing/missing"},
90		{"d/missing/missing", "a/a/missing/missing"},
91		{"e/missing/missing", "a/a/a/missing/missing"},
92		{"dangling/missing/missing", "missing/missing/missing"},
93	}
94
95	mock := symlinkMockFs()
96
97	for _, test := range testCases {
98		t.Run(test.from, func(t *testing.T) {
99			got := mock.followSymlinks(test.from)
100			if got != test.to {
101				t.Errorf("want: %v, got %v", test.to, got)
102			}
103		})
104	}
105}
106
107func runTestFs(t *testing.T, f func(t *testing.T, fs FileSystem, dir string)) {
108	mock := symlinkMockFs()
109	wd, _ := os.Getwd()
110	absTestDataDir := filepath.Join(wd, testdataDir)
111
112	run := func(t *testing.T, fs FileSystem) {
113		t.Run("relpath", func(t *testing.T) {
114			f(t, fs, "")
115		})
116		t.Run("abspath", func(t *testing.T) {
117			f(t, fs, absTestDataDir)
118		})
119	}
120
121	t.Run("mock", func(t *testing.T) {
122		f(t, mock, "")
123	})
124
125	t.Run("os", func(t *testing.T) {
126		os.Chdir(absTestDataDir)
127		defer os.Chdir(wd)
128		run(t, OsFs)
129	})
130
131	t.Run("os relative srcDir", func(t *testing.T) {
132		run(t, NewOsFs(testdataDir))
133	})
134
135	t.Run("os absolute srcDir", func(t *testing.T) {
136		os.Chdir("/")
137		defer os.Chdir(wd)
138		run(t, NewOsFs(filepath.Join(wd, testdataDir)))
139	})
140
141}
142
143func TestFs_IsDir(t *testing.T) {
144	testCases := []struct {
145		name  string
146		isDir bool
147		err   error
148	}{
149		{"a", true, nil},
150		{"a/a", true, nil},
151		{"a/a/a", false, nil},
152		{"a/a/f", false, nil},
153
154		{"b", true, nil},
155		{"b/a", true, nil},
156		{"b/a/a", false, nil},
157		{"b/a/f", false, nil},
158
159		{"c", true, nil},
160		{"c/a", false, nil},
161		{"c/f", false, nil},
162
163		{"d", true, nil},
164		{"d/a", false, nil},
165		{"d/f", false, nil},
166
167		{"e", false, nil},
168
169		{"f", false, nil},
170
171		{"dangling", false, os.ErrNotExist},
172
173		{"a/missing", false, os.ErrNotExist},
174		{"b/missing", false, os.ErrNotExist},
175		{"c/missing", false, os.ErrNotExist},
176		{"d/missing", false, os.ErrNotExist},
177		{"e/missing", false, syscall.ENOTDIR},
178		{"dangling/missing", false, os.ErrNotExist},
179
180		{"a/missing/missing", false, os.ErrNotExist},
181		{"b/missing/missing", false, os.ErrNotExist},
182		{"c/missing/missing", false, os.ErrNotExist},
183		{"d/missing/missing", false, os.ErrNotExist},
184		{"e/missing/missing", false, syscall.ENOTDIR},
185		{"dangling/missing/missing", false, os.ErrNotExist},
186
187		{"c/f/missing", false, syscall.ENOTDIR},
188	}
189
190	runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
191		for _, test := range testCases {
192			t.Run(test.name, func(t *testing.T) {
193				got, err := fs.IsDir(filepath.Join(dir, test.name))
194				checkErr(t, test.err, err)
195				if got != test.isDir {
196					t.Errorf("want: %v, got %v", test.isDir, got)
197				}
198			})
199		}
200	})
201}
202
203func TestFs_ListDirsRecursiveFollowSymlinks(t *testing.T) {
204	testCases := []struct {
205		name string
206		dirs []string
207		err  error
208	}{
209		{".", []string{".", "a", "a/a", "b", "b/a", "c", "d"}, nil},
210
211		{"a", []string{"a", "a/a"}, nil},
212		{"a/a", []string{"a/a"}, nil},
213		{"a/a/a", nil, nil},
214
215		{"b", []string{"b", "b/a"}, nil},
216		{"b/a", []string{"b/a"}, nil},
217		{"b/a/a", nil, nil},
218
219		{"c", []string{"c"}, nil},
220		{"c/a", nil, nil},
221
222		{"d", []string{"d"}, nil},
223		{"d/a", nil, nil},
224
225		{"e", nil, nil},
226
227		{"dangling", nil, os.ErrNotExist},
228
229		{"missing", nil, os.ErrNotExist},
230	}
231
232	runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
233		for _, test := range testCases {
234			t.Run(test.name, func(t *testing.T) {
235				got, err := fs.ListDirsRecursive(filepath.Join(dir, test.name), FollowSymlinks)
236				checkErr(t, test.err, err)
237				want := slices.Clone(test.dirs)
238				for i := range want {
239					want[i] = filepath.Join(dir, want[i])
240				}
241				if !reflect.DeepEqual(got, want) {
242					t.Errorf("want: %v, got %v", want, got)
243				}
244			})
245		}
246	})
247}
248
249func TestFs_ListDirsRecursiveDontFollowSymlinks(t *testing.T) {
250	testCases := []struct {
251		name string
252		dirs []string
253		err  error
254	}{
255		{".", []string{".", "a", "a/a"}, nil},
256
257		{"a", []string{"a", "a/a"}, nil},
258		{"a/a", []string{"a/a"}, nil},
259		{"a/a/a", nil, nil},
260
261		{"b", []string{"b", "b/a"}, nil},
262		{"b/a", []string{"b/a"}, nil},
263		{"b/a/a", nil, nil},
264
265		{"c", []string{"c"}, nil},
266		{"c/a", nil, nil},
267
268		{"d", []string{"d"}, nil},
269		{"d/a", nil, nil},
270
271		{"e", nil, nil},
272
273		{"dangling", nil, os.ErrNotExist},
274
275		{"missing", nil, os.ErrNotExist},
276	}
277
278	runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
279		for _, test := range testCases {
280			t.Run(test.name, func(t *testing.T) {
281				got, err := fs.ListDirsRecursive(filepath.Join(dir, test.name), DontFollowSymlinks)
282				checkErr(t, test.err, err)
283				want := slices.Clone(test.dirs)
284				for i := range want {
285					want[i] = filepath.Join(dir, want[i])
286				}
287				if !reflect.DeepEqual(got, want) {
288					t.Errorf("want: %v, got %v", want, got)
289				}
290			})
291		}
292	})
293}
294
295func TestFs_Readlink(t *testing.T) {
296	testCases := []struct {
297		from, to string
298		err      error
299	}{
300		{".", "", syscall.EINVAL},
301		{"/", "", syscall.EINVAL},
302
303		{"a", "", syscall.EINVAL},
304		{"a/a", "", syscall.EINVAL},
305		{"a/a/a", "", syscall.EINVAL},
306		{"a/a/f", "../../f", nil},
307
308		{"b", "a", nil},
309		{"b/a", "", syscall.EINVAL},
310		{"b/a/a", "", syscall.EINVAL},
311		{"b/a/f", "../../f", nil},
312
313		{"c", "a/a", nil},
314		{"c/a", "", syscall.EINVAL},
315		{"c/f", "../../f", nil},
316
317		{"d/a", "", syscall.EINVAL},
318		{"d/f", "../../f", nil},
319
320		{"e", "a/a/a", nil},
321
322		{"f", "", syscall.EINVAL},
323
324		{"dangling", "missing", nil},
325
326		{"a/missing", "", os.ErrNotExist},
327		{"b/missing", "", os.ErrNotExist},
328		{"c/missing", "", os.ErrNotExist},
329		{"d/missing", "", os.ErrNotExist},
330		{"e/missing", "", os.ErrNotExist},
331		{"dangling/missing", "", os.ErrNotExist},
332
333		{"a/missing/missing", "", os.ErrNotExist},
334		{"b/missing/missing", "", os.ErrNotExist},
335		{"c/missing/missing", "", os.ErrNotExist},
336		{"d/missing/missing", "", os.ErrNotExist},
337		{"e/missing/missing", "", os.ErrNotExist},
338		{"dangling/missing/missing", "", os.ErrNotExist},
339	}
340
341	runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
342		for _, test := range testCases {
343			t.Run(test.from, func(t *testing.T) {
344				got, err := fs.Readlink(test.from)
345				checkErr(t, test.err, err)
346				if got != test.to {
347					t.Errorf("fs.Readlink(%q) want: %q, got %q", test.from, test.to, got)
348				}
349			})
350		}
351	})
352}
353
354func TestFs_Lstat(t *testing.T) {
355	testCases := []struct {
356		name string
357		mode os.FileMode
358		size int64
359		err  error
360	}{
361		{".", os.ModeDir, 0, nil},
362		{"/", os.ModeDir, 0, nil},
363
364		{"a", os.ModeDir, 0, nil},
365		{"a/a", os.ModeDir, 0, nil},
366		{"a/a/a", 0, 0, nil},
367		{"a/a/f", os.ModeSymlink, 7, nil},
368
369		{"b", os.ModeSymlink, 1, nil},
370		{"b/a", os.ModeDir, 0, nil},
371		{"b/a/a", 0, 0, nil},
372		{"b/a/f", os.ModeSymlink, 7, nil},
373
374		{"c", os.ModeSymlink, 3, nil},
375		{"c/a", 0, 0, nil},
376		{"c/f", os.ModeSymlink, 7, nil},
377
378		{"d/a", 0, 0, nil},
379		{"d/f", os.ModeSymlink, 7, nil},
380
381		{"e", os.ModeSymlink, 5, nil},
382
383		{"f", 0, 0, nil},
384
385		{"dangling", os.ModeSymlink, 7, nil},
386
387		{"a/missing", 0, 0, os.ErrNotExist},
388		{"b/missing", 0, 0, os.ErrNotExist},
389		{"c/missing", 0, 0, os.ErrNotExist},
390		{"d/missing", 0, 0, os.ErrNotExist},
391		{"e/missing", 0, 0, os.ErrNotExist},
392		{"dangling/missing", 0, 0, os.ErrNotExist},
393
394		{"a/missing/missing", 0, 0, os.ErrNotExist},
395		{"b/missing/missing", 0, 0, os.ErrNotExist},
396		{"c/missing/missing", 0, 0, os.ErrNotExist},
397		{"d/missing/missing", 0, 0, os.ErrNotExist},
398		{"e/missing/missing", 0, 0, os.ErrNotExist},
399		{"dangling/missing/missing", 0, 0, os.ErrNotExist},
400	}
401
402	runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
403		for _, test := range testCases {
404			t.Run(test.name, func(t *testing.T) {
405				got, err := fs.Lstat(filepath.Join(dir, test.name))
406				checkErr(t, test.err, err)
407				if err != nil {
408					return
409				}
410				if got.Mode()&os.ModeType != test.mode {
411					t.Errorf("fs.Lstat(%q).Mode()&os.ModeType want: %x, got %x",
412						test.name, test.mode, got.Mode()&os.ModeType)
413				}
414				if test.mode == 0 && got.Size() != test.size {
415					t.Errorf("fs.Lstat(%q).Size() want: %d, got %d", test.name, test.size, got.Size())
416				}
417			})
418		}
419	})
420}
421
422func TestFs_Stat(t *testing.T) {
423	testCases := []struct {
424		name string
425		mode os.FileMode
426		size int64
427		err  error
428	}{
429		{".", os.ModeDir, 0, nil},
430		{"/", os.ModeDir, 0, nil},
431
432		{"a", os.ModeDir, 0, nil},
433		{"a/a", os.ModeDir, 0, nil},
434		{"a/a/a", 0, 0, nil},
435		{"a/a/f", 0, 0, nil},
436
437		{"b", os.ModeDir, 0, nil},
438		{"b/a", os.ModeDir, 0, nil},
439		{"b/a/a", 0, 0, nil},
440		{"b/a/f", 0, 0, nil},
441
442		{"c", os.ModeDir, 0, nil},
443		{"c/a", 0, 0, nil},
444		{"c/f", 0, 0, nil},
445
446		{"d/a", 0, 0, nil},
447		{"d/f", 0, 0, nil},
448
449		{"e", 0, 0, nil},
450
451		{"f", 0, 0, nil},
452
453		{"dangling", 0, 0, os.ErrNotExist},
454
455		{"a/missing", 0, 0, os.ErrNotExist},
456		{"b/missing", 0, 0, os.ErrNotExist},
457		{"c/missing", 0, 0, os.ErrNotExist},
458		{"d/missing", 0, 0, os.ErrNotExist},
459		{"e/missing", 0, 0, os.ErrNotExist},
460		{"dangling/missing", 0, 0, os.ErrNotExist},
461
462		{"a/missing/missing", 0, 0, os.ErrNotExist},
463		{"b/missing/missing", 0, 0, os.ErrNotExist},
464		{"c/missing/missing", 0, 0, os.ErrNotExist},
465		{"d/missing/missing", 0, 0, os.ErrNotExist},
466		{"e/missing/missing", 0, 0, os.ErrNotExist},
467		{"dangling/missing/missing", 0, 0, os.ErrNotExist},
468	}
469
470	runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
471		for _, test := range testCases {
472			t.Run(test.name, func(t *testing.T) {
473				got, err := fs.Stat(filepath.Join(dir, test.name))
474				checkErr(t, test.err, err)
475				if err != nil {
476					return
477				}
478				if got.Mode()&os.ModeType != test.mode {
479					t.Errorf("fs.Stat(%q).Mode()&os.ModeType want: %x, got %x",
480						test.name, test.mode, got.Mode()&os.ModeType)
481				}
482				if test.mode == 0 && got.Size() != test.size {
483					t.Errorf("fs.Stat(%q).Size() want: %d, got %d", test.name, test.size, got.Size())
484				}
485			})
486		}
487	})
488}
489
490func TestMockFs_glob(t *testing.T) {
491	testCases := []struct {
492		pattern string
493		files   []string
494	}{
495		{"*", []string{"a", "b", "c", "d", "dangling", "e", "f"}},
496		{"./*", []string{"a", "b", "c", "d", "dangling", "e", "f"}},
497		{"a", []string{"a"}},
498		{"a/a", []string{"a/a"}},
499		{"a/*", []string{"a/a"}},
500		{"a/a/a", []string{"a/a/a"}},
501		{"a/a/f", []string{"a/a/f"}},
502		{"a/a/*", []string{"a/a/a", "a/a/f"}},
503
504		{"b", []string{"b"}},
505		{"b/a", []string{"b/a"}},
506		{"b/*", []string{"b/a"}},
507		{"b/a/a", []string{"b/a/a"}},
508		{"b/a/f", []string{"b/a/f"}},
509		{"b/a/*", []string{"b/a/a", "b/a/f"}},
510
511		{"c", []string{"c"}},
512		{"c/a", []string{"c/a"}},
513		{"c/f", []string{"c/f"}},
514		{"c/*", []string{"c/a", "c/f"}},
515
516		{"d", []string{"d"}},
517		{"d/a", []string{"d/a"}},
518		{"d/f", []string{"d/f"}},
519		{"d/*", []string{"d/a", "d/f"}},
520
521		{"e", []string{"e"}},
522
523		{"dangling", []string{"dangling"}},
524
525		{"missing", nil},
526	}
527
528	runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
529		for _, test := range testCases {
530			t.Run(test.pattern, func(t *testing.T) {
531				got, err := fs.glob(test.pattern)
532				if err != nil {
533					t.Fatal(err)
534				}
535				if !reflect.DeepEqual(got, test.files) {
536					t.Errorf("want: %v, got %v", test.files, got)
537				}
538			})
539		}
540	})
541}
542
543func syscallError(err error) error {
544	if serr, ok := err.(*os.SyscallError); ok {
545		return serr.Err.(syscall.Errno)
546	} else if serr, ok := err.(syscall.Errno); ok {
547		return serr
548	} else {
549		return nil
550	}
551}
552
553func checkErr(t *testing.T, want, got error) {
554	t.Helper()
555	if (got != nil) != (want != nil) {
556		t.Fatalf("want: %v, got %v", want, got)
557	}
558
559	if os.IsNotExist(got) == os.IsNotExist(want) {
560		return
561	}
562
563	if syscallError(got) == syscallError(want) {
564		return
565	}
566
567	t.Fatalf("want: %v, got %v", want, got)
568}
569