From 94d87ef95db922c23ee9fff0a1808482635dcd6a Mon Sep 17 00:00:00 2001 From: Brice Rising Date: Sun, 9 Dec 2018 12:23:27 -0500 Subject: [PATCH 01/16] Fix rename for helm dep upgrade helm dependency upgrade wasn't working on certain file system because it assumes that os.rename is available. Since rename isn't available for subfolders in docker containers, I ripped a fallback rename strategy from dep (https://github.com/golang/dep/blob/5b1fe9e6d89372487d0aac78d9c5362a517857e7/internal/fs/fs.go#L103-L118) that works in docker. Signed-off-by: Brice Rising --- pkg/downloader/manager.go | 7 +- pkg/fsutil/fs.go | 694 ++++++++++ pkg/fsutil/fs_test.go | 1132 +++++++++++++++++ pkg/fsutil/rename.go | 31 + pkg/fsutil/rename_windows.go | 42 + pkg/fsutil/testdata/symlinks/file-symlink | 1 + pkg/fsutil/testdata/symlinks/invalid-symlink | 1 + .../testdata/symlinks/windows-file-symlink | 1 + pkg/fsutil/testdata/test.file | 0 9 files changed, 1906 insertions(+), 3 deletions(-) create mode 100644 pkg/fsutil/fs.go create mode 100644 pkg/fsutil/fs_test.go create mode 100644 pkg/fsutil/rename.go create mode 100644 pkg/fsutil/rename_windows.go create mode 120000 pkg/fsutil/testdata/symlinks/file-symlink create mode 120000 pkg/fsutil/testdata/symlinks/invalid-symlink create mode 120000 pkg/fsutil/testdata/symlinks/windows-file-symlink create mode 100644 pkg/fsutil/testdata/test.file diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index dc38cce05..138373402 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -31,6 +31,7 @@ import ( "github.com/ghodss/yaml" "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/fsutil" "k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/proto/hapi/chart" @@ -205,7 +206,7 @@ func (m *Manager) downloadAll(deps []*chartutil.Dependency) error { return fmt.Errorf("%q is not a directory", destPath) } - if err := os.Rename(destPath, tmpPath); err != nil { + if err := fs.RenameWithFallback(destPath, tmpPath); err != nil { return fmt.Errorf("Unable to move current charts to tmp dir: %v", err) } @@ -307,7 +308,7 @@ func (m *Manager) downloadAll(deps []*chartutil.Dependency) error { if err := os.RemoveAll(destPath); err != nil { return fmt.Errorf("Failed to remove %v: %v", destPath, err) } - if err := os.Rename(tmpPath, destPath); err != nil { + if err := fs.RenameWithFallback(tmpPath, destPath); err != nil { return fmt.Errorf("Unable to move current charts to tmp dir: %v", err) } return saveError @@ -686,7 +687,7 @@ func move(tmpPath, destPath string) error { filename := file.Name() tmpfile := filepath.Join(tmpPath, filename) destfile := filepath.Join(destPath, filename) - if err := os.Rename(tmpfile, destfile); err != nil { + if err := fs.RenameWithFallback(tmpfile, destfile); err != nil { return fmt.Errorf("Unable to move local charts to charts dir: %v", err) } } diff --git a/pkg/fsutil/fs.go b/pkg/fsutil/fs.go new file mode 100644 index 000000000..a1e44eee4 --- /dev/null +++ b/pkg/fsutil/fs.go @@ -0,0 +1,694 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs + +import ( + "io" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "strings" + "syscall" + "unicode" + + "github.com/pkg/errors" +) + +// HasFilepathPrefix will determine if "path" starts with "prefix" from +// the point of view of a filesystem. +// +// Unlike filepath.HasPrefix, this function is path-aware, meaning that +// it knows that two directories /foo and /foobar are not the same +// thing, and therefore HasFilepathPrefix("/foobar", "/foo") will return +// false. +// +// This function also handles the case where the involved filesystems +// are case-insensitive, meaning /foo/bar and /Foo/Bar correspond to the +// same file. In that situation HasFilepathPrefix("/Foo/Bar", "/foo") +// will return true. The implementation is *not* OS-specific, so a FAT32 +// filesystem mounted on Linux will be handled correctly. +func HasFilepathPrefix(path, prefix string) (bool, error) { + // this function is more convoluted then ideal due to need for special + // handling of volume name/drive letter on Windows. vnPath and vnPrefix + // are first compared, and then used to initialize initial values of p and + // d which will be appended to for incremental checks using + // IsCaseSensitiveFilesystem and then equality. + + // no need to check IsCaseSensitiveFilesystem because VolumeName return + // empty string on all non-Windows machines + vnPath := strings.ToLower(filepath.VolumeName(path)) + vnPrefix := strings.ToLower(filepath.VolumeName(prefix)) + if vnPath != vnPrefix { + return false, nil + } + + // Because filepath.Join("c:","dir") returns "c:dir", we have to manually + // add path separator to drive letters. Also, we need to set the path root + // on *nix systems, since filepath.Join("", "dir") returns a relative path. + vnPath += string(os.PathSeparator) + vnPrefix += string(os.PathSeparator) + + var dn string + + if isDir, err := IsDir(path); err != nil { + return false, errors.Wrap(err, "failed to check filepath prefix") + } else if isDir { + dn = path + } else { + dn = filepath.Dir(path) + } + + dn = filepath.Clean(dn) + prefix = filepath.Clean(prefix) + + // [1:] in the lines below eliminates empty string on *nix and volume name on Windows + dirs := strings.Split(dn, string(os.PathSeparator))[1:] + prefixes := strings.Split(prefix, string(os.PathSeparator))[1:] + + if len(prefixes) > len(dirs) { + return false, nil + } + + // d,p are initialized with "/" on *nix and volume name on Windows + d := vnPath + p := vnPrefix + + for i := range prefixes { + // need to test each component of the path for + // case-sensitiveness because on Unix we could have + // something like ext4 filesystem mounted on FAT + // mountpoint, mounted on ext4 filesystem, i.e. the + // problematic filesystem is not the last one. + caseSensitive, err := IsCaseSensitiveFilesystem(filepath.Join(d, dirs[i])) + if err != nil { + return false, errors.Wrap(err, "failed to check filepath prefix") + } + if caseSensitive { + d = filepath.Join(d, dirs[i]) + p = filepath.Join(p, prefixes[i]) + } else { + d = filepath.Join(d, strings.ToLower(dirs[i])) + p = filepath.Join(p, strings.ToLower(prefixes[i])) + } + + if p != d { + return false, nil + } + } + + return true, nil +} + +// EquivalentPaths compares the paths passed to check if they are equivalent. +// It respects the case-sensitivity of the underlying filesysyems. +func EquivalentPaths(p1, p2 string) (bool, error) { + p1 = filepath.Clean(p1) + p2 = filepath.Clean(p2) + + fi1, err := os.Stat(p1) + if err != nil { + return false, errors.Wrapf(err, "could not check for path equivalence") + } + fi2, err := os.Stat(p2) + if err != nil { + return false, errors.Wrapf(err, "could not check for path equivalence") + } + + p1Filename, p2Filename := "", "" + + if !fi1.IsDir() { + p1, p1Filename = filepath.Split(p1) + } + if !fi2.IsDir() { + p2, p2Filename = filepath.Split(p2) + } + + if isPrefix1, err := HasFilepathPrefix(p1, p2); err != nil { + return false, errors.Wrap(err, "failed to check for path equivalence") + } else if isPrefix2, err := HasFilepathPrefix(p2, p1); err != nil { + return false, errors.Wrap(err, "failed to check for path equivalence") + } else if !isPrefix1 || !isPrefix2 { + return false, nil + } + + if p1Filename != "" || p2Filename != "" { + caseSensitive, err := IsCaseSensitiveFilesystem(filepath.Join(p1, p1Filename)) + if err != nil { + return false, errors.Wrap(err, "could not check for filesystem case-sensitivity") + } + if caseSensitive { + if p1Filename != p2Filename { + return false, nil + } + } else { + if strings.ToLower(p1Filename) != strings.ToLower(p2Filename) { + return false, nil + } + } + } + + return true, nil +} + +// RenameWithFallback attempts to rename a file or directory, but falls back to +// copying in the event of a cross-device link error. If the fallback copy +// succeeds, src is still removed, emulating normal rename behavior. +func RenameWithFallback(src, dst string) error { + _, err := os.Stat(src) + if err != nil { + return errors.Wrapf(err, "cannot stat %s", src) + } + + err = os.Rename(src, dst) + if err == nil { + return nil + } + + return renameFallback(err, src, dst) +} + +// renameByCopy attempts to rename a file or directory by copying it to the +// destination and then removing the src thus emulating the rename behavior. +func renameByCopy(src, dst string) error { + var cerr error + if dir, _ := IsDir(src); dir { + cerr = CopyDir(src, dst) + if cerr != nil { + cerr = errors.Wrap(cerr, "copying directory failed") + } + } else { + cerr = copyFile(src, dst) + if cerr != nil { + cerr = errors.Wrap(cerr, "copying file failed") + } + } + + if cerr != nil { + return errors.Wrapf(cerr, "rename fallback failed: cannot rename %s to %s", src, dst) + } + + return errors.Wrapf(os.RemoveAll(src), "cannot delete %s", src) +} + +// IsCaseSensitiveFilesystem determines if the filesystem where dir +// exists is case sensitive or not. +// +// CAVEAT: this function works by taking the last component of the given +// path and flipping the case of the first letter for which case +// flipping is a reversible operation (/foo/Bar → /foo/bar), then +// testing for the existence of the new filename. There are two +// possibilities: +// +// 1. The alternate filename does not exist. We can conclude that the +// filesystem is case sensitive. +// +// 2. The filename happens to exist. We have to test if the two files +// are the same file (case insensitive file system) or different ones +// (case sensitive filesystem). +// +// If the input directory is such that the last component is composed +// exclusively of case-less codepoints (e.g. numbers), this function will +// return false. +func IsCaseSensitiveFilesystem(dir string) (bool, error) { + alt := filepath.Join(filepath.Dir(dir), genTestFilename(filepath.Base(dir))) + + dInfo, err := os.Stat(dir) + if err != nil { + return false, errors.Wrap(err, "could not determine the case-sensitivity of the filesystem") + } + + aInfo, err := os.Stat(alt) + if err != nil { + // If the file doesn't exists, assume we are on a case-sensitive filesystem. + if os.IsNotExist(err) { + return true, nil + } + + return false, errors.Wrap(err, "could not determine the case-sensitivity of the filesystem") + } + + return !os.SameFile(dInfo, aInfo), nil +} + +// genTestFilename returns a string with at most one rune case-flipped. +// +// The transformation is applied only to the first rune that can be +// reversibly case-flipped, meaning: +// +// * A lowercase rune for which it's true that lower(upper(r)) == r +// * An uppercase rune for which it's true that upper(lower(r)) == r +// +// All the other runes are left intact. +func genTestFilename(str string) string { + flip := true + return strings.Map(func(r rune) rune { + if flip { + if unicode.IsLower(r) { + u := unicode.ToUpper(r) + if unicode.ToLower(u) == r { + r = u + flip = false + } + } else if unicode.IsUpper(r) { + l := unicode.ToLower(r) + if unicode.ToUpper(l) == r { + r = l + flip = false + } + } + } + return r + }, str) +} + +var errPathNotDir = errors.New("given path is not a directory") + +// ReadActualFilenames is used to determine the actual file names in given directory. +// +// On case sensitive file systems like ext4, it will check if those files exist using +// `os.Stat` and return a map with key and value as filenames which exist in the folder. +// +// Otherwise, it reads the contents of the directory and returns a map which has the +// given file name as the key and actual filename as the value(if it was found). +func ReadActualFilenames(dirPath string, names []string) (map[string]string, error) { + actualFilenames := make(map[string]string, len(names)) + if len(names) == 0 { + // This isn't expected to happen for current usage. Adding edge case handling, + // as it may be useful in future. + return actualFilenames, nil + } + // First, check that the given path is valid and it is a directory + dirStat, err := os.Stat(dirPath) + if err != nil { + return nil, errors.Wrap(err, "failed to read actual filenames") + } + + if !dirStat.IsDir() { + return nil, errPathNotDir + } + + // Ideally, we would use `os.Stat` for getting the actual file names but that returns + // the name we passed in as an argument and not the actual filename. So we are forced + // to list the directory contents and check against that. Since this check is costly, + // we do it only if absolutely necessary. + caseSensitive, err := IsCaseSensitiveFilesystem(dirPath) + if err != nil { + return nil, errors.Wrap(err, "failed to read actual filenames") + } + if caseSensitive { + // There will be no difference between actual filename and given filename. So + // just check if those files exist. + for _, name := range names { + _, err := os.Stat(filepath.Join(dirPath, name)) + if err == nil { + actualFilenames[name] = name + } else if !os.IsNotExist(err) { + // Some unexpected err, wrap and return it. + return nil, errors.Wrap(err, "failed to read actual filenames") + } + } + return actualFilenames, nil + } + + dir, err := os.Open(dirPath) + if err != nil { + return nil, errors.Wrap(err, "failed to read actual filenames") + } + defer dir.Close() + + // Pass -1 to read all filenames in directory + filenames, err := dir.Readdirnames(-1) + if err != nil { + return nil, errors.Wrap(err, "failed to read actual filenames") + } + + // namesMap holds the mapping from lowercase name to search name. Using this, we can + // avoid repeatedly looping through names. + namesMap := make(map[string]string, len(names)) + for _, name := range names { + namesMap[strings.ToLower(name)] = name + } + + for _, filename := range filenames { + searchName, ok := namesMap[strings.ToLower(filename)] + if ok { + // We are interested in this file, case insensitive match successful. + actualFilenames[searchName] = filename + if len(actualFilenames) == len(names) { + // We found all that we were looking for. + return actualFilenames, nil + } + } + } + return actualFilenames, nil +} + +var ( + errSrcNotDir = errors.New("source is not a directory") + errDstExist = errors.New("destination already exists") +) + +// CopyDir recursively copies a directory tree, attempting to preserve permissions. +// Source directory must exist, destination directory must *not* exist. +func CopyDir(src, dst string) error { + src = filepath.Clean(src) + dst = filepath.Clean(dst) + + // We use os.Lstat() here to ensure we don't fall in a loop where a symlink + // actually links to a one of its parent directories. + fi, err := os.Lstat(src) + if err != nil { + return err + } + if !fi.IsDir() { + return errSrcNotDir + } + + _, err = os.Stat(dst) + if err != nil && !os.IsNotExist(err) { + return err + } + if err == nil { + return errDstExist + } + + if err = os.MkdirAll(dst, fi.Mode()); err != nil { + return errors.Wrapf(err, "cannot mkdir %s", dst) + } + + entries, err := ioutil.ReadDir(src) + if err != nil { + return errors.Wrapf(err, "cannot read directory %s", dst) + } + + for _, entry := range entries { + srcPath := filepath.Join(src, entry.Name()) + dstPath := filepath.Join(dst, entry.Name()) + + if entry.IsDir() { + if err = CopyDir(srcPath, dstPath); err != nil { + return errors.Wrap(err, "copying directory failed") + } + } else { + // This will include symlinks, which is what we want when + // copying things. + if err = copyFile(srcPath, dstPath); err != nil { + return errors.Wrap(err, "copying file failed") + } + } + } + + return nil +} + +// copyFile copies the contents of the file named src to the file named +// by dst. The file will be created if it does not already exist. If the +// destination file exists, all its contents will be replaced by the contents +// of the source file. The file mode will be copied from the source. +func copyFile(src, dst string) (err error) { + if sym, err := IsSymlink(src); err != nil { + return errors.Wrap(err, "symlink check failed") + } else if sym { + if err := cloneSymlink(src, dst); err != nil { + if runtime.GOOS == "windows" { + // If cloning the symlink fails on Windows because the user + // does not have the required privileges, ignore the error and + // fall back to copying the file contents. + // + // ERROR_PRIVILEGE_NOT_HELD is 1314 (0x522): + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx + if lerr, ok := err.(*os.LinkError); ok && lerr.Err != syscall.Errno(1314) { + return err + } + } else { + return err + } + } else { + return nil + } + } + + in, err := os.Open(src) + if err != nil { + return + } + defer in.Close() + + out, err := os.Create(dst) + if err != nil { + return + } + + if _, err = io.Copy(out, in); err != nil { + out.Close() + return + } + + // Check for write errors on Close + if err = out.Close(); err != nil { + return + } + + si, err := os.Stat(src) + if err != nil { + return + } + + // Temporary fix for Go < 1.9 + // + // See: https://github.com/golang/dep/issues/774 + // and https://github.com/golang/go/issues/20829 + if runtime.GOOS == "windows" { + dst = fixLongPath(dst) + } + err = os.Chmod(dst, si.Mode()) + + return +} + +// cloneSymlink will create a new symlink that points to the resolved path of sl. +// If sl is a relative symlink, dst will also be a relative symlink. +func cloneSymlink(sl, dst string) error { + resolved, err := os.Readlink(sl) + if err != nil { + return err + } + + return os.Symlink(resolved, dst) +} + +// EnsureDir tries to ensure that a directory is present at the given path. It first +// checks if the directory already exists at the given path. If there isn't one, it tries +// to create it with the given permissions. However, it does not try to create the +// directory recursively. +func EnsureDir(path string, perm os.FileMode) error { + _, err := IsDir(path) + + if os.IsNotExist(err) { + err = os.Mkdir(path, perm) + if err != nil { + return errors.Wrapf(err, "failed to ensure directory at %q", path) + } + } + + return err +} + +// IsDir determines is the path given is a directory or not. +func IsDir(name string) (bool, error) { + fi, err := os.Stat(name) + if err != nil { + return false, err + } + if !fi.IsDir() { + return false, errors.Errorf("%q is not a directory", name) + } + return true, nil +} + +// IsNonEmptyDir determines if the path given is a non-empty directory or not. +func IsNonEmptyDir(name string) (bool, error) { + isDir, err := IsDir(name) + if err != nil && !os.IsNotExist(err) { + return false, err + } else if !isDir { + return false, nil + } + + // Get file descriptor + f, err := os.Open(name) + if err != nil { + return false, err + } + defer f.Close() + + // Query only 1 child. EOF if no children. + _, err = f.Readdirnames(1) + switch err { + case io.EOF: + return false, nil + case nil: + return true, nil + default: + return false, err + } +} + +// IsRegular determines if the path given is a regular file or not. +func IsRegular(name string) (bool, error) { + fi, err := os.Stat(name) + if os.IsNotExist(err) { + return false, nil + } + if err != nil { + return false, err + } + mode := fi.Mode() + if mode&os.ModeType != 0 { + return false, errors.Errorf("%q is a %v, expected a file", name, mode) + } + return true, nil +} + +// IsSymlink determines if the given path is a symbolic link. +func IsSymlink(path string) (bool, error) { + l, err := os.Lstat(path) + if err != nil { + return false, err + } + + return l.Mode()&os.ModeSymlink == os.ModeSymlink, nil +} + +// fixLongPath returns the extended-length (\\?\-prefixed) form of +// path when needed, in order to avoid the default 260 character file +// path limit imposed by Windows. If path is not easily converted to +// the extended-length form (for example, if path is a relative path +// or contains .. elements), or is short enough, fixLongPath returns +// path unmodified. +// +// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath +func fixLongPath(path string) string { + // Do nothing (and don't allocate) if the path is "short". + // Empirically (at least on the Windows Server 2013 builder), + // the kernel is arbitrarily okay with < 248 bytes. That + // matches what the docs above say: + // "When using an API to create a directory, the specified + // path cannot be so long that you cannot append an 8.3 file + // name (that is, the directory name cannot exceed MAX_PATH + // minus 12)." Since MAX_PATH is 260, 260 - 12 = 248. + // + // The MSDN docs appear to say that a normal path that is 248 bytes long + // will work; empirically the path must be less then 248 bytes long. + if len(path) < 248 { + // Don't fix. (This is how Go 1.7 and earlier worked, + // not automatically generating the \\?\ form) + return path + } + + // The extended form begins with \\?\, as in + // \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt. + // The extended form disables evaluation of . and .. path + // elements and disables the interpretation of / as equivalent + // to \. The conversion here rewrites / to \ and elides + // . elements as well as trailing or duplicate separators. For + // simplicity it avoids the conversion entirely for relative + // paths or paths containing .. elements. For now, + // \\server\share paths are not converted to + // \\?\UNC\server\share paths because the rules for doing so + // are less well-specified. + if len(path) >= 2 && path[:2] == `\\` { + // Don't canonicalize UNC paths. + return path + } + if !isAbs(path) { + // Relative path + return path + } + + const prefix = `\\?` + + pathbuf := make([]byte, len(prefix)+len(path)+len(`\`)) + copy(pathbuf, prefix) + n := len(path) + r, w := 0, len(prefix) + for r < n { + switch { + case os.IsPathSeparator(path[r]): + // empty block + r++ + case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])): + // /./ + r++ + case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])): + // /../ is currently unhandled + return path + default: + pathbuf[w] = '\\' + w++ + for ; r < n && !os.IsPathSeparator(path[r]); r++ { + pathbuf[w] = path[r] + w++ + } + } + } + // A drive's root directory needs a trailing \ + if w == len(`\\?\c:`) { + pathbuf[w] = '\\' + w++ + } + return string(pathbuf[:w]) +} + +func isAbs(path string) (b bool) { + v := volumeName(path) + if v == "" { + return false + } + path = path[len(v):] + if path == "" { + return false + } + return os.IsPathSeparator(path[0]) +} + +func volumeName(path string) (v string) { + if len(path) < 2 { + return "" + } + // with drive letter + c := path[0] + if path[1] == ':' && + ('0' <= c && c <= '9' || 'a' <= c && c <= 'z' || + 'A' <= c && c <= 'Z') { + return path[:2] + } + // is it UNC + if l := len(path); l >= 5 && os.IsPathSeparator(path[0]) && os.IsPathSeparator(path[1]) && + !os.IsPathSeparator(path[2]) && path[2] != '.' { + // first, leading `\\` and next shouldn't be `\`. its server name. + for n := 3; n < l-1; n++ { + // second, next '\' shouldn't be repeated. + if os.IsPathSeparator(path[n]) { + n++ + // third, following something characters. its share name. + if !os.IsPathSeparator(path[n]) { + if path[n] == '.' { + break + } + for ; n < l; n++ { + if os.IsPathSeparator(path[n]) { + break + } + } + return path[:n] + } + break + } + } + } + return "" +} diff --git a/pkg/fsutil/fs_test.go b/pkg/fsutil/fs_test.go new file mode 100644 index 000000000..9af0c0ec9 --- /dev/null +++ b/pkg/fsutil/fs_test.go @@ -0,0 +1,1132 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fs + +import ( + "io/ioutil" + "os" + "path/filepath" + "reflect" + "runtime" + "strings" + "testing" + + "github.com/golang/dep/internal/test" + "github.com/pkg/errors" +) + +// This function tests HadFilepathPrefix. It should test it on both case +// sensitive and insensitive situations. However, the only reliable way to test +// case-insensitive behaviour is if using case-insensitive filesystem. This +// cannot be guaranteed in an automated test. Therefore, the behaviour of the +// tests is not to test case sensitivity on *nix and to assume that Windows is +// case-insensitive. Please see link below for some background. +// +// https://superuser.com/questions/266110/how-do-you-make-windows-7-fully-case-sensitive-with-respect-to-the-filesystem +// +// NOTE: NTFS can be made case-sensitive. However many Windows programs, +// including Windows Explorer do not handle gracefully multiple files that +// differ only in capitalization. It is possible that this can cause these tests +// to fail on some setups. +func TestHasFilepathPrefix(t *testing.T) { + dir, err := ioutil.TempDir("", "dep") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + // dir2 is the same as dir but with different capitalization on Windows to + // test case insensitivity + var dir2 string + if runtime.GOOS == "windows" { + dir = strings.ToLower(dir) + dir2 = strings.ToUpper(dir) + } else { + dir2 = dir + } + + // For testing trailing and repeated separators + sep := string(os.PathSeparator) + + cases := []struct { + path string + prefix string + want bool + }{ + {filepath.Join(dir, "a", "b"), filepath.Join(dir2), true}, + {filepath.Join(dir, "a", "b"), dir2 + sep + sep + "a", true}, + {filepath.Join(dir, "a", "b"), filepath.Join(dir2, "a") + sep, true}, + {filepath.Join(dir, "a", "b") + sep, filepath.Join(dir2), true}, + {dir + sep + sep + filepath.Join("a", "b"), filepath.Join(dir2, "a"), true}, + {filepath.Join(dir, "a", "b"), filepath.Join(dir2, "a"), true}, + {filepath.Join(dir, "a", "b"), filepath.Join(dir2, "a", "b"), true}, + {filepath.Join(dir, "a", "b"), filepath.Join(dir2, "c"), false}, + {filepath.Join(dir, "a", "b"), filepath.Join(dir2, "a", "d", "b"), false}, + {filepath.Join(dir, "a", "b"), filepath.Join(dir2, "a", "b2"), false}, + {filepath.Join(dir), filepath.Join(dir2, "a", "b"), false}, + {filepath.Join(dir, "ab"), filepath.Join(dir2, "a", "b"), false}, + {filepath.Join(dir, "ab"), filepath.Join(dir2, "a"), false}, + {filepath.Join(dir, "123"), filepath.Join(dir2, "123"), true}, + {filepath.Join(dir, "123"), filepath.Join(dir2, "1"), false}, + {filepath.Join(dir, "⌘"), filepath.Join(dir2, "⌘"), true}, + {filepath.Join(dir, "a"), filepath.Join(dir2, "⌘"), false}, + {filepath.Join(dir, "⌘"), filepath.Join(dir2, "a"), false}, + } + + for _, c := range cases { + if err := os.MkdirAll(c.path, 0755); err != nil { + t.Fatal(err) + } + + if err = os.MkdirAll(c.prefix, 0755); err != nil { + t.Fatal(err) + } + + got, err := HasFilepathPrefix(c.path, c.prefix) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if c.want != got { + t.Fatalf("dir: %q, prefix: %q, expected: %v, got: %v", c.path, c.prefix, c.want, got) + } + } +} + +// This function tests HadFilepathPrefix. It should test it on both case +// sensitive and insensitive situations. However, the only reliable way to test +// case-insensitive behaviour is if using case-insensitive filesystem. This +// cannot be guaranteed in an automated test. Therefore, the behaviour of the +// tests is not to test case sensitivity on *nix and to assume that Windows is +// case-insensitive. Please see link below for some background. +// +// https://superuser.com/questions/266110/how-do-you-make-windows-7-fully-case-sensitive-with-respect-to-the-filesystem +// +// NOTE: NTFS can be made case-sensitive. However many Windows programs, +// including Windows Explorer do not handle gracefully multiple files that +// differ only in capitalization. It is possible that this can cause these tests +// to fail on some setups. +func TestHasFilepathPrefix_Files(t *testing.T) { + dir, err := ioutil.TempDir("", "dep") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + // dir2 is the same as dir but with different capitalization on Windows to + // test case insensitivity + var dir2 string + if runtime.GOOS == "windows" { + dir = strings.ToLower(dir) + dir2 = strings.ToUpper(dir) + } else { + dir2 = dir + } + + existingFile := filepath.Join(dir, "exists") + if err = os.MkdirAll(existingFile, 0755); err != nil { + t.Fatal(err) + } + + nonExistingFile := filepath.Join(dir, "does_not_exists") + + cases := []struct { + path string + prefix string + want bool + err bool + }{ + {existingFile, filepath.Join(dir2), true, false}, + {nonExistingFile, filepath.Join(dir2), false, true}, + } + + for _, c := range cases { + got, err := HasFilepathPrefix(c.path, c.prefix) + if err != nil && !c.err { + t.Fatalf("unexpected error: %s", err) + } + if c.want != got { + t.Fatalf("dir: %q, prefix: %q, expected: %v, got: %v", c.path, c.prefix, c.want, got) + } + } +} + +func TestEquivalentPaths(t *testing.T) { + h := test.NewHelper(t) + h.TempDir("dir") + h.TempDir("dir2") + + h.TempFile("file", "") + h.TempFile("file2", "") + + h.TempDir("DIR") + h.TempFile("FILE", "") + + testcases := []struct { + p1, p2 string + caseSensitiveEquivalent bool + caseInensitiveEquivalent bool + err bool + }{ + {h.Path("dir"), h.Path("dir"), true, true, false}, + {h.Path("file"), h.Path("file"), true, true, false}, + {h.Path("dir"), h.Path("dir2"), false, false, false}, + {h.Path("file"), h.Path("file2"), false, false, false}, + {h.Path("dir"), h.Path("file"), false, false, false}, + {h.Path("dir"), h.Path("DIR"), false, true, false}, + {strings.ToLower(h.Path("dir")), strings.ToUpper(h.Path("dir")), false, true, true}, + } + + caseSensitive, err := IsCaseSensitiveFilesystem(h.Path("dir")) + if err != nil { + t.Fatal("unexpcted error:", err) + } + + for _, tc := range testcases { + got, err := EquivalentPaths(tc.p1, tc.p2) + if err != nil && !tc.err { + t.Error("unexpected error:", err) + } + if caseSensitive { + if tc.caseSensitiveEquivalent != got { + t.Errorf("expected EquivalentPaths(%q, %q) to be %t on case-sensitive filesystem, got %t", tc.p1, tc.p2, tc.caseSensitiveEquivalent, got) + } + } else { + if tc.caseInensitiveEquivalent != got { + t.Errorf("expected EquivalentPaths(%q, %q) to be %t on case-insensitive filesystem, got %t", tc.p1, tc.p2, tc.caseInensitiveEquivalent, got) + } + } + } +} + +func TestRenameWithFallback(t *testing.T) { + dir, err := ioutil.TempDir("", "dep") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + if err = RenameWithFallback(filepath.Join(dir, "does_not_exists"), filepath.Join(dir, "dst")); err == nil { + t.Fatal("expected an error for non existing file, but got nil") + } + + srcpath := filepath.Join(dir, "src") + + if srcf, err := os.Create(srcpath); err != nil { + t.Fatal(err) + } else { + srcf.Close() + } + + if err = RenameWithFallback(srcpath, filepath.Join(dir, "dst")); err != nil { + t.Fatal(err) + } + + srcpath = filepath.Join(dir, "a") + if err = os.MkdirAll(srcpath, 0777); err != nil { + t.Fatal(err) + } + + dstpath := filepath.Join(dir, "b") + if err = os.MkdirAll(dstpath, 0777); err != nil { + t.Fatal(err) + } + + if err = RenameWithFallback(srcpath, dstpath); err == nil { + t.Fatal("expected an error if dst is an existing directory, but got nil") + } +} + +func TestIsCaseSensitiveFilesystem(t *testing.T) { + isLinux := runtime.GOOS == "linux" + isWindows := runtime.GOOS == "windows" + isMacOS := runtime.GOOS == "darwin" + + if !isLinux && !isWindows && !isMacOS { + t.Skip("Run this test on Windows, Linux and macOS only") + } + + dir, err := ioutil.TempDir("", "TestCaseSensitivity") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + var want bool + if isLinux { + want = true + } else { + want = false + } + + got, err := IsCaseSensitiveFilesystem(dir) + + if err != nil { + t.Fatalf("unexpected error message: \n\t(GOT) %+v", err) + } + + if want != got { + t.Fatalf("unexpected value returned: \n\t(GOT) %t\n\t(WNT) %t", got, want) + } +} + +func TestReadActualFilenames(t *testing.T) { + // We are trying to skip this test on file systems which are case-sensiive. We could + // have used `fs.IsCaseSensitiveFilesystem` for this check. However, the code we are + // testing also relies on `fs.IsCaseSensitiveFilesystem`. So a bug in + // `fs.IsCaseSensitiveFilesystem` could prevent this test from being run. This is the + // only scenario where we prefer the OS heuristic over doing the actual work of + // validating filesystem case sensitivity via `fs.IsCaseSensitiveFilesystem`. + if runtime.GOOS != "windows" && runtime.GOOS != "darwin" { + t.Skip("skip this test on non-Windows, non-macOS") + } + + h := test.NewHelper(t) + defer h.Cleanup() + + h.TempDir("") + tmpPath := h.Path(".") + + // First, check the scenarios for which we expect an error. + _, err := ReadActualFilenames(filepath.Join(tmpPath, "does_not_exists"), []string{""}) + switch { + case err == nil: + t.Fatal("expected err for non-existing folder") + // use `errors.Cause` because the error is wrapped and returned + case !os.IsNotExist(errors.Cause(err)): + t.Fatalf("unexpected error: %+v", err) + } + h.TempFile("tmpFile", "") + _, err = ReadActualFilenames(h.Path("tmpFile"), []string{""}) + switch { + case err == nil: + t.Fatal("expected err for passing file instead of directory") + case err != errPathNotDir: + t.Fatalf("unexpected error: %+v", err) + } + + cases := []struct { + createFiles []string + names []string + want map[string]string + }{ + // If we supply no filenames to the function, it should return an empty map. + {nil, nil, map[string]string{}}, + // If the directory contains the given file with different case, it should return + // a map which has the given filename as the key and actual filename as the value. + { + []string{"test1.txt"}, + []string{"Test1.txt"}, + map[string]string{"Test1.txt": "test1.txt"}, + }, + // 1. If the given filename is same as the actual filename, map should have the + // same key and value for the file. + // 2. If the given filename is present with different case for file extension, + // it should return a map which has the given filename as the key and actual + // filename as the value. + // 3. If the given filename is not present even with a different case, the map + // returned should not have an entry for that filename. + { + []string{"test2.txt", "test3.TXT"}, + []string{"test2.txt", "Test3.txt", "Test4.txt"}, + map[string]string{ + "test2.txt": "test2.txt", + "Test3.txt": "test3.TXT", + }, + }, + } + for _, c := range cases { + for _, file := range c.createFiles { + h.TempFile(file, "") + } + got, err := ReadActualFilenames(tmpPath, c.names) + if err != nil { + t.Fatalf("unexpected error: %+v", err) + } + if !reflect.DeepEqual(c.want, got) { + t.Fatalf("returned value does not match expected: \n\t(GOT) %v\n\t(WNT) %v", + got, c.want) + } + } +} + +func TestGenTestFilename(t *testing.T) { + cases := []struct { + str string + want string + }{ + {"abc", "Abc"}, + {"ABC", "aBC"}, + {"AbC", "abC"}, + {"αβγ", "Αβγ"}, + {"123", "123"}, + {"1a2", "1A2"}, + {"12a", "12A"}, + {"⌘", "⌘"}, + } + + for _, c := range cases { + got := genTestFilename(c.str) + if c.want != got { + t.Fatalf("str: %q, expected: %q, got: %q", c.str, c.want, got) + } + } +} + +func BenchmarkGenTestFilename(b *testing.B) { + cases := []string{ + strings.Repeat("a", 128), + strings.Repeat("A", 128), + strings.Repeat("α", 128), + strings.Repeat("1", 128), + strings.Repeat("⌘", 128), + } + + for i := 0; i < b.N; i++ { + for _, str := range cases { + genTestFilename(str) + } + } +} + +func TestCopyDir(t *testing.T) { + dir, err := ioutil.TempDir("", "dep") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + srcdir := filepath.Join(dir, "src") + if err := os.MkdirAll(srcdir, 0755); err != nil { + t.Fatal(err) + } + + files := []struct { + path string + contents string + fi os.FileInfo + }{ + {path: "myfile", contents: "hello world"}, + {path: filepath.Join("subdir", "file"), contents: "subdir file"}, + } + + // Create structure indicated in 'files' + for i, file := range files { + fn := filepath.Join(srcdir, file.path) + dn := filepath.Dir(fn) + if err = os.MkdirAll(dn, 0755); err != nil { + t.Fatal(err) + } + + fh, err := os.Create(fn) + if err != nil { + t.Fatal(err) + } + + if _, err = fh.Write([]byte(file.contents)); err != nil { + t.Fatal(err) + } + fh.Close() + + files[i].fi, err = os.Stat(fn) + if err != nil { + t.Fatal(err) + } + } + + destdir := filepath.Join(dir, "dest") + if err := CopyDir(srcdir, destdir); err != nil { + t.Fatal(err) + } + + // Compare copy against structure indicated in 'files' + for _, file := range files { + fn := filepath.Join(srcdir, file.path) + dn := filepath.Dir(fn) + dirOK, err := IsDir(dn) + if err != nil { + t.Fatal(err) + } + if !dirOK { + t.Fatalf("expected %s to be a directory", dn) + } + + got, err := ioutil.ReadFile(fn) + if err != nil { + t.Fatal(err) + } + + if file.contents != string(got) { + t.Fatalf("expected: %s, got: %s", file.contents, string(got)) + } + + gotinfo, err := os.Stat(fn) + if err != nil { + t.Fatal(err) + } + + if file.fi.Mode() != gotinfo.Mode() { + t.Fatalf("expected %s: %#v\n to be the same mode as %s: %#v", + file.path, file.fi.Mode(), fn, gotinfo.Mode()) + } + } +} + +func TestCopyDirFail_SrcInaccessible(t *testing.T) { + if runtime.GOOS == "windows" { + // XXX: setting permissions works differently in + // Microsoft Windows. Skipping this this until a + // compatible implementation is provided. + t.Skip("skipping on windows") + } + + var srcdir, dstdir string + + cleanup := setupInaccessibleDir(t, func(dir string) error { + srcdir = filepath.Join(dir, "src") + return os.MkdirAll(srcdir, 0755) + }) + defer cleanup() + + dir, err := ioutil.TempDir("", "dep") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + dstdir = filepath.Join(dir, "dst") + if err = CopyDir(srcdir, dstdir); err == nil { + t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir) + } +} + +func TestCopyDirFail_DstInaccessible(t *testing.T) { + if runtime.GOOS == "windows" { + // XXX: setting permissions works differently in + // Microsoft Windows. Skipping this this until a + // compatible implementation is provided. + t.Skip("skipping on windows") + } + + var srcdir, dstdir string + + dir, err := ioutil.TempDir("", "dep") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + srcdir = filepath.Join(dir, "src") + if err = os.MkdirAll(srcdir, 0755); err != nil { + t.Fatal(err) + } + + cleanup := setupInaccessibleDir(t, func(dir string) error { + dstdir = filepath.Join(dir, "dst") + return nil + }) + defer cleanup() + + if err := CopyDir(srcdir, dstdir); err == nil { + t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir) + } +} + +func TestCopyDirFail_SrcIsNotDir(t *testing.T) { + var srcdir, dstdir string + + dir, err := ioutil.TempDir("", "dep") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + srcdir = filepath.Join(dir, "src") + if _, err = os.Create(srcdir); err != nil { + t.Fatal(err) + } + + dstdir = filepath.Join(dir, "dst") + + if err = CopyDir(srcdir, dstdir); err == nil { + t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir) + } + + if err != errSrcNotDir { + t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errSrcNotDir, srcdir, dstdir, err) + } + +} + +func TestCopyDirFail_DstExists(t *testing.T) { + var srcdir, dstdir string + + dir, err := ioutil.TempDir("", "dep") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + srcdir = filepath.Join(dir, "src") + if err = os.MkdirAll(srcdir, 0755); err != nil { + t.Fatal(err) + } + + dstdir = filepath.Join(dir, "dst") + if err = os.MkdirAll(dstdir, 0755); err != nil { + t.Fatal(err) + } + + if err = CopyDir(srcdir, dstdir); err == nil { + t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir) + } + + if err != errDstExist { + t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errDstExist, srcdir, dstdir, err) + } +} + +func TestCopyDirFailOpen(t *testing.T) { + if runtime.GOOS == "windows" { + // XXX: setting permissions works differently in + // Microsoft Windows. os.Chmod(..., 0222) below is not + // enough for the file to be readonly, and os.Chmod(..., + // 0000) returns an invalid argument error. Skipping + // this this until a compatible implementation is + // provided. + t.Skip("skipping on windows") + } + + var srcdir, dstdir string + + dir, err := ioutil.TempDir("", "dep") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + srcdir = filepath.Join(dir, "src") + if err = os.MkdirAll(srcdir, 0755); err != nil { + t.Fatal(err) + } + + srcfn := filepath.Join(srcdir, "file") + srcf, err := os.Create(srcfn) + if err != nil { + t.Fatal(err) + } + srcf.Close() + + // setup source file so that it cannot be read + if err = os.Chmod(srcfn, 0222); err != nil { + t.Fatal(err) + } + + dstdir = filepath.Join(dir, "dst") + + if err = CopyDir(srcdir, dstdir); err == nil { + t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir) + } +} + +func TestCopyFile(t *testing.T) { + dir, err := ioutil.TempDir("", "dep") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + srcf, err := os.Create(filepath.Join(dir, "srcfile")) + if err != nil { + t.Fatal(err) + } + + want := "hello world" + if _, err := srcf.Write([]byte(want)); err != nil { + t.Fatal(err) + } + srcf.Close() + + destf := filepath.Join(dir, "destf") + if err := copyFile(srcf.Name(), destf); err != nil { + t.Fatal(err) + } + + got, err := ioutil.ReadFile(destf) + if err != nil { + t.Fatal(err) + } + + if want != string(got) { + t.Fatalf("expected: %s, got: %s", want, string(got)) + } + + wantinfo, err := os.Stat(srcf.Name()) + if err != nil { + t.Fatal(err) + } + + gotinfo, err := os.Stat(destf) + if err != nil { + t.Fatal(err) + } + + if wantinfo.Mode() != gotinfo.Mode() { + t.Fatalf("expected %s: %#v\n to be the same mode as %s: %#v", srcf.Name(), wantinfo.Mode(), destf, gotinfo.Mode()) + } +} + +func TestCopyFileSymlink(t *testing.T) { + h := test.NewHelper(t) + defer h.Cleanup() + h.TempDir(".") + + testcases := map[string]string{ + filepath.Join("./testdata/symlinks/file-symlink"): filepath.Join(h.Path("."), "dst-file"), + filepath.Join("./testdata/symlinks/windows-file-symlink"): filepath.Join(h.Path("."), "windows-dst-file"), + filepath.Join("./testdata/symlinks/invalid-symlink"): filepath.Join(h.Path("."), "invalid-symlink"), + } + + for symlink, dst := range testcases { + t.Run(symlink, func(t *testing.T) { + var err error + if err = copyFile(symlink, dst); err != nil { + t.Fatalf("failed to copy symlink: %s", err) + } + + var want, got string + + if runtime.GOOS == "windows" { + // Creating symlinks on Windows require an additional permission + // regular users aren't granted usually. So we copy the file + // content as a fall back instead of creating a real symlink. + srcb, err := ioutil.ReadFile(symlink) + h.Must(err) + dstb, err := ioutil.ReadFile(dst) + h.Must(err) + + want = string(srcb) + got = string(dstb) + } else { + want, err = os.Readlink(symlink) + h.Must(err) + + got, err = os.Readlink(dst) + if err != nil { + t.Fatalf("could not resolve symlink: %s", err) + } + } + + if want != got { + t.Fatalf("resolved path is incorrect. expected %s, got %s", want, got) + } + }) + } +} + +func TestCopyFileLongFilePath(t *testing.T) { + if runtime.GOOS != "windows" { + // We want to ensure the temporary fix actually fixes the issue with + // os.Chmod and long file paths. This is only applicable on Windows. + t.Skip("skipping on non-windows") + } + + h := test.NewHelper(t) + h.TempDir(".") + defer h.Cleanup() + + tmpPath := h.Path(".") + + // Create a directory with a long-enough path name to cause the bug in #774. + dirName := "" + for len(tmpPath+string(os.PathSeparator)+dirName) <= 300 { + dirName += "directory" + } + + h.TempDir(dirName) + h.TempFile(dirName+string(os.PathSeparator)+"src", "") + + tmpDirPath := tmpPath + string(os.PathSeparator) + dirName + string(os.PathSeparator) + + err := copyFile(tmpDirPath+"src", tmpDirPath+"dst") + if err != nil { + t.Fatalf("unexpected error while copying file: %v", err) + } +} + +// C:\Users\appveyor\AppData\Local\Temp\1\gotest639065787\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890 + +func TestCopyFileFail(t *testing.T) { + if runtime.GOOS == "windows" { + // XXX: setting permissions works differently in + // Microsoft Windows. Skipping this this until a + // compatible implementation is provided. + t.Skip("skipping on windows") + } + + dir, err := ioutil.TempDir("", "dep") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + srcf, err := os.Create(filepath.Join(dir, "srcfile")) + if err != nil { + t.Fatal(err) + } + srcf.Close() + + var dstdir string + + cleanup := setupInaccessibleDir(t, func(dir string) error { + dstdir = filepath.Join(dir, "dir") + return os.Mkdir(dstdir, 0777) + }) + defer cleanup() + + fn := filepath.Join(dstdir, "file") + if err := copyFile(srcf.Name(), fn); err == nil { + t.Fatalf("expected error for %s, got none", fn) + } +} + +// setupInaccessibleDir creates a temporary location with a single +// directory in it, in such a way that that directory is not accessible +// after this function returns. +// +// op is called with the directory as argument, so that it can create +// files or other test artifacts. +// +// If setupInaccessibleDir fails in its preparation, or op fails, t.Fatal +// will be invoked. +// +// This function returns a cleanup function that removes all the temporary +// files this function creates. It is the caller's responsibility to call +// this function before the test is done running, whether there's an error or not. +func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() { + dir, err := ioutil.TempDir("", "dep") + if err != nil { + t.Fatal(err) + return nil // keep compiler happy + } + + subdir := filepath.Join(dir, "dir") + + cleanup := func() { + if err := os.Chmod(subdir, 0777); err != nil { + t.Error(err) + } + if err := os.RemoveAll(dir); err != nil { + t.Error(err) + } + } + + if err := os.Mkdir(subdir, 0777); err != nil { + cleanup() + t.Fatal(err) + return nil + } + + if err := op(subdir); err != nil { + cleanup() + t.Fatal(err) + return nil + } + + if err := os.Chmod(subdir, 0666); err != nil { + cleanup() + t.Fatal(err) + return nil + } + + return cleanup +} + +func TestEnsureDir(t *testing.T) { + h := test.NewHelper(t) + defer h.Cleanup() + h.TempDir(".") + h.TempFile("file", "") + + tmpPath := h.Path(".") + + var dn string + cleanup := setupInaccessibleDir(t, func(dir string) error { + dn = filepath.Join(dir, "dir") + return os.Mkdir(dn, 0777) + }) + defer cleanup() + + tests := map[string]bool{ + // [success] A dir already exists for the given path. + tmpPath: true, + // [success] Dir does not exist but parent dir exists, so should get created. + filepath.Join(tmpPath, "testdir"): true, + // [failure] Dir and parent dir do not exist, should return an error. + filepath.Join(tmpPath, "notexist", "testdir"): false, + // [failure] Regular file present at given path. + h.Path("file"): false, + // [failure] Path inaccessible. + dn: false, + } + + if runtime.GOOS == "windows" { + // This test doesn't work on Microsoft Windows because + // of the differences in how file permissions are + // implemented. For this to work, the directory where + // the directory exists should be inaccessible. + delete(tests, dn) + } + + for path, shouldEnsure := range tests { + err := EnsureDir(path, 0777) + if shouldEnsure { + if err != nil { + t.Fatalf("unexpected error %q for %q", err, path) + } else if ok, err := IsDir(path); !ok { + t.Fatalf("expected directory to be preset at %q", path) + t.Fatal(err) + } + } else if err == nil { + t.Fatalf("expected error for path %q, got none", path) + } + } +} + +func TestIsRegular(t *testing.T) { + wd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + + var fn string + + cleanup := setupInaccessibleDir(t, func(dir string) error { + fn = filepath.Join(dir, "file") + fh, err := os.Create(fn) + if err != nil { + return err + } + + return fh.Close() + }) + defer cleanup() + + tests := map[string]struct { + exists bool + err bool + }{ + wd: {false, true}, + filepath.Join(wd, "testdata"): {false, true}, + filepath.Join(wd, "testdata", "test.file"): {true, false}, + filepath.Join(wd, "this_file_does_not_exist.thing"): {false, false}, + fn: {false, true}, + } + + if runtime.GOOS == "windows" { + // This test doesn't work on Microsoft Windows because + // of the differences in how file permissions are + // implemented. For this to work, the directory where + // the file exists should be inaccessible. + delete(tests, fn) + } + + for f, want := range tests { + got, err := IsRegular(f) + if err != nil { + if want.exists != got { + t.Fatalf("expected %t for %s, got %t", want.exists, f, got) + } + if !want.err { + t.Fatalf("expected no error, got %v", err) + } + } else { + if want.err { + t.Fatalf("expected error for %s, got none", f) + } + } + + if got != want.exists { + t.Fatalf("expected %t for %s, got %t", want, f, got) + } + } + +} + +func TestIsDir(t *testing.T) { + wd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + + var dn string + + cleanup := setupInaccessibleDir(t, func(dir string) error { + dn = filepath.Join(dir, "dir") + return os.Mkdir(dn, 0777) + }) + defer cleanup() + + tests := map[string]struct { + exists bool + err bool + }{ + wd: {true, false}, + filepath.Join(wd, "testdata"): {true, false}, + filepath.Join(wd, "main.go"): {false, true}, + filepath.Join(wd, "this_file_does_not_exist.thing"): {false, true}, + dn: {false, true}, + } + + if runtime.GOOS == "windows" { + // This test doesn't work on Microsoft Windows because + // of the differences in how file permissions are + // implemented. For this to work, the directory where + // the directory exists should be inaccessible. + delete(tests, dn) + } + + for f, want := range tests { + got, err := IsDir(f) + if err != nil && !want.err { + t.Fatalf("expected no error, got %v", err) + } + + if got != want.exists { + t.Fatalf("expected %t for %s, got %t", want.exists, f, got) + } + } +} + +func TestIsNonEmptyDir(t *testing.T) { + wd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + + h := test.NewHelper(t) + defer h.Cleanup() + + h.TempDir("empty") + + testCases := []struct { + path string + empty bool + err bool + }{ + {wd, true, false}, + {"testdata", true, false}, + {filepath.Join(wd, "fs.go"), false, true}, + {filepath.Join(wd, "this_file_does_not_exist.thing"), false, false}, + {h.Path("empty"), false, false}, + } + + // This test case doesn't work on Microsoft Windows because of the + // differences in how file permissions are implemented. + if runtime.GOOS != "windows" { + var inaccessibleDir string + cleanup := setupInaccessibleDir(t, func(dir string) error { + inaccessibleDir = filepath.Join(dir, "empty") + return os.Mkdir(inaccessibleDir, 0777) + }) + defer cleanup() + + testCases = append(testCases, struct { + path string + empty bool + err bool + }{inaccessibleDir, false, true}) + } + + for _, want := range testCases { + got, err := IsNonEmptyDir(want.path) + if want.err && err == nil { + if got { + t.Fatalf("wanted false with error for %v, but got true", want.path) + } + t.Fatalf("wanted an error for %v, but it was nil", want.path) + } + + if got != want.empty { + t.Fatalf("wanted %t for %v, but got %t", want.empty, want.path, got) + } + } +} + +func TestIsSymlink(t *testing.T) { + dir, err := ioutil.TempDir("", "dep") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + dirPath := filepath.Join(dir, "directory") + if err = os.MkdirAll(dirPath, 0777); err != nil { + t.Fatal(err) + } + + filePath := filepath.Join(dir, "file") + f, err := os.Create(filePath) + if err != nil { + t.Fatal(err) + } + f.Close() + + dirSymlink := filepath.Join(dir, "dirSymlink") + fileSymlink := filepath.Join(dir, "fileSymlink") + + if err = os.Symlink(dirPath, dirSymlink); err != nil { + t.Fatal(err) + } + if err = os.Symlink(filePath, fileSymlink); err != nil { + t.Fatal(err) + } + + var ( + inaccessibleFile string + inaccessibleSymlink string + ) + + cleanup := setupInaccessibleDir(t, func(dir string) error { + inaccessibleFile = filepath.Join(dir, "file") + if fh, err := os.Create(inaccessibleFile); err != nil { + return err + } else if err = fh.Close(); err != nil { + return err + } + + inaccessibleSymlink = filepath.Join(dir, "symlink") + return os.Symlink(inaccessibleFile, inaccessibleSymlink) + }) + defer cleanup() + + tests := map[string]struct{ expected, err bool }{ + dirPath: {false, false}, + filePath: {false, false}, + dirSymlink: {true, false}, + fileSymlink: {true, false}, + inaccessibleFile: {false, true}, + inaccessibleSymlink: {false, true}, + } + + if runtime.GOOS == "windows" { + // XXX: setting permissions works differently in Windows. Skipping + // these cases until a compatible implementation is provided. + delete(tests, inaccessibleFile) + delete(tests, inaccessibleSymlink) + } + + for path, want := range tests { + got, err := IsSymlink(path) + if err != nil { + if !want.err { + t.Errorf("expected no error, got %v", err) + } + } + + if got != want.expected { + t.Errorf("expected %t for %s, got %t", want.expected, path, got) + } + } +} diff --git a/pkg/fsutil/rename.go b/pkg/fsutil/rename.go new file mode 100644 index 000000000..c48f69f1a --- /dev/null +++ b/pkg/fsutil/rename.go @@ -0,0 +1,31 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !windows + +package fs + +import ( + "os" + "syscall" + + "github.com/pkg/errors" +) + +// renameFallback attempts to determine the appropriate fallback to failed rename +// operation depending on the resulting error. +func renameFallback(err error, src, dst string) error { + // Rename may fail if src and dst are on different devices; fall back to + // copy if we detect that case. syscall.EXDEV is the common name for the + // cross device link error which has varying output text across different + // operating systems. + terr, ok := err.(*os.LinkError) + if !ok { + return err + } else if terr.Err != syscall.EXDEV { + return errors.Wrapf(terr, "link error: cannot rename %s to %s", src, dst) + } + + return renameByCopy(src, dst) +} diff --git a/pkg/fsutil/rename_windows.go b/pkg/fsutil/rename_windows.go new file mode 100644 index 000000000..50829a5cd --- /dev/null +++ b/pkg/fsutil/rename_windows.go @@ -0,0 +1,42 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package fs + +import ( + "os" + "syscall" + + "github.com/pkg/errors" +) + +// renameFallback attempts to determine the appropriate fallback to failed rename +// operation depending on the resulting error. +func renameFallback(err error, src, dst string) error { + // Rename may fail if src and dst are on different devices; fall back to + // copy if we detect that case. syscall.EXDEV is the common name for the + // cross device link error which has varying output text across different + // operating systems. + terr, ok := err.(*os.LinkError) + if !ok { + return err + } + + if terr.Err != syscall.EXDEV { + // In windows it can drop down to an operating system call that + // returns an operating system error with a different number and + // message. Checking for that as a fall back. + noerr, ok := terr.Err.(syscall.Errno) + + // 0x11 (ERROR_NOT_SAME_DEVICE) is the windows error. + // See https://msdn.microsoft.com/en-us/library/cc231199.aspx + if ok && noerr != 0x11 { + return errors.Wrapf(terr, "link error: cannot rename %s to %s", src, dst) + } + } + + return renameByCopy(src, dst) +} diff --git a/pkg/fsutil/testdata/symlinks/file-symlink b/pkg/fsutil/testdata/symlinks/file-symlink new file mode 120000 index 000000000..4c52274de --- /dev/null +++ b/pkg/fsutil/testdata/symlinks/file-symlink @@ -0,0 +1 @@ +../test.file \ No newline at end of file diff --git a/pkg/fsutil/testdata/symlinks/invalid-symlink b/pkg/fsutil/testdata/symlinks/invalid-symlink new file mode 120000 index 000000000..0edf4f301 --- /dev/null +++ b/pkg/fsutil/testdata/symlinks/invalid-symlink @@ -0,0 +1 @@ +/non/existing/file \ No newline at end of file diff --git a/pkg/fsutil/testdata/symlinks/windows-file-symlink b/pkg/fsutil/testdata/symlinks/windows-file-symlink new file mode 120000 index 000000000..af1d6c8f5 --- /dev/null +++ b/pkg/fsutil/testdata/symlinks/windows-file-symlink @@ -0,0 +1 @@ +C:/Users/ibrahim/go/src/github.com/golang/dep/internal/fs/testdata/test.file \ No newline at end of file diff --git a/pkg/fsutil/testdata/test.file b/pkg/fsutil/testdata/test.file new file mode 100644 index 000000000..e69de29bb From 51dbd7c1a6dd1704059e9248221e78e269158cfd Mon Sep 17 00:00:00 2001 From: Brice Rising Date: Sun, 9 Dec 2018 22:04:36 -0500 Subject: [PATCH 02/16] Removed unused methods from fs package The fs package tests are going to be a lot of work to get working without the internal test package used by golang/dep, so I removed all of the unused ones by this project to make the work easier. Signed-off-by: Brice Rising --- pkg/fsutil/fs.go | 350 ----------------------------- pkg/fsutil/fs_test.go | 506 ------------------------------------------ 2 files changed, 856 deletions(-) diff --git a/pkg/fsutil/fs.go b/pkg/fsutil/fs.go index a1e44eee4..82b7d4e76 100644 --- a/pkg/fsutil/fs.go +++ b/pkg/fsutil/fs.go @@ -10,149 +10,11 @@ import ( "os" "path/filepath" "runtime" - "strings" "syscall" - "unicode" "github.com/pkg/errors" ) -// HasFilepathPrefix will determine if "path" starts with "prefix" from -// the point of view of a filesystem. -// -// Unlike filepath.HasPrefix, this function is path-aware, meaning that -// it knows that two directories /foo and /foobar are not the same -// thing, and therefore HasFilepathPrefix("/foobar", "/foo") will return -// false. -// -// This function also handles the case where the involved filesystems -// are case-insensitive, meaning /foo/bar and /Foo/Bar correspond to the -// same file. In that situation HasFilepathPrefix("/Foo/Bar", "/foo") -// will return true. The implementation is *not* OS-specific, so a FAT32 -// filesystem mounted on Linux will be handled correctly. -func HasFilepathPrefix(path, prefix string) (bool, error) { - // this function is more convoluted then ideal due to need for special - // handling of volume name/drive letter on Windows. vnPath and vnPrefix - // are first compared, and then used to initialize initial values of p and - // d which will be appended to for incremental checks using - // IsCaseSensitiveFilesystem and then equality. - - // no need to check IsCaseSensitiveFilesystem because VolumeName return - // empty string on all non-Windows machines - vnPath := strings.ToLower(filepath.VolumeName(path)) - vnPrefix := strings.ToLower(filepath.VolumeName(prefix)) - if vnPath != vnPrefix { - return false, nil - } - - // Because filepath.Join("c:","dir") returns "c:dir", we have to manually - // add path separator to drive letters. Also, we need to set the path root - // on *nix systems, since filepath.Join("", "dir") returns a relative path. - vnPath += string(os.PathSeparator) - vnPrefix += string(os.PathSeparator) - - var dn string - - if isDir, err := IsDir(path); err != nil { - return false, errors.Wrap(err, "failed to check filepath prefix") - } else if isDir { - dn = path - } else { - dn = filepath.Dir(path) - } - - dn = filepath.Clean(dn) - prefix = filepath.Clean(prefix) - - // [1:] in the lines below eliminates empty string on *nix and volume name on Windows - dirs := strings.Split(dn, string(os.PathSeparator))[1:] - prefixes := strings.Split(prefix, string(os.PathSeparator))[1:] - - if len(prefixes) > len(dirs) { - return false, nil - } - - // d,p are initialized with "/" on *nix and volume name on Windows - d := vnPath - p := vnPrefix - - for i := range prefixes { - // need to test each component of the path for - // case-sensitiveness because on Unix we could have - // something like ext4 filesystem mounted on FAT - // mountpoint, mounted on ext4 filesystem, i.e. the - // problematic filesystem is not the last one. - caseSensitive, err := IsCaseSensitiveFilesystem(filepath.Join(d, dirs[i])) - if err != nil { - return false, errors.Wrap(err, "failed to check filepath prefix") - } - if caseSensitive { - d = filepath.Join(d, dirs[i]) - p = filepath.Join(p, prefixes[i]) - } else { - d = filepath.Join(d, strings.ToLower(dirs[i])) - p = filepath.Join(p, strings.ToLower(prefixes[i])) - } - - if p != d { - return false, nil - } - } - - return true, nil -} - -// EquivalentPaths compares the paths passed to check if they are equivalent. -// It respects the case-sensitivity of the underlying filesysyems. -func EquivalentPaths(p1, p2 string) (bool, error) { - p1 = filepath.Clean(p1) - p2 = filepath.Clean(p2) - - fi1, err := os.Stat(p1) - if err != nil { - return false, errors.Wrapf(err, "could not check for path equivalence") - } - fi2, err := os.Stat(p2) - if err != nil { - return false, errors.Wrapf(err, "could not check for path equivalence") - } - - p1Filename, p2Filename := "", "" - - if !fi1.IsDir() { - p1, p1Filename = filepath.Split(p1) - } - if !fi2.IsDir() { - p2, p2Filename = filepath.Split(p2) - } - - if isPrefix1, err := HasFilepathPrefix(p1, p2); err != nil { - return false, errors.Wrap(err, "failed to check for path equivalence") - } else if isPrefix2, err := HasFilepathPrefix(p2, p1); err != nil { - return false, errors.Wrap(err, "failed to check for path equivalence") - } else if !isPrefix1 || !isPrefix2 { - return false, nil - } - - if p1Filename != "" || p2Filename != "" { - caseSensitive, err := IsCaseSensitiveFilesystem(filepath.Join(p1, p1Filename)) - if err != nil { - return false, errors.Wrap(err, "could not check for filesystem case-sensitivity") - } - if caseSensitive { - if p1Filename != p2Filename { - return false, nil - } - } else { - if strings.ToLower(p1Filename) != strings.ToLower(p2Filename) { - return false, nil - } - } - } - - return true, nil -} - // RenameWithFallback attempts to rename a file or directory, but falls back to // copying in the event of a cross-device link error. If the fallback copy // succeeds, src is still removed, emulating normal rename behavior. @@ -193,159 +55,8 @@ func renameByCopy(src, dst string) error { return errors.Wrapf(os.RemoveAll(src), "cannot delete %s", src) } -// IsCaseSensitiveFilesystem determines if the filesystem where dir -// exists is case sensitive or not. -// -// CAVEAT: this function works by taking the last component of the given -// path and flipping the case of the first letter for which case -// flipping is a reversible operation (/foo/Bar → /foo/bar), then -// testing for the existence of the new filename. There are two -// possibilities: -// -// 1. The alternate filename does not exist. We can conclude that the -// filesystem is case sensitive. -// -// 2. The filename happens to exist. We have to test if the two files -// are the same file (case insensitive file system) or different ones -// (case sensitive filesystem). -// -// If the input directory is such that the last component is composed -// exclusively of case-less codepoints (e.g. numbers), this function will -// return false. -func IsCaseSensitiveFilesystem(dir string) (bool, error) { - alt := filepath.Join(filepath.Dir(dir), genTestFilename(filepath.Base(dir))) - - dInfo, err := os.Stat(dir) - if err != nil { - return false, errors.Wrap(err, "could not determine the case-sensitivity of the filesystem") - } - - aInfo, err := os.Stat(alt) - if err != nil { - // If the file doesn't exists, assume we are on a case-sensitive filesystem. - if os.IsNotExist(err) { - return true, nil - } - - return false, errors.Wrap(err, "could not determine the case-sensitivity of the filesystem") - } - - return !os.SameFile(dInfo, aInfo), nil -} - -// genTestFilename returns a string with at most one rune case-flipped. -// -// The transformation is applied only to the first rune that can be -// reversibly case-flipped, meaning: -// -// * A lowercase rune for which it's true that lower(upper(r)) == r -// * An uppercase rune for which it's true that upper(lower(r)) == r -// -// All the other runes are left intact. -func genTestFilename(str string) string { - flip := true - return strings.Map(func(r rune) rune { - if flip { - if unicode.IsLower(r) { - u := unicode.ToUpper(r) - if unicode.ToLower(u) == r { - r = u - flip = false - } - } else if unicode.IsUpper(r) { - l := unicode.ToLower(r) - if unicode.ToUpper(l) == r { - r = l - flip = false - } - } - } - return r - }, str) -} - var errPathNotDir = errors.New("given path is not a directory") -// ReadActualFilenames is used to determine the actual file names in given directory. -// -// On case sensitive file systems like ext4, it will check if those files exist using -// `os.Stat` and return a map with key and value as filenames which exist in the folder. -// -// Otherwise, it reads the contents of the directory and returns a map which has the -// given file name as the key and actual filename as the value(if it was found). -func ReadActualFilenames(dirPath string, names []string) (map[string]string, error) { - actualFilenames := make(map[string]string, len(names)) - if len(names) == 0 { - // This isn't expected to happen for current usage. Adding edge case handling, - // as it may be useful in future. - return actualFilenames, nil - } - // First, check that the given path is valid and it is a directory - dirStat, err := os.Stat(dirPath) - if err != nil { - return nil, errors.Wrap(err, "failed to read actual filenames") - } - - if !dirStat.IsDir() { - return nil, errPathNotDir - } - - // Ideally, we would use `os.Stat` for getting the actual file names but that returns - // the name we passed in as an argument and not the actual filename. So we are forced - // to list the directory contents and check against that. Since this check is costly, - // we do it only if absolutely necessary. - caseSensitive, err := IsCaseSensitiveFilesystem(dirPath) - if err != nil { - return nil, errors.Wrap(err, "failed to read actual filenames") - } - if caseSensitive { - // There will be no difference between actual filename and given filename. So - // just check if those files exist. - for _, name := range names { - _, err := os.Stat(filepath.Join(dirPath, name)) - if err == nil { - actualFilenames[name] = name - } else if !os.IsNotExist(err) { - // Some unexpected err, wrap and return it. - return nil, errors.Wrap(err, "failed to read actual filenames") - } - } - return actualFilenames, nil - } - - dir, err := os.Open(dirPath) - if err != nil { - return nil, errors.Wrap(err, "failed to read actual filenames") - } - defer dir.Close() - - // Pass -1 to read all filenames in directory - filenames, err := dir.Readdirnames(-1) - if err != nil { - return nil, errors.Wrap(err, "failed to read actual filenames") - } - - // namesMap holds the mapping from lowercase name to search name. Using this, we can - // avoid repeatedly looping through names. - namesMap := make(map[string]string, len(names)) - for _, name := range names { - namesMap[strings.ToLower(name)] = name - } - - for _, filename := range filenames { - searchName, ok := namesMap[strings.ToLower(filename)] - if ok { - // We are interested in this file, case insensitive match successful. - actualFilenames[searchName] = filename - if len(actualFilenames) == len(names) { - // We found all that we were looking for. - return actualFilenames, nil - } - } - } - return actualFilenames, nil -} - var ( errSrcNotDir = errors.New("source is not a directory") errDstExist = errors.New("destination already exists") @@ -480,23 +191,6 @@ func cloneSymlink(sl, dst string) error { return os.Symlink(resolved, dst) } -// EnsureDir tries to ensure that a directory is present at the given path. It first -// checks if the directory already exists at the given path. If there isn't one, it tries -// to create it with the given permissions. However, it does not try to create the -// directory recursively. -func EnsureDir(path string, perm os.FileMode) error { - _, err := IsDir(path) - - if os.IsNotExist(err) { - err = os.Mkdir(path, perm) - if err != nil { - return errors.Wrapf(err, "failed to ensure directory at %q", path) - } - } - - return err -} - // IsDir determines is the path given is a directory or not. func IsDir(name string) (bool, error) { fi, err := os.Stat(name) @@ -509,50 +203,6 @@ func IsDir(name string) (bool, error) { return true, nil } -// IsNonEmptyDir determines if the path given is a non-empty directory or not. -func IsNonEmptyDir(name string) (bool, error) { - isDir, err := IsDir(name) - if err != nil && !os.IsNotExist(err) { - return false, err - } else if !isDir { - return false, nil - } - - // Get file descriptor - f, err := os.Open(name) - if err != nil { - return false, err - } - defer f.Close() - - // Query only 1 child. EOF if no children. - _, err = f.Readdirnames(1) - switch err { - case io.EOF: - return false, nil - case nil: - return true, nil - default: - return false, err - } -} - -// IsRegular determines if the path given is a regular file or not. -func IsRegular(name string) (bool, error) { - fi, err := os.Stat(name) - if os.IsNotExist(err) { - return false, nil - } - if err != nil { - return false, err - } - mode := fi.Mode() - if mode&os.ModeType != 0 { - return false, errors.Errorf("%q is a %v, expected a file", name, mode) - } - return true, nil -} - // IsSymlink determines if the given path is a symbolic link. func IsSymlink(path string) (bool, error) { l, err := os.Lstat(path) diff --git a/pkg/fsutil/fs_test.go b/pkg/fsutil/fs_test.go index 9af0c0ec9..873ab0f68 100644 --- a/pkg/fsutil/fs_test.go +++ b/pkg/fsutil/fs_test.go @@ -8,198 +8,10 @@ import ( "io/ioutil" "os" "path/filepath" - "reflect" "runtime" - "strings" "testing" - - "github.com/golang/dep/internal/test" - "github.com/pkg/errors" ) -// This function tests HadFilepathPrefix. It should test it on both case -// sensitive and insensitive situations. However, the only reliable way to test -// case-insensitive behaviour is if using case-insensitive filesystem. This -// cannot be guaranteed in an automated test. Therefore, the behaviour of the -// tests is not to test case sensitivity on *nix and to assume that Windows is -// case-insensitive. Please see link below for some background. -// -// https://superuser.com/questions/266110/how-do-you-make-windows-7-fully-case-sensitive-with-respect-to-the-filesystem -// -// NOTE: NTFS can be made case-sensitive. However many Windows programs, -// including Windows Explorer do not handle gracefully multiple files that -// differ only in capitalization. It is possible that this can cause these tests -// to fail on some setups. -func TestHasFilepathPrefix(t *testing.T) { - dir, err := ioutil.TempDir("", "dep") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - - // dir2 is the same as dir but with different capitalization on Windows to - // test case insensitivity - var dir2 string - if runtime.GOOS == "windows" { - dir = strings.ToLower(dir) - dir2 = strings.ToUpper(dir) - } else { - dir2 = dir - } - - // For testing trailing and repeated separators - sep := string(os.PathSeparator) - - cases := []struct { - path string - prefix string - want bool - }{ - {filepath.Join(dir, "a", "b"), filepath.Join(dir2), true}, - {filepath.Join(dir, "a", "b"), dir2 + sep + sep + "a", true}, - {filepath.Join(dir, "a", "b"), filepath.Join(dir2, "a") + sep, true}, - {filepath.Join(dir, "a", "b") + sep, filepath.Join(dir2), true}, - {dir + sep + sep + filepath.Join("a", "b"), filepath.Join(dir2, "a"), true}, - {filepath.Join(dir, "a", "b"), filepath.Join(dir2, "a"), true}, - {filepath.Join(dir, "a", "b"), filepath.Join(dir2, "a", "b"), true}, - {filepath.Join(dir, "a", "b"), filepath.Join(dir2, "c"), false}, - {filepath.Join(dir, "a", "b"), filepath.Join(dir2, "a", "d", "b"), false}, - {filepath.Join(dir, "a", "b"), filepath.Join(dir2, "a", "b2"), false}, - {filepath.Join(dir), filepath.Join(dir2, "a", "b"), false}, - {filepath.Join(dir, "ab"), filepath.Join(dir2, "a", "b"), false}, - {filepath.Join(dir, "ab"), filepath.Join(dir2, "a"), false}, - {filepath.Join(dir, "123"), filepath.Join(dir2, "123"), true}, - {filepath.Join(dir, "123"), filepath.Join(dir2, "1"), false}, - {filepath.Join(dir, "⌘"), filepath.Join(dir2, "⌘"), true}, - {filepath.Join(dir, "a"), filepath.Join(dir2, "⌘"), false}, - {filepath.Join(dir, "⌘"), filepath.Join(dir2, "a"), false}, - } - - for _, c := range cases { - if err := os.MkdirAll(c.path, 0755); err != nil { - t.Fatal(err) - } - - if err = os.MkdirAll(c.prefix, 0755); err != nil { - t.Fatal(err) - } - - got, err := HasFilepathPrefix(c.path, c.prefix) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - if c.want != got { - t.Fatalf("dir: %q, prefix: %q, expected: %v, got: %v", c.path, c.prefix, c.want, got) - } - } -} - -// This function tests HadFilepathPrefix. It should test it on both case -// sensitive and insensitive situations. However, the only reliable way to test -// case-insensitive behaviour is if using case-insensitive filesystem. This -// cannot be guaranteed in an automated test. Therefore, the behaviour of the -// tests is not to test case sensitivity on *nix and to assume that Windows is -// case-insensitive. Please see link below for some background. -// -// https://superuser.com/questions/266110/how-do-you-make-windows-7-fully-case-sensitive-with-respect-to-the-filesystem -// -// NOTE: NTFS can be made case-sensitive. However many Windows programs, -// including Windows Explorer do not handle gracefully multiple files that -// differ only in capitalization. It is possible that this can cause these tests -// to fail on some setups. -func TestHasFilepathPrefix_Files(t *testing.T) { - dir, err := ioutil.TempDir("", "dep") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - - // dir2 is the same as dir but with different capitalization on Windows to - // test case insensitivity - var dir2 string - if runtime.GOOS == "windows" { - dir = strings.ToLower(dir) - dir2 = strings.ToUpper(dir) - } else { - dir2 = dir - } - - existingFile := filepath.Join(dir, "exists") - if err = os.MkdirAll(existingFile, 0755); err != nil { - t.Fatal(err) - } - - nonExistingFile := filepath.Join(dir, "does_not_exists") - - cases := []struct { - path string - prefix string - want bool - err bool - }{ - {existingFile, filepath.Join(dir2), true, false}, - {nonExistingFile, filepath.Join(dir2), false, true}, - } - - for _, c := range cases { - got, err := HasFilepathPrefix(c.path, c.prefix) - if err != nil && !c.err { - t.Fatalf("unexpected error: %s", err) - } - if c.want != got { - t.Fatalf("dir: %q, prefix: %q, expected: %v, got: %v", c.path, c.prefix, c.want, got) - } - } -} - -func TestEquivalentPaths(t *testing.T) { - h := test.NewHelper(t) - h.TempDir("dir") - h.TempDir("dir2") - - h.TempFile("file", "") - h.TempFile("file2", "") - - h.TempDir("DIR") - h.TempFile("FILE", "") - - testcases := []struct { - p1, p2 string - caseSensitiveEquivalent bool - caseInensitiveEquivalent bool - err bool - }{ - {h.Path("dir"), h.Path("dir"), true, true, false}, - {h.Path("file"), h.Path("file"), true, true, false}, - {h.Path("dir"), h.Path("dir2"), false, false, false}, - {h.Path("file"), h.Path("file2"), false, false, false}, - {h.Path("dir"), h.Path("file"), false, false, false}, - {h.Path("dir"), h.Path("DIR"), false, true, false}, - {strings.ToLower(h.Path("dir")), strings.ToUpper(h.Path("dir")), false, true, true}, - } - - caseSensitive, err := IsCaseSensitiveFilesystem(h.Path("dir")) - if err != nil { - t.Fatal("unexpcted error:", err) - } - - for _, tc := range testcases { - got, err := EquivalentPaths(tc.p1, tc.p2) - if err != nil && !tc.err { - t.Error("unexpected error:", err) - } - if caseSensitive { - if tc.caseSensitiveEquivalent != got { - t.Errorf("expected EquivalentPaths(%q, %q) to be %t on case-sensitive filesystem, got %t", tc.p1, tc.p2, tc.caseSensitiveEquivalent, got) - } - } else { - if tc.caseInensitiveEquivalent != got { - t.Errorf("expected EquivalentPaths(%q, %q) to be %t on case-insensitive filesystem, got %t", tc.p1, tc.p2, tc.caseInensitiveEquivalent, got) - } - } - } -} - func TestRenameWithFallback(t *testing.T) { dir, err := ioutil.TempDir("", "dep") if err != nil { @@ -238,158 +50,6 @@ func TestRenameWithFallback(t *testing.T) { } } -func TestIsCaseSensitiveFilesystem(t *testing.T) { - isLinux := runtime.GOOS == "linux" - isWindows := runtime.GOOS == "windows" - isMacOS := runtime.GOOS == "darwin" - - if !isLinux && !isWindows && !isMacOS { - t.Skip("Run this test on Windows, Linux and macOS only") - } - - dir, err := ioutil.TempDir("", "TestCaseSensitivity") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - - var want bool - if isLinux { - want = true - } else { - want = false - } - - got, err := IsCaseSensitiveFilesystem(dir) - - if err != nil { - t.Fatalf("unexpected error message: \n\t(GOT) %+v", err) - } - - if want != got { - t.Fatalf("unexpected value returned: \n\t(GOT) %t\n\t(WNT) %t", got, want) - } -} - -func TestReadActualFilenames(t *testing.T) { - // We are trying to skip this test on file systems which are case-sensiive. We could - // have used `fs.IsCaseSensitiveFilesystem` for this check. However, the code we are - // testing also relies on `fs.IsCaseSensitiveFilesystem`. So a bug in - // `fs.IsCaseSensitiveFilesystem` could prevent this test from being run. This is the - // only scenario where we prefer the OS heuristic over doing the actual work of - // validating filesystem case sensitivity via `fs.IsCaseSensitiveFilesystem`. - if runtime.GOOS != "windows" && runtime.GOOS != "darwin" { - t.Skip("skip this test on non-Windows, non-macOS") - } - - h := test.NewHelper(t) - defer h.Cleanup() - - h.TempDir("") - tmpPath := h.Path(".") - - // First, check the scenarios for which we expect an error. - _, err := ReadActualFilenames(filepath.Join(tmpPath, "does_not_exists"), []string{""}) - switch { - case err == nil: - t.Fatal("expected err for non-existing folder") - // use `errors.Cause` because the error is wrapped and returned - case !os.IsNotExist(errors.Cause(err)): - t.Fatalf("unexpected error: %+v", err) - } - h.TempFile("tmpFile", "") - _, err = ReadActualFilenames(h.Path("tmpFile"), []string{""}) - switch { - case err == nil: - t.Fatal("expected err for passing file instead of directory") - case err != errPathNotDir: - t.Fatalf("unexpected error: %+v", err) - } - - cases := []struct { - createFiles []string - names []string - want map[string]string - }{ - // If we supply no filenames to the function, it should return an empty map. - {nil, nil, map[string]string{}}, - // If the directory contains the given file with different case, it should return - // a map which has the given filename as the key and actual filename as the value. - { - []string{"test1.txt"}, - []string{"Test1.txt"}, - map[string]string{"Test1.txt": "test1.txt"}, - }, - // 1. If the given filename is same as the actual filename, map should have the - // same key and value for the file. - // 2. If the given filename is present with different case for file extension, - // it should return a map which has the given filename as the key and actual - // filename as the value. - // 3. If the given filename is not present even with a different case, the map - // returned should not have an entry for that filename. - { - []string{"test2.txt", "test3.TXT"}, - []string{"test2.txt", "Test3.txt", "Test4.txt"}, - map[string]string{ - "test2.txt": "test2.txt", - "Test3.txt": "test3.TXT", - }, - }, - } - for _, c := range cases { - for _, file := range c.createFiles { - h.TempFile(file, "") - } - got, err := ReadActualFilenames(tmpPath, c.names) - if err != nil { - t.Fatalf("unexpected error: %+v", err) - } - if !reflect.DeepEqual(c.want, got) { - t.Fatalf("returned value does not match expected: \n\t(GOT) %v\n\t(WNT) %v", - got, c.want) - } - } -} - -func TestGenTestFilename(t *testing.T) { - cases := []struct { - str string - want string - }{ - {"abc", "Abc"}, - {"ABC", "aBC"}, - {"AbC", "abC"}, - {"αβγ", "Αβγ"}, - {"123", "123"}, - {"1a2", "1A2"}, - {"12a", "12A"}, - {"⌘", "⌘"}, - } - - for _, c := range cases { - got := genTestFilename(c.str) - if c.want != got { - t.Fatalf("str: %q, expected: %q, got: %q", c.str, c.want, got) - } - } -} - -func BenchmarkGenTestFilename(b *testing.B) { - cases := []string{ - strings.Repeat("a", 128), - strings.Repeat("A", 128), - strings.Repeat("α", 128), - strings.Repeat("1", 128), - strings.Repeat("⌘", 128), - } - - for i := 0; i < b.N; i++ { - for _, str := range cases { - genTestFilename(str) - } - } -} - func TestCopyDir(t *testing.T) { dir, err := ioutil.TempDir("", "dep") if err != nil { @@ -843,117 +503,6 @@ func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() { return cleanup } -func TestEnsureDir(t *testing.T) { - h := test.NewHelper(t) - defer h.Cleanup() - h.TempDir(".") - h.TempFile("file", "") - - tmpPath := h.Path(".") - - var dn string - cleanup := setupInaccessibleDir(t, func(dir string) error { - dn = filepath.Join(dir, "dir") - return os.Mkdir(dn, 0777) - }) - defer cleanup() - - tests := map[string]bool{ - // [success] A dir already exists for the given path. - tmpPath: true, - // [success] Dir does not exist but parent dir exists, so should get created. - filepath.Join(tmpPath, "testdir"): true, - // [failure] Dir and parent dir do not exist, should return an error. - filepath.Join(tmpPath, "notexist", "testdir"): false, - // [failure] Regular file present at given path. - h.Path("file"): false, - // [failure] Path inaccessible. - dn: false, - } - - if runtime.GOOS == "windows" { - // This test doesn't work on Microsoft Windows because - // of the differences in how file permissions are - // implemented. For this to work, the directory where - // the directory exists should be inaccessible. - delete(tests, dn) - } - - for path, shouldEnsure := range tests { - err := EnsureDir(path, 0777) - if shouldEnsure { - if err != nil { - t.Fatalf("unexpected error %q for %q", err, path) - } else if ok, err := IsDir(path); !ok { - t.Fatalf("expected directory to be preset at %q", path) - t.Fatal(err) - } - } else if err == nil { - t.Fatalf("expected error for path %q, got none", path) - } - } -} - -func TestIsRegular(t *testing.T) { - wd, err := os.Getwd() - if err != nil { - t.Fatal(err) - } - - var fn string - - cleanup := setupInaccessibleDir(t, func(dir string) error { - fn = filepath.Join(dir, "file") - fh, err := os.Create(fn) - if err != nil { - return err - } - - return fh.Close() - }) - defer cleanup() - - tests := map[string]struct { - exists bool - err bool - }{ - wd: {false, true}, - filepath.Join(wd, "testdata"): {false, true}, - filepath.Join(wd, "testdata", "test.file"): {true, false}, - filepath.Join(wd, "this_file_does_not_exist.thing"): {false, false}, - fn: {false, true}, - } - - if runtime.GOOS == "windows" { - // This test doesn't work on Microsoft Windows because - // of the differences in how file permissions are - // implemented. For this to work, the directory where - // the file exists should be inaccessible. - delete(tests, fn) - } - - for f, want := range tests { - got, err := IsRegular(f) - if err != nil { - if want.exists != got { - t.Fatalf("expected %t for %s, got %t", want.exists, f, got) - } - if !want.err { - t.Fatalf("expected no error, got %v", err) - } - } else { - if want.err { - t.Fatalf("expected error for %s, got none", f) - } - } - - if got != want.exists { - t.Fatalf("expected %t for %s, got %t", want, f, got) - } - } - -} - func TestIsDir(t *testing.T) { wd, err := os.Getwd() if err != nil { @@ -999,61 +548,6 @@ func TestIsDir(t *testing.T) { } } -func TestIsNonEmptyDir(t *testing.T) { - wd, err := os.Getwd() - if err != nil { - t.Fatal(err) - } - - h := test.NewHelper(t) - defer h.Cleanup() - - h.TempDir("empty") - - testCases := []struct { - path string - empty bool - err bool - }{ - {wd, true, false}, - {"testdata", true, false}, - {filepath.Join(wd, "fs.go"), false, true}, - {filepath.Join(wd, "this_file_does_not_exist.thing"), false, false}, - {h.Path("empty"), false, false}, - } - - // This test case doesn't work on Microsoft Windows because of the - // differences in how file permissions are implemented. - if runtime.GOOS != "windows" { - var inaccessibleDir string - cleanup := setupInaccessibleDir(t, func(dir string) error { - inaccessibleDir = filepath.Join(dir, "empty") - return os.Mkdir(inaccessibleDir, 0777) - }) - defer cleanup() - - testCases = append(testCases, struct { - path string - empty bool - err bool - }{inaccessibleDir, false, true}) - } - - for _, want := range testCases { - got, err := IsNonEmptyDir(want.path) - if want.err && err == nil { - if got { - t.Fatalf("wanted false with error for %v, but got true", want.path) - } - t.Fatalf("wanted an error for %v, but it was nil", want.path) - } - - if got != want.empty { - t.Fatalf("wanted %t for %v, but got %t", want.empty, want.path, got) - } - } -} - func TestIsSymlink(t *testing.T) { dir, err := ioutil.TempDir("", "dep") if err != nil { From 60bd2a56fdde72c26e29a2350d5cdee215f3e928 Mon Sep 17 00:00:00 2001 From: Brice Rising Date: Mon, 10 Dec 2018 13:04:29 -0500 Subject: [PATCH 03/16] Remove dep/internal/test dependency Recreated the features of the test package in dep that were necessary to run the tests in the fs package. Signed-off-by: Brice Rising --- pkg/fsutil/fs_test.go | 101 ++++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 52 deletions(-) diff --git a/pkg/fsutil/fs_test.go b/pkg/fsutil/fs_test.go index 873ab0f68..c04a8ddd1 100644 --- a/pkg/fsutil/fs_test.go +++ b/pkg/fsutil/fs_test.go @@ -7,13 +7,19 @@ package fs import ( "io/ioutil" "os" + "os/exec" "path/filepath" "runtime" "testing" + "sync" +) + +var ( + mu sync.Mutex ) func TestRenameWithFallback(t *testing.T) { - dir, err := ioutil.TempDir("", "dep") + dir, err := ioutil.TempDir("", "helm-tmp") if err != nil { t.Fatal(err) } @@ -51,7 +57,7 @@ func TestRenameWithFallback(t *testing.T) { } func TestCopyDir(t *testing.T) { - dir, err := ioutil.TempDir("", "dep") + dir, err := ioutil.TempDir("", "helm-tmp") if err != nil { t.Fatal(err) } @@ -149,7 +155,7 @@ func TestCopyDirFail_SrcInaccessible(t *testing.T) { }) defer cleanup() - dir, err := ioutil.TempDir("", "dep") + dir, err := ioutil.TempDir("", "helm-tmp") if err != nil { t.Fatal(err) } @@ -171,7 +177,7 @@ func TestCopyDirFail_DstInaccessible(t *testing.T) { var srcdir, dstdir string - dir, err := ioutil.TempDir("", "dep") + dir, err := ioutil.TempDir("", "helm-tmp") if err != nil { t.Fatal(err) } @@ -196,7 +202,7 @@ func TestCopyDirFail_DstInaccessible(t *testing.T) { func TestCopyDirFail_SrcIsNotDir(t *testing.T) { var srcdir, dstdir string - dir, err := ioutil.TempDir("", "dep") + dir, err := ioutil.TempDir("", "helm-tmp") if err != nil { t.Fatal(err) } @@ -222,7 +228,7 @@ func TestCopyDirFail_SrcIsNotDir(t *testing.T) { func TestCopyDirFail_DstExists(t *testing.T) { var srcdir, dstdir string - dir, err := ioutil.TempDir("", "dep") + dir, err := ioutil.TempDir("", "helm-tmp") if err != nil { t.Fatal(err) } @@ -260,7 +266,7 @@ func TestCopyDirFailOpen(t *testing.T) { var srcdir, dstdir string - dir, err := ioutil.TempDir("", "dep") + dir, err := ioutil.TempDir("", "helm-tmp") if err != nil { t.Fatal(err) } @@ -291,7 +297,7 @@ func TestCopyDirFailOpen(t *testing.T) { } func TestCopyFile(t *testing.T) { - dir, err := ioutil.TempDir("", "dep") + dir, err := ioutil.TempDir("", "helm-tmp") if err != nil { t.Fatal(err) } @@ -337,15 +343,32 @@ func TestCopyFile(t *testing.T) { } } +func cleanUpDir(dir string) { + // NOTE(mattn): It seems that sometimes git.exe is not dead + // when cleanUpDir() is called. But we do not know any way to wait for it. + if runtime.GOOS == "windows" { + mu.Lock() + exec.Command(`taskkill`, `/F`, `/IM`, `git.exe`).Run() + mu.Unlock() + } + if dir != "" { + os.RemoveAll(dir) + } +} + func TestCopyFileSymlink(t *testing.T) { - h := test.NewHelper(t) - defer h.Cleanup() - h.TempDir(".") + var tempdir, err = ioutil.TempDir("", "gotest") + + if err != nil { + t.Fatalf("failed to create directory: %s", err); + } + + defer cleanUpDir(tempdir) testcases := map[string]string{ - filepath.Join("./testdata/symlinks/file-symlink"): filepath.Join(h.Path("."), "dst-file"), - filepath.Join("./testdata/symlinks/windows-file-symlink"): filepath.Join(h.Path("."), "windows-dst-file"), - filepath.Join("./testdata/symlinks/invalid-symlink"): filepath.Join(h.Path("."), "invalid-symlink"), + filepath.Join("./testdata/symlinks/file-symlink"): filepath.Join(tempdir, "dst-file"), + filepath.Join("./testdata/symlinks/windows-file-symlink"): filepath.Join(tempdir, "windows-dst-file"), + filepath.Join("./testdata/symlinks/invalid-symlink"): filepath.Join(tempdir, "invalid-symlink"), } for symlink, dst := range testcases { @@ -362,15 +385,21 @@ func TestCopyFileSymlink(t *testing.T) { // regular users aren't granted usually. So we copy the file // content as a fall back instead of creating a real symlink. srcb, err := ioutil.ReadFile(symlink) - h.Must(err) + if err != nil { + t.Fatalf("%+v", err) + } dstb, err := ioutil.ReadFile(dst) - h.Must(err) + if err != nil { + t.Fatalf("%+v", err) + } want = string(srcb) got = string(dstb) } else { want, err = os.Readlink(symlink) - h.Must(err) + if err != nil { + t.Fatalf("%+v", err) + } got, err = os.Readlink(dst) if err != nil { @@ -385,38 +414,6 @@ func TestCopyFileSymlink(t *testing.T) { } } -func TestCopyFileLongFilePath(t *testing.T) { - if runtime.GOOS != "windows" { - // We want to ensure the temporary fix actually fixes the issue with - // os.Chmod and long file paths. This is only applicable on Windows. - t.Skip("skipping on non-windows") - } - - h := test.NewHelper(t) - h.TempDir(".") - defer h.Cleanup() - - tmpPath := h.Path(".") - - // Create a directory with a long-enough path name to cause the bug in #774. - dirName := "" - for len(tmpPath+string(os.PathSeparator)+dirName) <= 300 { - dirName += "directory" - } - - h.TempDir(dirName) - h.TempFile(dirName+string(os.PathSeparator)+"src", "") - - tmpDirPath := tmpPath + string(os.PathSeparator) + dirName + string(os.PathSeparator) - - err := copyFile(tmpDirPath+"src", tmpDirPath+"dst") - if err != nil { - t.Fatalf("unexpected error while copying file: %v", err) - } -} - -// C:\Users\appveyor\AppData\Local\Temp\1\gotest639065787\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890\dir4567890 - func TestCopyFileFail(t *testing.T) { if runtime.GOOS == "windows" { // XXX: setting permissions works differently in @@ -425,7 +422,7 @@ func TestCopyFileFail(t *testing.T) { t.Skip("skipping on windows") } - dir, err := ioutil.TempDir("", "dep") + dir, err := ioutil.TempDir("", "helm-tmp") if err != nil { t.Fatal(err) } @@ -465,7 +462,7 @@ func TestCopyFileFail(t *testing.T) { // files this function creates. It is the caller's responsibility to call // this function before the test is done running, whether there's an error or not. func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() { - dir, err := ioutil.TempDir("", "dep") + dir, err := ioutil.TempDir("", "helm-tmp") if err != nil { t.Fatal(err) return nil // keep compiler happy @@ -549,7 +546,7 @@ func TestIsDir(t *testing.T) { } func TestIsSymlink(t *testing.T) { - dir, err := ioutil.TempDir("", "dep") + dir, err := ioutil.TempDir("", "helm-tmp") if err != nil { t.Fatal(err) } From 3469c9a17c3a78164b8160c3c2bb60063d829457 Mon Sep 17 00:00:00 2001 From: Brice Rising Date: Mon, 10 Dec 2018 14:35:14 -0500 Subject: [PATCH 04/16] Skip inaccessible file tests for root Root can access all files, so the tests for inaccessible files were failing. Now we will skip these tests when running as root. Signed-off-by: Brice Rising --- pkg/fsutil/fs_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/pkg/fsutil/fs_test.go b/pkg/fsutil/fs_test.go index c04a8ddd1..994683725 100644 --- a/pkg/fsutil/fs_test.go +++ b/pkg/fsutil/fs_test.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "os" "os/exec" + "os/user" "path/filepath" "runtime" "testing" @@ -147,6 +148,13 @@ func TestCopyDirFail_SrcInaccessible(t *testing.T) { t.Skip("skipping on windows") } + var current_user, err = user.Current() + + if current_user.Name == "root" { + // Skipping if root, because all files are accessible + t.Skip("Skipping for root user") + } + var srcdir, dstdir string cleanup := setupInaccessibleDir(t, func(dir string) error { @@ -175,6 +183,13 @@ func TestCopyDirFail_DstInaccessible(t *testing.T) { t.Skip("skipping on windows") } + var current_user, err = user.Current() + + if current_user.Name == "root" { + // Skipping if root, because all files are accessible + t.Skip("Skipping for root user") + } + var srcdir, dstdir string dir, err := ioutil.TempDir("", "helm-tmp") @@ -264,6 +279,13 @@ func TestCopyDirFailOpen(t *testing.T) { t.Skip("skipping on windows") } + var current_user, err = user.Current() + + if current_user.Name == "root" { + // Skipping if root, because all files are accessible + t.Skip("Skipping for root user") + } + var srcdir, dstdir string dir, err := ioutil.TempDir("", "helm-tmp") @@ -422,6 +444,13 @@ func TestCopyFileFail(t *testing.T) { t.Skip("skipping on windows") } + var current_user, err = user.Current() + + if current_user.Name == "root" { + // Skipping if root, because all files are accessible + t.Skip("Skipping for root user") + } + dir, err := ioutil.TempDir("", "helm-tmp") if err != nil { t.Fatal(err) @@ -501,6 +530,14 @@ func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() { } func TestIsDir(t *testing.T) { + + var current_user, err = user.Current() + + if current_user.Name == "root" { + // Skipping if root, because all files are accessible + t.Skip("Skipping for root user") + } + wd, err := os.Getwd() if err != nil { t.Fatal(err) @@ -546,6 +583,14 @@ func TestIsDir(t *testing.T) { } func TestIsSymlink(t *testing.T) { + + var current_user, err = user.Current() + + if current_user.Name == "root" { + // Skipping if root, because all files are accessible + t.Skip("Skipping for root user") + } + dir, err := ioutil.TempDir("", "helm-tmp") if err != nil { t.Fatal(err) From 3d15ec7125bff4a43f4da57225c88c086cbdf108 Mon Sep 17 00:00:00 2001 From: Brice Rising Date: Mon, 10 Dec 2018 14:52:21 -0500 Subject: [PATCH 05/16] Fix style issues Our style settings don't like underscores in variable names, so I switched to camel case. Also, err was unused in some places, so I made an error a fail condition for some test. Signed-off-by: Brice Rising --- pkg/fsutil/fs_test.go | 48 ++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/pkg/fsutil/fs_test.go b/pkg/fsutil/fs_test.go index 994683725..a2f0b9fbc 100644 --- a/pkg/fsutil/fs_test.go +++ b/pkg/fsutil/fs_test.go @@ -148,9 +148,13 @@ func TestCopyDirFail_SrcInaccessible(t *testing.T) { t.Skip("skipping on windows") } - var current_user, err = user.Current() + var currentUser, err = user.Current() - if current_user.Name == "root" { + if err != nil { + t.Fatalf("Failed to get name of current user: %s", err) + } + + if currentUser.Name == "root" { // Skipping if root, because all files are accessible t.Skip("Skipping for root user") } @@ -183,9 +187,13 @@ func TestCopyDirFail_DstInaccessible(t *testing.T) { t.Skip("skipping on windows") } - var current_user, err = user.Current() + var currentUser, err = user.Current() - if current_user.Name == "root" { + if err != nil { + t.Fatalf("Failed to get name of current user: %s", err) + } + + if currentUser.Name == "root" { // Skipping if root, because all files are accessible t.Skip("Skipping for root user") } @@ -279,9 +287,13 @@ func TestCopyDirFailOpen(t *testing.T) { t.Skip("skipping on windows") } - var current_user, err = user.Current() + var currentUser, err = user.Current() + + if err != nil { + t.Fatalf("Failed to get name of current user: %s", err) + } - if current_user.Name == "root" { + if currentUser.Name == "root" { // Skipping if root, because all files are accessible t.Skip("Skipping for root user") } @@ -444,9 +456,13 @@ func TestCopyFileFail(t *testing.T) { t.Skip("skipping on windows") } - var current_user, err = user.Current() + var currentUser, err = user.Current() - if current_user.Name == "root" { + if err != nil { + t.Fatalf("Failed to get name of current user: %s", err) + } + + if currentUser.Name == "root" { // Skipping if root, because all files are accessible t.Skip("Skipping for root user") } @@ -531,9 +547,13 @@ func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() { func TestIsDir(t *testing.T) { - var current_user, err = user.Current() + var currentUser, err = user.Current() - if current_user.Name == "root" { + if err != nil { + t.Fatalf("Failed to get name of current user: %s", err) + } + + if currentUser.Name == "root" { // Skipping if root, because all files are accessible t.Skip("Skipping for root user") } @@ -584,9 +604,13 @@ func TestIsDir(t *testing.T) { func TestIsSymlink(t *testing.T) { - var current_user, err = user.Current() + var currentUser, err = user.Current() + + if err != nil { + t.Fatalf("Failed to get name of current user: %s", err) + } - if current_user.Name == "root" { + if currentUser.Name == "root" { // Skipping if root, because all files are accessible t.Skip("Skipping for root user") } From aacffe5fb2870f686f5bf620c13fc63ff4552f2d Mon Sep 17 00:00:00 2001 From: Brice Rising Date: Mon, 10 Dec 2018 14:58:54 -0500 Subject: [PATCH 06/16] Remove dead code Unused var was in fs.go. Removed it. Signed-off-by: Brice Rising --- pkg/fsutil/fs.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/fsutil/fs.go b/pkg/fsutil/fs.go index 82b7d4e76..bee8a6cd7 100644 --- a/pkg/fsutil/fs.go +++ b/pkg/fsutil/fs.go @@ -55,8 +55,6 @@ func renameByCopy(src, dst string) error { return errors.Wrapf(os.RemoveAll(src), "cannot delete %s", src) } -var errPathNotDir = errors.New("given path is not a directory") - var ( errSrcNotDir = errors.New("source is not a directory") errDstExist = errors.New("destination already exists") From c292ba5c8ff4748ee78fbd21af441a173e28aab2 Mon Sep 17 00:00:00 2001 From: Brice Rising Date: Mon, 10 Dec 2018 15:02:17 -0500 Subject: [PATCH 07/16] Run gofmt -s on fs_test Neeted to run formatting on fs_test.go Signed-off-by: Brice Rising --- pkg/fsutil/fs_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/fsutil/fs_test.go b/pkg/fsutil/fs_test.go index a2f0b9fbc..8652cfec7 100644 --- a/pkg/fsutil/fs_test.go +++ b/pkg/fsutil/fs_test.go @@ -11,12 +11,12 @@ import ( "os/user" "path/filepath" "runtime" - "testing" "sync" + "testing" ) var ( - mu sync.Mutex + mu sync.Mutex ) func TestRenameWithFallback(t *testing.T) { @@ -394,7 +394,7 @@ func TestCopyFileSymlink(t *testing.T) { var tempdir, err = ioutil.TempDir("", "gotest") if err != nil { - t.Fatalf("failed to create directory: %s", err); + t.Fatalf("failed to create directory: %s", err) } defer cleanUpDir(tempdir) From 3d78e6a1b2d0a6cf25c024894a2dda4bd92a956d Mon Sep 17 00:00:00 2001 From: Brice Rising Date: Tue, 11 Dec 2018 08:55:43 -0500 Subject: [PATCH 08/16] Run gofmt -s -w using official golang docker image For some reason, gofmt -s -w produced a different result than running gofmt on my host os Signed-off-by: Brice Rising --- pkg/fsutil/fs_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/fsutil/fs_test.go b/pkg/fsutil/fs_test.go index 8652cfec7..072c5ce49 100644 --- a/pkg/fsutil/fs_test.go +++ b/pkg/fsutil/fs_test.go @@ -575,9 +575,9 @@ func TestIsDir(t *testing.T) { exists bool err bool }{ - wd: {true, false}, - filepath.Join(wd, "testdata"): {true, false}, - filepath.Join(wd, "main.go"): {false, true}, + wd: {true, false}, + filepath.Join(wd, "testdata"): {true, false}, + filepath.Join(wd, "main.go"): {false, true}, filepath.Join(wd, "this_file_does_not_exist.thing"): {false, true}, dn: {false, true}, } From 55ea6987841b240ce65d6e7231ddb8046b4369c0 Mon Sep 17 00:00:00 2001 From: Brice Rising Date: Mon, 17 Dec 2018 16:10:55 -0500 Subject: [PATCH 09/16] Add Apache License line to fsutil This is probably not the correct apporach to using bsd licensed code under an apache license. Signed-off-by: Brice Rising --- pkg/fsutil/fs.go | 4 +++- pkg/fsutil/fs_test.go | 5 ++++- pkg/fsutil/rename.go | 4 +++- pkg/fsutil/rename_windows.go | 4 +++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/pkg/fsutil/fs.go b/pkg/fsutil/fs.go index bee8a6cd7..f1a26a411 100644 --- a/pkg/fsutil/fs.go +++ b/pkg/fsutil/fs.go @@ -1,6 +1,8 @@ // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// license. + +// Licensed under the Apache License, Version 2.0 (the "License"); package fs diff --git a/pkg/fsutil/fs_test.go b/pkg/fsutil/fs_test.go index 072c5ce49..8093eea48 100644 --- a/pkg/fsutil/fs_test.go +++ b/pkg/fsutil/fs_test.go @@ -1,6 +1,9 @@ // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// license. + +// Licensed under the Apache License, Version 2.0 (the "License"); + package fs diff --git a/pkg/fsutil/rename.go b/pkg/fsutil/rename.go index c48f69f1a..059e027ee 100644 --- a/pkg/fsutil/rename.go +++ b/pkg/fsutil/rename.go @@ -1,6 +1,8 @@ // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// license. + +// Licensed under the Apache License, Version 2.0 (the "License"); // +build !windows diff --git a/pkg/fsutil/rename_windows.go b/pkg/fsutil/rename_windows.go index 50829a5cd..c8491e94a 100644 --- a/pkg/fsutil/rename_windows.go +++ b/pkg/fsutil/rename_windows.go @@ -1,6 +1,8 @@ // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// license. + +// Licensed under the Apache License, Version 2.0 (the "License"); // +build windows From 5ab89c9794b86c0b76e2342a9704d9f25c87158b Mon Sep 17 00:00:00 2001 From: Brice Rising Date: Mon, 17 Dec 2018 16:17:05 -0500 Subject: [PATCH 10/16] Copy approach done in another project for using bsd license I found another package that took code from a BSD licensed open source project. I copied their approach for reusing that license. Signed-off-by: Brice Rising --- pkg/fsutil/fs.go | 22 ++++++++++++++++++---- pkg/fsutil/fs_test.go | 22 ++++++++++++++++++---- pkg/fsutil/rename.go | 22 ++++++++++++++++++---- pkg/fsutil/rename_windows.go | 22 ++++++++++++++++++---- 4 files changed, 72 insertions(+), 16 deletions(-) diff --git a/pkg/fsutil/fs.go b/pkg/fsutil/fs.go index f1a26a411..0795f0f0f 100644 --- a/pkg/fsutil/fs.go +++ b/pkg/fsutil/fs.go @@ -1,8 +1,22 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license. +/* +Copyright (c) for portions of fs.go are held by The Go Authors, 2016 and are provided under +the BSD license. -// Licensed under the Apache License, Version 2.0 (the "License"); +https://github.com/golang/go/blob/master/LICENSE + +Copyright The Helm Authors. +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 fs diff --git a/pkg/fsutil/fs_test.go b/pkg/fsutil/fs_test.go index 8093eea48..2f7d16e0e 100644 --- a/pkg/fsutil/fs_test.go +++ b/pkg/fsutil/fs_test.go @@ -1,8 +1,22 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license. +/* +Copyright (c) for portions of fs_test.go are held by The Go Authors, 2016 and are provided under +the BSD license. -// Licensed under the Apache License, Version 2.0 (the "License"); +https://github.com/golang/go/blob/master/LICENSE + +Copyright The Helm Authors. +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 fs diff --git a/pkg/fsutil/rename.go b/pkg/fsutil/rename.go index 059e027ee..18df9ce7e 100644 --- a/pkg/fsutil/rename.go +++ b/pkg/fsutil/rename.go @@ -1,8 +1,22 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license. +/* +Copyright (c) for portions of rename.go are held by The Go Authors, 2016 and are provided under +the BSD license. -// Licensed under the Apache License, Version 2.0 (the "License"); +https://github.com/golang/go/blob/master/LICENSE + +Copyright The Helm Authors. +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. +*/ // +build !windows diff --git a/pkg/fsutil/rename_windows.go b/pkg/fsutil/rename_windows.go index c8491e94a..26b35a334 100644 --- a/pkg/fsutil/rename_windows.go +++ b/pkg/fsutil/rename_windows.go @@ -1,8 +1,22 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license. +/* +Copyright (c) for portions of rename_windows.go are held by The Go Authors, 2016 and are provided under +the BSD license. -// Licensed under the Apache License, Version 2.0 (the "License"); +https://github.com/golang/go/blob/master/LICENSE + +Copyright The Helm Authors. +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. +*/ // +build windows From 45d723359eba0806b49148209a3bdfefac8cb7e4 Mon Sep 17 00:00:00 2001 From: Brice Rising Date: Mon, 17 Dec 2018 16:18:46 -0500 Subject: [PATCH 11/16] Switch BSD license in fsutil package to dep project license Signed-off-by: Brice Rising --- pkg/fsutil/fs.go | 2 +- pkg/fsutil/fs_test.go | 2 +- pkg/fsutil/rename.go | 2 +- pkg/fsutil/rename_windows.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/fsutil/fs.go b/pkg/fsutil/fs.go index 0795f0f0f..0ad826be2 100644 --- a/pkg/fsutil/fs.go +++ b/pkg/fsutil/fs.go @@ -2,7 +2,7 @@ Copyright (c) for portions of fs.go are held by The Go Authors, 2016 and are provided under the BSD license. -https://github.com/golang/go/blob/master/LICENSE +https://github.com/golang/dep/blob/master/LICENSE Copyright The Helm Authors. Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/pkg/fsutil/fs_test.go b/pkg/fsutil/fs_test.go index 2f7d16e0e..6d4ea1cad 100644 --- a/pkg/fsutil/fs_test.go +++ b/pkg/fsutil/fs_test.go @@ -2,7 +2,7 @@ Copyright (c) for portions of fs_test.go are held by The Go Authors, 2016 and are provided under the BSD license. -https://github.com/golang/go/blob/master/LICENSE +https://github.com/golang/dep/blob/master/LICENSE Copyright The Helm Authors. Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/pkg/fsutil/rename.go b/pkg/fsutil/rename.go index 18df9ce7e..e24d46615 100644 --- a/pkg/fsutil/rename.go +++ b/pkg/fsutil/rename.go @@ -2,7 +2,7 @@ Copyright (c) for portions of rename.go are held by The Go Authors, 2016 and are provided under the BSD license. -https://github.com/golang/go/blob/master/LICENSE +https://github.com/golang/dep/blob/master/LICENSE Copyright The Helm Authors. Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/pkg/fsutil/rename_windows.go b/pkg/fsutil/rename_windows.go index 26b35a334..fec128bc4 100644 --- a/pkg/fsutil/rename_windows.go +++ b/pkg/fsutil/rename_windows.go @@ -2,7 +2,7 @@ Copyright (c) for portions of rename_windows.go are held by The Go Authors, 2016 and are provided under the BSD license. -https://github.com/golang/go/blob/master/LICENSE +https://github.com/golang/dep/blob/master/LICENSE Copyright The Helm Authors. Licensed under the Apache License, Version 2.0 (the "License"); From 16852f04d92b97de4599b60c2c6a540e950cb97e Mon Sep 17 00:00:00 2001 From: Brice Rising Date: Mon, 17 Dec 2018 16:34:11 -0500 Subject: [PATCH 12/16] Fix linting issues Signed-off-by: Brice Rising --- pkg/fsutil/fs_test.go | 1 - pkg/fsutil/rename.go | 6 +++--- pkg/fsutil/rename_windows.go | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/fsutil/fs_test.go b/pkg/fsutil/fs_test.go index 6d4ea1cad..2888f7953 100644 --- a/pkg/fsutil/fs_test.go +++ b/pkg/fsutil/fs_test.go @@ -18,7 +18,6 @@ See the License for the specific language governing permissions and limitations under the License. */ - package fs import ( diff --git a/pkg/fsutil/rename.go b/pkg/fsutil/rename.go index e24d46615..944e4831f 100644 --- a/pkg/fsutil/rename.go +++ b/pkg/fsutil/rename.go @@ -1,8 +1,10 @@ +// +build !windows + /* Copyright (c) for portions of rename.go are held by The Go Authors, 2016 and are provided under the BSD license. -https://github.com/golang/dep/blob/master/LICENSE +https://github.com/golang/go/blob/master/LICENSE Copyright The Helm Authors. Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +20,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +build !windows - package fs import ( diff --git a/pkg/fsutil/rename_windows.go b/pkg/fsutil/rename_windows.go index fec128bc4..d9540290a 100644 --- a/pkg/fsutil/rename_windows.go +++ b/pkg/fsutil/rename_windows.go @@ -1,3 +1,5 @@ +// +build windows + /* Copyright (c) for portions of rename_windows.go are held by The Go Authors, 2016 and are provided under the BSD license. @@ -18,8 +20,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +build windows - package fs import ( From 394ba588b9ecb0c23d86cf007dcf443b9684b1a2 Mon Sep 17 00:00:00 2001 From: Brice Rising Date: Mon, 17 Dec 2018 16:38:42 -0500 Subject: [PATCH 13/16] Fix url for bsd license Signed-off-by: Brice Rising --- pkg/fsutil/rename.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/fsutil/rename.go b/pkg/fsutil/rename.go index 944e4831f..91c00d075 100644 --- a/pkg/fsutil/rename.go +++ b/pkg/fsutil/rename.go @@ -4,7 +4,7 @@ Copyright (c) for portions of rename.go are held by The Go Authors, 2016 and are provided under the BSD license. -https://github.com/golang/go/blob/master/LICENSE +https://github.com/golang/dep/blob/master/LICENSE Copyright The Helm Authors. Licensed under the Apache License, Version 2.0 (the "License"); From 7786caa6b08953e0d68944d95f460fc99b8191f7 Mon Sep 17 00:00:00 2001 From: Yagnesh Mistry Date: Wed, 9 Oct 2019 15:58:34 +0530 Subject: [PATCH 14/16] Move fs to third_party/dep and include full bsd license text Signed-off-by: Yagnesh Mistry --- pkg/downloader/manager.go | 2 +- pkg/fsutil/rename.go | 47 ------------ {pkg/fsutil => third_party/dep/fs}/fs.go | 26 ++++++- {pkg/fsutil => third_party/dep/fs}/fs_test.go | 26 ++++++- third_party/dep/fs/rename.go | 71 +++++++++++++++++++ .../dep/fs}/rename_windows.go | 26 ++++++- .../dep/fs}/testdata/symlinks/file-symlink | 0 .../dep/fs}/testdata/symlinks/invalid-symlink | 0 .../testdata/symlinks/windows-file-symlink | 0 .../dep/fs}/testdata/test.file | 0 10 files changed, 147 insertions(+), 51 deletions(-) delete mode 100644 pkg/fsutil/rename.go rename {pkg/fsutil => third_party/dep/fs}/fs.go (87%) rename {pkg/fsutil => third_party/dep/fs}/fs_test.go (92%) create mode 100644 third_party/dep/fs/rename.go rename {pkg/fsutil => third_party/dep/fs}/rename_windows.go (55%) rename {pkg/fsutil => third_party/dep/fs}/testdata/symlinks/file-symlink (100%) rename {pkg/fsutil => third_party/dep/fs}/testdata/symlinks/invalid-symlink (100%) rename {pkg/fsutil => third_party/dep/fs}/testdata/symlinks/windows-file-symlink (100%) rename {pkg/fsutil => third_party/dep/fs}/testdata/test.file (100%) diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 138373402..8e4f57072 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -31,13 +31,13 @@ import ( "github.com/ghodss/yaml" "k8s.io/helm/pkg/chartutil" - "k8s.io/helm/pkg/fsutil" "k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/resolver" "k8s.io/helm/pkg/urlutil" + "k8s.io/helm/third_party/dep/fs" ) // Manager handles the lifecycle of fetching, resolving, and storing dependencies. diff --git a/pkg/fsutil/rename.go b/pkg/fsutil/rename.go deleted file mode 100644 index 91c00d075..000000000 --- a/pkg/fsutil/rename.go +++ /dev/null @@ -1,47 +0,0 @@ -// +build !windows - -/* -Copyright (c) for portions of rename.go are held by The Go Authors, 2016 and are provided under -the BSD license. - -https://github.com/golang/dep/blob/master/LICENSE - -Copyright The Helm Authors. -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 fs - -import ( - "os" - "syscall" - - "github.com/pkg/errors" -) - -// renameFallback attempts to determine the appropriate fallback to failed rename -// operation depending on the resulting error. -func renameFallback(err error, src, dst string) error { - // Rename may fail if src and dst are on different devices; fall back to - // copy if we detect that case. syscall.EXDEV is the common name for the - // cross device link error which has varying output text across different - // operating systems. - terr, ok := err.(*os.LinkError) - if !ok { - return err - } else if terr.Err != syscall.EXDEV { - return errors.Wrapf(terr, "link error: cannot rename %s to %s", src, dst) - } - - return renameByCopy(src, dst) -} diff --git a/pkg/fsutil/fs.go b/third_party/dep/fs/fs.go similarity index 87% rename from pkg/fsutil/fs.go rename to third_party/dep/fs/fs.go index 0ad826be2..cd5f70b74 100644 --- a/pkg/fsutil/fs.go +++ b/third_party/dep/fs/fs.go @@ -2,7 +2,31 @@ Copyright (c) for portions of fs.go are held by The Go Authors, 2016 and are provided under the BSD license. -https://github.com/golang/dep/blob/master/LICENSE +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Copyright The Helm Authors. Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/pkg/fsutil/fs_test.go b/third_party/dep/fs/fs_test.go similarity index 92% rename from pkg/fsutil/fs_test.go rename to third_party/dep/fs/fs_test.go index 2888f7953..687a45040 100644 --- a/pkg/fsutil/fs_test.go +++ b/third_party/dep/fs/fs_test.go @@ -2,7 +2,31 @@ Copyright (c) for portions of fs_test.go are held by The Go Authors, 2016 and are provided under the BSD license. -https://github.com/golang/dep/blob/master/LICENSE +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Copyright The Helm Authors. Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/third_party/dep/fs/rename.go b/third_party/dep/fs/rename.go new file mode 100644 index 000000000..a697c48d4 --- /dev/null +++ b/third_party/dep/fs/rename.go @@ -0,0 +1,71 @@ +// +build !windows + +/* +Copyright (c) for portions of rename.go are held by The Go Authors, 2016 and are provided under +the BSD license. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Copyright The Helm Authors. +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 fs + +import ( + "os" + "syscall" + + "github.com/pkg/errors" +) + +// renameFallback attempts to determine the appropriate fallback to failed rename +// operation depending on the resulting error. +func renameFallback(err error, src, dst string) error { + // Rename may fail if src and dst are on different devices; fall back to + // copy if we detect that case. syscall.EXDEV is the common name for the + // cross device link error which has varying output text across different + // operating systems. + terr, ok := err.(*os.LinkError) + if !ok { + return err + } else if terr.Err != syscall.EXDEV { + return errors.Wrapf(terr, "link error: cannot rename %s to %s", src, dst) + } + + return renameByCopy(src, dst) +} diff --git a/pkg/fsutil/rename_windows.go b/third_party/dep/fs/rename_windows.go similarity index 55% rename from pkg/fsutil/rename_windows.go rename to third_party/dep/fs/rename_windows.go index d9540290a..f3731157e 100644 --- a/pkg/fsutil/rename_windows.go +++ b/third_party/dep/fs/rename_windows.go @@ -4,7 +4,31 @@ Copyright (c) for portions of rename_windows.go are held by The Go Authors, 2016 and are provided under the BSD license. -https://github.com/golang/dep/blob/master/LICENSE +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Copyright The Helm Authors. Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/pkg/fsutil/testdata/symlinks/file-symlink b/third_party/dep/fs/testdata/symlinks/file-symlink similarity index 100% rename from pkg/fsutil/testdata/symlinks/file-symlink rename to third_party/dep/fs/testdata/symlinks/file-symlink diff --git a/pkg/fsutil/testdata/symlinks/invalid-symlink b/third_party/dep/fs/testdata/symlinks/invalid-symlink similarity index 100% rename from pkg/fsutil/testdata/symlinks/invalid-symlink rename to third_party/dep/fs/testdata/symlinks/invalid-symlink diff --git a/pkg/fsutil/testdata/symlinks/windows-file-symlink b/third_party/dep/fs/testdata/symlinks/windows-file-symlink similarity index 100% rename from pkg/fsutil/testdata/symlinks/windows-file-symlink rename to third_party/dep/fs/testdata/symlinks/windows-file-symlink diff --git a/pkg/fsutil/testdata/test.file b/third_party/dep/fs/testdata/test.file similarity index 100% rename from pkg/fsutil/testdata/test.file rename to third_party/dep/fs/testdata/test.file From 465870a22ee12219ec4ccb3011db9ac69697d749 Mon Sep 17 00:00:00 2001 From: Yagnesh Mistry Date: Sun, 20 Oct 2019 13:00:09 +0530 Subject: [PATCH 15/16] Move third_party/fs to internal/third_party/fs and update scripts/validate-license.sh Signed-off-by: Yagnesh Mistry --- {third_party => internal/third_party}/dep/fs/fs.go | 13 ------------- .../third_party}/dep/fs/fs_test.go | 13 ------------- .../third_party}/dep/fs/rename.go | 13 ------------- .../third_party}/dep/fs/rename_windows.go | 13 ------------- .../dep/fs/testdata/symlinks/file-symlink | 0 .../dep/fs/testdata/symlinks/invalid-symlink | 0 .../dep/fs/testdata/symlinks/windows-file-symlink | 0 .../third_party}/dep/fs/testdata/test.file | 0 pkg/downloader/manager.go | 2 +- scripts/validate-license.sh | 1 + 10 files changed, 2 insertions(+), 53 deletions(-) rename {third_party => internal/third_party}/dep/fs/fs.go (94%) rename {third_party => internal/third_party}/dep/fs/fs_test.go (96%) rename {third_party => internal/third_party}/dep/fs/rename.go (80%) rename {third_party => internal/third_party}/dep/fs/rename_windows.go (82%) rename {third_party => internal/third_party}/dep/fs/testdata/symlinks/file-symlink (100%) rename {third_party => internal/third_party}/dep/fs/testdata/symlinks/invalid-symlink (100%) rename {third_party => internal/third_party}/dep/fs/testdata/symlinks/windows-file-symlink (100%) rename {third_party => internal/third_party}/dep/fs/testdata/test.file (100%) diff --git a/third_party/dep/fs/fs.go b/internal/third_party/dep/fs/fs.go similarity index 94% rename from third_party/dep/fs/fs.go rename to internal/third_party/dep/fs/fs.go index cd5f70b74..c8b41a098 100644 --- a/third_party/dep/fs/fs.go +++ b/internal/third_party/dep/fs/fs.go @@ -27,19 +27,6 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -Copyright The Helm Authors. -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 fs diff --git a/third_party/dep/fs/fs_test.go b/internal/third_party/dep/fs/fs_test.go similarity index 96% rename from third_party/dep/fs/fs_test.go rename to internal/third_party/dep/fs/fs_test.go index 687a45040..bf4b803f8 100644 --- a/third_party/dep/fs/fs_test.go +++ b/internal/third_party/dep/fs/fs_test.go @@ -27,19 +27,6 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -Copyright The Helm Authors. -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 fs diff --git a/third_party/dep/fs/rename.go b/internal/third_party/dep/fs/rename.go similarity index 80% rename from third_party/dep/fs/rename.go rename to internal/third_party/dep/fs/rename.go index a697c48d4..0bb600949 100644 --- a/third_party/dep/fs/rename.go +++ b/internal/third_party/dep/fs/rename.go @@ -29,19 +29,6 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -Copyright The Helm Authors. -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 fs diff --git a/third_party/dep/fs/rename_windows.go b/internal/third_party/dep/fs/rename_windows.go similarity index 82% rename from third_party/dep/fs/rename_windows.go rename to internal/third_party/dep/fs/rename_windows.go index f3731157e..14f017d09 100644 --- a/third_party/dep/fs/rename_windows.go +++ b/internal/third_party/dep/fs/rename_windows.go @@ -29,19 +29,6 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -Copyright The Helm Authors. -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 fs diff --git a/third_party/dep/fs/testdata/symlinks/file-symlink b/internal/third_party/dep/fs/testdata/symlinks/file-symlink similarity index 100% rename from third_party/dep/fs/testdata/symlinks/file-symlink rename to internal/third_party/dep/fs/testdata/symlinks/file-symlink diff --git a/third_party/dep/fs/testdata/symlinks/invalid-symlink b/internal/third_party/dep/fs/testdata/symlinks/invalid-symlink similarity index 100% rename from third_party/dep/fs/testdata/symlinks/invalid-symlink rename to internal/third_party/dep/fs/testdata/symlinks/invalid-symlink diff --git a/third_party/dep/fs/testdata/symlinks/windows-file-symlink b/internal/third_party/dep/fs/testdata/symlinks/windows-file-symlink similarity index 100% rename from third_party/dep/fs/testdata/symlinks/windows-file-symlink rename to internal/third_party/dep/fs/testdata/symlinks/windows-file-symlink diff --git a/third_party/dep/fs/testdata/test.file b/internal/third_party/dep/fs/testdata/test.file similarity index 100% rename from third_party/dep/fs/testdata/test.file rename to internal/third_party/dep/fs/testdata/test.file diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 8e4f57072..ce2e2ef07 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -30,6 +30,7 @@ import ( "github.com/Masterminds/semver" "github.com/ghodss/yaml" + "k8s.io/helm/internal/third_party/dep/fs" "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/helm/helmpath" @@ -37,7 +38,6 @@ import ( "k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/resolver" "k8s.io/helm/pkg/urlutil" - "k8s.io/helm/third_party/dep/fs" ) // Manager handles the lifecycle of fetching, resolving, and storing dependencies. diff --git a/scripts/validate-license.sh b/scripts/validate-license.sh index c4f02ba3e..c9d59c723 100755 --- a/scripts/validate-license.sh +++ b/scripts/validate-license.sh @@ -22,6 +22,7 @@ find_files() { -wholename './vendor' \ -o -wholename './pkg/proto' \ -o -wholename '*testdata*' \ + -o -wholename '*third_party*' \ \) -prune \ \) \ \( -name '*.go' -o -name '*.sh' -o -name 'Dockerfile' \) From b66ae7493f11917e3d9245273c2e3388b41bb98b Mon Sep 17 00:00:00 2001 From: Yagnesh Mistry Date: Wed, 23 Oct 2019 01:26:43 +0530 Subject: [PATCH 16/16] add comment to indicate source of fs.go Signed-off-by: Yagnesh Mistry --- internal/third_party/dep/fs/fs.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/third_party/dep/fs/fs.go b/internal/third_party/dep/fs/fs.go index c8b41a098..832592197 100644 --- a/internal/third_party/dep/fs/fs.go +++ b/internal/third_party/dep/fs/fs.go @@ -42,6 +42,10 @@ import ( "github.com/pkg/errors" ) +// fs contains a copy of a few functions from dep tool code to avoid a dependency on golang/dep. +// This code is copied from https://github.com/golang/dep/blob/37d6c560cdf407be7b6cd035b23dba89df9275cf/internal/fs/fs.go +// No changes to the code were made other than removing some unused functions + // RenameWithFallback attempts to rename a file or directory, but falls back to // copying in the event of a cross-device link error. If the fallback copy // succeeds, src is still removed, emulating normal rename behavior.