mirror of
https://github.com/golang/go
synced 2024-11-19 08:24:41 -07:00
1d3088effd
This also makes path/filepath.Walk more consistent between Windows and POSIX platforms. According to https://pubs.opengroup.org/onlinepubs/9699919799.2013edition/basedefs/V1_chap04.html#tag_04_12 symlinks in a path that includes a trailing slash must be resolved before a function acts on that path. POSIX defines an lstat function, whereas the Win32 API does not, so Go's os.Lstat should follow the (defined) POSIX semantics instead of doing something arbitrarily different. CL 134195 added a test for the correct POSIX behavior when os.Lstat is called on a symlink. However, the test turned out to be broken on Windows, and when it was fixed (in CL 143578) it was fixed with different Lstat behavior on Windows than on all other platforms that support symlinks. In #50807 we are attempting to provide consistent symlink behavior for cmd/go. This unnecessary platform difference, if left uncorrected, will make that fix much more difficult. CL 460595 reworked the implementation of Stat and Lstat on Windows, and with the new implementation this fix is straightforward. For #50807. Updates #27225. Change-Id: Ia28821aa4aab6cefa021da2d9b803506cdb2621b Reviewed-on: https://go-review.googlesource.com/c/go/+/463177 Reviewed-by: Quim Muntal <quimmuntal@gmail.com> Auto-Submit: Bryan Mills <bcmills@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Alex Brainman <alex.brainman@gmail.com> Reviewed-by: Russ Cox <rsc@golang.org> Run-TryBot: Bryan Mills <bcmills@google.com>
297 lines
6.5 KiB
Go
297 lines
6.5 KiB
Go
// 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/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"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, fs.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 fs.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 fs.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 fs.FileInfo) {
|
|
t.Helper()
|
|
if !fi.IsDir() {
|
|
t.Errorf("%q should be a directory", path)
|
|
}
|
|
if fi.Mode()&fs.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 fs.FileInfo) {
|
|
t.Helper()
|
|
if fi.IsDir() {
|
|
t.Errorf("%q should not be a directory", path)
|
|
}
|
|
if fi.Mode()&fs.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 fs.FileInfo) {
|
|
t.Helper()
|
|
if fi.IsDir() {
|
|
t.Errorf("%q should not be a directory", path)
|
|
}
|
|
if fi.Mode()&fs.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 testSymlinkSameFileOpen(t *testing.T, link string) {
|
|
f, err := os.Open(link)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
defer f.Close()
|
|
|
|
fi, err := f.Stat()
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
fi2, err := os.Stat(link)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
if !os.SameFile(fi, fi2) {
|
|
t.Errorf("os.Open(%q).Stat() and os.Stat(%q) are not the same file", link, link)
|
|
}
|
|
}
|
|
|
|
func TestDirAndSymlinkStats(t *testing.T) {
|
|
testenv.MustHaveSymlink(t)
|
|
t.Parallel()
|
|
|
|
tmpdir := t.TempDir()
|
|
dir := filepath.Join(tmpdir, "dir")
|
|
if err := os.Mkdir(dir, 0777); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
testDirStats(t, dir)
|
|
|
|
dirlink := filepath.Join(tmpdir, "link")
|
|
if err := os.Symlink(dir, dirlink); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
testSymlinkStats(t, dirlink, true)
|
|
testSymlinkSameFile(t, dir, dirlink)
|
|
testSymlinkSameFileOpen(t, dirlink)
|
|
|
|
linklink := filepath.Join(tmpdir, "linklink")
|
|
if err := os.Symlink(dirlink, linklink); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
testSymlinkStats(t, linklink, true)
|
|
testSymlinkSameFile(t, dir, linklink)
|
|
testSymlinkSameFileOpen(t, linklink)
|
|
}
|
|
|
|
func TestFileAndSymlinkStats(t *testing.T) {
|
|
testenv.MustHaveSymlink(t)
|
|
t.Parallel()
|
|
|
|
tmpdir := t.TempDir()
|
|
file := filepath.Join(tmpdir, "file")
|
|
if err := os.WriteFile(file, []byte(""), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
testFileStats(t, file)
|
|
|
|
filelink := filepath.Join(tmpdir, "link")
|
|
if err := os.Symlink(file, filelink); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
testSymlinkStats(t, filelink, false)
|
|
testSymlinkSameFile(t, file, filelink)
|
|
testSymlinkSameFileOpen(t, filelink)
|
|
|
|
linklink := filepath.Join(tmpdir, "linklink")
|
|
if err := os.Symlink(filelink, linklink); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
testSymlinkStats(t, linklink, false)
|
|
testSymlinkSameFile(t, file, linklink)
|
|
testSymlinkSameFileOpen(t, linklink)
|
|
}
|
|
|
|
// see issue 27225 for details
|
|
func TestSymlinkWithTrailingSlash(t *testing.T) {
|
|
testenv.MustHaveSymlink(t)
|
|
t.Parallel()
|
|
|
|
tmpdir := t.TempDir()
|
|
dir := filepath.Join(tmpdir, "dir")
|
|
if err := os.Mkdir(dir, 0777); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dirlink := filepath.Join(tmpdir, "link")
|
|
if err := os.Symlink(dir, dirlink); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dirlinkWithSlash := dirlink + string(os.PathSeparator)
|
|
|
|
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)
|
|
}
|
|
}
|