github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/sysfs/dirfs_test.go (about) 1 package sysfs 2 3 import ( 4 "errors" 5 "fmt" 6 "io/fs" 7 "os" 8 "path" 9 "runtime" 10 "testing" 11 "time" 12 13 "github.com/bananabytelabs/wazero/experimental/sys" 14 "github.com/bananabytelabs/wazero/internal/fstest" 15 "github.com/bananabytelabs/wazero/internal/platform" 16 "github.com/bananabytelabs/wazero/internal/testing/require" 17 ) 18 19 func TestDirFS(t *testing.T) { 20 testFS := DirFS(".") 21 22 // Guest can look up / 23 f, errno := testFS.OpenFile("/", sys.O_RDONLY, 0) 24 require.EqualErrno(t, 0, errno) 25 require.EqualErrno(t, 0, f.Close()) 26 27 t.Run("host path not found", func(t *testing.T) { 28 testFS := DirFS("a") 29 _, errno = testFS.OpenFile(".", sys.O_RDONLY, 0) 30 require.EqualErrno(t, sys.ENOENT, errno) 31 }) 32 t.Run("host path not a directory", func(t *testing.T) { 33 arg0 := os.Args[0] // should be safe in scratch tests which don't have the source mounted. 34 35 testFS := DirFS(arg0) 36 d, errno := testFS.OpenFile(".", sys.O_RDONLY, 0) 37 require.EqualErrno(t, 0, errno) 38 _, errno = d.Readdir(-1) 39 require.EqualErrno(t, sys.EBADF, errno) 40 }) 41 } 42 43 func TestDirFS_join(t *testing.T) { 44 testFS := DirFS("/").(*dirFS) 45 require.Equal(t, "/", testFS.join("")) 46 require.Equal(t, "/", testFS.join(".")) 47 require.Equal(t, "/", testFS.join("/")) 48 require.Equal(t, "/tmp", testFS.join("tmp")) 49 50 testFS = DirFS(".").(*dirFS) 51 require.Equal(t, ".", testFS.join("")) 52 require.Equal(t, ".", testFS.join(".")) 53 require.Equal(t, ".", testFS.join("/")) 54 require.Equal(t, "."+string(os.PathSeparator)+"tmp", testFS.join("tmp")) 55 } 56 57 func TestDirFS_String(t *testing.T) { 58 testFS := DirFS(".") 59 60 // String has the name of the path entered 61 require.Equal(t, ".", testFS.(fmt.Stringer).String()) 62 } 63 64 func TestDirFS_Lstat(t *testing.T) { 65 tmpDir := t.TempDir() 66 require.NoError(t, fstest.WriteTestFiles(tmpDir)) 67 68 testFS := DirFS(tmpDir) 69 for _, path := range []string{"animals.txt", "sub", "sub-link"} { 70 require.EqualErrno(t, 0, testFS.Symlink(path, path+"-link")) 71 } 72 73 testLstat(t, testFS) 74 } 75 76 func TestDirFS_MkDir(t *testing.T) { 77 tmpDir := t.TempDir() 78 testFS := DirFS(tmpDir) 79 80 name := "mkdir" 81 realPath := path.Join(tmpDir, name) 82 83 t.Run("doesn't exist", func(t *testing.T) { 84 require.EqualErrno(t, 0, testFS.Mkdir(name, fs.ModeDir)) 85 86 stat, err := os.Stat(realPath) 87 require.NoError(t, err) 88 89 require.Equal(t, name, stat.Name()) 90 require.True(t, stat.IsDir()) 91 }) 92 93 t.Run("dir exists", func(t *testing.T) { 94 err := testFS.Mkdir(name, fs.ModeDir) 95 require.EqualErrno(t, sys.EEXIST, err) 96 }) 97 98 t.Run("file exists", func(t *testing.T) { 99 require.NoError(t, os.Remove(realPath)) 100 require.NoError(t, os.Mkdir(realPath, 0o700)) 101 102 err := testFS.Mkdir(name, fs.ModeDir) 103 require.EqualErrno(t, sys.EEXIST, err) 104 }) 105 t.Run("try creating on file", func(t *testing.T) { 106 filePath := path.Join("non-existing-dir", "foo.txt") 107 err := testFS.Mkdir(filePath, fs.ModeDir) 108 require.EqualErrno(t, sys.ENOENT, err) 109 }) 110 111 // Remove the path so that we can test creating it with perms. 112 require.NoError(t, os.Remove(realPath)) 113 114 // Setting mode only applies to files on windows 115 if runtime.GOOS != "windows" { 116 t.Run("dir", func(t *testing.T) { 117 require.NoError(t, os.Mkdir(realPath, 0o444)) 118 defer os.RemoveAll(realPath) 119 testChmod(t, testFS, name) 120 }) 121 } 122 123 t.Run("file", func(t *testing.T) { 124 require.NoError(t, os.WriteFile(realPath, nil, 0o444)) 125 defer os.RemoveAll(realPath) 126 testChmod(t, testFS, name) 127 }) 128 } 129 130 func testChmod(t *testing.T, testFS sys.FS, path string) { 131 // Test base case, using 0o444 not 0o400 for read-back on windows. 132 requireMode(t, testFS, path, 0o444) 133 134 // Test adding write, using 0o666 not 0o600 for read-back on windows. 135 require.EqualErrno(t, 0, testFS.Chmod(path, 0o666)) 136 requireMode(t, testFS, path, 0o666) 137 138 if runtime.GOOS != "windows" { 139 // Test clearing group and world, setting owner read+execute. 140 require.EqualErrno(t, 0, testFS.Chmod(path, 0o500)) 141 requireMode(t, testFS, path, 0o500) 142 } 143 } 144 145 func requireMode(t *testing.T, testFS sys.FS, path string, mode fs.FileMode) { 146 st, errno := testFS.Stat(path) 147 require.EqualErrno(t, 0, errno) 148 149 require.Equal(t, mode, st.Mode.Perm()) 150 } 151 152 func TestDirFS_Rename(t *testing.T) { 153 t.Run("from doesn't exist", func(t *testing.T) { 154 tmpDir := t.TempDir() 155 testFS := DirFS(tmpDir) 156 157 file1 := "file1" 158 file1Path := path.Join(tmpDir, file1) 159 err := os.WriteFile(file1Path, []byte{1}, 0o600) 160 require.NoError(t, err) 161 162 err = testFS.Rename("file2", file1) 163 require.EqualErrno(t, sys.ENOENT, err) 164 }) 165 t.Run("file to non-exist", func(t *testing.T) { 166 tmpDir := t.TempDir() 167 testFS := DirFS(tmpDir) 168 169 file1 := "file1" 170 file1Path := path.Join(tmpDir, file1) 171 file1Contents := []byte{1} 172 errno := os.WriteFile(file1Path, file1Contents, 0o600) 173 require.NoError(t, errno) 174 175 file2 := "file2" 176 file2Path := path.Join(tmpDir, file2) 177 errno = testFS.Rename(file1, file2) 178 require.EqualErrno(t, 0, errno) 179 180 // Show the prior path no longer exists 181 _, errno = os.Stat(file1Path) 182 require.EqualErrno(t, sys.ENOENT, sys.UnwrapOSError(errno)) 183 184 s, err := os.Stat(file2Path) 185 require.NoError(t, err) 186 require.False(t, s.IsDir()) 187 }) 188 t.Run("dir to non-exist", func(t *testing.T) { 189 tmpDir := t.TempDir() 190 testFS := DirFS(tmpDir) 191 192 dir1 := "dir1" 193 dir1Path := path.Join(tmpDir, dir1) 194 require.NoError(t, os.Mkdir(dir1Path, 0o700)) 195 196 dir2 := "dir2" 197 dir2Path := path.Join(tmpDir, dir2) 198 errrno := testFS.Rename(dir1, dir2) 199 require.EqualErrno(t, 0, errrno) 200 201 // Show the prior path no longer exists 202 _, err := os.Stat(dir1Path) 203 require.EqualErrno(t, sys.ENOENT, sys.UnwrapOSError(err)) 204 205 s, err := os.Stat(dir2Path) 206 require.NoError(t, err) 207 require.True(t, s.IsDir()) 208 }) 209 t.Run("dir to file", func(t *testing.T) { 210 tmpDir := t.TempDir() 211 testFS := DirFS(tmpDir) 212 213 dir1 := "dir1" 214 dir1Path := path.Join(tmpDir, dir1) 215 require.NoError(t, os.Mkdir(dir1Path, 0o700)) 216 217 dir2 := "dir2" 218 dir2Path := path.Join(tmpDir, dir2) 219 220 // write a file to that path 221 f, err := os.OpenFile(dir2Path, os.O_RDWR|os.O_CREATE, 0o600) 222 require.NoError(t, err) 223 require.NoError(t, f.Close()) 224 225 errno := testFS.Rename(dir1, dir2) 226 require.EqualErrno(t, sys.ENOTDIR, errno) 227 }) 228 t.Run("file to dir", func(t *testing.T) { 229 tmpDir := t.TempDir() 230 testFS := DirFS(tmpDir) 231 232 file1 := "file1" 233 file1Path := path.Join(tmpDir, file1) 234 file1Contents := []byte{1} 235 err := os.WriteFile(file1Path, file1Contents, 0o600) 236 require.NoError(t, err) 237 238 dir1 := "dir1" 239 dir1Path := path.Join(tmpDir, dir1) 240 require.NoError(t, os.Mkdir(dir1Path, 0o700)) 241 242 errno := testFS.Rename(file1, dir1) 243 require.EqualErrno(t, sys.EISDIR, errno) 244 }) 245 246 // Similar to https://github.com/ziglang/zig/blob/0.10.1/lib/std/fs/test.zig#L567-L582 247 t.Run("dir to empty dir should be fine", func(t *testing.T) { 248 tmpDir := t.TempDir() 249 testFS := DirFS(tmpDir) 250 251 dir1 := "dir1" 252 dir1Path := path.Join(tmpDir, dir1) 253 require.NoError(t, os.Mkdir(dir1Path, 0o700)) 254 255 // add a file to that directory 256 file1 := "file1" 257 file1Path := path.Join(dir1Path, file1) 258 file1Contents := []byte{1} 259 err := os.WriteFile(file1Path, file1Contents, 0o600) 260 require.NoError(t, err) 261 262 dir2 := "dir2" 263 dir2Path := path.Join(tmpDir, dir2) 264 require.NoError(t, os.Mkdir(dir2Path, 0o700)) 265 266 errno := testFS.Rename(dir1, dir2) 267 require.EqualErrno(t, 0, errno) 268 269 // Show the prior path no longer exists 270 _, err = os.Stat(dir1Path) 271 require.EqualErrno(t, sys.ENOENT, sys.UnwrapOSError(err)) 272 273 // Show the file inside that directory moved 274 s, err := os.Stat(path.Join(dir2Path, file1)) 275 require.NoError(t, err) 276 require.False(t, s.IsDir()) 277 }) 278 279 // Similar to https://github.com/ziglang/zig/blob/0.10.1/lib/std/fs/test.zig#L584-L604 280 t.Run("dir to non empty dir should be EXIST", func(t *testing.T) { 281 tmpDir := t.TempDir() 282 testFS := DirFS(tmpDir) 283 284 dir1 := "dir1" 285 dir1Path := path.Join(tmpDir, dir1) 286 require.NoError(t, os.Mkdir(dir1Path, 0o700)) 287 288 // add a file to that directory 289 file1 := "file1" 290 file1Path := path.Join(dir1Path, file1) 291 file1Contents := []byte{1} 292 err := os.WriteFile(file1Path, file1Contents, 0o600) 293 require.NoError(t, err) 294 295 dir2 := "dir2" 296 dir2Path := path.Join(tmpDir, dir2) 297 require.NoError(t, os.Mkdir(dir2Path, 0o700)) 298 299 // Make the destination non-empty. 300 err = os.WriteFile(path.Join(dir2Path, "existing.txt"), []byte("any thing"), 0o600) 301 require.NoError(t, err) 302 303 errno := testFS.Rename(dir1, dir2) 304 require.EqualErrno(t, sys.ENOTEMPTY, errno) 305 }) 306 307 t.Run("file to file", func(t *testing.T) { 308 tmpDir := t.TempDir() 309 testFS := DirFS(tmpDir) 310 311 file1 := "file1" 312 file1Path := path.Join(tmpDir, file1) 313 file1Contents := []byte{1} 314 err := os.WriteFile(file1Path, file1Contents, 0o600) 315 require.NoError(t, err) 316 317 file2 := "file2" 318 file2Path := path.Join(tmpDir, file2) 319 file2Contents := []byte{2} 320 err = os.WriteFile(file2Path, file2Contents, 0o600) 321 require.NoError(t, err) 322 323 errno := testFS.Rename(file1, file2) 324 require.EqualErrno(t, 0, errno) 325 326 // Show the prior path no longer exists 327 _, err = os.Stat(file1Path) 328 require.EqualErrno(t, sys.ENOENT, sys.UnwrapOSError(err)) 329 330 // Show the file1 overwrote file2 331 b, err := os.ReadFile(file2Path) 332 require.NoError(t, err) 333 require.Equal(t, file1Contents, b) 334 }) 335 t.Run("dir to itself", func(t *testing.T) { 336 tmpDir := t.TempDir() 337 testFS := DirFS(tmpDir) 338 339 dir1 := "dir1" 340 dir1Path := path.Join(tmpDir, dir1) 341 require.NoError(t, os.Mkdir(dir1Path, 0o700)) 342 343 errno := testFS.Rename(dir1, dir1) 344 require.EqualErrno(t, 0, errno) 345 346 s, err := os.Stat(dir1Path) 347 require.NoError(t, err) 348 require.True(t, s.IsDir()) 349 }) 350 t.Run("file to itself", func(t *testing.T) { 351 tmpDir := t.TempDir() 352 testFS := DirFS(tmpDir) 353 354 file1 := "file1" 355 file1Path := path.Join(tmpDir, file1) 356 file1Contents := []byte{1} 357 err := os.WriteFile(file1Path, file1Contents, 0o600) 358 require.NoError(t, err) 359 360 errno := testFS.Rename(file1, file1) 361 require.EqualErrno(t, 0, errno) 362 363 b, err := os.ReadFile(file1Path) 364 require.NoError(t, err) 365 require.Equal(t, file1Contents, b) 366 }) 367 } 368 369 func TestDirFS_Rmdir(t *testing.T) { 370 t.Run("doesn't exist", func(t *testing.T) { 371 tmpDir := t.TempDir() 372 testFS := DirFS(tmpDir) 373 374 name := "rmdir" 375 376 err := testFS.Rmdir(name) 377 require.EqualErrno(t, sys.ENOENT, err) 378 }) 379 380 t.Run("dir not empty", func(t *testing.T) { 381 tmpDir := t.TempDir() 382 testFS := DirFS(tmpDir) 383 384 name := "rmdir" 385 realPath := path.Join(tmpDir, name) 386 387 require.NoError(t, os.Mkdir(realPath, 0o700)) 388 fileInDir := path.Join(realPath, "file") 389 require.NoError(t, os.WriteFile(fileInDir, []byte{}, 0o600)) 390 391 err := testFS.Rmdir(name) 392 require.EqualErrno(t, sys.ENOTEMPTY, err) 393 394 require.NoError(t, os.Remove(fileInDir)) 395 }) 396 397 t.Run("dir previously not empty", func(t *testing.T) { 398 tmpDir := t.TempDir() 399 testFS := DirFS(tmpDir) 400 401 name := "rmdir" 402 realPath := path.Join(tmpDir, name) 403 require.NoError(t, os.Mkdir(realPath, 0o700)) 404 405 // Create a file and then delete it. 406 fileInDir := path.Join(realPath, "file") 407 require.NoError(t, os.WriteFile(fileInDir, []byte{}, 0o600)) 408 require.NoError(t, os.Remove(fileInDir)) 409 410 // After deletion, try removing directory. 411 errno := testFS.Rmdir(name) 412 require.EqualErrno(t, 0, errno) 413 }) 414 415 t.Run("dir empty", func(t *testing.T) { 416 tmpDir := t.TempDir() 417 testFS := DirFS(tmpDir) 418 419 name := "rmdir" 420 realPath := path.Join(tmpDir, name) 421 require.NoError(t, os.Mkdir(realPath, 0o700)) 422 require.EqualErrno(t, 0, testFS.Rmdir(name)) 423 _, err := os.Stat(realPath) 424 require.Error(t, err) 425 }) 426 427 t.Run("dir empty while opening", func(t *testing.T) { 428 tmpDir := t.TempDir() 429 testFS := DirFS(tmpDir) 430 431 name := "rmdir" 432 realPath := path.Join(tmpDir, name) 433 require.NoError(t, os.Mkdir(realPath, 0o700)) 434 435 f, errno := testFS.OpenFile(name, sys.O_DIRECTORY, 0o700) 436 require.EqualErrno(t, 0, errno) 437 defer f.Close() 438 439 require.EqualErrno(t, 0, testFS.Rmdir(name)) 440 _, err := os.Stat(realPath) 441 require.Error(t, err) 442 }) 443 444 t.Run("not directory", func(t *testing.T) { 445 tmpDir := t.TempDir() 446 testFS := DirFS(tmpDir) 447 448 name := "rmdir" 449 realPath := path.Join(tmpDir, name) 450 451 require.NoError(t, os.WriteFile(realPath, []byte{}, 0o600)) 452 453 err := testFS.Rmdir(name) 454 require.EqualErrno(t, sys.ENOTDIR, err) 455 456 require.NoError(t, os.Remove(realPath)) 457 }) 458 } 459 460 func TestDirFS_Unlink(t *testing.T) { 461 t.Run("doesn't exist", func(t *testing.T) { 462 tmpDir := t.TempDir() 463 testFS := DirFS(tmpDir) 464 name := "unlink" 465 466 err := testFS.Unlink(name) 467 require.EqualErrno(t, sys.ENOENT, err) 468 }) 469 470 t.Run("target: dir", func(t *testing.T) { 471 tmpDir := t.TempDir() 472 testFS := DirFS(tmpDir) 473 474 dir := "dir" 475 realPath := path.Join(tmpDir, dir) 476 477 require.NoError(t, os.Mkdir(realPath, 0o700)) 478 479 err := testFS.Unlink(dir) 480 require.EqualErrno(t, sys.EISDIR, err) 481 482 require.NoError(t, os.Remove(realPath)) 483 }) 484 485 t.Run("target: symlink to dir", func(t *testing.T) { 486 tmpDir := t.TempDir() 487 testFS := DirFS(tmpDir) 488 489 // Create link target dir. 490 subDirName := "subdir" 491 subDirRealPath := path.Join(tmpDir, subDirName) 492 require.NoError(t, os.Mkdir(subDirRealPath, 0o700)) 493 494 // Create a symlink to the subdirectory. 495 const symlinkName = "symlink-to-dir" 496 require.EqualErrno(t, 0, testFS.Symlink("subdir", symlinkName)) 497 498 // Unlinking the symlink should suceed. 499 errno := testFS.Unlink(symlinkName) 500 require.EqualErrno(t, 0, errno) 501 }) 502 503 t.Run("file exists", func(t *testing.T) { 504 tmpDir := t.TempDir() 505 testFS := DirFS(tmpDir) 506 507 name := "unlink" 508 realPath := path.Join(tmpDir, name) 509 510 require.NoError(t, os.WriteFile(realPath, []byte{}, 0o600)) 511 512 require.EqualErrno(t, 0, testFS.Unlink(name)) 513 514 _, err := os.Stat(realPath) 515 require.Error(t, err) 516 }) 517 } 518 519 func TestDirFS_Utimesns(t *testing.T) { 520 tmpDir := t.TempDir() 521 testFS := DirFS(tmpDir) 522 523 file := "file" 524 err := os.WriteFile(path.Join(tmpDir, file), []byte{}, 0o700) 525 require.NoError(t, err) 526 527 dir := "dir" 528 err = os.Mkdir(path.Join(tmpDir, dir), 0o700) 529 require.NoError(t, err) 530 531 t.Run("doesn't exist", func(t *testing.T) { 532 err := testFS.Utimens("nope", 0, 0) 533 require.EqualErrno(t, sys.ENOENT, err) 534 }) 535 536 // Note: This sets microsecond granularity because Windows doesn't support 537 // nanosecond. 538 // 539 // Negative isn't tested as most platforms don't return consistent results. 540 tests := []struct { 541 name string 542 atim, mtim int64 543 }{ 544 { 545 name: "nil", 546 }, 547 { 548 name: "a=omit,m=omit", 549 atim: sys.UTIME_OMIT, 550 mtim: sys.UTIME_OMIT, 551 }, 552 { 553 name: "a=set,m=omit", 554 atim: int64(123*time.Second + 4*time.Microsecond), 555 mtim: sys.UTIME_OMIT, 556 }, 557 { 558 name: "a=omit,m=set", 559 atim: sys.UTIME_OMIT, 560 mtim: int64(123*time.Second + 4*time.Microsecond), 561 }, 562 { 563 name: "a=set,m=set", 564 atim: int64(123*time.Second + 4*time.Microsecond), 565 mtim: int64(223*time.Second + 5*time.Microsecond), 566 }, 567 } 568 569 for _, fileType := range []string{"dir", "file", "link"} { 570 for _, tt := range tests { 571 tc := tt 572 fileType := fileType 573 name := fileType + " " + tc.name 574 575 t.Run(name, func(t *testing.T) { 576 tmpDir := t.TempDir() 577 testFS := DirFS(tmpDir) 578 579 file := path.Join(tmpDir, "file") 580 errno := os.WriteFile(file, []byte{}, 0o700) 581 require.NoError(t, errno) 582 583 link := file + "-link" 584 require.NoError(t, os.Symlink(file, link)) 585 586 dir := path.Join(tmpDir, "dir") 587 errno = os.Mkdir(dir, 0o700) 588 require.NoError(t, errno) 589 590 var path, statPath string 591 switch fileType { 592 case "dir": 593 path = "dir" 594 statPath = "dir" 595 case "file": 596 path = "file" 597 statPath = "file" 598 case "link": 599 path = "file-link" 600 statPath = "file" 601 default: 602 panic(tc) 603 } 604 605 oldSt, errno := testFS.Lstat(statPath) 606 require.EqualErrno(t, 0, errno) 607 608 errno = testFS.Utimens(path, tc.atim, tc.mtim) 609 require.EqualErrno(t, 0, errno) 610 611 newSt, errno := testFS.Lstat(statPath) 612 require.EqualErrno(t, 0, errno) 613 614 if platform.CompilerSupported() { 615 if tc.atim == sys.UTIME_OMIT { 616 require.Equal(t, oldSt.Atim, newSt.Atim) 617 } else { 618 require.Equal(t, tc.atim, newSt.Atim) 619 } 620 } 621 622 // When compiler isn't supported, we can still check mtim. 623 if tc.mtim == sys.UTIME_OMIT { 624 require.Equal(t, oldSt.Mtim, newSt.Mtim) 625 } else { 626 require.Equal(t, tc.mtim, newSt.Mtim) 627 } 628 }) 629 } 630 } 631 } 632 633 func TestDirFS_OpenFile(t *testing.T) { 634 tmpDir := t.TempDir() 635 636 // Create a subdirectory, so we can test reads outside the sys.FS root. 637 tmpDir = path.Join(tmpDir, t.Name()) 638 require.NoError(t, os.Mkdir(tmpDir, 0o700)) 639 require.NoError(t, fstest.WriteTestFiles(tmpDir)) 640 641 testFS := DirFS(tmpDir) 642 643 testOpen_Read(t, testFS, statSetsIno(), true) 644 645 testOpen_O_RDWR(t, tmpDir, testFS) 646 647 t.Run("path outside root valid", func(t *testing.T) { 648 _, err := testFS.OpenFile("../foo", sys.O_RDONLY, 0) 649 650 // sys.FS allows relative path lookups 651 require.True(t, errors.Is(err, fs.ErrNotExist)) 652 }) 653 } 654 655 func TestDirFS_Stat(t *testing.T) { 656 tmpDir := t.TempDir() 657 require.NoError(t, fstest.WriteTestFiles(tmpDir)) 658 659 testFS := DirFS(tmpDir) 660 testStat(t, testFS) 661 662 // from os.TestDirFSPathsValid 663 if runtime.GOOS != "windows" { 664 t.Run("strange name", func(t *testing.T) { 665 name := `e:xperi\ment.txt` 666 require.NoError(t, os.WriteFile(path.Join(tmpDir, name), nil, 0o600)) 667 668 _, errno := testFS.Stat(name) 669 require.EqualErrno(t, 0, errno) 670 }) 671 } 672 } 673 674 func TestDirFS_Readdir(t *testing.T) { 675 root := t.TempDir() 676 testFS := DirFS(root) 677 678 const readDirTarget = "dir" 679 errno := testFS.Mkdir(readDirTarget, 0o700) 680 require.EqualErrno(t, 0, errno) 681 682 // Open the empty directory 683 dirFile, errno := testFS.OpenFile(readDirTarget, sys.O_RDONLY, 0) 684 require.EqualErrno(t, 0, errno) 685 defer dirFile.Close() 686 687 // Write files to the directory after it is open. 688 require.NoError(t, os.WriteFile(path.Join(root, readDirTarget, "1"), nil, 0o444)) 689 require.NoError(t, os.WriteFile(path.Join(root, readDirTarget, "2"), nil, 0o444)) 690 691 // Test files are visible. This fails in windows unless the implementation 692 // re-opens the underlying file. 693 // https://github.com/ziglang/zig/blob/e3736baddb8ecff90f0594be9f604c7484ce9aa2/lib/std/fs/test.zig#L290-L317 694 t.Run("Sees files written after open", func(t *testing.T) { 695 dirents, errno := dirFile.Readdir(1) 696 require.EqualErrno(t, 0, errno) 697 698 require.Equal(t, 1, len(dirents)) 699 n := dirents[0].Name 700 switch n { 701 case "1", "2": // order is inconsistent on scratch images. 702 default: 703 require.Equal(t, "1", n) 704 } 705 }) 706 707 // Test there is no error reading the directory if it was deleted while 708 // iterating. See docs on Readdir for why in general, but specifically Zig 709 // tests enforce this. This test is Windows sensitive as well. 710 // 711 // https://github.com/ziglang/zig/blob/e3736baddb8ecff90f0594be9f604c7484ce9aa2/lib/std/fs/test.zig#L311C1-L311C1 712 t.Run("sys.ENOENT or no error, deleted while reading", func(t *testing.T) { 713 require.NoError(t, os.RemoveAll(path.Join(root, readDirTarget))) 714 715 dirents, errno := dirFile.Readdir(-1) 716 if errno != 0 { 717 require.EqualErrno(t, sys.ENOENT, errno) 718 } 719 720 require.Equal(t, 0, len(dirents)) 721 }) 722 } 723 724 func TestDirFS_Link(t *testing.T) { 725 t.Parallel() 726 727 // Set up the test files 728 tmpDir := t.TempDir() 729 require.NoError(t, fstest.WriteTestFiles(tmpDir)) 730 731 testFS := DirFS(tmpDir) 732 733 require.EqualErrno(t, testFS.Link("cat", ""), sys.ENOENT) 734 require.EqualErrno(t, testFS.Link("sub/test.txt", "sub/test.txt"), sys.EEXIST) 735 require.EqualErrno(t, testFS.Link("sub/test.txt", "."), sys.EEXIST) 736 require.EqualErrno(t, testFS.Link("sub/test.txt", ""), sys.EEXIST) 737 require.EqualErrno(t, testFS.Link("sub/test.txt", "/"), sys.EEXIST) 738 require.EqualErrno(t, 0, testFS.Link("sub/test.txt", "foo")) 739 } 740 741 func TestDirFS_Symlink(t *testing.T) { 742 t.Parallel() 743 744 // Set up the test files 745 tmpDir := t.TempDir() 746 require.NoError(t, fstest.WriteTestFiles(tmpDir)) 747 748 testFS := DirFS(tmpDir) 749 750 require.EqualErrno(t, sys.EEXIST, testFS.Symlink("sub/test.txt", "sub/test.txt")) 751 // Non-existing old name is allowed. 752 require.EqualErrno(t, 0, testFS.Symlink("non-existing", "aa")) 753 require.EqualErrno(t, 0, testFS.Symlink("sub/", "symlinked-subdir")) 754 755 st, err := os.Lstat(path.Join(tmpDir, "aa")) 756 require.NoError(t, err) 757 require.Equal(t, "aa", st.Name()) 758 require.True(t, st.Mode()&fs.ModeSymlink > 0 && !st.IsDir()) 759 760 st, err = os.Lstat(path.Join(tmpDir, "symlinked-subdir")) 761 require.NoError(t, err) 762 require.Equal(t, "symlinked-subdir", st.Name()) 763 require.True(t, st.Mode()&fs.ModeSymlink > 0) 764 } 765 766 func TestDirFS_Readlink(t *testing.T) { 767 tmpDir := t.TempDir() 768 require.NoError(t, fstest.WriteTestFiles(tmpDir)) 769 770 testFS := DirFS(tmpDir) 771 testReadlink(t, testFS, testFS) 772 }