internal/fs/fs_test.go

Summary

Maintainability
A
0 mins
Test Coverage
// 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)
        }
    }
}