|
5 | 5 | package os_test
|
6 | 6 |
|
7 | 7 | import (
|
| 8 | + "bytes" |
8 | 9 | "errors"
|
9 | 10 | "flag"
|
10 | 11 | "fmt"
|
@@ -3030,35 +3031,44 @@ func TestOpenFileKeepsPermissions(t *testing.T) {
|
3030 | 3031 | }
|
3031 | 3032 | }
|
3032 | 3033 |
|
3033 |
| -func TestDirFS(t *testing.T) { |
3034 |
| - t.Parallel() |
| 3034 | +func forceMFTUpdateOnWindows(t *testing.T, path string) { |
| 3035 | + t.Helper() |
| 3036 | + |
| 3037 | + if runtime.GOOS != "windows" { |
| 3038 | + return |
| 3039 | + } |
3035 | 3040 |
|
3036 | 3041 | // On Windows, we force the MFT to update by reading the actual metadata from GetFileInformationByHandle and then
|
3037 | 3042 | // explicitly setting that. Otherwise it might get out of sync with FindFirstFile. See golang.org/issues/42637.
|
3038 |
| - if runtime.GOOS == "windows" { |
3039 |
| - if err := filepath.WalkDir("./testdata/dirfs", func(path string, d fs.DirEntry, err error) error { |
3040 |
| - if err != nil { |
3041 |
| - t.Fatal(err) |
3042 |
| - } |
3043 |
| - info, err := d.Info() |
3044 |
| - if err != nil { |
3045 |
| - t.Fatal(err) |
3046 |
| - } |
3047 |
| - stat, err := Stat(path) // This uses GetFileInformationByHandle internally. |
3048 |
| - if err != nil { |
3049 |
| - t.Fatal(err) |
3050 |
| - } |
3051 |
| - if stat.ModTime() == info.ModTime() { |
3052 |
| - return nil |
3053 |
| - } |
3054 |
| - if err := Chtimes(path, stat.ModTime(), stat.ModTime()); err != nil { |
3055 |
| - t.Log(err) // We only log, not die, in case the test directory is not writable. |
3056 |
| - } |
3057 |
| - return nil |
3058 |
| - }); err != nil { |
| 3043 | + if err := filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error { |
| 3044 | + if err != nil { |
| 3045 | + t.Fatal(err) |
| 3046 | + } |
| 3047 | + info, err := d.Info() |
| 3048 | + if err != nil { |
3059 | 3049 | t.Fatal(err)
|
3060 | 3050 | }
|
| 3051 | + stat, err := Stat(path) // This uses GetFileInformationByHandle internally. |
| 3052 | + if err != nil { |
| 3053 | + t.Fatal(err) |
| 3054 | + } |
| 3055 | + if stat.ModTime() == info.ModTime() { |
| 3056 | + return nil |
| 3057 | + } |
| 3058 | + if err := Chtimes(path, stat.ModTime(), stat.ModTime()); err != nil { |
| 3059 | + t.Log(err) // We only log, not die, in case the test directory is not writable. |
| 3060 | + } |
| 3061 | + return nil |
| 3062 | + }); err != nil { |
| 3063 | + t.Fatal(err) |
3061 | 3064 | }
|
| 3065 | +} |
| 3066 | + |
| 3067 | +func TestDirFS(t *testing.T) { |
| 3068 | + t.Parallel() |
| 3069 | + |
| 3070 | + forceMFTUpdateOnWindows(t, "./testdata/dirfs") |
| 3071 | + |
3062 | 3072 | fsys := DirFS("./testdata/dirfs")
|
3063 | 3073 | if err := fstest.TestFS(fsys, "a", "b", "dir/x"); err != nil {
|
3064 | 3074 | t.Fatal(err)
|
@@ -3358,3 +3368,223 @@ func TestRandomLen(t *testing.T) {
|
3358 | 3368 | }
|
3359 | 3369 | }
|
3360 | 3370 | }
|
| 3371 | + |
| 3372 | +func TestCopyFS(t *testing.T) { |
| 3373 | + t.Parallel() |
| 3374 | + |
| 3375 | + // Test with disk filesystem. |
| 3376 | + forceMFTUpdateOnWindows(t, "./testdata/dirfs") |
| 3377 | + fsys := DirFS("./testdata/dirfs") |
| 3378 | + tmpDir := t.TempDir() |
| 3379 | + if err := CopyFS(tmpDir, fsys); err != nil { |
| 3380 | + t.Fatal("CopyFS:", err) |
| 3381 | + } |
| 3382 | + forceMFTUpdateOnWindows(t, tmpDir) |
| 3383 | + tmpFsys := DirFS(tmpDir) |
| 3384 | + if err := fstest.TestFS(tmpFsys, "a", "b", "dir/x"); err != nil { |
| 3385 | + t.Fatal("TestFS:", err) |
| 3386 | + } |
| 3387 | + if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { |
| 3388 | + if d.IsDir() { |
| 3389 | + return nil |
| 3390 | + } |
| 3391 | + |
| 3392 | + data, err := fs.ReadFile(fsys, path) |
| 3393 | + if err != nil { |
| 3394 | + return err |
| 3395 | + } |
| 3396 | + newData, err := fs.ReadFile(tmpFsys, path) |
| 3397 | + if err != nil { |
| 3398 | + return err |
| 3399 | + } |
| 3400 | + if !bytes.Equal(data, newData) { |
| 3401 | + return errors.New("file " + path + " contents differ") |
| 3402 | + } |
| 3403 | + return nil |
| 3404 | + }); err != nil { |
| 3405 | + t.Fatal("comparing two directories:", err) |
| 3406 | + } |
| 3407 | + |
| 3408 | + // Test with memory filesystem. |
| 3409 | + fsys = fstest.MapFS{ |
| 3410 | + "william": {Data: []byte("Shakespeare\n")}, |
| 3411 | + "carl": {Data: []byte("Gauss\n")}, |
| 3412 | + "daVinci": {Data: []byte("Leonardo\n")}, |
| 3413 | + "einstein": {Data: []byte("Albert\n")}, |
| 3414 | + "dir/newton": {Data: []byte("Sir Isaac\n")}, |
| 3415 | + } |
| 3416 | + tmpDir = t.TempDir() |
| 3417 | + if err := CopyFS(tmpDir, fsys); err != nil { |
| 3418 | + t.Fatal("CopyFS:", err) |
| 3419 | + } |
| 3420 | + forceMFTUpdateOnWindows(t, tmpDir) |
| 3421 | + tmpFsys = DirFS(tmpDir) |
| 3422 | + if err := fstest.TestFS(tmpFsys, "william", "carl", "daVinci", "einstein", "dir/newton"); err != nil { |
| 3423 | + t.Fatal("TestFS:", err) |
| 3424 | + } |
| 3425 | + if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { |
| 3426 | + if d.IsDir() { |
| 3427 | + return nil |
| 3428 | + } |
| 3429 | + |
| 3430 | + data, err := fs.ReadFile(fsys, path) |
| 3431 | + if err != nil { |
| 3432 | + return err |
| 3433 | + } |
| 3434 | + newData, err := fs.ReadFile(tmpFsys, path) |
| 3435 | + if err != nil { |
| 3436 | + return err |
| 3437 | + } |
| 3438 | + if !bytes.Equal(data, newData) { |
| 3439 | + return errors.New("file " + path + " contents differ") |
| 3440 | + } |
| 3441 | + return nil |
| 3442 | + }); err != nil { |
| 3443 | + t.Fatal("comparing two directories:", err) |
| 3444 | + } |
| 3445 | +} |
| 3446 | + |
| 3447 | +func TestCopyFSWithSymlinks(t *testing.T) { |
| 3448 | + // Test it with absolute and relative symlinks that point inside and outside the tree. |
| 3449 | + testenv.MustHaveSymlink(t) |
| 3450 | + |
| 3451 | + // Create a directory and file outside. |
| 3452 | + tmpDir := t.TempDir() |
| 3453 | + outsideDir, err := MkdirTemp(tmpDir, "copyfs") |
| 3454 | + if err != nil { |
| 3455 | + t.Fatalf("MkdirTemp: %v", err) |
| 3456 | + } |
| 3457 | + outsideFile := filepath.Join(outsideDir, "file.out.txt") |
| 3458 | + |
| 3459 | + if err := WriteFile(outsideFile, []byte("Testing CopyFS outside"), 0644); err != nil { |
| 3460 | + t.Fatalf("WriteFile: %v", err) |
| 3461 | + } |
| 3462 | + |
| 3463 | + // Create a directory and file inside. |
| 3464 | + testDataDir, err := filepath.Abs("./testdata/") |
| 3465 | + if err != nil { |
| 3466 | + t.Fatalf("filepath.Abs: %v", err) |
| 3467 | + } |
| 3468 | + insideDir := filepath.Join(testDataDir, "copyfs") |
| 3469 | + if err := Mkdir(insideDir, 0755); err != nil { |
| 3470 | + t.Fatalf("Mkdir: %v", err) |
| 3471 | + } |
| 3472 | + defer RemoveAll(insideDir) |
| 3473 | + insideFile := filepath.Join(insideDir, "file.in.txt") |
| 3474 | + if err := WriteFile(insideFile, []byte("Testing CopyFS inside"), 0644); err != nil { |
| 3475 | + t.Fatalf("WriteFile: %v", err) |
| 3476 | + } |
| 3477 | + |
| 3478 | + // Create directories for symlinks. |
| 3479 | + linkInDir := filepath.Join(insideDir, "in_symlinks") |
| 3480 | + if err := Mkdir(linkInDir, 0755); err != nil { |
| 3481 | + t.Fatalf("Mkdir: %v", err) |
| 3482 | + } |
| 3483 | + linkOutDir := filepath.Join(insideDir, "out_symlinks") |
| 3484 | + if err := Mkdir(linkOutDir, 0755); err != nil { |
| 3485 | + t.Fatalf("Mkdir: %v", err) |
| 3486 | + } |
| 3487 | + |
| 3488 | + // First, we create the absolute symlink pointing outside. |
| 3489 | + outLinkFile := filepath.Join(linkOutDir, "file.abs.out.link") |
| 3490 | + if err := Symlink(outsideFile, outLinkFile); err != nil { |
| 3491 | + t.Fatalf("Symlink: %v", err) |
| 3492 | + } |
| 3493 | + |
| 3494 | + // Then, we create the relative symlink pointing outside. |
| 3495 | + relOutsideFile, err := filepath.Rel(filepath.Join(linkOutDir, "."), outsideFile) |
| 3496 | + if err != nil { |
| 3497 | + t.Fatalf("filepath.Rel: %v", err) |
| 3498 | + } |
| 3499 | + relOutLinkFile := filepath.Join(linkOutDir, "file.rel.out.link") |
| 3500 | + if err := Symlink(relOutsideFile, relOutLinkFile); err != nil { |
| 3501 | + t.Fatalf("Symlink: %v", err) |
| 3502 | + } |
| 3503 | + |
| 3504 | + // Last, we create the relative symlink pointing inside. |
| 3505 | + relInsideFile, err := filepath.Rel(filepath.Join(linkInDir, "."), insideFile) |
| 3506 | + if err != nil { |
| 3507 | + t.Fatalf("filepath.Rel: %v", err) |
| 3508 | + } |
| 3509 | + relInLinkFile := filepath.Join(linkInDir, "file.rel.in.link") |
| 3510 | + if err := Symlink(relInsideFile, relInLinkFile); err != nil { |
| 3511 | + t.Fatalf("Symlink: %v", err) |
| 3512 | + } |
| 3513 | + |
| 3514 | + // Copy the directory tree and verify. |
| 3515 | + forceMFTUpdateOnWindows(t, insideDir) |
| 3516 | + fsys := DirFS(insideDir) |
| 3517 | + tmpDupDir, err := MkdirTemp(tmpDir, "copyfs_dup") |
| 3518 | + if err != nil { |
| 3519 | + t.Fatalf("MkdirTemp: %v", err) |
| 3520 | + } |
| 3521 | + |
| 3522 | + // TODO(panjf2000): symlinks are currently not supported, and a specific error |
| 3523 | + // will be returned. Verify that error and skip the subsequent test, |
| 3524 | + // revisit this once #49580 is closed. |
| 3525 | + if err := CopyFS(tmpDupDir, fsys); !errors.Is(err, ErrInvalid) { |
| 3526 | + t.Fatalf("got %v, want ErrInvalid", err) |
| 3527 | + } |
| 3528 | + t.Skip("skip the subsequent test and wait for #49580") |
| 3529 | + |
| 3530 | + forceMFTUpdateOnWindows(t, tmpDupDir) |
| 3531 | + tmpFsys := DirFS(tmpDupDir) |
| 3532 | + if err := fstest.TestFS(tmpFsys, "file.in.txt", "out_symlinks/file.abs.out.link", "out_symlinks/file.rel.out.link", "in_symlinks/file.rel.in.link"); err != nil { |
| 3533 | + t.Fatal("TestFS:", err) |
| 3534 | + } |
| 3535 | + if err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { |
| 3536 | + if d.IsDir() { |
| 3537 | + return nil |
| 3538 | + } |
| 3539 | + |
| 3540 | + fi, err := d.Info() |
| 3541 | + if err != nil { |
| 3542 | + return err |
| 3543 | + } |
| 3544 | + if filepath.Ext(path) == ".link" { |
| 3545 | + if fi.Mode()&ModeSymlink == 0 { |
| 3546 | + return errors.New("original file " + path + " should be a symlink") |
| 3547 | + } |
| 3548 | + tmpfi, err := fs.Stat(tmpFsys, path) |
| 3549 | + if err != nil { |
| 3550 | + return err |
| 3551 | + } |
| 3552 | + if tmpfi.Mode()&ModeSymlink != 0 { |
| 3553 | + return errors.New("copied file " + path + " should not be a symlink") |
| 3554 | + } |
| 3555 | + } |
| 3556 | + |
| 3557 | + data, err := fs.ReadFile(fsys, path) |
| 3558 | + if err != nil { |
| 3559 | + return err |
| 3560 | + } |
| 3561 | + newData, err := fs.ReadFile(tmpFsys, path) |
| 3562 | + if err != nil { |
| 3563 | + return err |
| 3564 | + } |
| 3565 | + if !bytes.Equal(data, newData) { |
| 3566 | + return errors.New("file " + path + " contents differ") |
| 3567 | + } |
| 3568 | + |
| 3569 | + var target string |
| 3570 | + switch fileName := filepath.Base(path); fileName { |
| 3571 | + case "file.abs.out.link", "file.rel.out.link": |
| 3572 | + target = outsideFile |
| 3573 | + case "file.rel.in.link": |
| 3574 | + target = insideFile |
| 3575 | + } |
| 3576 | + if len(target) > 0 { |
| 3577 | + targetData, err := ReadFile(target) |
| 3578 | + if err != nil { |
| 3579 | + return err |
| 3580 | + } |
| 3581 | + if !bytes.Equal(targetData, newData) { |
| 3582 | + return errors.New("file " + path + " contents differ from target") |
| 3583 | + } |
| 3584 | + } |
| 3585 | + |
| 3586 | + return nil |
| 3587 | + }); err != nil { |
| 3588 | + t.Fatal("comparing two directories:", err) |
| 3589 | + } |
| 3590 | +} |
0 commit comments