internal/fs/fs_test.go
// 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)
}
}
}