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 var path string
351 if test.arg != "" {
352 path = filepath.Clean(test.arg)
353 }
354 got, err := filepath.ToNorm(path, stubBase)
355 if err != nil {
356 t.Errorf("toNorm(%s) failed: %v\n", test.arg, err)
357 } else if got != test.want {
358 t.Errorf("toNorm(%s) returns %s, but %s expected\n", test.arg, got, test.want)
359 }
360 }
361
362 testPath := `{{tmp}}\test\foo\bar`
363
364 testsDir := []struct {
365 wd string
366 arg string
367 want string
368 }{
369
370 {".", `{{tmp}}\test\foo\bar`, `{{tmp}}\test\foo\bar`},
371 {".", `{{tmp}}\.\test/foo\bar`, `{{tmp}}\test\foo\bar`},
372 {".", `{{tmp}}\test\..\test\foo\bar`, `{{tmp}}\test\foo\bar`},
373 {".", `{{tmp}}\TEST\FOO\BAR`, `{{tmp}}\test\foo\bar`},
374
375
376 {`{{tmp}}\test`, `{{tmpvol}}.`, `{{tmpvol}}.`},
377 {`{{tmp}}\test`, `{{tmpvol}}..`, `{{tmpvol}}..`},
378 {`{{tmp}}\test`, `{{tmpvol}}foo\bar`, `{{tmpvol}}foo\bar`},
379 {`{{tmp}}\test`, `{{tmpvol}}.\foo\bar`, `{{tmpvol}}foo\bar`},
380 {`{{tmp}}\test`, `{{tmpvol}}foo\..\foo\bar`, `{{tmpvol}}foo\bar`},
381 {`{{tmp}}\test`, `{{tmpvol}}FOO\BAR`, `{{tmpvol}}foo\bar`},
382
383
384 {"{{tmp}}", `{{tmpnovol}}\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
385 {"{{tmp}}", `{{tmpnovol}}\.\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
386 {"{{tmp}}", `{{tmpnovol}}\test\..\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
387 {"{{tmp}}", `{{tmpnovol}}\TEST\FOO\BAR`, `{{tmpnovol}}\test\foo\bar`},
388
389
390 {`{{tmp}}\test`, ".", `.`},
391 {`{{tmp}}\test`, "..", `..`},
392 {`{{tmp}}\test`, `foo\bar`, `foo\bar`},
393 {`{{tmp}}\test`, `.\foo\bar`, `foo\bar`},
394 {`{{tmp}}\test`, `foo\..\foo\bar`, `foo\bar`},
395 {`{{tmp}}\test`, `FOO\BAR`, `foo\bar`},
396
397
398 {".", `\\localhost\c$`, `\\localhost\c$`},
399 }
400
401 ctmp := tempDirCanonical(t)
402 if err := os.MkdirAll(strings.ReplaceAll(testPath, "{{tmp}}", ctmp), 0777); err != nil {
403 t.Fatal(err)
404 }
405
406 cwd, err := os.Getwd()
407 if err != nil {
408 t.Fatal(err)
409 }
410 defer func() {
411 err := os.Chdir(cwd)
412 if err != nil {
413 t.Fatal(err)
414 }
415 }()
416
417 tmpVol := filepath.VolumeName(ctmp)
418 if len(tmpVol) != 2 {
419 t.Fatalf("unexpected temp volume name %q", tmpVol)
420 }
421
422 tmpNoVol := ctmp[len(tmpVol):]
423
424 replacer := strings.NewReplacer("{{tmp}}", ctmp, "{{tmpvol}}", tmpVol, "{{tmpnovol}}", tmpNoVol)
425
426 for _, test := range testsDir {
427 wd := replacer.Replace(test.wd)
428 arg := replacer.Replace(test.arg)
429 want := replacer.Replace(test.want)
430
431 if test.wd == "." {
432 err := os.Chdir(cwd)
433 if err != nil {
434 t.Error(err)
435
436 continue
437 }
438 } else {
439 err := os.Chdir(wd)
440 if err != nil {
441 t.Error(err)
442
443 continue
444 }
445 }
446 if arg != "" {
447 arg = filepath.Clean(arg)
448 }
449 got, err := filepath.ToNorm(arg, filepath.NormBase)
450 if err != nil {
451 t.Errorf("toNorm(%s) failed: %v (wd=%s)\n", arg, err, wd)
452 } else if got != want {
453 t.Errorf("toNorm(%s) returns %s, but %s expected (wd=%s)\n", arg, got, want, wd)
454 }
455 }
456 }
457
458 func TestUNC(t *testing.T) {
459
460
461 defer debug.SetMaxStack(debug.SetMaxStack(1e6))
462 filepath.Glob(`\\?\c:\*`)
463 }
464
465 func testWalkMklink(t *testing.T, linktype string) {
466 output, _ := exec.Command("cmd", "/c", "mklink", "/?").Output()
467 if !strings.Contains(string(output), fmt.Sprintf(" /%s ", linktype)) {
468 t.Skipf(`skipping test; mklink does not supports /%s parameter`, linktype)
469 }
470 testWalkSymlink(t, func(target, link string) error {
471 output, err := exec.Command("cmd", "/c", "mklink", "/"+linktype, link, target).CombinedOutput()
472 if err != nil {
473 return fmt.Errorf(`"mklink /%s %v %v" command failed: %v\n%v`, linktype, link, target, err, string(output))
474 }
475 return nil
476 })
477 }
478
479 func TestWalkDirectoryJunction(t *testing.T) {
480 testenv.MustHaveSymlink(t)
481 testWalkMklink(t, "J")
482 }
483
484 func TestWalkDirectorySymlink(t *testing.T) {
485 testenv.MustHaveSymlink(t)
486 testWalkMklink(t, "D")
487 }
488
489 func TestNTNamespaceSymlink(t *testing.T) {
490 output, _ := exec.Command("cmd", "/c", "mklink", "/?").Output()
491 if !strings.Contains(string(output), " /J ") {
492 t.Skip("skipping test because mklink command does not support junctions")
493 }
494
495 tmpdir := tempDirCanonical(t)
496
497 vol := filepath.VolumeName(tmpdir)
498 output, err := exec.Command("cmd", "/c", "mountvol", vol, "/L").CombinedOutput()
499 if err != nil {
500 t.Fatalf("failed to run mountvol %v /L: %v %q", vol, err, output)
501 }
502 target := strings.Trim(string(output), " \n\r")
503
504 dirlink := filepath.Join(tmpdir, "dirlink")
505 output, err = exec.Command("cmd", "/c", "mklink", "/J", dirlink, target).CombinedOutput()
506 if err != nil {
507 t.Fatalf("failed to run mklink %v %v: %v %q", dirlink, target, err, output)
508 }
509
510 got, err := filepath.EvalSymlinks(dirlink)
511 if err != nil {
512 t.Fatal(err)
513 }
514 if want := vol + `\`; got != want {
515 t.Errorf(`EvalSymlinks(%q): got %q, want %q`, dirlink, got, want)
516 }
517
518
519 testenv.MustHaveSymlink(t)
520
521 file := filepath.Join(tmpdir, "file")
522 err = os.WriteFile(file, []byte(""), 0666)
523 if err != nil {
524 t.Fatal(err)
525 }
526
527 target += file[len(filepath.VolumeName(file)):]
528
529 filelink := filepath.Join(tmpdir, "filelink")
530 output, err = exec.Command("cmd", "/c", "mklink", filelink, target).CombinedOutput()
531 if err != nil {
532 t.Fatalf("failed to run mklink %v %v: %v %q", filelink, target, err, output)
533 }
534
535 got, err = filepath.EvalSymlinks(filelink)
536 if err != nil {
537 t.Fatal(err)
538 }
539 if want := file; got != want {
540 t.Errorf(`EvalSymlinks(%q): got %q, want %q`, filelink, got, want)
541 }
542 }
543
544 func TestIssue52476(t *testing.T) {
545 tests := []struct {
546 lhs, rhs string
547 want string
548 }{
549 {`..\.`, `C:`, `..\C:`},
550 {`..`, `C:`, `..\C:`},
551 {`.`, `:`, `.\:`},
552 {`.`, `C:`, `.\C:`},
553 {`.`, `C:/a/b/../c`, `.\C:\a\c`},
554 {`.`, `\C:`, `.\C:`},
555 {`C:\`, `.`, `C:\`},
556 {`C:\`, `C:\`, `C:\C:`},
557 {`C`, `:`, `C\:`},
558 {`\.`, `C:`, `\C:`},
559 {`\`, `C:`, `\C:`},
560 }
561
562 for _, test := range tests {
563 got := filepath.Join(test.lhs, test.rhs)
564 if got != test.want {
565 t.Errorf(`Join(%q, %q): got %q, want %q`, test.lhs, test.rhs, got, test.want)
566 }
567 }
568 }
569
570 func TestAbsWindows(t *testing.T) {
571 for _, test := range []struct {
572 path string
573 want string
574 }{
575 {`C:\foo`, `C:\foo`},
576 {`\\host\share\foo`, `\\host\share\foo`},
577 {`\\host`, `\\host`},
578 {`\\.\NUL`, `\\.\NUL`},
579 {`NUL`, `\\.\NUL`},
580 {`COM1`, `\\.\COM1`},
581 {`a/NUL`, `\\.\NUL`},
582 } {
583 got, err := filepath.Abs(test.path)
584 if err != nil || got != test.want {
585 t.Errorf("Abs(%q) = %q, %v; want %q, nil", test.path, got, err, test.want)
586 }
587 }
588 }
589
View as plain text