| // 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" |
| "runtime" |
| "testing" |
| ) |
| |
| type testStatAndLstatParams struct { |
| isLink bool |
| statCheck func(*testing.T, string, fs.FileInfo) |
| lstatCheck func(*testing.T, string, fs.FileInfo) |
| } |
| |
| // testStatAndLstat verifies that all os.Stat, os.Lstat os.File.Stat and os.Readdir work. |
| func testStatAndLstat(t *testing.T, path string, params testStatAndLstatParams) { |
| // test os.Stat |
| sfi, err := os.Stat(path) |
| if err != nil { |
| t.Error(err) |
| return |
| } |
| params.statCheck(t, path, sfi) |
| |
| // test os.Lstat |
| lsfi, err := os.Lstat(path) |
| if err != nil { |
| t.Error(err) |
| return |
| } |
| params.lstatCheck(t, path, lsfi) |
| |
| if params.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 |
| } |
| params.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 params.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) |
| } |
| } |
| |
| parentdir, base := filepath.Split(path) |
| if parentdir == "" || base == "" { |
| // skip os.Readdir test of files without directory or file name component, |
| // such as directories with slash at the end or Windows device names. |
| return |
| } |
| |
| 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 |
| 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 |
| } |
| params.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) { |
| params := testStatAndLstatParams{ |
| isLink: false, |
| statCheck: testIsDir, |
| lstatCheck: testIsDir, |
| } |
| testStatAndLstat(t, path, params) |
| } |
| |
| func testFileStats(t *testing.T, path string) { |
| params := testStatAndLstatParams{ |
| isLink: false, |
| statCheck: testIsFile, |
| lstatCheck: testIsFile, |
| } |
| testStatAndLstat(t, path, params) |
| } |
| |
| func testSymlinkStats(t *testing.T, path string, isdir bool) { |
| params := testStatAndLstatParams{ |
| isLink: true, |
| lstatCheck: testIsSymlink, |
| } |
| if isdir { |
| params.statCheck = testIsDir |
| } else { |
| params.statCheck = testIsFile |
| } |
| testStatAndLstat(t, path, params) |
| } |
| |
| 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) |
| } |
| } |
| |
| func TestStatConsole(t *testing.T) { |
| if runtime.GOOS != "windows" { |
| t.Skip("skipping on non-Windows") |
| } |
| t.Parallel() |
| consoleNames := []string{ |
| "CONIN$", |
| "CONOUT$", |
| "CON", |
| } |
| for _, name := range consoleNames { |
| params := testStatAndLstatParams{ |
| isLink: false, |
| statCheck: testIsFile, |
| lstatCheck: testIsFile, |
| } |
| testStatAndLstat(t, name, params) |
| testStatAndLstat(t, `\\.\`+name, params) |
| } |
| } |