mirror of https://go.googlesource.com/go
281 lines
8.0 KiB
Go
281 lines
8.0 KiB
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 os_test
|
|
|
|
import (
|
|
"fmt"
|
|
"internal/syscall/windows"
|
|
"internal/testenv"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"testing"
|
|
)
|
|
|
|
func TestAddExtendedPrefix(t *testing.T) {
|
|
// Test addExtendedPrefix instead of fixLongPath so the path manipulation code
|
|
// is exercised even if long path are supported by the system, else the
|
|
// function might not be tested at all if/when all test builders support long paths.
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatal("cannot get cwd")
|
|
}
|
|
drive := strings.ToLower(filepath.VolumeName(cwd))
|
|
cwd = strings.ToLower(cwd[len(drive)+1:])
|
|
// Build a very long pathname. Paths in Go are supposed to be arbitrarily long,
|
|
// so let's make a long path which is comfortably bigger than MAX_PATH on Windows
|
|
// (256) and thus requires fixLongPath to be correctly interpreted in I/O syscalls.
|
|
veryLong := "l" + strings.Repeat("o", 500) + "ng"
|
|
for _, test := range []struct{ in, want string }{
|
|
// Test cases use word substitutions:
|
|
// * "long" is replaced with a very long pathname
|
|
// * "c:" or "C:" are replaced with the drive of the current directory (preserving case)
|
|
// * "cwd" is replaced with the current directory
|
|
|
|
// Drive Absolute
|
|
{`C:\long\foo.txt`, `\\?\C:\long\foo.txt`},
|
|
{`C:/long/foo.txt`, `\\?\C:\long\foo.txt`},
|
|
{`C:\\\long///foo.txt`, `\\?\C:\long\foo.txt`},
|
|
{`C:\long\.\foo.txt`, `\\?\C:\long\foo.txt`},
|
|
{`C:\long\..\foo.txt`, `\\?\C:\foo.txt`},
|
|
{`C:\long\..\..\foo.txt`, `\\?\C:\foo.txt`},
|
|
|
|
// Drive Relative
|
|
{`C:long\foo.txt`, `\\?\C:\cwd\long\foo.txt`},
|
|
{`C:long/foo.txt`, `\\?\C:\cwd\long\foo.txt`},
|
|
{`C:long///foo.txt`, `\\?\C:\cwd\long\foo.txt`},
|
|
{`C:long\.\foo.txt`, `\\?\C:\cwd\long\foo.txt`},
|
|
{`C:long\..\foo.txt`, `\\?\C:\cwd\foo.txt`},
|
|
|
|
// Rooted
|
|
{`\long\foo.txt`, `\\?\C:\long\foo.txt`},
|
|
{`/long/foo.txt`, `\\?\C:\long\foo.txt`},
|
|
{`\long///foo.txt`, `\\?\C:\long\foo.txt`},
|
|
{`\long\.\foo.txt`, `\\?\C:\long\foo.txt`},
|
|
{`\long\..\foo.txt`, `\\?\C:\foo.txt`},
|
|
|
|
// Relative
|
|
{`long\foo.txt`, `\\?\C:\cwd\long\foo.txt`},
|
|
{`long/foo.txt`, `\\?\C:\cwd\long\foo.txt`},
|
|
{`long///foo.txt`, `\\?\C:\cwd\long\foo.txt`},
|
|
{`long\.\foo.txt`, `\\?\C:\cwd\long\foo.txt`},
|
|
{`long\..\foo.txt`, `\\?\C:\cwd\foo.txt`},
|
|
{`.\long\foo.txt`, `\\?\C:\cwd\long\foo.txt`},
|
|
|
|
// UNC Absolute
|
|
{`\\srv\share\long`, `\\?\UNC\srv\share\long`},
|
|
{`//srv/share/long`, `\\?\UNC\srv\share\long`},
|
|
{`/\srv/share/long`, `\\?\UNC\srv\share\long`},
|
|
{`\\srv\share\long\`, `\\?\UNC\srv\share\long\`},
|
|
{`\\srv\share\bar\.\long`, `\\?\UNC\srv\share\bar\long`},
|
|
{`\\srv\share\bar\..\long`, `\\?\UNC\srv\share\long`},
|
|
{`\\srv\share\bar\..\..\long`, `\\?\UNC\srv\share\long`}, // share name is not removed by ".."
|
|
|
|
// Local Device
|
|
{`\\.\C:\long\foo.txt`, `\\.\C:\long\foo.txt`},
|
|
{`//./C:/long/foo.txt`, `\\.\C:\long\foo.txt`},
|
|
{`/\./C:/long/foo.txt`, `\\.\C:\long\foo.txt`},
|
|
{`\\.\C:\long///foo.txt`, `\\.\C:\long\foo.txt`},
|
|
{`\\.\C:\long\.\foo.txt`, `\\.\C:\long\foo.txt`},
|
|
{`\\.\C:\long\..\foo.txt`, `\\.\C:\foo.txt`},
|
|
|
|
// Misc tests
|
|
{`C:\short.txt`, `C:\short.txt`},
|
|
{`C:\`, `C:\`},
|
|
{`C:`, `C:`},
|
|
{`\\srv\path`, `\\srv\path`},
|
|
{`long.txt`, `\\?\C:\cwd\long.txt`},
|
|
{`C:long.txt`, `\\?\C:\cwd\long.txt`},
|
|
{`C:\long\.\bar\baz`, `\\?\C:\long\bar\baz`},
|
|
{`C:long\.\bar\baz`, `\\?\C:\cwd\long\bar\baz`},
|
|
{`C:\long\..\bar\baz`, `\\?\C:\bar\baz`},
|
|
{`C:long\..\bar\baz`, `\\?\C:\cwd\bar\baz`},
|
|
{`C:\long\foo\\bar\.\baz\\`, `\\?\C:\long\foo\bar\baz\`},
|
|
{`C:\long\..`, `\\?\C:\`},
|
|
{`C:\.\long\..\.`, `\\?\C:\`},
|
|
{`\\?\C:\long\foo.txt`, `\\?\C:\long\foo.txt`},
|
|
{`\\?\C:\long/foo.txt`, `\\?\C:\long/foo.txt`},
|
|
} {
|
|
in := strings.ReplaceAll(test.in, "long", veryLong)
|
|
in = strings.ToLower(in)
|
|
in = strings.ReplaceAll(in, "c:", drive)
|
|
|
|
want := strings.ReplaceAll(test.want, "long", veryLong)
|
|
want = strings.ToLower(want)
|
|
want = strings.ReplaceAll(want, "c:", drive)
|
|
want = strings.ReplaceAll(want, "cwd", cwd)
|
|
|
|
got := os.AddExtendedPrefix(in)
|
|
got = strings.ToLower(got)
|
|
if got != want {
|
|
in = strings.ReplaceAll(in, veryLong, "long")
|
|
got = strings.ReplaceAll(got, veryLong, "long")
|
|
want = strings.ReplaceAll(want, veryLong, "long")
|
|
t.Errorf("addExtendedPrefix(%#q) = %#q; want %#q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMkdirAllLongPath(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tmpDir := t.TempDir()
|
|
path := tmpDir
|
|
for i := 0; i < 100; i++ {
|
|
path += `\another-path-component`
|
|
}
|
|
if err := os.MkdirAll(path, 0777); err != nil {
|
|
t.Fatalf("MkdirAll(%q) failed; %v", path, err)
|
|
}
|
|
if err := os.RemoveAll(tmpDir); err != nil {
|
|
t.Fatalf("RemoveAll(%q) failed; %v", tmpDir, err)
|
|
}
|
|
}
|
|
|
|
func TestMkdirAllExtendedLength(t *testing.T) {
|
|
t.Parallel()
|
|
tmpDir := t.TempDir()
|
|
|
|
const prefix = `\\?\`
|
|
if len(tmpDir) < 4 || tmpDir[:4] != prefix {
|
|
fullPath, err := syscall.FullPath(tmpDir)
|
|
if err != nil {
|
|
t.Fatalf("FullPath(%q) fails: %v", tmpDir, err)
|
|
}
|
|
tmpDir = prefix + fullPath
|
|
}
|
|
path := tmpDir + `\dir\`
|
|
if err := os.MkdirAll(path, 0777); err != nil {
|
|
t.Fatalf("MkdirAll(%q) failed: %v", path, err)
|
|
}
|
|
|
|
path = path + `.\dir2`
|
|
if err := os.MkdirAll(path, 0777); err == nil {
|
|
t.Fatalf("MkdirAll(%q) should have failed, but did not", path)
|
|
}
|
|
}
|
|
|
|
func TestOpenRootSlash(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []string{
|
|
`/`,
|
|
`\`,
|
|
}
|
|
|
|
for _, test := range tests {
|
|
dir, err := os.Open(test)
|
|
if err != nil {
|
|
t.Fatalf("Open(%q) failed: %v", test, err)
|
|
}
|
|
dir.Close()
|
|
}
|
|
}
|
|
|
|
func testMkdirAllAtRoot(t *testing.T, root string) {
|
|
// Create a unique-enough directory name in root.
|
|
base := fmt.Sprintf("%s-%d", t.Name(), os.Getpid())
|
|
path := filepath.Join(root, base)
|
|
if err := os.MkdirAll(path, 0777); err != nil {
|
|
t.Fatalf("MkdirAll(%q) failed: %v", path, err)
|
|
}
|
|
// Clean up
|
|
if err := os.RemoveAll(path); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestMkdirAllExtendedLengthAtRoot(t *testing.T) {
|
|
if testenv.Builder() == "" {
|
|
t.Skipf("skipping non-hermetic test outside of Go builders")
|
|
}
|
|
|
|
const prefix = `\\?\`
|
|
vol := filepath.VolumeName(t.TempDir()) + `\`
|
|
if len(vol) < 4 || vol[:4] != prefix {
|
|
vol = prefix + vol
|
|
}
|
|
testMkdirAllAtRoot(t, vol)
|
|
}
|
|
|
|
func TestMkdirAllVolumeNameAtRoot(t *testing.T) {
|
|
if testenv.Builder() == "" {
|
|
t.Skipf("skipping non-hermetic test outside of Go builders")
|
|
}
|
|
|
|
vol, err := syscall.UTF16PtrFromString(filepath.VolumeName(t.TempDir()) + `\`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
const maxVolNameLen = 50
|
|
var buf [maxVolNameLen]uint16
|
|
err = windows.GetVolumeNameForVolumeMountPoint(vol, &buf[0], maxVolNameLen)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
volName := syscall.UTF16ToString(buf[:])
|
|
testMkdirAllAtRoot(t, volName)
|
|
}
|
|
|
|
func TestRemoveAllLongPathRelative(t *testing.T) {
|
|
// Test that RemoveAll doesn't hang with long relative paths.
|
|
// See go.dev/issue/36375.
|
|
tmp := t.TempDir()
|
|
chdir(t, tmp)
|
|
dir := filepath.Join(tmp, "foo", "bar", strings.Repeat("a", 150), strings.Repeat("b", 150))
|
|
err := os.MkdirAll(dir, 0755)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = os.RemoveAll("foo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func testLongPathAbs(t *testing.T, target string) {
|
|
t.Helper()
|
|
testWalkFn := func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
return err
|
|
}
|
|
if err := os.MkdirAll(target, 0777); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Test that Walk doesn't fail with long paths.
|
|
// See go.dev/issue/21782.
|
|
filepath.Walk(target, testWalkFn)
|
|
// Test that RemoveAll doesn't hang with long paths.
|
|
// See go.dev/issue/36375.
|
|
if err := os.RemoveAll(target); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
|
|
func TestLongPathAbs(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
target := t.TempDir() + "\\" + strings.Repeat("a\\", 300)
|
|
testLongPathAbs(t, target)
|
|
}
|
|
|
|
func TestLongPathRel(t *testing.T) {
|
|
chdir(t, t.TempDir())
|
|
|
|
target := strings.Repeat("b\\", 300)
|
|
testLongPathAbs(t, target)
|
|
}
|
|
|
|
func BenchmarkAddExtendedPrefix(b *testing.B) {
|
|
veryLong := `C:\l` + strings.Repeat("o", 248) + "ng"
|
|
b.ReportAllocs()
|
|
for i := 0; i < b.N; i++ {
|
|
os.AddExtendedPrefix(veryLong)
|
|
}
|
|
}
|