| // Copyright 2018 Google Inc. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package pathtools |
| |
| import ( |
| "os" |
| "path/filepath" |
| "reflect" |
| "syscall" |
| "testing" |
| ) |
| |
| func symlinkMockFs() *mockFs { |
| files := []string{ |
| "a/a/a", |
| "a/a/f -> ../../f", |
| "b -> a", |
| "c -> a/a", |
| "d -> c", |
| "e -> a/a/a", |
| "dangling -> missing", |
| "f", |
| } |
| |
| mockFiles := make(map[string][]byte) |
| |
| for _, f := range files { |
| mockFiles[f] = nil |
| mockFiles[filepath.Join(pwd, "testdata", f)] = nil |
| } |
| |
| return MockFs(mockFiles).(*mockFs) |
| } |
| |
| func TestMockFs_followSymlinks(t *testing.T) { |
| |
| testCases := []struct { |
| from, to string |
| }{ |
| {".", "."}, |
| {"/", "/"}, |
| |
| {"a", "a"}, |
| {"a/a", "a/a"}, |
| {"a/a/a", "a/a/a"}, |
| {"a/a/f", "f"}, |
| |
| {"b", "a"}, |
| {"b/a", "a/a"}, |
| {"b/a/a", "a/a/a"}, |
| {"b/a/f", "f"}, |
| |
| {"c/a", "a/a/a"}, |
| {"c/f", "f"}, |
| |
| {"d/a", "a/a/a"}, |
| {"d/f", "f"}, |
| |
| {"e", "a/a/a"}, |
| |
| {"f", "f"}, |
| |
| {"dangling", "missing"}, |
| |
| {"a/missing", "a/missing"}, |
| {"b/missing", "a/missing"}, |
| {"c/missing", "a/a/missing"}, |
| {"d/missing", "a/a/missing"}, |
| {"e/missing", "a/a/a/missing"}, |
| {"dangling/missing", "missing/missing"}, |
| |
| {"a/missing/missing", "a/missing/missing"}, |
| {"b/missing/missing", "a/missing/missing"}, |
| {"c/missing/missing", "a/a/missing/missing"}, |
| {"d/missing/missing", "a/a/missing/missing"}, |
| {"e/missing/missing", "a/a/a/missing/missing"}, |
| {"dangling/missing/missing", "missing/missing/missing"}, |
| } |
| |
| mock := symlinkMockFs() |
| |
| for _, test := range testCases { |
| t.Run(test.from, func(t *testing.T) { |
| got := mock.followSymlinks(test.from) |
| if got != test.to { |
| t.Errorf("want: %v, got %v", test.to, got) |
| } |
| }) |
| } |
| } |
| |
| func TestFs_IsDir(t *testing.T) { |
| testCases := []struct { |
| name string |
| isDir bool |
| err error |
| }{ |
| {"a", true, nil}, |
| {"a/a", true, nil}, |
| {"a/a/a", false, nil}, |
| {"a/a/f", false, nil}, |
| |
| {"b", true, nil}, |
| {"b/a", true, nil}, |
| {"b/a/a", false, nil}, |
| {"b/a/f", false, nil}, |
| |
| {"c", true, nil}, |
| {"c/a", false, nil}, |
| {"c/f", false, nil}, |
| |
| {"d", true, nil}, |
| {"d/a", false, nil}, |
| {"d/f", false, nil}, |
| |
| {"e", false, nil}, |
| |
| {"f", false, nil}, |
| |
| {"dangling", false, os.ErrNotExist}, |
| |
| {"a/missing", false, os.ErrNotExist}, |
| {"b/missing", false, os.ErrNotExist}, |
| {"c/missing", false, os.ErrNotExist}, |
| {"d/missing", false, os.ErrNotExist}, |
| {"e/missing", false, syscall.ENOTDIR}, |
| {"dangling/missing", false, os.ErrNotExist}, |
| |
| {"a/missing/missing", false, os.ErrNotExist}, |
| {"b/missing/missing", false, os.ErrNotExist}, |
| {"c/missing/missing", false, os.ErrNotExist}, |
| {"d/missing/missing", false, os.ErrNotExist}, |
| {"e/missing/missing", false, syscall.ENOTDIR}, |
| {"dangling/missing/missing", false, os.ErrNotExist}, |
| |
| {"c/f/missing", false, syscall.ENOTDIR}, |
| } |
| |
| mock := symlinkMockFs() |
| fsList := []FileSystem{mock, OsFs} |
| names := []string{"mock", "os"} |
| |
| os.Chdir("testdata/dangling") |
| defer os.Chdir("../..") |
| |
| for i, fs := range fsList { |
| t.Run(names[i], func(t *testing.T) { |
| for _, test := range testCases { |
| t.Run(test.name, func(t *testing.T) { |
| got, err := fs.IsDir(test.name) |
| checkErr(t, test.err, err) |
| if got != test.isDir { |
| t.Errorf("want: %v, got %v", test.isDir, got) |
| } |
| }) |
| } |
| }) |
| } |
| } |
| |
| func TestFs_ListDirsRecursiveFollowSymlinks(t *testing.T) { |
| testCases := []struct { |
| name string |
| dirs []string |
| err error |
| }{ |
| {".", []string{".", "a", "a/a", "b", "b/a", "c", "d"}, nil}, |
| |
| {"a", []string{"a", "a/a"}, nil}, |
| {"a/a", []string{"a/a"}, nil}, |
| {"a/a/a", nil, nil}, |
| |
| {"b", []string{"b", "b/a"}, nil}, |
| {"b/a", []string{"b/a"}, nil}, |
| {"b/a/a", nil, nil}, |
| |
| {"c", []string{"c"}, nil}, |
| {"c/a", nil, nil}, |
| |
| {"d", []string{"d"}, nil}, |
| {"d/a", nil, nil}, |
| |
| {"e", nil, nil}, |
| |
| {"dangling", nil, os.ErrNotExist}, |
| |
| {"missing", nil, os.ErrNotExist}, |
| } |
| |
| mock := symlinkMockFs() |
| fsList := []FileSystem{mock, OsFs} |
| names := []string{"mock", "os"} |
| |
| os.Chdir("testdata/dangling") |
| defer os.Chdir("../..") |
| |
| for i, fs := range fsList { |
| t.Run(names[i], func(t *testing.T) { |
| |
| for _, test := range testCases { |
| t.Run(test.name, func(t *testing.T) { |
| got, err := fs.ListDirsRecursive(test.name, FollowSymlinks) |
| checkErr(t, test.err, err) |
| if !reflect.DeepEqual(got, test.dirs) { |
| t.Errorf("want: %v, got %v", test.dirs, got) |
| } |
| }) |
| } |
| }) |
| } |
| } |
| |
| func TestFs_ListDirsRecursiveDontFollowSymlinks(t *testing.T) { |
| testCases := []struct { |
| name string |
| dirs []string |
| err error |
| }{ |
| {".", []string{".", "a", "a/a"}, nil}, |
| |
| {"a", []string{"a", "a/a"}, nil}, |
| {"a/a", []string{"a/a"}, nil}, |
| {"a/a/a", nil, nil}, |
| |
| {"b", []string{"b", "b/a"}, nil}, |
| {"b/a", []string{"b/a"}, nil}, |
| {"b/a/a", nil, nil}, |
| |
| {"c", []string{"c"}, nil}, |
| {"c/a", nil, nil}, |
| |
| {"d", []string{"d"}, nil}, |
| {"d/a", nil, nil}, |
| |
| {"e", nil, nil}, |
| |
| {"dangling", nil, os.ErrNotExist}, |
| |
| {"missing", nil, os.ErrNotExist}, |
| } |
| |
| mock := symlinkMockFs() |
| fsList := []FileSystem{mock, OsFs} |
| names := []string{"mock", "os"} |
| |
| os.Chdir("testdata/dangling") |
| defer os.Chdir("../..") |
| |
| for i, fs := range fsList { |
| t.Run(names[i], func(t *testing.T) { |
| |
| for _, test := range testCases { |
| t.Run(test.name, func(t *testing.T) { |
| got, err := fs.ListDirsRecursive(test.name, DontFollowSymlinks) |
| checkErr(t, test.err, err) |
| if !reflect.DeepEqual(got, test.dirs) { |
| t.Errorf("want: %v, got %v", test.dirs, got) |
| } |
| }) |
| } |
| }) |
| } |
| } |
| |
| func TestFs_Readlink(t *testing.T) { |
| testCases := []struct { |
| from, to string |
| err error |
| }{ |
| {".", "", syscall.EINVAL}, |
| {"/", "", syscall.EINVAL}, |
| |
| {"a", "", syscall.EINVAL}, |
| {"a/a", "", syscall.EINVAL}, |
| {"a/a/a", "", syscall.EINVAL}, |
| {"a/a/f", "../../f", nil}, |
| |
| {"b", "a", nil}, |
| {"b/a", "", syscall.EINVAL}, |
| {"b/a/a", "", syscall.EINVAL}, |
| {"b/a/f", "../../f", nil}, |
| |
| {"c", "a/a", nil}, |
| {"c/a", "", syscall.EINVAL}, |
| {"c/f", "../../f", nil}, |
| |
| {"d/a", "", syscall.EINVAL}, |
| {"d/f", "../../f", nil}, |
| |
| {"e", "a/a/a", nil}, |
| |
| {"f", "", syscall.EINVAL}, |
| |
| {"dangling", "missing", nil}, |
| |
| {"a/missing", "", os.ErrNotExist}, |
| {"b/missing", "", os.ErrNotExist}, |
| {"c/missing", "", os.ErrNotExist}, |
| {"d/missing", "", os.ErrNotExist}, |
| {"e/missing", "", os.ErrNotExist}, |
| {"dangling/missing", "", os.ErrNotExist}, |
| |
| {"a/missing/missing", "", os.ErrNotExist}, |
| {"b/missing/missing", "", os.ErrNotExist}, |
| {"c/missing/missing", "", os.ErrNotExist}, |
| {"d/missing/missing", "", os.ErrNotExist}, |
| {"e/missing/missing", "", os.ErrNotExist}, |
| {"dangling/missing/missing", "", os.ErrNotExist}, |
| } |
| |
| mock := symlinkMockFs() |
| fsList := []FileSystem{mock, OsFs} |
| names := []string{"mock", "os"} |
| |
| os.Chdir("testdata/dangling") |
| defer os.Chdir("../..") |
| |
| for i, fs := range fsList { |
| t.Run(names[i], func(t *testing.T) { |
| |
| for _, test := range testCases { |
| t.Run(test.from, func(t *testing.T) { |
| got, err := fs.Readlink(test.from) |
| checkErr(t, test.err, err) |
| if got != test.to { |
| t.Errorf("fs.Readlink(%q) want: %q, got %q", test.from, test.to, got) |
| } |
| }) |
| } |
| }) |
| } |
| } |
| |
| func TestFs_Lstat(t *testing.T) { |
| testCases := []struct { |
| name string |
| mode os.FileMode |
| size int64 |
| err error |
| }{ |
| {".", os.ModeDir, 0, nil}, |
| {"/", os.ModeDir, 0, nil}, |
| |
| {"a", os.ModeDir, 0, nil}, |
| {"a/a", os.ModeDir, 0, nil}, |
| {"a/a/a", 0, 0, nil}, |
| {"a/a/f", os.ModeSymlink, 7, nil}, |
| |
| {"b", os.ModeSymlink, 1, nil}, |
| {"b/a", os.ModeDir, 0, nil}, |
| {"b/a/a", 0, 0, nil}, |
| {"b/a/f", os.ModeSymlink, 7, nil}, |
| |
| {"c", os.ModeSymlink, 3, nil}, |
| {"c/a", 0, 0, nil}, |
| {"c/f", os.ModeSymlink, 7, nil}, |
| |
| {"d/a", 0, 0, nil}, |
| {"d/f", os.ModeSymlink, 7, nil}, |
| |
| {"e", os.ModeSymlink, 5, nil}, |
| |
| {"f", 0, 0, nil}, |
| |
| {"dangling", os.ModeSymlink, 7, nil}, |
| |
| {"a/missing", 0, 0, os.ErrNotExist}, |
| {"b/missing", 0, 0, os.ErrNotExist}, |
| {"c/missing", 0, 0, os.ErrNotExist}, |
| {"d/missing", 0, 0, os.ErrNotExist}, |
| {"e/missing", 0, 0, os.ErrNotExist}, |
| {"dangling/missing", 0, 0, os.ErrNotExist}, |
| |
| {"a/missing/missing", 0, 0, os.ErrNotExist}, |
| {"b/missing/missing", 0, 0, os.ErrNotExist}, |
| {"c/missing/missing", 0, 0, os.ErrNotExist}, |
| {"d/missing/missing", 0, 0, os.ErrNotExist}, |
| {"e/missing/missing", 0, 0, os.ErrNotExist}, |
| {"dangling/missing/missing", 0, 0, os.ErrNotExist}, |
| } |
| |
| mock := symlinkMockFs() |
| fsList := []FileSystem{mock, OsFs} |
| names := []string{"mock", "os"} |
| |
| os.Chdir("testdata/dangling") |
| defer os.Chdir("../..") |
| |
| for i, fs := range fsList { |
| t.Run(names[i], func(t *testing.T) { |
| |
| for _, test := range testCases { |
| t.Run(test.name, func(t *testing.T) { |
| got, err := fs.Lstat(test.name) |
| checkErr(t, test.err, err) |
| if err != nil { |
| return |
| } |
| if got.Mode()&os.ModeType != test.mode { |
| t.Errorf("fs.Lstat(%q).Mode()&os.ModeType want: %x, got %x", |
| test.name, test.mode, got.Mode()&os.ModeType) |
| } |
| if test.mode == 0 && got.Size() != test.size { |
| t.Errorf("fs.Lstat(%q).Size() want: %d, got %d", test.name, test.size, got.Size()) |
| } |
| }) |
| } |
| }) |
| } |
| } |
| |
| func TestFs_Stat(t *testing.T) { |
| testCases := []struct { |
| name string |
| mode os.FileMode |
| size int64 |
| err error |
| }{ |
| {".", os.ModeDir, 0, nil}, |
| {"/", os.ModeDir, 0, nil}, |
| |
| {"a", os.ModeDir, 0, nil}, |
| {"a/a", os.ModeDir, 0, nil}, |
| {"a/a/a", 0, 0, nil}, |
| {"a/a/f", 0, 0, nil}, |
| |
| {"b", os.ModeDir, 0, nil}, |
| {"b/a", os.ModeDir, 0, nil}, |
| {"b/a/a", 0, 0, nil}, |
| {"b/a/f", 0, 0, nil}, |
| |
| {"c", os.ModeDir, 0, nil}, |
| {"c/a", 0, 0, nil}, |
| {"c/f", 0, 0, nil}, |
| |
| {"d/a", 0, 0, nil}, |
| {"d/f", 0, 0, nil}, |
| |
| {"e", 0, 0, nil}, |
| |
| {"f", 0, 0, nil}, |
| |
| {"dangling", 0, 0, os.ErrNotExist}, |
| |
| {"a/missing", 0, 0, os.ErrNotExist}, |
| {"b/missing", 0, 0, os.ErrNotExist}, |
| {"c/missing", 0, 0, os.ErrNotExist}, |
| {"d/missing", 0, 0, os.ErrNotExist}, |
| {"e/missing", 0, 0, os.ErrNotExist}, |
| {"dangling/missing", 0, 0, os.ErrNotExist}, |
| |
| {"a/missing/missing", 0, 0, os.ErrNotExist}, |
| {"b/missing/missing", 0, 0, os.ErrNotExist}, |
| {"c/missing/missing", 0, 0, os.ErrNotExist}, |
| {"d/missing/missing", 0, 0, os.ErrNotExist}, |
| {"e/missing/missing", 0, 0, os.ErrNotExist}, |
| {"dangling/missing/missing", 0, 0, os.ErrNotExist}, |
| } |
| |
| mock := symlinkMockFs() |
| fsList := []FileSystem{mock, OsFs} |
| names := []string{"mock", "os"} |
| |
| os.Chdir("testdata/dangling") |
| defer os.Chdir("../..") |
| |
| for i, fs := range fsList { |
| t.Run(names[i], func(t *testing.T) { |
| |
| for _, test := range testCases { |
| t.Run(test.name, func(t *testing.T) { |
| got, err := fs.Stat(test.name) |
| checkErr(t, test.err, err) |
| if err != nil { |
| return |
| } |
| if got.Mode()&os.ModeType != test.mode { |
| t.Errorf("fs.Stat(%q).Mode()&os.ModeType want: %x, got %x", |
| test.name, test.mode, got.Mode()&os.ModeType) |
| } |
| if test.mode == 0 && got.Size() != test.size { |
| t.Errorf("fs.Stat(%q).Size() want: %d, got %d", test.name, test.size, got.Size()) |
| } |
| }) |
| } |
| }) |
| } |
| } |
| |
| func TestMockFs_glob(t *testing.T) { |
| testCases := []struct { |
| pattern string |
| files []string |
| }{ |
| {"*", []string{"a", "b", "c", "d", "dangling", "e", "f"}}, |
| {"./*", []string{"a", "b", "c", "d", "dangling", "e", "f"}}, |
| {"a", []string{"a"}}, |
| {"a/a", []string{"a/a"}}, |
| {"a/*", []string{"a/a"}}, |
| {"a/a/a", []string{"a/a/a"}}, |
| {"a/a/f", []string{"a/a/f"}}, |
| {"a/a/*", []string{"a/a/a", "a/a/f"}}, |
| |
| {"b", []string{"b"}}, |
| {"b/a", []string{"b/a"}}, |
| {"b/*", []string{"b/a"}}, |
| {"b/a/a", []string{"b/a/a"}}, |
| {"b/a/f", []string{"b/a/f"}}, |
| {"b/a/*", []string{"b/a/a", "b/a/f"}}, |
| |
| {"c", []string{"c"}}, |
| {"c/a", []string{"c/a"}}, |
| {"c/f", []string{"c/f"}}, |
| {"c/*", []string{"c/a", "c/f"}}, |
| |
| {"d", []string{"d"}}, |
| {"d/a", []string{"d/a"}}, |
| {"d/f", []string{"d/f"}}, |
| {"d/*", []string{"d/a", "d/f"}}, |
| |
| {"e", []string{"e"}}, |
| |
| {"dangling", []string{"dangling"}}, |
| |
| {"missing", nil}, |
| } |
| |
| mock := symlinkMockFs() |
| fsList := []FileSystem{mock, OsFs} |
| names := []string{"mock", "os"} |
| |
| os.Chdir("testdata/dangling") |
| defer os.Chdir("../..") |
| |
| for i, fs := range fsList { |
| t.Run(names[i], func(t *testing.T) { |
| for _, test := range testCases { |
| t.Run(test.pattern, func(t *testing.T) { |
| got, err := fs.glob(test.pattern) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !reflect.DeepEqual(got, test.files) { |
| t.Errorf("want: %v, got %v", test.files, got) |
| } |
| }) |
| } |
| }) |
| } |
| } |
| |
| func syscallError(err error) error { |
| if serr, ok := err.(*os.SyscallError); ok { |
| return serr.Err.(syscall.Errno) |
| } else if serr, ok := err.(syscall.Errno); ok { |
| return serr |
| } else { |
| return nil |
| } |
| } |
| |
| func checkErr(t *testing.T, want, got error) { |
| t.Helper() |
| if (got != nil) != (want != nil) { |
| t.Fatalf("want: %v, got %v", want, got) |
| } |
| |
| if os.IsNotExist(got) == os.IsNotExist(want) { |
| return |
| } |
| |
| if syscallError(got) == syscallError(want) { |
| return |
| } |
| |
| t.Fatalf("want: %v, got %v", want, got) |
| } |