platform_build_blueprint/pathtools/fs_test.go
Colin Cross 2f95ec7031 Fix reading absolute paths through OsFs
OsFs may be asked to read absolute paths if buildDir is absolute.
Check if the path is absolute before prepending srcDir to it.

Bug: 146437378
Test: fs_test.go
Change-Id: I2a67593e9d836ca3e11dc10b81f49a4fb49d2cdf
2020-01-10 13:52:22 -08:00

567 lines
13 KiB
Go

// 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"
)
const testdataDir = "testdata/dangling"
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 runTestFs(t *testing.T, f func(t *testing.T, fs FileSystem, dir string)) {
mock := symlinkMockFs()
wd, _ := os.Getwd()
absTestDataDir := filepath.Join(wd, testdataDir)
run := func(t *testing.T, fs FileSystem) {
t.Run("relpath", func(t *testing.T) {
f(t, fs, "")
})
t.Run("abspath", func(t *testing.T) {
f(t, fs, absTestDataDir)
})
}
t.Run("mock", func(t *testing.T) {
f(t, mock, "")
})
t.Run("os", func(t *testing.T) {
os.Chdir(absTestDataDir)
defer os.Chdir(wd)
run(t, OsFs)
})
t.Run("os relative srcDir", func(t *testing.T) {
run(t, NewOsFs(testdataDir))
})
t.Run("os absolute srcDir", func(t *testing.T) {
os.Chdir("/")
defer os.Chdir(wd)
run(t, NewOsFs(filepath.Join(wd, testdataDir)))
})
}
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},
}
runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
got, err := fs.IsDir(filepath.Join(dir, 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},
}
runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
got, err := fs.ListDirsRecursive(filepath.Join(dir, test.name), FollowSymlinks)
checkErr(t, test.err, err)
want := append([]string(nil), test.dirs...)
for i := range want {
want[i] = filepath.Join(dir, want[i])
}
if !reflect.DeepEqual(got, want) {
t.Errorf("want: %v, got %v", want, 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},
}
runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
got, err := fs.ListDirsRecursive(filepath.Join(dir, test.name), DontFollowSymlinks)
checkErr(t, test.err, err)
want := append([]string(nil), test.dirs...)
for i := range want {
want[i] = filepath.Join(dir, want[i])
}
if !reflect.DeepEqual(got, want) {
t.Errorf("want: %v, got %v", want, 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},
}
runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
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},
}
runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
got, err := fs.Lstat(filepath.Join(dir, 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},
}
runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
got, err := fs.Stat(filepath.Join(dir, 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},
}
runTestFs(t, func(t *testing.T, fs FileSystem, dir string) {
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)
}