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 func checkVolume8dot3Setting(vol string, enabled bool) error {
211
212
213 out, _ := exec.Command("fsutil", "8dot3name", "query", vol).CombinedOutput()
214
215 expected := "The registry state of NtfsDisable8dot3NameCreation is 2, the default (Volume level setting)"
216 if !strings.Contains(string(out), expected) {
217
218 expectedWindow10 := "The registry state is: 2 (Per volume setting - the default)"
219 if !strings.Contains(string(out), expectedWindow10) {
220 return fmt.Errorf("fsutil output should contain %q, but is %q", expected, string(out))
221 }
222 }
223
224 expected = "Based on the above two settings, 8dot3 name creation is %s on %s"
225 if enabled {
226 expected = fmt.Sprintf(expected, "enabled", vol)
227 } else {
228 expected = fmt.Sprintf(expected, "disabled", vol)
229 }
230 if !strings.Contains(string(out), expected) {
231 return fmt.Errorf("unexpected fsutil output: %q", string(out))
232 }
233 return nil
234 }
235
236 func setVolume8dot3Setting(vol string, enabled bool) error {
237 cmd := []string{"fsutil", "8dot3name", "set", vol}
238 if enabled {
239 cmd = append(cmd, "0")
240 } else {
241 cmd = append(cmd, "1")
242 }
243
244
245 out, _ := exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
246 if string(out) != "\r\nSuccessfully set 8dot3name behavior.\r\n" {
247
248 expectedWindow10 := "Successfully %s 8dot3name generation on %s\r\n"
249 if enabled {
250 expectedWindow10 = fmt.Sprintf(expectedWindow10, "enabled", vol)
251 } else {
252 expectedWindow10 = fmt.Sprintf(expectedWindow10, "disabled", vol)
253 }
254 if string(out) != expectedWindow10 {
255 return fmt.Errorf("%v command failed: %q", cmd, string(out))
256 }
257 }
258 return nil
259 }
260
261 var runFSModifyTests = flag.Bool("run_fs_modify_tests", false, "run tests which modify filesystem parameters")
262
263
264
265 func TestEvalSymlinksCanonicalNamesWith8dot3Disabled(t *testing.T) {
266 if !*runFSModifyTests {
267 t.Skip("skipping test that modifies file system setting; enable with -run_fs_modify_tests")
268 }
269 tempVol := filepath.VolumeName(os.TempDir())
270 if len(tempVol) != 2 {
271 t.Fatalf("unexpected temp volume name %q", tempVol)
272 }
273
274 err := checkVolume8dot3Setting(tempVol, true)
275 if err != nil {
276 t.Fatal(err)
277 }
278 err = setVolume8dot3Setting(tempVol, false)
279 if err != nil {
280 t.Fatal(err)
281 }
282 defer func() {
283 err := setVolume8dot3Setting(tempVol, true)
284 if err != nil {
285 t.Fatal(err)
286 }
287 err = checkVolume8dot3Setting(tempVol, true)
288 if err != nil {
289 t.Fatal(err)
290 }
291 }()
292 err = checkVolume8dot3Setting(tempVol, false)
293 if err != nil {
294 t.Fatal(err)
295 }
296 TestEvalSymlinksCanonicalNames(t)
297 }
298
299 func TestToNorm(t *testing.T) {
300 stubBase := func(path string) (string, error) {
301 vol := filepath.VolumeName(path)
302 path = path[len(vol):]
303
304 if strings.Contains(path, "/") {
305 return "", fmt.Errorf("invalid path is given to base: %s", vol+path)
306 }
307
308 if path == "" || path == "." || path == `\` {
309 return "", fmt.Errorf("invalid path is given to base: %s", vol+path)
310 }
311
312 i := strings.LastIndexByte(path, filepath.Separator)
313 if i == len(path)-1 {
314 return "", fmt.Errorf("invalid path is given to base: %s", vol+path)
315 }
316 if i == -1 {
317 return strings.ToUpper(path), nil
318 }
319
320 return strings.ToUpper(path[i+1:]), nil
321 }
322
323
324 tests := []struct {
325 arg string
326 want string
327 }{
328 {"", ""},
329 {".", "."},
330 {"./foo/bar", `FOO\BAR`},
331 {"/", `\`},
332 {"/foo/bar", `\FOO\BAR`},
333 {"/foo/bar/baz/qux", `\FOO\BAR\BAZ\QUX`},
334 {"foo/bar", `FOO\BAR`},
335 {"C:/foo/bar", `C:\FOO\BAR`},
336 {"C:foo/bar", `C:FOO\BAR`},
337 {"c:/foo/bar", `C:\FOO\BAR`},
338 {"C:/foo/bar", `C:\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:/ふー/バー", `C:\ふー\バー`},
343 }
344
345 for _, test := range tests {
346 got, err := filepath.ToNorm(test.arg, stubBase)
347 if err != nil {
348 t.Errorf("toNorm(%s) failed: %v\n", test.arg, err)
349 } else if got != test.want {
350 t.Errorf("toNorm(%s) returns %s, but %s expected\n", test.arg, got, test.want)
351 }
352 }
353
354 testPath := `{{tmp}}\test\foo\bar`
355
356 testsDir := []struct {
357 wd string
358 arg string
359 want string
360 }{
361
362 {".", `{{tmp}}\test\foo\bar`, `{{tmp}}\test\foo\bar`},
363 {".", `{{tmp}}\.\test/foo\bar`, `{{tmp}}\test\foo\bar`},
364 {".", `{{tmp}}\test\..\test\foo\bar`, `{{tmp}}\test\foo\bar`},
365 {".", `{{tmp}}\TEST\FOO\BAR`, `{{tmp}}\test\foo\bar`},
366
367
368 {`{{tmp}}\test`, `{{tmpvol}}.`, `{{tmpvol}}.`},
369 {`{{tmp}}\test`, `{{tmpvol}}..`, `{{tmpvol}}..`},
370 {`{{tmp}}\test`, `{{tmpvol}}foo\bar`, `{{tmpvol}}foo\bar`},
371 {`{{tmp}}\test`, `{{tmpvol}}.\foo\bar`, `{{tmpvol}}foo\bar`},
372 {`{{tmp}}\test`, `{{tmpvol}}foo\..\foo\bar`, `{{tmpvol}}foo\bar`},
373 {`{{tmp}}\test`, `{{tmpvol}}FOO\BAR`, `{{tmpvol}}foo\bar`},
374
375
376 {"{{tmp}}", `{{tmpnovol}}\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
377 {"{{tmp}}", `{{tmpnovol}}\.\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
378 {"{{tmp}}", `{{tmpnovol}}\test\..\test\foo\bar`, `{{tmpnovol}}\test\foo\bar`},
379 {"{{tmp}}", `{{tmpnovol}}\TEST\FOO\BAR`, `{{tmpnovol}}\test\foo\bar`},
380
381
382 {`{{tmp}}\test`, ".", `.`},
383 {`{{tmp}}\test`, "..", `..`},
384 {`{{tmp}}\test`, `foo\bar`, `foo\bar`},
385 {`{{tmp}}\test`, `.\foo\bar`, `foo\bar`},
386 {`{{tmp}}\test`, `foo\..\foo\bar`, `foo\bar`},
387 {`{{tmp}}\test`, `FOO\BAR`, `foo\bar`},
388
389
390 {".", `\\localhost\c$`, `\\localhost\c$`},
391 }
392
393 ctmp := tempDirCanonical(t)
394 if err := os.MkdirAll(strings.ReplaceAll(testPath, "{{tmp}}", ctmp), 0777); err != nil {
395 t.Fatal(err)
396 }
397
398 cwd, err := os.Getwd()
399 if err != nil {
400 t.Fatal(err)
401 }
402 defer func() {
403 err := os.Chdir(cwd)
404 if err != nil {
405 t.Fatal(err)
406 }
407 }()
408
409 tmpVol := filepath.VolumeName(ctmp)
410 if len(tmpVol) != 2 {
411 t.Fatalf("unexpected temp volume name %q", tmpVol)
412 }
413
414 tmpNoVol := ctmp[len(tmpVol):]
415
416 replacer := strings.NewReplacer("{{tmp}}", ctmp, "{{tmpvol}}", tmpVol, "{{tmpnovol}}", tmpNoVol)
417
418 for _, test := range testsDir {
419 wd := replacer.Replace(test.wd)
420 arg := replacer.Replace(test.arg)
421 want := replacer.Replace(test.want)
422
423 if test.wd == "." {
424 err := os.Chdir(cwd)
425 if err != nil {
426 t.Error(err)
427
428 continue
429 }
430 } else {
431 err := os.Chdir(wd)
432 if err != nil {
433 t.Error(err)
434
435 continue
436 }
437 }
438
439 got, err := filepath.ToNorm(arg, filepath.NormBase)
440 if err != nil {
441 t.Errorf("toNorm(%s) failed: %v (wd=%s)\n", arg, err, wd)
442 } else if got != want {
443 t.Errorf("toNorm(%s) returns %s, but %s expected (wd=%s)\n", arg, got, want, wd)
444 }
445 }
446 }
447
448 func TestUNC(t *testing.T) {
449
450
451 defer debug.SetMaxStack(debug.SetMaxStack(1e6))
452 filepath.Glob(`\\?\c:\*`)
453 }
454
455 func testWalkMklink(t *testing.T, linktype string) {
456 output, _ := exec.Command("cmd", "/c", "mklink", "/?").Output()
457 if !strings.Contains(string(output), fmt.Sprintf(" /%s ", linktype)) {
458 t.Skipf(`skipping test; mklink does not supports /%s parameter`, linktype)
459 }
460 testWalkSymlink(t, func(target, link string) error {
461 output, err := exec.Command("cmd", "/c", "mklink", "/"+linktype, link, target).CombinedOutput()
462 if err != nil {
463 return fmt.Errorf(`"mklink /%s %v %v" command failed: %v\n%v`, linktype, link, target, err, string(output))
464 }
465 return nil
466 })
467 }
468
469 func TestWalkDirectoryJunction(t *testing.T) {
470 testenv.MustHaveSymlink(t)
471 testWalkMklink(t, "J")
472 }
473
474 func TestWalkDirectorySymlink(t *testing.T) {
475 testenv.MustHaveSymlink(t)
476 testWalkMklink(t, "D")
477 }
478
479 func TestNTNamespaceSymlink(t *testing.T) {
480 output, _ := exec.Command("cmd", "/c", "mklink", "/?").Output()
481 if !strings.Contains(string(output), " /J ") {
482 t.Skip("skipping test because mklink command does not support junctions")
483 }
484
485 tmpdir := tempDirCanonical(t)
486
487 vol := filepath.VolumeName(tmpdir)
488 output, err := exec.Command("cmd", "/c", "mountvol", vol, "/L").CombinedOutput()
489 if err != nil {
490 t.Fatalf("failed to run mountvol %v /L: %v %q", vol, err, output)
491 }
492 target := strings.Trim(string(output), " \n\r")
493
494 dirlink := filepath.Join(tmpdir, "dirlink")
495 output, err = exec.Command("cmd", "/c", "mklink", "/J", dirlink, target).CombinedOutput()
496 if err != nil {
497 t.Fatalf("failed to run mklink %v %v: %v %q", dirlink, target, err, output)
498 }
499
500 got, err := filepath.EvalSymlinks(dirlink)
501 if err != nil {
502 t.Fatal(err)
503 }
504 if want := vol + `\`; got != want {
505 t.Errorf(`EvalSymlinks(%q): got %q, want %q`, dirlink, got, want)
506 }
507
508
509 testenv.MustHaveSymlink(t)
510
511 file := filepath.Join(tmpdir, "file")
512 err = os.WriteFile(file, []byte(""), 0666)
513 if err != nil {
514 t.Fatal(err)
515 }
516
517 target += file[len(filepath.VolumeName(file)):]
518
519 filelink := filepath.Join(tmpdir, "filelink")
520 output, err = exec.Command("cmd", "/c", "mklink", filelink, target).CombinedOutput()
521 if err != nil {
522 t.Fatalf("failed to run mklink %v %v: %v %q", filelink, target, err, output)
523 }
524
525 got, err = filepath.EvalSymlinks(filelink)
526 if err != nil {
527 t.Fatal(err)
528 }
529 if want := file; got != want {
530 t.Errorf(`EvalSymlinks(%q): got %q, want %q`, filelink, got, want)
531 }
532 }
533
534 func TestIssue52476(t *testing.T) {
535 tests := []struct {
536 lhs, rhs string
537 want string
538 }{
539 {`..\.`, `C:`, `..\C:`},
540 {`..`, `C:`, `..\C:`},
541 {`.`, `:`, `:`},
542 {`.`, `C:`, `.\C:`},
543 {`.`, `C:/a/b/../c`, `.\C:\a\c`},
544 {`.`, `\C:`, `.\C:`},
545 {`C:\`, `.`, `C:\`},
546 {`C:\`, `C:\`, `C:\C:`},
547 {`C`, `:`, `C\:`},
548 {`\.`, `C:`, `\C:`},
549 {`\`, `C:`, `\C:`},
550 }
551
552 for _, test := range tests {
553 got := filepath.Join(test.lhs, test.rhs)
554 if got != test.want {
555 t.Errorf(`Join(%q, %q): got %q, want %q`, test.lhs, test.rhs, got, test.want)
556 }
557 }
558 }
559
View as plain text