Source file src/os/path_windows_test.go

     1  // Copyright 2016 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package os_test
     6  
     7  import (
     8  	"fmt"
     9  	"internal/syscall/windows"
    10  	"internal/testenv"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"syscall"
    15  	"testing"
    16  )
    17  
    18  func TestAddExtendedPrefix(t *testing.T) {
    19  	// Test addExtendedPrefix instead of fixLongPath so the path manipulation code
    20  	// is exercised even if long path are supported by the system, else the
    21  	// function might not be tested at all if/when all test builders support long paths.
    22  	cwd, err := os.Getwd()
    23  	if err != nil {
    24  		t.Fatal("cannot get cwd")
    25  	}
    26  	drive := strings.ToLower(filepath.VolumeName(cwd))
    27  	cwd = strings.ToLower(cwd[len(drive)+1:])
    28  	// Build a very long pathname. Paths in Go are supposed to be arbitrarily long,
    29  	// so let's make a long path which is comfortably bigger than MAX_PATH on Windows
    30  	// (256) and thus requires fixLongPath to be correctly interpreted in I/O syscalls.
    31  	veryLong := "l" + strings.Repeat("o", 500) + "ng"
    32  	for _, test := range []struct{ in, want string }{
    33  		// Test cases use word substitutions:
    34  		//   * "long" is replaced with a very long pathname
    35  		//   * "c:" or "C:" are replaced with the drive of the current directory (preserving case)
    36  		//   * "cwd" is replaced with the current directory
    37  
    38  		// Drive Absolute
    39  		{`C:\long\foo.txt`, `\\?\C:\long\foo.txt`},
    40  		{`C:/long/foo.txt`, `\\?\C:\long\foo.txt`},
    41  		{`C:\\\long///foo.txt`, `\\?\C:\long\foo.txt`},
    42  		{`C:\long\.\foo.txt`, `\\?\C:\long\foo.txt`},
    43  		{`C:\long\..\foo.txt`, `\\?\C:\foo.txt`},
    44  		{`C:\long\..\..\foo.txt`, `\\?\C:\foo.txt`},
    45  
    46  		// Drive Relative
    47  		{`C:long\foo.txt`, `\\?\C:\cwd\long\foo.txt`},
    48  		{`C:long/foo.txt`, `\\?\C:\cwd\long\foo.txt`},
    49  		{`C:long///foo.txt`, `\\?\C:\cwd\long\foo.txt`},
    50  		{`C:long\.\foo.txt`, `\\?\C:\cwd\long\foo.txt`},
    51  		{`C:long\..\foo.txt`, `\\?\C:\cwd\foo.txt`},
    52  
    53  		// Rooted
    54  		{`\long\foo.txt`, `\\?\C:\long\foo.txt`},
    55  		{`/long/foo.txt`, `\\?\C:\long\foo.txt`},
    56  		{`\long///foo.txt`, `\\?\C:\long\foo.txt`},
    57  		{`\long\.\foo.txt`, `\\?\C:\long\foo.txt`},
    58  		{`\long\..\foo.txt`, `\\?\C:\foo.txt`},
    59  
    60  		// Relative
    61  		{`long\foo.txt`, `\\?\C:\cwd\long\foo.txt`},
    62  		{`long/foo.txt`, `\\?\C:\cwd\long\foo.txt`},
    63  		{`long///foo.txt`, `\\?\C:\cwd\long\foo.txt`},
    64  		{`long\.\foo.txt`, `\\?\C:\cwd\long\foo.txt`},
    65  		{`long\..\foo.txt`, `\\?\C:\cwd\foo.txt`},
    66  		{`.\long\foo.txt`, `\\?\C:\cwd\long\foo.txt`},
    67  
    68  		// UNC Absolute
    69  		{`\\srv\share\long`, `\\?\UNC\srv\share\long`},
    70  		{`//srv/share/long`, `\\?\UNC\srv\share\long`},
    71  		{`/\srv/share/long`, `\\?\UNC\srv\share\long`},
    72  		{`\\srv\share\long\`, `\\?\UNC\srv\share\long\`},
    73  		{`\\srv\share\bar\.\long`, `\\?\UNC\srv\share\bar\long`},
    74  		{`\\srv\share\bar\..\long`, `\\?\UNC\srv\share\long`},
    75  		{`\\srv\share\bar\..\..\long`, `\\?\UNC\srv\share\long`}, // share name is not removed by ".."
    76  
    77  		// Local Device
    78  		{`\\.\C:\long\foo.txt`, `\\.\C:\long\foo.txt`},
    79  		{`//./C:/long/foo.txt`, `\\.\C:\long\foo.txt`},
    80  		{`/\./C:/long/foo.txt`, `\\.\C:\long\foo.txt`},
    81  		{`\\.\C:\long///foo.txt`, `\\.\C:\long\foo.txt`},
    82  		{`\\.\C:\long\.\foo.txt`, `\\.\C:\long\foo.txt`},
    83  		{`\\.\C:\long\..\foo.txt`, `\\.\C:\foo.txt`},
    84  
    85  		// Misc tests
    86  		{`C:\short.txt`, `C:\short.txt`},
    87  		{`C:\`, `C:\`},
    88  		{`C:`, `C:`},
    89  		{`\\srv\path`, `\\srv\path`},
    90  		{`long.txt`, `\\?\C:\cwd\long.txt`},
    91  		{`C:long.txt`, `\\?\C:\cwd\long.txt`},
    92  		{`C:\long\.\bar\baz`, `\\?\C:\long\bar\baz`},
    93  		{`C:long\.\bar\baz`, `\\?\C:\cwd\long\bar\baz`},
    94  		{`C:\long\..\bar\baz`, `\\?\C:\bar\baz`},
    95  		{`C:long\..\bar\baz`, `\\?\C:\cwd\bar\baz`},
    96  		{`C:\long\foo\\bar\.\baz\\`, `\\?\C:\long\foo\bar\baz\`},
    97  		{`C:\long\..`, `\\?\C:\`},
    98  		{`C:\.\long\..\.`, `\\?\C:\`},
    99  		{`\\?\C:\long\foo.txt`, `\\?\C:\long\foo.txt`},
   100  		{`\\?\C:\long/foo.txt`, `\\?\C:\long/foo.txt`},
   101  	} {
   102  		in := strings.ReplaceAll(test.in, "long", veryLong)
   103  		in = strings.ToLower(in)
   104  		in = strings.ReplaceAll(in, "c:", drive)
   105  
   106  		want := strings.ReplaceAll(test.want, "long", veryLong)
   107  		want = strings.ToLower(want)
   108  		want = strings.ReplaceAll(want, "c:", drive)
   109  		want = strings.ReplaceAll(want, "cwd", cwd)
   110  
   111  		got := os.AddExtendedPrefix(in)
   112  		got = strings.ToLower(got)
   113  		if got != want {
   114  			in = strings.ReplaceAll(in, veryLong, "long")
   115  			got = strings.ReplaceAll(got, veryLong, "long")
   116  			want = strings.ReplaceAll(want, veryLong, "long")
   117  			t.Errorf("addExtendedPrefix(%#q) = %#q; want %#q", in, got, want)
   118  		}
   119  	}
   120  }
   121  
   122  func TestMkdirAllLongPath(t *testing.T) {
   123  	t.Parallel()
   124  
   125  	tmpDir := t.TempDir()
   126  	path := tmpDir
   127  	for i := 0; i < 100; i++ {
   128  		path += `\another-path-component`
   129  	}
   130  	if err := os.MkdirAll(path, 0777); err != nil {
   131  		t.Fatalf("MkdirAll(%q) failed; %v", path, err)
   132  	}
   133  	if err := os.RemoveAll(tmpDir); err != nil {
   134  		t.Fatalf("RemoveAll(%q) failed; %v", tmpDir, err)
   135  	}
   136  }
   137  
   138  func TestMkdirAllExtendedLength(t *testing.T) {
   139  	t.Parallel()
   140  	tmpDir := t.TempDir()
   141  
   142  	const prefix = `\\?\`
   143  	if len(tmpDir) < 4 || tmpDir[:4] != prefix {
   144  		fullPath, err := syscall.FullPath(tmpDir)
   145  		if err != nil {
   146  			t.Fatalf("FullPath(%q) fails: %v", tmpDir, err)
   147  		}
   148  		tmpDir = prefix + fullPath
   149  	}
   150  	path := tmpDir + `\dir\`
   151  	if err := os.MkdirAll(path, 0777); err != nil {
   152  		t.Fatalf("MkdirAll(%q) failed: %v", path, err)
   153  	}
   154  
   155  	path = path + `.\dir2`
   156  	if err := os.MkdirAll(path, 0777); err == nil {
   157  		t.Fatalf("MkdirAll(%q) should have failed, but did not", path)
   158  	}
   159  }
   160  
   161  func TestOpenRootSlash(t *testing.T) {
   162  	t.Parallel()
   163  
   164  	tests := []string{
   165  		`/`,
   166  		`\`,
   167  	}
   168  
   169  	for _, test := range tests {
   170  		dir, err := os.Open(test)
   171  		if err != nil {
   172  			t.Fatalf("Open(%q) failed: %v", test, err)
   173  		}
   174  		dir.Close()
   175  	}
   176  }
   177  
   178  func testMkdirAllAtRoot(t *testing.T, root string) {
   179  	// Create a unique-enough directory name in root.
   180  	base := fmt.Sprintf("%s-%d", t.Name(), os.Getpid())
   181  	path := filepath.Join(root, base)
   182  	if err := os.MkdirAll(path, 0777); err != nil {
   183  		t.Fatalf("MkdirAll(%q) failed: %v", path, err)
   184  	}
   185  	// Clean up
   186  	if err := os.RemoveAll(path); err != nil {
   187  		t.Fatal(err)
   188  	}
   189  }
   190  
   191  func TestMkdirAllExtendedLengthAtRoot(t *testing.T) {
   192  	if testenv.Builder() == "" {
   193  		t.Skipf("skipping non-hermetic test outside of Go builders")
   194  	}
   195  
   196  	const prefix = `\\?\`
   197  	vol := filepath.VolumeName(t.TempDir()) + `\`
   198  	if len(vol) < 4 || vol[:4] != prefix {
   199  		vol = prefix + vol
   200  	}
   201  	testMkdirAllAtRoot(t, vol)
   202  }
   203  
   204  func TestMkdirAllVolumeNameAtRoot(t *testing.T) {
   205  	if testenv.Builder() == "" {
   206  		t.Skipf("skipping non-hermetic test outside of Go builders")
   207  	}
   208  
   209  	vol, err := syscall.UTF16PtrFromString(filepath.VolumeName(t.TempDir()) + `\`)
   210  	if err != nil {
   211  		t.Fatal(err)
   212  	}
   213  	const maxVolNameLen = 50
   214  	var buf [maxVolNameLen]uint16
   215  	err = windows.GetVolumeNameForVolumeMountPoint(vol, &buf[0], maxVolNameLen)
   216  	if err != nil {
   217  		t.Fatal(err)
   218  	}
   219  	volName := syscall.UTF16ToString(buf[:])
   220  	testMkdirAllAtRoot(t, volName)
   221  }
   222  
   223  func TestRemoveAllLongPathRelative(t *testing.T) {
   224  	// Test that RemoveAll doesn't hang with long relative paths.
   225  	// See go.dev/issue/36375.
   226  	tmp := t.TempDir()
   227  	chdir(t, tmp)
   228  	dir := filepath.Join(tmp, "foo", "bar", strings.Repeat("a", 150), strings.Repeat("b", 150))
   229  	err := os.MkdirAll(dir, 0755)
   230  	if err != nil {
   231  		t.Fatal(err)
   232  	}
   233  	err = os.RemoveAll("foo")
   234  	if err != nil {
   235  		t.Fatal(err)
   236  	}
   237  }
   238  
   239  func testLongPathAbs(t *testing.T, target string) {
   240  	t.Helper()
   241  	testWalkFn := func(path string, info os.FileInfo, err error) error {
   242  		if err != nil {
   243  			t.Error(err)
   244  		}
   245  		return err
   246  	}
   247  	if err := os.MkdirAll(target, 0777); err != nil {
   248  		t.Fatal(err)
   249  	}
   250  	// Test that Walk doesn't fail with long paths.
   251  	// See go.dev/issue/21782.
   252  	filepath.Walk(target, testWalkFn)
   253  	// Test that RemoveAll doesn't hang with long paths.
   254  	// See go.dev/issue/36375.
   255  	if err := os.RemoveAll(target); err != nil {
   256  		t.Error(err)
   257  	}
   258  }
   259  
   260  func TestLongPathAbs(t *testing.T) {
   261  	t.Parallel()
   262  
   263  	target := t.TempDir() + "\\" + strings.Repeat("a\\", 300)
   264  	testLongPathAbs(t, target)
   265  }
   266  
   267  func TestLongPathRel(t *testing.T) {
   268  	chdir(t, t.TempDir())
   269  
   270  	target := strings.Repeat("b\\", 300)
   271  	testLongPathAbs(t, target)
   272  }
   273  
   274  func BenchmarkAddExtendedPrefix(b *testing.B) {
   275  	veryLong := `C:\l` + strings.Repeat("o", 248) + "ng"
   276  	b.ReportAllocs()
   277  	for i := 0; i < b.N; i++ {
   278  		os.AddExtendedPrefix(veryLong)
   279  	}
   280  }
   281  

View as plain text