github.com/tetratelabs/wazero@v1.2.1/internal/sysfs/dirfs_test.go (about) 1 package sysfs 2 3 import ( 4 "errors" 5 "io/fs" 6 "os" 7 "path" 8 "runtime" 9 "syscall" 10 "testing" 11 "time" 12 13 "github.com/tetratelabs/wazero/internal/fsapi" 14 "github.com/tetratelabs/wazero/internal/fstest" 15 "github.com/tetratelabs/wazero/internal/platform" 16 "github.com/tetratelabs/wazero/internal/testing/require" 17 ) 18 19 func TestNewDirFS(t *testing.T) { 20 testFS := NewDirFS(".") 21 22 // Guest can look up / 23 f, errno := testFS.OpenFile("/", os.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 := NewDirFS("a") 29 _, errno = testFS.OpenFile(".", os.O_RDONLY, 0) 30 require.EqualErrno(t, syscall.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 := NewDirFS(arg0) 36 d, errno := testFS.OpenFile(".", os.O_RDONLY, 0) 37 require.EqualErrno(t, 0, errno) 38 _, errno = d.Readdir(-1) 39 require.EqualErrno(t, syscall.ENOTDIR, errno) 40 }) 41 } 42 43 func TestDirFS_join(t *testing.T) { 44 testFS := NewDirFS("/").(*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 = NewDirFS(".").(*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 := NewDirFS(".") 59 60 // String has the name of the path entered 61 require.Equal(t, ".", testFS.String()) 62 } 63 64 func TestDirFS_Lstat(t *testing.T) { 65 tmpDir := t.TempDir() 66 require.NoError(t, fstest.WriteTestFiles(tmpDir)) 67 68 testFS := NewDirFS(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 := NewDirFS(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, syscall.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, syscall.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, syscall.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 fsapi.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 fsapi.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 := NewDirFS(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, syscall.ENOENT, err) 164 }) 165 t.Run("file to non-exist", func(t *testing.T) { 166 tmpDir := t.TempDir() 167 testFS := NewDirFS(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, syscall.ENOENT, platform.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 := NewDirFS(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, syscall.ENOENT, platform.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 := NewDirFS(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, syscall.ENOTDIR, errno) 227 }) 228 t.Run("file to dir", func(t *testing.T) { 229 tmpDir := t.TempDir() 230 testFS := NewDirFS(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, syscall.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 := NewDirFS(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, syscall.ENOENT, platform.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 := NewDirFS(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, syscall.ENOTEMPTY, errno) 305 }) 306 307 t.Run("file to file", func(t *testing.T) { 308 tmpDir := t.TempDir() 309 testFS := NewDirFS(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, syscall.ENOENT, platform.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 := NewDirFS(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 := NewDirFS(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 := NewDirFS(tmpDir) 373 374 name := "rmdir" 375 376 err := testFS.Rmdir(name) 377 require.EqualErrno(t, syscall.ENOENT, err) 378 }) 379 380 t.Run("dir not empty", func(t *testing.T) { 381 tmpDir := t.TempDir() 382 testFS := NewDirFS(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, syscall.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 := NewDirFS(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 := NewDirFS(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 := NewDirFS(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, fsapi.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 := NewDirFS(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, syscall.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 := NewDirFS(tmpDir) 464 name := "unlink" 465 466 err := testFS.Unlink(name) 467 require.EqualErrno(t, syscall.ENOENT, err) 468 }) 469 470 t.Run("target: dir", func(t *testing.T) { 471 tmpDir := t.TempDir() 472 testFS := NewDirFS(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, syscall.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 := NewDirFS(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 := NewDirFS(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 := NewDirFS(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", nil, true) 533 require.EqualErrno(t, syscall.ENOENT, err) 534 err = testFS.Utimens("nope", nil, false) 535 if SupportsSymlinkNoFollow { 536 require.EqualErrno(t, syscall.ENOENT, err) 537 } else { 538 require.EqualErrno(t, syscall.ENOSYS, err) 539 } 540 }) 541 542 // Note: This sets microsecond granularity because Windows doesn't support 543 // nanosecond. 544 // 545 // Negative isn't tested as most platforms don't return consistent results. 546 tests := []struct { 547 name string 548 times *[2]syscall.Timespec 549 }{ 550 { 551 name: "nil", 552 }, 553 { 554 name: "a=omit,m=omit", 555 times: &[2]syscall.Timespec{ 556 {Sec: 123, Nsec: UTIME_OMIT}, 557 {Sec: 123, Nsec: UTIME_OMIT}, 558 }, 559 }, 560 { 561 name: "a=now,m=omit", 562 times: &[2]syscall.Timespec{ 563 {Sec: 123, Nsec: UTIME_NOW}, 564 {Sec: 123, Nsec: UTIME_OMIT}, 565 }, 566 }, 567 { 568 name: "a=omit,m=now", 569 times: &[2]syscall.Timespec{ 570 {Sec: 123, Nsec: UTIME_OMIT}, 571 {Sec: 123, Nsec: UTIME_NOW}, 572 }, 573 }, 574 { 575 name: "a=now,m=now", 576 times: &[2]syscall.Timespec{ 577 {Sec: 123, Nsec: UTIME_NOW}, 578 {Sec: 123, Nsec: UTIME_NOW}, 579 }, 580 }, 581 { 582 name: "a=now,m=set", 583 times: &[2]syscall.Timespec{ 584 {Sec: 123, Nsec: UTIME_NOW}, 585 {Sec: 123, Nsec: 4 * 1e3}, 586 }, 587 }, 588 { 589 name: "a=set,m=now", 590 times: &[2]syscall.Timespec{ 591 {Sec: 123, Nsec: 4 * 1e3}, 592 {Sec: 123, Nsec: UTIME_NOW}, 593 }, 594 }, 595 { 596 name: "a=set,m=set", 597 times: &[2]syscall.Timespec{ 598 {Sec: 123, Nsec: 4 * 1e3}, 599 {Sec: 223, Nsec: 5 * 1e3}, 600 }, 601 }, 602 } 603 604 for _, fileType := range []string{"dir", "file", "link", "link-follow"} { 605 for _, tt := range tests { 606 tc := tt 607 fileType := fileType 608 name := fileType + " " + tc.name 609 symlinkNoFollow := fileType == "link" 610 611 t.Run(name, func(t *testing.T) { 612 tmpDir := t.TempDir() 613 testFS := NewDirFS(tmpDir) 614 615 file := path.Join(tmpDir, "file") 616 errno := os.WriteFile(file, []byte{}, 0o700) 617 require.NoError(t, errno) 618 619 link := file + "-link" 620 require.NoError(t, os.Symlink(file, link)) 621 622 dir := path.Join(tmpDir, "dir") 623 errno = os.Mkdir(dir, 0o700) 624 require.NoError(t, errno) 625 626 var path, statPath string 627 switch fileType { 628 case "dir": 629 path = "dir" 630 statPath = "dir" 631 case "file": 632 path = "file" 633 statPath = "file" 634 case "link": 635 path = "file-link" 636 statPath = "file-link" 637 case "link-follow": 638 path = "file-link" 639 statPath = "file" 640 default: 641 panic(tc) 642 } 643 644 oldSt, errno := testFS.Lstat(statPath) 645 require.EqualErrno(t, 0, errno) 646 647 errno = testFS.Utimens(path, tc.times, !symlinkNoFollow) 648 if symlinkNoFollow && !SupportsSymlinkNoFollow { 649 require.EqualErrno(t, syscall.ENOSYS, errno) 650 return 651 } 652 require.EqualErrno(t, 0, errno) 653 654 newSt, errno := testFS.Lstat(statPath) 655 require.EqualErrno(t, 0, errno) 656 657 if platform.CompilerSupported() { 658 if tc.times != nil && tc.times[0].Nsec == UTIME_OMIT { 659 require.Equal(t, oldSt.Atim, newSt.Atim) 660 } else if tc.times == nil || tc.times[0].Nsec == UTIME_NOW { 661 now := time.Now().UnixNano() 662 require.True(t, newSt.Atim <= now, "expected atim %d <= now %d", newSt.Atim, now) 663 } else { 664 require.Equal(t, tc.times[0].Nano(), newSt.Atim) 665 } 666 } 667 668 // When compiler isn't supported, we can still check mtim. 669 if tc.times != nil && tc.times[1].Nsec == UTIME_OMIT { 670 require.Equal(t, oldSt.Mtim, newSt.Mtim) 671 } else if tc.times == nil || tc.times[1].Nsec == UTIME_NOW { 672 now := time.Now().UnixNano() 673 require.True(t, newSt.Mtim <= now, "expected mtim %d <= now %d", newSt.Mtim, now) 674 } else { 675 require.Equal(t, tc.times[1].Nano(), newSt.Mtim) 676 } 677 }) 678 } 679 } 680 } 681 682 func TestDirFS_OpenFile(t *testing.T) { 683 tmpDir := t.TempDir() 684 685 // Create a subdirectory, so we can test reads outside the fsapi.FS root. 686 tmpDir = path.Join(tmpDir, t.Name()) 687 require.NoError(t, os.Mkdir(tmpDir, 0o700)) 688 require.NoError(t, fstest.WriteTestFiles(tmpDir)) 689 690 testFS := NewDirFS(tmpDir) 691 692 testOpen_Read(t, testFS, true) 693 694 testOpen_O_RDWR(t, tmpDir, testFS) 695 696 t.Run("path outside root valid", func(t *testing.T) { 697 _, err := testFS.OpenFile("../foo", os.O_RDONLY, 0) 698 699 // fsapi.FS allows relative path lookups 700 require.True(t, errors.Is(err, fs.ErrNotExist)) 701 }) 702 } 703 704 func TestDirFS_Stat(t *testing.T) { 705 tmpDir := t.TempDir() 706 require.NoError(t, fstest.WriteTestFiles(tmpDir)) 707 708 testFS := NewDirFS(tmpDir) 709 testStat(t, testFS) 710 711 // from os.TestDirFSPathsValid 712 if runtime.GOOS != "windows" { 713 t.Run("strange name", func(t *testing.T) { 714 name := `e:xperi\ment.txt` 715 require.NoError(t, os.WriteFile(path.Join(tmpDir, name), nil, 0o600)) 716 717 _, errno := testFS.Stat(name) 718 require.EqualErrno(t, 0, errno) 719 }) 720 } 721 } 722 723 func TestDirFS_Truncate(t *testing.T) { 724 content := []byte("123456") 725 726 tests := []struct { 727 name string 728 size int64 729 expectedContent []byte 730 expectedErr error 731 }{ 732 { 733 name: "one less", 734 size: 5, 735 expectedContent: []byte("12345"), 736 }, 737 { 738 name: "same", 739 size: 6, 740 expectedContent: content, 741 }, 742 { 743 name: "zero", 744 size: 0, 745 expectedContent: []byte(""), 746 }, 747 { 748 name: "larger", 749 size: 106, 750 expectedContent: append(content, make([]byte, 100)...), 751 }, 752 } 753 754 for _, tt := range tests { 755 tc := tt 756 t.Run(tc.name, func(t *testing.T) { 757 tmpDir := t.TempDir() 758 testFS := NewDirFS(tmpDir) 759 760 name := "truncate" 761 realPath := path.Join(tmpDir, name) 762 require.NoError(t, os.WriteFile(realPath, content, 0o0666)) 763 764 errno := testFS.Truncate(name, tc.size) 765 require.EqualErrno(t, 0, errno) 766 767 actual, err := os.ReadFile(realPath) 768 require.NoError(t, err) 769 require.Equal(t, tc.expectedContent, actual) 770 }) 771 } 772 773 tmpDir := t.TempDir() 774 testFS := NewDirFS(tmpDir) 775 776 name := "truncate" 777 realPath := path.Join(tmpDir, name) 778 779 if runtime.GOOS != "windows" { 780 // TODO: os.Truncate on windows can create the file even when it 781 // doesn't exist. 782 t.Run("doesn't exist", func(t *testing.T) { 783 err := testFS.Truncate(name, 0) 784 require.Equal(t, syscall.ENOENT, err) 785 }) 786 } 787 788 t.Run("not file", func(t *testing.T) { 789 require.NoError(t, os.Mkdir(realPath, 0o700)) 790 791 err := testFS.Truncate(name, 0) 792 require.Equal(t, syscall.EISDIR, err) 793 794 require.NoError(t, os.Remove(realPath)) 795 }) 796 797 require.NoError(t, os.WriteFile(realPath, []byte{}, 0o600)) 798 799 t.Run("negative", func(t *testing.T) { 800 err := testFS.Truncate(name, -1) 801 require.Equal(t, syscall.EINVAL, err) 802 }) 803 } 804 805 // Test_fdReaddir_opened_file_written ensures that writing files to the already-opened directory 806 // is visible. This is significant on Windows. 807 // https://github.com/ziglang/zig/blob/2ccff5115454bab4898bae3de88f5619310bc5c1/lib/std/fs/test.zig#L156-L184 808 func Test_fdReaddir_opened_file_written(t *testing.T) { 809 root := t.TempDir() 810 testFS := NewDirFS(root) 811 812 const readDirTarget = "dir" 813 errno := testFS.Mkdir(readDirTarget, 0o700) 814 require.EqualErrno(t, 0, errno) 815 816 // Open the directory, before writing files! 817 dirFile, errno := testFS.OpenFile(readDirTarget, os.O_RDONLY, 0) 818 require.EqualErrno(t, 0, errno) 819 defer dirFile.Close() 820 821 // Then write a file to the directory. 822 f, err := os.Create(path.Join(root, readDirTarget, "my-file")) 823 require.NoError(t, err) 824 defer f.Close() 825 826 dirents, errno := dirFile.Readdir(-1) 827 require.EqualErrno(t, 0, errno) 828 829 require.Equal(t, 1, len(dirents)) 830 require.Equal(t, "my-file", dirents[0].Name) 831 } 832 833 func TestDirFS_Link(t *testing.T) { 834 t.Parallel() 835 836 // Set up the test files 837 tmpDir := t.TempDir() 838 require.NoError(t, fstest.WriteTestFiles(tmpDir)) 839 840 testFS := NewDirFS(tmpDir) 841 842 require.EqualErrno(t, testFS.Link("cat", ""), syscall.ENOENT) 843 require.EqualErrno(t, testFS.Link("sub/test.txt", "sub/test.txt"), syscall.EEXIST) 844 require.EqualErrno(t, testFS.Link("sub/test.txt", "."), syscall.EEXIST) 845 require.EqualErrno(t, testFS.Link("sub/test.txt", ""), syscall.EEXIST) 846 require.EqualErrno(t, testFS.Link("sub/test.txt", "/"), syscall.EEXIST) 847 require.EqualErrno(t, 0, testFS.Link("sub/test.txt", "foo")) 848 } 849 850 func TestDirFS_Symlink(t *testing.T) { 851 t.Parallel() 852 853 // Set up the test files 854 tmpDir := t.TempDir() 855 require.NoError(t, fstest.WriteTestFiles(tmpDir)) 856 857 testFS := NewDirFS(tmpDir) 858 859 require.EqualErrno(t, syscall.EEXIST, testFS.Symlink("sub/test.txt", "sub/test.txt")) 860 // Non-existing old name is allowed. 861 require.EqualErrno(t, 0, testFS.Symlink("non-existing", "aa")) 862 require.EqualErrno(t, 0, testFS.Symlink("sub/", "symlinked-subdir")) 863 864 st, err := os.Lstat(path.Join(tmpDir, "aa")) 865 require.NoError(t, err) 866 require.Equal(t, "aa", st.Name()) 867 require.True(t, st.Mode()&fs.ModeSymlink > 0 && !st.IsDir()) 868 869 st, err = os.Lstat(path.Join(tmpDir, "symlinked-subdir")) 870 require.NoError(t, err) 871 require.Equal(t, "symlinked-subdir", st.Name()) 872 require.True(t, st.Mode()&fs.ModeSymlink > 0) 873 } 874 875 func TestDirFS_Readlink(t *testing.T) { 876 tmpDir := t.TempDir() 877 require.NoError(t, fstest.WriteTestFiles(tmpDir)) 878 879 testFS := NewDirFS(tmpDir) 880 testReadlink(t, testFS, testFS) 881 }