// Copyright 2018 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 os_test
import (
"internal/testenv"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"testing"
)
// testStatAndLstat verifies that all os.Stat, os.Lstat os.File.Stat and os.Readdir work.
func testStatAndLstat(t *testing.T, path string, isLink bool, statCheck, lstatCheck func(*testing.T, string, os.FileInfo)) {
// test os.Stat
sfi, err := os.Stat(path)
if err != nil {
t.Error(err)
return
}
statCheck(t, path, sfi)
// test os.Lstat
lsfi, err := os.Lstat(path)
if err != nil {
t.Error(err)
return
}
lstatCheck(t, path, lsfi)
if isLink {
if os.SameFile(sfi, lsfi) {
t.Errorf("stat and lstat of %q should not be the same", path)
}
} else {
if !os.SameFile(sfi, lsfi) {
t.Errorf("stat and lstat of %q should be the same", path)
}
}
// test os.File.Stat
f, err := os.Open(path)
if err != nil {
t.Error(err)
return
}
defer f.Close()
sfi2, err := f.Stat()
if err != nil {
t.Error(err)
return
}
statCheck(t, path, sfi2)
if !os.SameFile(sfi, sfi2) {
t.Errorf("stat of open %q file and stat of %q should be the same", path, path)
}
if isLink {
if os.SameFile(sfi2, lsfi) {
t.Errorf("stat of opened %q file and lstat of %q should not be the same", path, path)
}
} else {
if !os.SameFile(sfi2, lsfi) {
t.Errorf("stat of opened %q file and lstat of %q should be the same", path, path)
}
}
// test os.FileInfo returned by os.Readdir
if len(path) > 0 && os.IsPathSeparator(path[len(path)-1]) {
// skip os.Readdir test of directories with slash at the end
return
}
parentdir := filepath.Dir(path)
parent, err := os.Open(parentdir)
if err != nil {
t.Error(err)
return
}
defer parent.Close()
fis, err := parent.Readdir(-1)
if err != nil {
t.Error(err)
return
}
var lsfi2 os.FileInfo
base := filepath.Base(path)
for _, fi2 := range fis {
if fi2.Name() == base {
lsfi2 = fi2
break
}
}
if lsfi2 == nil {
t.Errorf("failed to find %q in its parent", path)
return
}
lstatCheck(t, path, lsfi2)
if !os.SameFile(lsfi, lsfi2) {
t.Errorf("lstat of %q file in %q directory and %q should be the same", lsfi2.Name(), parentdir, path)
}
}
// testIsDir verifies that fi refers to directory.
func testIsDir(t *testing.T, path string, fi os.FileInfo) {
t.Helper()
if !fi.IsDir() {
t.Errorf("%q should be a directory", path)
}
if fi.Mode()&os.ModeSymlink != 0 {
t.Errorf("%q should not be a symlink", path)
}
}
// testIsSymlink verifies that fi refers to symlink.
func testIsSymlink(t *testing.T, path string, fi os.FileInfo) {
t.Helper()
if fi.IsDir() {
t.Errorf("%q should not be a directory", path)
}
if fi.Mode()&os.ModeSymlink == 0 {
t.Errorf("%q should be a symlink", path)
}
}
// testIsFile verifies that fi refers to file.
func testIsFile(t *testing.T, path string, fi os.FileInfo) {
t.Helper()
if fi.IsDir() {
t.Errorf("%q should not be a directory", path)
}
if fi.Mode()&os.ModeSymlink != 0 {
t.Errorf("%q should not be a symlink", path)
}
}
func testDirStats(t *testing.T, path string) {
testStatAndLstat(t, path, false, testIsDir, testIsDir)
}
func testFileStats(t *testing.T, path string) {
testStatAndLstat(t, path, false, testIsFile, testIsFile)
}
func testSymlinkStats(t *testing.T, path string, isdir bool) {
if isdir {
testStatAndLstat(t, path, true, testIsDir, testIsSymlink)
} else {
testStatAndLstat(t, path, true, testIsFile, testIsSymlink)
}
}
func testSymlinkSameFile(t *testing.T, path, link string) {
pathfi, err := os.Stat(path)
if err != nil {
t.Error(err)
return
}
linkfi, err := os.Stat(link)
if err != nil {
t.Error(err)
return
}
if !os.SameFile(pathfi, linkfi) {
t.Errorf("os.Stat(%q) and os.Stat(%q) are not the same file", path, link)
}
linkfi, err = os.Lstat(link)
if err != nil {
t.Error(err)
return
}
if os.SameFile(pathfi, linkfi) {
t.Errorf("os.Stat(%q) and os.Lstat(%q) are the same file", path, link)
}
}
func TestDirAndSymlinkStats(t *testing.T) {
testenv.MustHaveSymlink(t)
tmpdir, err := ioutil.TempDir("", "TestDirAndSymlinkStats")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
dir := filepath.Join(tmpdir, "dir")
err = os.Mkdir(dir, 0777)
if err != nil {
t.Fatal(err)
}
testDirStats(t, dir)
dirlink := filepath.Join(tmpdir, "link")
err = os.Symlink(dir, dirlink)
if err != nil {
t.Fatal(err)
}
testSymlinkStats(t, dirlink, true)
testSymlinkSameFile(t, dir, dirlink)
linklink := filepath.Join(tmpdir, "linklink")
err = os.Symlink(dirlink, linklink)
if err != nil {
t.Fatal(err)
}
testSymlinkStats(t, linklink, true)
testSymlinkSameFile(t, dir, linklink)
}
func TestFileAndSymlinkStats(t *testing.T) {
testenv.MustHaveSymlink(t)
tmpdir, err := ioutil.TempDir("", "TestFileAndSymlinkStats")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
file := filepath.Join(tmpdir, "file")
err = ioutil.WriteFile(file, []byte(""), 0644)
if err != nil {
t.Fatal(err)
}
testFileStats(t, file)
filelink := filepath.Join(tmpdir, "link")
err = os.Symlink(file, filelink)
if err != nil {
t.Fatal(err)
}
testSymlinkStats(t, filelink, false)
testSymlinkSameFile(t, file, filelink)
linklink := filepath.Join(tmpdir, "linklink")
err = os.Symlink(filelink, linklink)
if err != nil {
t.Fatal(err)
}
testSymlinkStats(t, linklink, false)
testSymlinkSameFile(t, file, linklink)
}
// see issue 27225 for details
func TestSymlinkWithTrailingSlash(t *testing.T) {
testenv.MustHaveSymlink(t)
tmpdir, err := ioutil.TempDir("", "TestSymlinkWithTrailingSlash")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
dir := filepath.Join(tmpdir, "dir")
err = os.Mkdir(dir, 0777)
if err != nil {
t.Fatal(err)
}
dirlink := filepath.Join(tmpdir, "link")
err = os.Symlink(dir, dirlink)
if err != nil {
t.Fatal(err)
}
dirlinkWithSlash := dirlink + string(os.PathSeparator)
if runtime.GOOS == "windows" {
testSymlinkStats(t, dirlinkWithSlash, true)
} else {
testDirStats(t, dirlinkWithSlash)
}
fi1, err := os.Stat(dir)
if err != nil {
t.Error(err)
return
}
fi2, err := os.Stat(dirlinkWithSlash)
if err != nil {
t.Error(err)
return
}
if !os.SameFile(fi1, fi2) {
t.Errorf("os.Stat(%q) and os.Stat(%q) are not the same file", dir, dirlinkWithSlash)
}
}