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