tractor.dev/toolkit-go@v0.0.0-20241010005851-214d91207d07/engine/fs/memfs/fs_test.go (about) 1 // Copyright © 2014 Steve Francia <spf@spf13.com>. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package memfs 15 16 import ( 17 "fmt" 18 "io" 19 "io/fs" 20 "os" 21 "path/filepath" 22 "reflect" 23 "runtime" 24 "testing" 25 "time" 26 27 "tractor.dev/toolkit-go/engine/fs/fsutil" 28 ) 29 30 var Fss = []*FS{{}} 31 32 var testRegistry map[*FS][]string = make(map[*FS][]string) 33 34 func removeAllTestFiles(t *testing.T) { 35 for fs, list := range testRegistry { 36 for _, path := range list { 37 if err := fs.RemoveAll(path); err != nil { 38 t.Error(err) 39 } 40 } 41 } 42 testRegistry = make(map[*FS][]string) 43 } 44 45 func testDir(fsys *FS) string { 46 name, err := fsutil.TempDir(fsys, "", "vfs") 47 if err != nil { 48 panic(fmt.Sprint("unable to work with test dir", err)) 49 } 50 testRegistry[fsys] = append(testRegistry[fsys], name) 51 52 return name 53 } 54 55 func fsName(fs *FS) string { 56 return filepath.Base(reflect.TypeOf(fs).Elem().PkgPath()) + ".FS" 57 } 58 59 func TestNormalizePath(t *testing.T) { 60 type test struct { 61 input string 62 expected string 63 } 64 65 data := []test{ 66 {".", filePathSeparator}, 67 {"./", filePathSeparator}, 68 {"..", filePathSeparator}, 69 {"../", filePathSeparator}, 70 {"./..", filePathSeparator}, 71 {"./../", filePathSeparator}, 72 } 73 74 for i, d := range data { 75 cpath := normalizePath(d.input) 76 if d.expected != cpath { 77 t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, cpath) 78 } 79 } 80 } 81 82 func TestPathErrors(t *testing.T) { 83 path := filepath.Join(".", "some", "path") 84 path2 := filepath.Join(".", "different", "path") 85 fs := New() 86 perm := os.FileMode(0755) 87 uid := 1000 88 gid := 1000 89 90 // relevant functions: 91 // func (m *MemMapFs) Chmod(name string, mode os.FileMode) error 92 // func (m *MemMapFs) Chtimes(name string, atime time.Time, mtime time.Time) error 93 // func (m *MemMapFs) Create(name string) (File, error) 94 // func (m *MemMapFs) Mkdir(name string, perm os.FileMode) error 95 // func (m *MemMapFs) MkdirAll(path string, perm os.FileMode) error 96 // func (m *MemMapFs) Open(name string) (File, error) 97 // func (m *MemMapFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) 98 // func (m *MemMapFs) Remove(name string) error 99 // func (m *MemMapFs) Rename(oldname, newname string) error 100 // func (m *MemMapFs) Stat(name string) (os.FileInfo, error) 101 102 err := fs.Chmod(path, perm) 103 checkPathError(t, err, "Chmod") 104 105 err = fs.Chown(path, uid, gid) 106 checkPathError(t, err, "Chown") 107 108 err = fs.Chtimes(path, time.Now(), time.Now()) 109 checkPathError(t, err, "Chtimes") 110 111 // fs.Create doesn't return an error 112 113 err = fs.Mkdir(path2, perm) 114 if err != nil { 115 t.Error(err) 116 } 117 err = fs.Mkdir(path2, perm) 118 checkPathError(t, err, "Mkdir") 119 120 err = fs.MkdirAll(path2, perm) 121 if err != nil { 122 t.Error("MkdirAll:", err) 123 } 124 125 _, err = fs.Open(path) 126 checkPathError(t, err, "Open") 127 128 _, err = fs.OpenFile(path, os.O_RDWR, perm) 129 checkPathError(t, err, "OpenFile") 130 131 err = fs.Remove(path) 132 checkPathError(t, err, "Remove") 133 134 err = fs.RemoveAll(path) 135 if err != nil { 136 t.Error("RemoveAll:", err) 137 } 138 139 err = fs.Rename(path, path2) 140 checkPathError(t, err, "Rename") 141 142 _, err = fs.Stat(path) 143 checkPathError(t, err, "Stat") 144 } 145 146 func checkPathError(t *testing.T, err error, op string) { 147 pathErr, ok := err.(*os.PathError) 148 if !ok { 149 t.Error(op+":", err, "is not a os.PathError") 150 return 151 } 152 _, ok = pathErr.Err.(*os.PathError) 153 if ok { 154 t.Error(op+":", err, "contains another os.PathError") 155 } 156 } 157 158 // Ensure os.O_EXCL is correctly handled. 159 func TestOpenFileExcl(t *testing.T) { 160 const fileName = "/myFileTest" 161 const fileMode = os.FileMode(0765) 162 163 fs := New() 164 165 // First creation should succeed. 166 f, err := fs.OpenFile(fileName, os.O_CREATE|os.O_EXCL, fileMode) 167 if err != nil { 168 t.Errorf("OpenFile Create Excl failed: %s", err) 169 return 170 } 171 f.Close() 172 173 // Second creation should fail. 174 _, err = fs.OpenFile(fileName, os.O_CREATE|os.O_EXCL, fileMode) 175 if err == nil { 176 t.Errorf("OpenFile Create Excl should have failed, but it didn't") 177 } 178 checkPathError(t, err, "Open") 179 } 180 181 // Ensure Permissions are set on OpenFile/Mkdir/MkdirAll 182 func TestPermSet(t *testing.T) { 183 const fileName = "/myFileTest" 184 const dirPath = "/myDirTest" 185 const dirPathAll = "/my/path/to/dir" 186 187 const fileMode = os.FileMode(0765) 188 // directories will also have the directory bit set 189 const dirMode = fileMode | os.ModeDir 190 191 fs := New() 192 193 // Test Openfile 194 f, err := fs.OpenFile(fileName, os.O_CREATE, fileMode) 195 if err != nil { 196 t.Errorf("OpenFile Create failed: %s", err) 197 return 198 } 199 f.Close() 200 201 s, err := fs.Stat(fileName) 202 if err != nil { 203 t.Errorf("Stat failed: %s", err) 204 return 205 } 206 if s.Mode().String() != fileMode.String() { 207 t.Errorf("Permissions Incorrect: %s != %s", s.Mode().String(), fileMode.String()) 208 return 209 } 210 211 // Test Mkdir 212 err = fs.Mkdir(dirPath, dirMode) 213 if err != nil { 214 t.Errorf("MkDir Create failed: %s", err) 215 return 216 } 217 s, err = fs.Stat(dirPath) 218 if err != nil { 219 t.Errorf("Stat failed: %s", err) 220 return 221 } 222 // sets File 223 if s.Mode().String() != dirMode.String() { 224 t.Errorf("Permissions Incorrect: %s != %s", s.Mode().String(), dirMode.String()) 225 return 226 } 227 228 // Test MkdirAll 229 err = fs.MkdirAll(dirPathAll, dirMode) 230 if err != nil { 231 t.Errorf("MkDir Create failed: %s", err) 232 return 233 } 234 s, err = fs.Stat(dirPathAll) 235 if err != nil { 236 t.Errorf("Stat failed: %s", err) 237 return 238 } 239 if s.Mode().String() != dirMode.String() { 240 t.Errorf("Permissions Incorrect: %s != %s", s.Mode().String(), dirMode.String()) 241 return 242 } 243 } 244 245 // Fails if multiple file objects use the same file.at counter in MemMapFs 246 func TestMultipleOpenFiles(t *testing.T) { 247 defer removeAllTestFiles(t) 248 const fileName = "afero-demo2.txt" 249 250 var data = make([][]byte, len(Fss)) 251 252 for i, fsys := range Fss { 253 dir := testDir(fsys) 254 path := filepath.Join(dir, fileName) 255 fh1, err := fsys.Create(path) 256 if err != nil { 257 t.Error("fs.Create failed: " + err.Error()) 258 } 259 fw1, ok := fh1.(io.Writer) 260 if !ok { 261 t.Fatal("file not a io.Writer") 262 } 263 _, err = fw1.Write([]byte("test")) 264 if err != nil { 265 t.Error("fh.Write failed: " + err.Error()) 266 } 267 fs1, ok := fh1.(io.Seeker) 268 if !ok { 269 t.Fatal("file not a io.Seeker") 270 } 271 _, err = fs1.Seek(0, os.SEEK_SET) 272 if err != nil { 273 t.Error(err) 274 } 275 276 fh2, err := fsys.OpenFile(path, os.O_RDWR, 0777) 277 if err != nil { 278 t.Error("fs.OpenFile failed: " + err.Error()) 279 } 280 fs2, ok := fh2.(io.Seeker) 281 if !ok { 282 t.Fatal("file not a io.Seeker") 283 } 284 _, err = fs2.Seek(0, os.SEEK_END) 285 if err != nil { 286 t.Error(err) 287 } 288 fw2, ok := fh2.(io.Writer) 289 if !ok { 290 t.Fatal("file not a io.Writer") 291 } 292 _, err = fw2.Write([]byte("data")) 293 if err != nil { 294 t.Error(err) 295 } 296 err = fh2.Close() 297 if err != nil { 298 t.Error(err) 299 } 300 301 _, err = fw1.Write([]byte("data")) 302 if err != nil { 303 t.Error(err) 304 } 305 err = fh1.Close() 306 if err != nil { 307 t.Error(err) 308 } 309 // the file now should contain "datadata" 310 data[i], err = fs.ReadFile(fsys, path) 311 if err != nil { 312 t.Error(err) 313 } 314 } 315 316 for i, fs := range Fss { 317 if i == 0 { 318 continue 319 } 320 if string(data[0]) != string(data[i]) { 321 t.Errorf("%s and %s don't behave the same\n"+ 322 "%s: \"%s\"\n%s: \"%s\"\n", 323 fsName(Fss[0]), fsName(fs), fsName(Fss[0]), data[0], fsName(fs), data[i]) 324 } 325 } 326 } 327 328 // Test if file.Write() fails when opened as read only 329 func TestReadOnly(t *testing.T) { 330 defer removeAllTestFiles(t) 331 const fileName = "afero-demo.txt" 332 333 for _, fs := range Fss { 334 dir := testDir(fs) 335 path := filepath.Join(dir, fileName) 336 337 f, err := fs.Create(path) 338 if err != nil { 339 t.Error(fsName(fs)+":", "fs.Create failed: "+err.Error()) 340 } 341 fw, ok := f.(io.Writer) 342 if !ok { 343 t.Fatal("file not a io.Writer") 344 } 345 _, err = fw.Write([]byte("test")) 346 if err != nil { 347 t.Error(fsName(fs)+":", "Write failed: "+err.Error()) 348 } 349 f.Close() 350 351 f, err = fs.Open(path) 352 if err != nil { 353 t.Error("fs.Open failed: " + err.Error()) 354 } 355 fw, ok = f.(io.Writer) 356 if !ok { 357 t.Fatal("file not a io.Writer") 358 } 359 _, err = fw.Write([]byte("data")) 360 if err == nil { 361 t.Error(fsName(fs)+":", "No write error") 362 } 363 f.Close() 364 365 f, err = fs.OpenFile(path, os.O_RDONLY, 0644) 366 if err != nil { 367 t.Error("fs.Open failed: " + err.Error()) 368 } 369 fw, ok = f.(io.Writer) 370 if !ok { 371 t.Fatal("file not a io.Writer") 372 } 373 _, err = fw.Write([]byte("data")) 374 if err == nil { 375 t.Error(fsName(fs)+":", "No write error") 376 } 377 f.Close() 378 } 379 } 380 381 func TestWriteCloseTime(t *testing.T) { 382 defer removeAllTestFiles(t) 383 const fileName = "afero-demo.txt" 384 385 for _, fs := range Fss { 386 dir := testDir(fs) 387 path := filepath.Join(dir, fileName) 388 389 f, err := fs.Create(path) 390 if err != nil { 391 t.Error(fsName(fs)+":", "fs.Create failed: "+err.Error()) 392 } 393 f.Close() 394 395 f, err = fs.Create(path) 396 if err != nil { 397 t.Error(fsName(fs)+":", "fs.Create failed: "+err.Error()) 398 } 399 fi, err := f.Stat() 400 if err != nil { 401 t.Error(fsName(fs)+":", "Stat failed: "+err.Error()) 402 } 403 timeBefore := fi.ModTime() 404 405 // sorry for the delay, but we have to make sure time advances, 406 // also on non Un*x systems... 407 switch runtime.GOOS { 408 case "windows": 409 time.Sleep(2 * time.Second) 410 case "darwin": 411 time.Sleep(1 * time.Second) 412 default: // depending on the FS, this may work with < 1 second, on my old ext3 it does not 413 time.Sleep(1 * time.Second) 414 } 415 416 fw, ok := f.(io.Writer) 417 if !ok { 418 t.Fatal("file not a io.Writer") 419 } 420 _, err = fw.Write([]byte("test")) 421 if err != nil { 422 t.Error(fsName(fs)+":", "Write failed: "+err.Error()) 423 } 424 f.Close() 425 fi, err = fs.Stat(path) 426 if err != nil { 427 t.Error(fsName(fs)+":", "fs.Stat failed: "+err.Error()) 428 } 429 if fi.ModTime().Equal(timeBefore) { 430 t.Error(fsName(fs)+":", "ModTime was not set on Close()") 431 } 432 } 433 } 434 435 // This test should be run with the race detector on: 436 // go test -race -v -timeout 10s -run TestRacingDeleteAndClose 437 func TestRacingDeleteAndClose(t *testing.T) { 438 fs := New() 439 pathname := "testfile" 440 f, err := fs.Create(pathname) 441 if err != nil { 442 t.Fatal(err) 443 } 444 445 in := make(chan bool) 446 447 go func() { 448 <-in 449 f.Close() 450 }() 451 go func() { 452 <-in 453 fs.Remove(pathname) 454 }() 455 close(in) 456 } 457 458 // This test should be run with the race detector on: 459 // go test -run TestMemFsDataRace -race 460 func TestMemFsDataRace(t *testing.T) { 461 const dir = "test_dir" 462 fsys := New() 463 464 if err := fsys.MkdirAll(dir, 0777); err != nil { 465 t.Fatal(err) 466 } 467 468 const n = 1000 469 done := make(chan struct{}) 470 471 go func() { 472 defer close(done) 473 for i := 0; i < n; i++ { 474 fname := filepath.Join(dir, fmt.Sprintf("%d.txt", i)) 475 if err := fsutil.WriteFile(fsys, fname, []byte(""), 0777); err != nil { 476 panic(err) 477 } 478 if err := fsys.Remove(fname); err != nil { 479 panic(err) 480 } 481 } 482 }() 483 484 loop: 485 for { 486 select { 487 case <-done: 488 break loop 489 default: 490 _, err := fs.ReadDir(fsys, dir) 491 if err != nil { 492 t.Fatal(err) 493 } 494 } 495 } 496 } 497 498 // root is a directory 499 func TestMemFsRootDirMode(t *testing.T) { 500 t.Parallel() 501 502 fs := New() 503 info, err := fs.Stat("/") 504 if err != nil { 505 t.Fatal(err) 506 } 507 if !info.IsDir() { 508 t.Error("should be a directory") 509 } 510 if !info.Mode().IsDir() { 511 t.Errorf("FileMode is not directory, is %s", info.Mode().String()) 512 } 513 } 514 515 // MkdirAll creates intermediate directories with correct mode 516 func TestMemFsMkdirAllMode(t *testing.T) { 517 t.Parallel() 518 519 fs := New() 520 err := fs.MkdirAll("/a/b/c", 0755) 521 if err != nil { 522 t.Fatal(err) 523 } 524 info, err := fs.Stat("/a") 525 if err != nil { 526 t.Fatal(err) 527 } 528 if !info.Mode().IsDir() { 529 t.Error("/a: mode is not directory") 530 } 531 if info.Mode() != os.FileMode(os.ModeDir|0755) { 532 t.Errorf("/a: wrong permissions, expected drwxr-xr-x, got %s", info.Mode()) 533 } 534 info, err = fs.Stat("/a/b") 535 if err != nil { 536 t.Fatal(err) 537 } 538 if !info.Mode().IsDir() { 539 t.Error("/a/b: mode is not directory") 540 } 541 if info.Mode() != os.FileMode(os.ModeDir|0755) { 542 t.Errorf("/a/b: wrong permissions, expected drwxr-xr-x, got %s", info.Mode()) 543 } 544 info, err = fs.Stat("/a/b/c") 545 if err != nil { 546 t.Fatal(err) 547 } 548 if !info.Mode().IsDir() { 549 t.Error("/a/b/c: mode is not directory") 550 } 551 if info.Mode() != os.FileMode(os.ModeDir|0755) { 552 t.Errorf("/a/b/c: wrong permissions, expected drwxr-xr-x, got %s", info.Mode()) 553 } 554 } 555 556 // MkdirAll does not change permissions of already-existing directories 557 func TestMemFsMkdirAllNoClobber(t *testing.T) { 558 t.Parallel() 559 560 fs := New() 561 err := fs.MkdirAll("/a/b/c", 0755) 562 if err != nil { 563 t.Fatal(err) 564 } 565 info, err := fs.Stat("/a/b") 566 if err != nil { 567 t.Fatal(err) 568 } 569 if info.Mode() != os.FileMode(os.ModeDir|0755) { 570 t.Errorf("/a/b: wrong permissions, expected drwxr-xr-x, got %s", info.Mode()) 571 } 572 err = fs.MkdirAll("/a/b/c/d/e/f", 0710) 573 // '/a/b' is unchanged 574 if err != nil { 575 t.Fatal(err) 576 } 577 info, err = fs.Stat("/a/b") 578 if err != nil { 579 t.Fatal(err) 580 } 581 if info.Mode() != os.FileMode(os.ModeDir|0755) { 582 t.Errorf("/a/b: wrong permissions, expected drwxr-xr-x, got %s", info.Mode()) 583 } 584 // new directories created with proper permissions 585 info, err = fs.Stat("/a/b/c/d") 586 if err != nil { 587 t.Fatal(err) 588 } 589 if info.Mode() != os.FileMode(os.ModeDir|0710) { 590 t.Errorf("/a/b/c/d: wrong permissions, expected drwx--x---, got %s", info.Mode()) 591 } 592 info, err = fs.Stat("/a/b/c/d/e") 593 if err != nil { 594 t.Fatal(err) 595 } 596 if info.Mode() != os.FileMode(os.ModeDir|0710) { 597 t.Errorf("/a/b/c/d/e: wrong permissions, expected drwx--x---, got %s", info.Mode()) 598 } 599 info, err = fs.Stat("/a/b/c/d/e/f") 600 if err != nil { 601 t.Fatal(err) 602 } 603 if info.Mode() != os.FileMode(os.ModeDir|0710) { 604 t.Errorf("/a/b/c/d/e/f: wrong permissions, expected drwx--x---, got %s", info.Mode()) 605 } 606 } 607 608 func TestMemFsDirMode(t *testing.T) { 609 fs := New() 610 err := fs.Mkdir("/testDir1", 0644) 611 if err != nil { 612 t.Error(err) 613 } 614 err = fs.MkdirAll("/sub/testDir2", 0644) 615 if err != nil { 616 t.Error(err) 617 } 618 info, err := fs.Stat("/testDir1") 619 if err != nil { 620 t.Error(err) 621 } 622 if !info.IsDir() { 623 t.Error("should be a directory") 624 } 625 if !info.Mode().IsDir() { 626 t.Error("FileMode is not directory") 627 } 628 info, err = fs.Stat("/sub/testDir2") 629 if err != nil { 630 t.Error(err) 631 } 632 if !info.IsDir() { 633 t.Error("should be a directory") 634 } 635 if !info.Mode().IsDir() { 636 t.Error("FileMode is not directory") 637 } 638 } 639 640 func TestMemFsUnexpectedEOF(t *testing.T) { 641 t.Parallel() 642 643 fs := New() 644 645 if err := fsutil.WriteFile(fs, "file.txt", []byte("abc"), 0777); err != nil { 646 t.Fatal(err) 647 } 648 649 f, err := fs.Open("file.txt") 650 if err != nil { 651 t.Fatal(err) 652 } 653 defer f.Close() 654 655 // Seek beyond the end. 656 fseek, ok := f.(io.Seeker) 657 if !ok { 658 t.Fatal("file not a io.Seeker") 659 } 660 _, err = fseek.Seek(512, 0) 661 if err != nil { 662 t.Fatal(err) 663 } 664 665 buff := make([]byte, 256) 666 _, err = io.ReadAtLeast(f, buff, 256) 667 668 if err != io.ErrUnexpectedEOF { 669 t.Fatal("Expected ErrUnexpectedEOF") 670 } 671 } 672 673 func TestMemFsChmod(t *testing.T) { 674 t.Parallel() 675 676 fs := New() 677 const file = "hello" 678 if err := fs.Mkdir(file, 0700); err != nil { 679 t.Fatal(err) 680 } 681 682 info, err := fs.Stat(file) 683 if err != nil { 684 t.Fatal(err) 685 } 686 if info.Mode().String() != "drwx------" { 687 t.Fatal("mkdir failed to create a directory: mode =", info.Mode()) 688 } 689 690 err = fs.Chmod(file, 0) 691 if err != nil { 692 t.Error("Failed to run chmod:", err) 693 } 694 695 info, err = fs.Stat(file) 696 if err != nil { 697 t.Fatal(err) 698 } 699 if info.Mode().String() != "d---------" { 700 t.Error("chmod should not change file type. New mode =", info.Mode()) 701 } 702 } 703 704 // can't use Mkdir to get around which permissions we're allowed to set 705 func TestMemFsMkdirModeIllegal(t *testing.T) { 706 t.Parallel() 707 708 fs := New() 709 err := fs.Mkdir("/a", os.ModeSocket|0755) 710 if err != nil { 711 t.Fatal(err) 712 } 713 info, err := fs.Stat("/a") 714 if err != nil { 715 t.Fatal(err) 716 } 717 if info.Mode() != os.FileMode(os.ModeDir|0755) { 718 t.Fatalf("should not be able to use Mkdir to set illegal mode: %s", info.Mode().String()) 719 } 720 } 721 722 // can't use OpenFile to get around which permissions we're allowed to set 723 func TestMemFsOpenFileModeIllegal(t *testing.T) { 724 t.Parallel() 725 726 fs := New() 727 file, err := fs.OpenFile("/a", os.O_CREATE, os.ModeSymlink|0644) 728 if err != nil { 729 t.Fatal(err) 730 } 731 defer file.Close() 732 info, err := fs.Stat("/a") 733 if err != nil { 734 t.Fatal(err) 735 } 736 if info.Mode() != os.FileMode(0644) { 737 t.Fatalf("should not be able to use OpenFile to set illegal mode: %s", info.Mode().String()) 738 } 739 }