Source file
src/path/filepath/path_windows_test.go
1
2
3
4
5 package filepath_test
6
7 import (
8 "flag"
9 "fmt"
10 "internal/testenv"
11 "io/fs"
12 "os"
13 "os/exec"
14 "path/filepath"
15 "reflect"
16 "runtime/debug"
17 "strings"
18 "testing"
19 )
20
21 func TestWinSplitListTestsAreValid(t *testing.T) {
22 comspec := os.Getenv("ComSpec")
23 if comspec == "" {
24 t.Fatal("%ComSpec% must be set")
25 }
26
27 for ti, tt := range winsplitlisttests {
28 testWinSplitListTestIsValid(t, ti, tt, comspec)
29 }
30 }
31
32 func testWinSplitListTestIsValid(t *testing.T, ti int, tt SplitListTest,
33 comspec string) {
34
35 const (
36 cmdfile = `printdir.cmd`
37 perm fs.FileMode = 0700
38 )
39
40 tmp := t.TempDir()
41 for i, d := range tt.result {
42 if d == "" {
43 continue
44 }
45 if cd := filepath.Clean(d); filepath.VolumeName(cd) != "" ||
46 cd[0] == '\\' || cd == ".." || (len(cd) >= 3 && cd[0:3] == `..\`) {
47 t.Errorf("%d,%d: %#q refers outside working directory", ti, i, d)
48 return
49 }
50 dd := filepath.Join(tmp, d)
51 if _, err := os.Stat(dd); err == nil {
52 t.Errorf("%d,%d: %#q already exists", ti, i, d)
53 return
54 }
55 if err := os.MkdirAll(dd, perm); err != nil {
56 t.Errorf("%d,%d: MkdirAll(%#q) failed: %v", ti, i, dd, err)
57 return
58 }
59 fn, data := filepath.Join(dd, cmdfile), []byte("@echo "+d+"\r\n")
60 if err := os.WriteFile(fn, data, perm); err != nil {
61 t.Errorf("%d,%d: WriteFile(%#q) failed: %v", ti, i, fn, err)
62 return
63 }
64 }
65
66
67 systemRoot := os.Getenv("SystemRoot")
68
69 for i, d := range tt.result {
70 if d == "" {
71 continue
72 }
73 exp := []byte(d + "\r\n")
74 cmd := &exec.Cmd{
75 Path: comspec,
76 Args: []string{`/c`, cmdfile},
77 Env: []string{`Path=` + systemRoot + "/System32;" + tt.list, `SystemRoot=` + systemRoot},
78 Dir: tmp,
79 }
80 out, err := cmd.CombinedOutput()
81 switch {
82 case err != nil:
83 t.Errorf("%d,%d: execution error %v\n%q", ti, i, err, out)
84 return
85 case !reflect.DeepEqual(out, exp):
86 t.Errorf("%d,%d: expected %#q, got %#q", ti, i, exp, out)
87 return
88 default:
89
90 err = os.Remove(filepath.Join(tmp, d, cmdfile))
91 if err != nil {
92 t.Fatalf("Remove test command failed: %v", err)
93 }
94 }
95 }
96 }
97
98 func TestWindowsEvalSymlinks(t *testing.T) {
99 testenv.MustHaveSymlink(t)
100
101 tmpDir := tempDirCanonical(t)
102
103 if len(tmpDir) < 3 {
104 t.Fatalf("tmpDir path %q is too short", tmpDir)
105 }
106 if tmpDir[1] != ':' {
107 t.Fatalf("tmpDir path %q must have drive letter in it", tmpDir)
108 }
109 test := EvalSymlinksTest{"test/linkabswin", tmpDir[:3]}
110
111
112 testdirs := append(EvalSymlinksTestDirs, test)
113 for _, d := range testdirs {
114 var err error
115 path := simpleJoin(tmpDir, d.path)
116 if d.dest == "" {
117 err = os.Mkdir(path, 0755)
118 } else {
119 err = os.Symlink(d.dest, path)
120 }
121 if err != nil {
122 t.Fatal(err)
123 }
124 }
125
126 path := simpleJoin(tmpDir, test.path)
127
128 testEvalSymlinks(t, path, test.dest)
129
130 testEvalSymlinksAfterChdir(t, path, ".", test.dest)
131
132 testEvalSymlinksAfterChdir(t,
133 path,
134 filepath.VolumeName(tmpDir)+".",
135 test.dest)
136
137 testEvalSymlinksAfterChdir(t,
138 simpleJoin(tmpDir, "test"),
139 simpleJoin("..", test.path),
140 test.dest)
141
142 testEvalSymlinksAfterChdir(t, tmpDir, test.path, test.dest)
143 }
144
145
146
147 func TestEvalSymlinksCanonicalNames(t *testing.T) {
148 ctmp := tempDirCanonical(t)
149 dirs := []string{
150 "test",
151 "test/dir",
152 "testing_long_dir",
153 "TEST2",
154 }
155
156 for _, d := range dirs {
157 dir := filepath.Join(ctmp, d)
158 err := os.Mkdir(dir, 0755)
159 if err != nil {
160 t.Fatal(err)
161 }
162 cname, err := filepath.EvalSymlinks(dir)
163 if err != nil {
164 t.Errorf("EvalSymlinks(%q) error: %v", dir, err)
165 continue
166 }
167 if dir != cname {
168 t.Errorf("EvalSymlinks(%q) returns %q, but should return %q", dir, cname, dir)
169 continue
170 }
171
172 test := strings.ToUpper(dir)
173 p, err := filepath.EvalSymlinks(test)
174 if err != nil {
175 t.Errorf("EvalSymlinks(%q) error: %v", test, err)
176 continue
177 }
178 if p != cname {
179 t.Errorf("EvalSymlinks(%q) returns %q, but should return %q", test, p, cname)
180 continue
181 }
182
183 test = strings.ToLower(dir)
184 p, err = filepath.EvalSymlinks(test)
185 if err != nil {
186 t.Errorf("EvalSymlinks(%q) error: %v", test, err)
187 continue
188 }
189 if p != cname {
190 t.Errorf("EvalSymlinks(%q) returns %q, but should return %q", test, p, cname)
191 continue
192 }
193 }
194 }
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214 func checkVolume8dot3Setting(vol string, enabled bool) error {
215
216
217 out, _ := exec.Command("fsutil", "8dot3name", "query", vol).CombinedOutput()
218
219 expected := "The registry state of NtfsDisable8dot3NameCreation is 2, the default (Volume level setting)"
220 if !strings.Contains(string(out), expected) {
221
222 expectedWindow10 := "The registry state is: 2 (Per volume setting - the default)"
223 if !strings.Contains(string(out), expectedWindow10) {
224 return fmt.Errorf("fsutil output should contain %q, but is %q", expected, string(out))
225 }
226 }
227
228 expected = "Based on the above two settings, 8dot3 name creation is %s on %s"
229 if enabled {
230 expected = fmt.Sprintf(expected, "enabled", vol)
231 } else {
232 expected = fmt.Sprintf(expected, "disabled", vol)
233 }
234 if !strings.Contains(string(out), expected) {
235 return fmt.Errorf("unexpected fsutil output: %q", string(out))
236 }
237 return nil
238 }
239
240 func setVolume8dot3Setting(vol string, enabled bool) error {
241 cmd := []string{"fsutil", "8dot3name", "set", vol}
242 if enabled {
243 cmd = append(cmd, "0")
244 } else {
245 cmd = append(cmd, "1")
246 }
247
248
249 out, _ := exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
250 if string(out) != "\r\nSuccessfully set 8dot3name behavior.\r\n" {
251
252 expectedWindow10 := "Successfully %s 8dot3name generation on %s\r\n"
253 if enabled {
254 expectedWindow10 = fmt.Sprintf(expectedWindow10, "enabled", vol)
255 } else {
256 expectedWindow10 = fmt.Sprintf(expectedWindow10, "disabled", vol)
257 }
258 if string(out) != expectedWindow10 {
259 return fmt.Errorf("%v command failed: %q", cmd, string(out))
260 }
261 }
262 return nil
263 }
264
265 var runFSModifyTests = flag.Bool("run_fs_modify_tests", false, "run tests which modify filesystem parameters")
266
267
268
269 func TestEvalSymlinksCanonicalNamesWith8dot3Disabled(t *testing.T) {
270 if !*runFSModifyTests {
271 t.Skip("skipping test that modifies file system setting; enable with -run_fs_modify_tests")
272 }
273 tempVol := filepath.VolumeName(os.TempDir())
274 if len(tempVol) != 2 {
275 t.Fatalf("unexpected temp volume name %q", tempVol)
276 }
277
278 err := checkVolume8dot3Setting(tempVol, true)
279 if err != nil {
280 t.Fatal(err)
281 }
282 err = setVolume8dot3Setting(tempVol, false)
283 if err != nil {
284 t.Fatal(err)
285 }
286 defer func() {
287 err := setVolume8dot3Setting(tempVol, true)
288 if err != nil {
289 t.Fatal(err)
290 }
291 err = checkVolume8dot3Setting(tempVol, true)
292 if err != nil {
293 t.Fatal(err)
294 }
295 }()
296 err = checkVolume8dot3Setting(tempVol, false)
297 if err != nil {
298 t.Fatal(err)
299 }
300 TestEvalSymlinksCanonicalNames(t)
301 }
302
303 func TestToNorm(t *testing.T) {
304 stubBase := func(path string) (string, error) {
305 vol := filepath.VolumeName(path)
306 path = path[len(vol):]
307
308 if strings.Contains(path, "/") {
309 return "", fmt.Errorf("invalid path is given to base: %s", vol+path)
310 }
311
312 if path == "" || path == "." || path == `\` {
313 return "", fmt.Errorf("invalid path is given to base: %s", vol+path)
314 }
315
316 i := strings.LastIndexByte(path, filepath.Separator)
317 if i == len(path)-1 {
318 return "", fmt.Errorf("invalid path is given to base: %s", vol+path)
319 }
320 if i == -1 {
321 return strings.ToUpper(path), nil
322 }
323
324 return strings.ToUpper(path[i+1:]), nil
325 }
326
327
328 tests := []struct {
329 arg string
330 want string
331 }{
332 {"", ""},
333 {".", "."},
334 {"./foo/bar", `FOO\BAR`},
335 {"/", `\`},
336 {"/foo/bar", `\FOO\BAR`},
337 {"/foo/bar/baz/qux", `\FOO\BAR\BAZ\QUX`},
338 {"foo/bar", `FOO\BAR`},
339 {"C:/foo/bar", `C:\FOO\BAR`},
340 {"C:foo/bar", `C:FOO\BAR`},
341 {"c:/foo/bar", `C:\FOO\BAR`},
342 {"C:/foo/bar", `C:\FOO\BAR`},
343 {"C:/foo/bar/", `C:\FOO\BAR`},
344 {`C:\foo\bar`, `C:\FOO\BAR`},
345 {`C:\foo/bar\`, `C:\FOO\BAR`},
346 {"C:/ふー/バー", `C:\ふー\バー`},
347 }
348
349 for _, test := range tests {
350 got, err := filepath.ToNorm(test.arg, stubBase)
351 if err != nil {
352 t.Errorf("toNorm(%s) failed: %v\n", test.arg, err)
353 } else if got != test.want {
354 t.Errorf("toNorm(%s) returns %s, but %s expected\n", test.arg, got, test.want)
355 }
356 }
357
358 testPath := `{{tmp}}\test\foo\bar`
359
360 testsDir := []struct {
361 wd string
362 arg string
363 want string
364 }{
365
366 {".", `{{tmp}}\test\foo\bar`, `{{tmp}}\test\foo\bar`},
367 {".", `{{tmp}}\.\test/foo\bar`, `{{tmp}}\test\foo\bar`},
368 {".", `{{tmp}}\test\..\test\foo\bar`, `{{tmp}}\test\foo\bar`},
369 {".", `{{tmp}}\TEST\FOO\BAR`, `{{tmp}}\test\foo\bar`},
370
371
372 {`{{tmp}}\test`, `{{tmpvol}}.`, `{{tmpvol}}.`},
373 {`{{tmp}}\test`, `{{tmpvol}}..`, `{{tmpvol}}..`},
374 {`{{tmp}}\test`, `{{tmpvol}}foo\bar`, `{{tmpvol}}foo\bar`},
375 {`{{tmp}}\test`, `{{tmpvol}}.\foo\bar`, `{{tmpvol}}foo\bar`},
376 {`{{tmp}}\test`, `{{tmpvol}}foo\..\foo\bar`, `{{tmpvol}}foo\bar`},
377 {`{{tmp}}\test`, `{{tmpvol}}FOO\BAR`, `{{tmpvol}}foo\bar`},
378
379
380 {"{{tmp}}", `{{tmpnovol}}\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
381 {"{{tmp}}", `{{tmpnovol}}\.\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
382 {"{{tmp}}", `{{tmpnovol}}\test\..\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
383 {"{{tmp}}", `{{tmpnovol}}\TEST\FOO\BAR`, `{{tmpnovol}}\test\foo\bar`},
384
385
386 {`{{tmp}}\test`, ".", `.`},
387 {`{{tmp}}\test`, "..", `..`},
388 {`{{tmp}}\test`, `foo\bar`, `foo\bar`},
389 {`{{tmp}}\test`, `.\foo\bar`, `foo\bar`},
390 {`{{tmp}}\test`, `foo\..\foo\bar`, `foo\bar`},
391 {`{{tmp}}\test`, `FOO\BAR`, `foo\bar`},
392
393
394 {".", `\\localhost\c$`, `\\localhost\c$`},
395 }
396
397 ctmp := tempDirCanonical(t)
398 if err := os.MkdirAll(strings.ReplaceAll(testPath, "{{tmp}}", ctmp), 0777); err != nil {
399 t.Fatal(err)
400 }
401
402 cwd, err := os.Getwd()
403 if err != nil {
404 t.Fatal(err)
405 }
406 defer func() {
407 err := os.Chdir(cwd)
408 if err != nil {
409 t.Fatal(err)
410 }
411 }()
412
413 tmpVol := filepath.VolumeName(ctmp)
414 if len(tmpVol) != 2 {
415 t.Fatalf("unexpected temp volume name %q", tmpVol)
416 }
417
418 tmpNoVol := ctmp[len(tmpVol):]
419
420 replacer := strings.NewReplacer("{{tmp}}", ctmp, "{{tmpvol}}", tmpVol, "{{tmpnovol}}", tmpNoVol)
421
422 for _, test := range testsDir {
423 wd := replacer.Replace(test.wd)
424 arg := replacer.Replace(test.arg)
425 want := replacer.Replace(test.want)
426
427 if test.wd == "." {
428 err := os.Chdir(cwd)
429 if err != nil {
430 t.Error(err)
431
432 continue
433 }
434 } else {
435 err := os.Chdir(wd)
436 if err != nil {
437 t.Error(err)
438
439 continue
440 }
441 }
442
443 got, err := filepath.ToNorm(arg, filepath.NormBase)
444 if err != nil {
445 t.Errorf("toNorm(%s) failed: %v (wd=%s)\n", arg, err, wd)
446 } else if got != want {
447 t.Errorf("toNorm(%s) returns %s, but %s expected (wd=%s)\n", arg, got, want, wd)
448 }
449 }
450 }
451
452 func TestUNC(t *testing.T) {
453
454
455 defer debug.SetMaxStack(debug.SetMaxStack(1e6))
456 filepath.Glob(`\\?\c:\*`)
457 }
458
459 func testWalkMklink(t *testing.T, linktype string) {
460 output, _ := exec.Command("cmd", "/c", "mklink", "/?").Output()
461 if !strings.Contains(string(output), fmt.Sprintf(" /%s ", linktype)) {
462 t.Skipf(`skipping test; mklink does not supports /%s parameter`, linktype)
463 }
464 testWalkSymlink(t, func(target, link string) error {
465 output, err := exec.Command("cmd", "/c", "mklink", "/"+linktype, link, target).CombinedOutput()
466 if err != nil {
467 return fmt.Errorf(`"mklink /%s %v %v" command failed: %v\n%v`, linktype, link, target, err, string(output))
468 }
469 return nil
470 })
471 }
472
473 func TestWalkDirectoryJunction(t *testing.T) {
474 testenv.MustHaveSymlink(t)
475 testWalkMklink(t, "J")
476 }
477
478 func TestWalkDirectorySymlink(t *testing.T) {
479 testenv.MustHaveSymlink(t)
480 testWalkMklink(t, "D")
481 }
482
483 func TestNTNamespaceSymlink(t *testing.T) {
484 output, _ := exec.Command("cmd", "/c", "mklink", "/?").Output()
485 if !strings.Contains(string(output), " /J ") {
486 t.Skip("skipping test because mklink command does not support junctions")
487 }
488
489 tmpdir := tempDirCanonical(t)
490
491 vol := filepath.VolumeName(tmpdir)
492 output, err := exec.Command("cmd", "/c", "mountvol", vol, "/L").CombinedOutput()
493 if err != nil {
494 t.Fatalf("failed to run mountvol %v /L: %v %q", vol, err, output)
495 }
496 target := strings.Trim(string(output), " \n\r")
497
498 dirlink := filepath.Join(tmpdir, "dirlink")
499 output, err = exec.Command("cmd", "/c", "mklink", "/J", dirlink, target).CombinedOutput()
500 if err != nil {
501 t.Fatalf("failed to run mklink %v %v: %v %q", dirlink, target, err, output)
502 }
503
504 got, err := filepath.EvalSymlinks(dirlink)
505 if err != nil {
506 t.Fatal(err)
507 }
508 if want := vol + `\`; got != want {
509 t.Errorf(`EvalSymlinks(%q): got %q, want %q`, dirlink, got, want)
510 }
511
512
513 testenv.MustHaveSymlink(t)
514
515 file := filepath.Join(tmpdir, "file")
516 err = os.WriteFile(file, []byte(""), 0666)
517 if err != nil {
518 t.Fatal(err)
519 }
520
521 target += file[len(filepath.VolumeName(file)):]
522
523 filelink := filepath.Join(tmpdir, "filelink")
524 output, err = exec.Command("cmd", "/c", "mklink", filelink, target).CombinedOutput()
525 if err != nil {
526 t.Fatalf("failed to run mklink %v %v: %v %q", filelink, target, err, output)
527 }
528
529 got, err = filepath.EvalSymlinks(filelink)
530 if err != nil {
531 t.Fatal(err)
532 }
533 if want := file; got != want {
534 t.Errorf(`EvalSymlinks(%q): got %q, want %q`, filelink, got, want)
535 }
536 }
537
538 func TestIssue52476(t *testing.T) {
539 tests := []struct {
540 lhs, rhs string
541 want string
542 }{
543 {`..\.`, `C:`, `..\C:`},
544 {`..`, `C:`, `..\C:`},
545 {`.`, `:`, `:`},
546 {`.`, `C:`, `.\C:`},
547 {`.`, `C:/a/b/../c`, `.\C:\a\c`},
548 {`.`, `\C:`, `.\C:`},
549 {`C:\`, `.`, `C:\`},
550 {`C:\`, `C:\`, `C:\C:`},
551 {`C`, `:`, `C\:`},
552 {`\.`, `C:`, `\C:`},
553 {`\`, `C:`, `\C:`},
554 }
555
556 for _, test := range tests {
557 got := filepath.Join(test.lhs, test.rhs)
558 if got != test.want {
559 t.Errorf(`Join(%q, %q): got %q, want %q`, test.lhs, test.rhs, got, test.want)
560 }
561 }
562 }
563
564 func TestAbsWindows(t *testing.T) {
565 for _, test := range []struct {
566 path string
567 want string
568 }{
569 {`C:\foo`, `C:\foo`},
570 {`\\host\share\foo`, `\\host\share\foo`},
571 {`\\host`, `\\host`},
572 {`\\.\NUL`, `\\.\NUL`},
573 {`NUL`, `\\.\NUL`},
574 {`COM1`, `\\.\COM1`},
575 {`a/NUL`, `\\.\NUL`},
576 } {
577 got, err := filepath.Abs(test.path)
578 if err != nil || got != test.want {
579 t.Errorf("Abs(%q) = %q, %v; want %q, nil", test.path, got, err, test.want)
580 }
581 }
582 }
583
View as plain text