github.com/tetratelabs/wazero@v1.2.1/internal/sysfs/file_test.go (about) 1 package sysfs 2 3 import ( 4 "embed" 5 "io" 6 "io/fs" 7 "os" 8 "path" 9 "runtime" 10 "strings" 11 "syscall" 12 "testing" 13 gofstest "testing/fstest" 14 "time" 15 16 "github.com/tetratelabs/wazero/internal/fsapi" 17 "github.com/tetratelabs/wazero/internal/platform" 18 "github.com/tetratelabs/wazero/internal/testing/require" 19 ) 20 21 //go:embed file_test.go 22 var embedFS embed.FS 23 24 var ( 25 //go:embed testdata 26 testdata embed.FS 27 wazeroFile = "wazero.txt" 28 emptyFile = "empty.txt" 29 ) 30 31 func TestStdioFileSetNonblock(t *testing.T) { 32 // Test using os.Pipe as it is known to support non-blocking reads. 33 r, w, err := os.Pipe() 34 require.NoError(t, err) 35 defer r.Close() 36 defer w.Close() 37 38 rF, err := NewStdioFile(true, r) 39 require.NoError(t, err) 40 41 errno := rF.SetNonblock(true) 42 require.EqualErrno(t, 0, errno) 43 require.True(t, rF.IsNonblock()) 44 45 errno = rF.SetNonblock(false) 46 require.EqualErrno(t, 0, errno) 47 require.False(t, rF.IsNonblock()) 48 } 49 50 func TestRegularFileSetNonblock(t *testing.T) { 51 if runtime.GOOS == "windows" { 52 t.Skip("Nonblock on regular files is not supported on Windows") 53 } 54 55 // Test using os.Pipe as it is known to support non-blocking reads. 56 r, w, err := os.Pipe() 57 require.NoError(t, err) 58 defer r.Close() 59 defer w.Close() 60 61 rF := newOsFile("", syscall.O_RDONLY, 0, r) 62 63 errno := rF.SetNonblock(true) 64 require.EqualErrno(t, 0, errno) 65 require.True(t, rF.IsNonblock()) 66 67 // Read from the file without ever writing to it should not block. 68 buf := make([]byte, 8) 69 _, e := rF.Read(buf) 70 require.EqualErrno(t, syscall.EAGAIN, e) 71 72 errno = rF.SetNonblock(false) 73 require.EqualErrno(t, 0, errno) 74 require.False(t, rF.IsNonblock()) 75 } 76 77 func TestReadFdNonblock(t *testing.T) { 78 // Test using os.Pipe as it is known to support non-blocking reads. 79 r, w, err := os.Pipe() 80 require.NoError(t, err) 81 defer r.Close() 82 defer w.Close() 83 84 fd := r.Fd() 85 err = setNonblock(fd, true) 86 require.NoError(t, err) 87 88 // Read from the file without ever writing to it should not block. 89 buf := make([]byte, 8) 90 _, e := readFd(fd, buf) 91 if runtime.GOOS == "windows" { 92 require.EqualErrno(t, syscall.ENOSYS, e) 93 } else { 94 require.EqualErrno(t, syscall.EAGAIN, e) 95 } 96 } 97 98 func TestFileSetAppend(t *testing.T) { 99 tmpDir := t.TempDir() 100 101 fPath := path.Join(tmpDir, "file") 102 require.NoError(t, os.WriteFile(fPath, []byte("0123456789"), 0o600)) 103 104 // Open without APPEND. 105 f, errno := OpenOSFile(fPath, os.O_RDWR, 0o600) 106 require.EqualErrno(t, 0, errno) 107 require.False(t, f.IsAppend()) 108 109 // Set the APPEND flag. 110 require.EqualErrno(t, 0, f.SetAppend(true)) 111 require.True(t, f.IsAppend()) 112 113 requireFileContent := func(exp string) { 114 buf, err := os.ReadFile(fPath) 115 require.NoError(t, err) 116 require.Equal(t, exp, string(buf)) 117 } 118 119 // with O_APPEND flag, the data is appended to buffer. 120 _, errno = f.Write([]byte("wazero")) 121 require.EqualErrno(t, 0, errno) 122 requireFileContent("0123456789wazero") 123 124 // Remove the APPEND flag. 125 require.EqualErrno(t, 0, f.SetAppend(false)) 126 require.False(t, f.IsAppend()) 127 128 // without O_APPEND flag, the data writes at offset zero 129 _, errno = f.Write([]byte("wazero")) 130 require.EqualErrno(t, 0, errno) 131 requireFileContent("wazero6789wazero") 132 } 133 134 func TestFileIno(t *testing.T) { 135 tmpDir := t.TempDir() 136 dirFS, embedFS, mapFS := dirEmbedMapFS(t, tmpDir) 137 138 // get the expected inode 139 st, errno := stat(tmpDir) 140 require.EqualErrno(t, 0, errno) 141 142 tests := []struct { 143 name string 144 fs fs.FS 145 expectedIno uint64 146 }{ 147 {name: "os.DirFS", fs: dirFS, expectedIno: st.Ino}, 148 {name: "embed.api.FS", fs: embedFS}, 149 {name: "fstest.MapFS", fs: mapFS}, 150 } 151 152 for _, tc := range tests { 153 tc := tc 154 155 t.Run(tc.name, func(t *testing.T) { 156 d, errno := OpenFSFile(tc.fs, ".", syscall.O_RDONLY, 0) 157 require.EqualErrno(t, 0, errno) 158 defer d.Close() 159 160 ino, errno := d.Ino() 161 require.EqualErrno(t, 0, errno) 162 if !canReadDirInode() { 163 tc.expectedIno = 0 164 } 165 require.Equal(t, tc.expectedIno, ino) 166 }) 167 } 168 169 t.Run("OS", func(t *testing.T) { 170 d, errno := OpenOSFile(tmpDir, syscall.O_RDONLY, 0) 171 require.EqualErrno(t, 0, errno) 172 defer d.Close() 173 174 ino, errno := d.Ino() 175 require.EqualErrno(t, 0, errno) 176 if canReadDirInode() { 177 require.Equal(t, st.Ino, ino) 178 } else { 179 require.Zero(t, ino) 180 } 181 }) 182 } 183 184 func canReadDirInode() bool { 185 if runtime.GOOS != "windows" { 186 return true 187 } else { 188 return strings.HasPrefix(runtime.Version(), "go1.20") 189 } 190 } 191 192 func TestFileIsDir(t *testing.T) { 193 dirFS, embedFS, mapFS := dirEmbedMapFS(t, t.TempDir()) 194 195 tests := []struct { 196 name string 197 fs fs.FS 198 }{ 199 {name: "os.DirFS", fs: dirFS}, 200 {name: "embed.api.FS", fs: embedFS}, 201 {name: "fstest.MapFS", fs: mapFS}, 202 } 203 204 for _, tc := range tests { 205 tc := tc 206 207 t.Run(tc.name, func(t *testing.T) { 208 t.Run("file", func(t *testing.T) { 209 f, errno := OpenFSFile(tc.fs, wazeroFile, syscall.O_RDONLY, 0) 210 require.EqualErrno(t, 0, errno) 211 defer f.Close() 212 213 isDir, errno := f.IsDir() 214 require.EqualErrno(t, 0, errno) 215 require.False(t, isDir) 216 }) 217 218 t.Run("dir", func(t *testing.T) { 219 d, errno := OpenFSFile(tc.fs, ".", syscall.O_RDONLY, 0) 220 require.EqualErrno(t, 0, errno) 221 defer d.Close() 222 223 isDir, errno := d.IsDir() 224 require.EqualErrno(t, 0, errno) 225 require.True(t, isDir) 226 }) 227 }) 228 } 229 230 t.Run("OS dir", func(t *testing.T) { 231 d, errno := OpenOSFile(t.TempDir(), syscall.O_RDONLY, 0) 232 require.EqualErrno(t, 0, errno) 233 defer d.Close() 234 235 isDir, errno := d.IsDir() 236 require.EqualErrno(t, 0, errno) 237 require.True(t, isDir) 238 }) 239 } 240 241 func TestFileReadAndPread(t *testing.T) { 242 dirFS, embedFS, mapFS := dirEmbedMapFS(t, t.TempDir()) 243 244 tests := []struct { 245 name string 246 fs fs.FS 247 }{ 248 {name: "os.DirFS", fs: dirFS}, 249 {name: "embed.api.FS", fs: embedFS}, 250 {name: "fstest.MapFS", fs: mapFS}, 251 } 252 253 buf := make([]byte, 3) 254 255 for _, tc := range tests { 256 tc := tc 257 258 t.Run(tc.name, func(t *testing.T) { 259 f, errno := OpenFSFile(tc.fs, wazeroFile, syscall.O_RDONLY, 0) 260 require.EqualErrno(t, 0, errno) 261 defer f.Close() 262 263 // The file should be readable (base case) 264 requireRead(t, f, buf) 265 require.Equal(t, "waz", string(buf)) 266 buf = buf[:] 267 268 // We should be able to pread from zero also 269 requirePread(t, f, buf, 0) 270 require.Equal(t, "waz", string(buf)) 271 buf = buf[:] 272 273 // If the offset didn't change, read should expect the next three chars. 274 requireRead(t, f, buf) 275 require.Equal(t, "ero", string(buf)) 276 buf = buf[:] 277 278 // We should also be able pread from any offset 279 requirePread(t, f, buf, 2) 280 require.Equal(t, "zer", string(buf)) 281 }) 282 } 283 } 284 285 func TestFilePollRead(t *testing.T) { 286 // Test using os.Pipe as it is known to support poll. 287 r, w, err := os.Pipe() 288 require.NoError(t, err) 289 defer r.Close() 290 defer w.Close() 291 292 rF, err := NewStdioFile(true, r) 293 require.NoError(t, err) 294 buf := make([]byte, 10) 295 timeout := time.Duration(0) // return immediately 296 297 // When there's nothing in the pipe, it isn't ready. 298 ready, errno := rF.PollRead(&timeout) 299 if runtime.GOOS == "windows" { 300 require.EqualErrno(t, syscall.ENOSYS, errno) 301 t.Skip("TODO: windows File.PollRead") 302 } 303 require.EqualErrno(t, 0, errno) 304 require.False(t, ready) 305 306 // Write to the pipe to make the data available 307 expected := []byte("wazero") 308 _, err = w.Write([]byte("wazero")) 309 require.NoError(t, err) 310 311 // We should now be able to poll ready 312 ready, errno = rF.PollRead(&timeout) 313 require.EqualErrno(t, 0, errno) 314 require.True(t, ready) 315 316 // We should now be able to read from the pipe 317 n, errno := rF.Read(buf) 318 require.EqualErrno(t, 0, errno) 319 require.Equal(t, len(expected), n) 320 require.Equal(t, expected, buf[:len(expected)]) 321 } 322 323 func requireRead(t *testing.T, f fsapi.File, buf []byte) { 324 n, errno := f.Read(buf) 325 require.EqualErrno(t, 0, errno) 326 require.Equal(t, len(buf), n) 327 } 328 329 func requirePread(t *testing.T, f fsapi.File, buf []byte, off int64) { 330 n, errno := f.Pread(buf, off) 331 require.EqualErrno(t, 0, errno) 332 require.Equal(t, len(buf), n) 333 } 334 335 func TestFileRead_empty(t *testing.T) { 336 dirFS, embedFS, mapFS := dirEmbedMapFS(t, t.TempDir()) 337 338 tests := []struct { 339 name string 340 fs fs.FS 341 }{ 342 {name: "os.DirFS", fs: dirFS}, 343 {name: "embed.api.FS", fs: embedFS}, 344 {name: "fstest.MapFS", fs: mapFS}, 345 } 346 347 buf := make([]byte, 3) 348 349 for _, tc := range tests { 350 tc := tc 351 352 t.Run(tc.name, func(t *testing.T) { 353 f, errno := OpenFSFile(tc.fs, emptyFile, syscall.O_RDONLY, 0) 354 require.EqualErrno(t, 0, errno) 355 defer f.Close() 356 357 t.Run("Read", func(t *testing.T) { 358 // We should be able to read an empty file 359 n, errno := f.Read(buf) 360 require.EqualErrno(t, 0, errno) 361 require.Zero(t, n) 362 }) 363 364 t.Run("Pread", func(t *testing.T) { 365 n, errno := f.Pread(buf, 0) 366 require.EqualErrno(t, 0, errno) 367 require.Zero(t, n) 368 }) 369 }) 370 } 371 } 372 373 type maskFS struct { 374 fs.FS 375 } 376 377 func (m *maskFS) Open(name string) (fs.File, error) { 378 f, err := m.FS.Open(name) 379 return struct{ fs.File }{f}, err 380 } 381 382 func TestFilePread_Unsupported(t *testing.T) { 383 embedFS, err := fs.Sub(testdata, "testdata") 384 require.NoError(t, err) 385 386 f, errno := OpenFSFile(&maskFS{embedFS}, emptyFile, syscall.O_RDONLY, 0) 387 require.EqualErrno(t, 0, errno) 388 defer f.Close() 389 390 buf := make([]byte, 3) 391 _, errno = f.Pread(buf, 0) 392 require.EqualErrno(t, syscall.ENOSYS, errno) 393 } 394 395 func TestFileRead_Errors(t *testing.T) { 396 // Create the file 397 path := path.Join(t.TempDir(), emptyFile) 398 399 // Open the file write-only 400 flag := syscall.O_WRONLY | syscall.O_CREAT 401 f := requireOpenFile(t, path, flag, 0o600) 402 defer f.Close() 403 buf := make([]byte, 5) 404 405 tests := []struct { 406 name string 407 fn func(fsapi.File) syscall.Errno 408 }{ 409 {name: "Read", fn: func(f fsapi.File) syscall.Errno { 410 _, errno := f.Read(buf) 411 return errno 412 }}, 413 {name: "Pread", fn: func(f fsapi.File) syscall.Errno { 414 _, errno := f.Pread(buf, 0) 415 return errno 416 }}, 417 } 418 419 for _, tc := range tests { 420 tc := tc 421 422 t.Run(tc.name, func(t *testing.T) { 423 t.Run("EBADF when not open for reading", func(t *testing.T) { 424 // The descriptor exists, but not open for reading 425 errno := tc.fn(f) 426 require.EqualErrno(t, syscall.EBADF, errno) 427 }) 428 testEISDIR(t, tc.fn) 429 }) 430 } 431 } 432 433 func TestFileSeek(t *testing.T) { 434 dirFS, embedFS, mapFS := dirEmbedMapFS(t, t.TempDir()) 435 436 tests := []struct { 437 name string 438 fs fs.FS 439 }{ 440 {name: "os.DirFS", fs: dirFS}, 441 {name: "embed.api.FS", fs: embedFS}, 442 {name: "fstest.MapFS", fs: mapFS}, 443 } 444 445 buf := make([]byte, 3) 446 447 for _, tc := range tests { 448 tc := tc 449 450 t.Run(tc.name, func(t *testing.T) { 451 f, errno := OpenFSFile(tc.fs, wazeroFile, syscall.O_RDONLY, 0) 452 require.EqualErrno(t, 0, errno) 453 defer f.Close() 454 455 // Shouldn't be able to use an invalid whence 456 _, errno = f.Seek(0, io.SeekEnd+1) 457 require.EqualErrno(t, syscall.EINVAL, errno) 458 _, errno = f.Seek(0, -1) 459 require.EqualErrno(t, syscall.EINVAL, errno) 460 461 // Shouldn't be able to seek before the file starts. 462 _, errno = f.Seek(-1, io.SeekStart) 463 require.EqualErrno(t, syscall.EINVAL, errno) 464 465 requireRead(t, f, buf) // read 3 bytes 466 467 // Seek to the start 468 newOffset, errno := f.Seek(0, io.SeekStart) 469 require.EqualErrno(t, 0, errno) 470 471 // verify we can re-read from the beginning now. 472 require.Zero(t, newOffset) 473 requireRead(t, f, buf) // read 3 bytes again 474 require.Equal(t, "waz", string(buf)) 475 buf = buf[:] 476 477 // Seek to the start with zero allows you to read it back. 478 newOffset, errno = f.Seek(0, io.SeekCurrent) 479 require.EqualErrno(t, 0, errno) 480 require.Equal(t, int64(3), newOffset) 481 482 // Seek to the last two bytes 483 newOffset, errno = f.Seek(-2, io.SeekEnd) 484 require.EqualErrno(t, 0, errno) 485 486 // verify we can read the last two bytes 487 require.Equal(t, int64(5), newOffset) 488 n, errno := f.Read(buf) 489 require.EqualErrno(t, 0, errno) 490 require.Equal(t, 2, n) 491 require.Equal(t, "o\n", string(buf[:2])) 492 493 t.Run("directory seek to zero", func(t *testing.T) { 494 d, errno := OpenFSFile(tc.fs, ".", syscall.O_RDONLY, 0) 495 require.EqualErrno(t, 0, errno) 496 defer d.Close() 497 498 _, errno = d.Seek(0, io.SeekStart) 499 require.EqualErrno(t, 0, errno) 500 }) 501 }) 502 } 503 504 t.Run("os.File directory seek to zero", func(t *testing.T) { 505 d := requireOpenFile(t, os.TempDir(), syscall.O_RDONLY|fsapi.O_DIRECTORY, 0o666) 506 defer d.Close() 507 508 _, errno := d.Seek(0, io.SeekStart) 509 require.EqualErrno(t, 0, errno) 510 }) 511 512 seekToZero := func(f fsapi.File) syscall.Errno { 513 _, errno := f.Seek(0, io.SeekStart) 514 return errno 515 } 516 testEBADFIfFileClosed(t, seekToZero) 517 } 518 519 func requireSeek(t *testing.T, f fsapi.File, off int64, whence int) int64 { 520 n, errno := f.Seek(off, whence) 521 require.EqualErrno(t, 0, errno) 522 return n 523 } 524 525 func TestFileSeek_empty(t *testing.T) { 526 dirFS, embedFS, mapFS := dirEmbedMapFS(t, t.TempDir()) 527 528 tests := []struct { 529 name string 530 fs fs.FS 531 }{ 532 {name: "os.DirFS", fs: dirFS}, 533 {name: "embed.api.FS", fs: embedFS}, 534 {name: "fstest.MapFS", fs: mapFS}, 535 } 536 537 for _, tc := range tests { 538 tc := tc 539 540 t.Run(tc.name, func(t *testing.T) { 541 f, errno := OpenFSFile(tc.fs, emptyFile, syscall.O_RDONLY, 0) 542 require.EqualErrno(t, 0, errno) 543 defer f.Close() 544 545 t.Run("Start", func(t *testing.T) { 546 require.Zero(t, requireSeek(t, f, 0, io.SeekStart)) 547 }) 548 549 t.Run("Current", func(t *testing.T) { 550 require.Zero(t, requireSeek(t, f, 0, io.SeekCurrent)) 551 }) 552 553 t.Run("End", func(t *testing.T) { 554 require.Zero(t, requireSeek(t, f, 0, io.SeekEnd)) 555 }) 556 }) 557 } 558 } 559 560 func TestFileSeek_Unsupported(t *testing.T) { 561 embedFS, err := fs.Sub(testdata, "testdata") 562 require.NoError(t, err) 563 564 f, errno := OpenFSFile(&maskFS{embedFS}, emptyFile, syscall.O_RDONLY, 0) 565 require.EqualErrno(t, 0, errno) 566 defer f.Close() 567 568 _, errno = f.Seek(0, io.SeekCurrent) 569 require.EqualErrno(t, syscall.ENOSYS, errno) 570 } 571 572 func TestFileWriteAndPwrite(t *testing.T) { 573 // fsapi.FS doesn't support writes, and there is no other built-in 574 // implementation except os.File. 575 path := path.Join(t.TempDir(), wazeroFile) 576 f := requireOpenFile(t, path, syscall.O_RDWR|os.O_CREATE, 0o600) 577 defer f.Close() 578 579 text := "wazero" 580 buf := make([]byte, 3) 581 copy(buf, text[:3]) 582 583 // The file should be writeable 584 requireWrite(t, f, buf) 585 586 // We should be able to pwrite at gap 587 requirePwrite(t, f, buf, 6) 588 589 copy(buf, text[3:]) 590 591 // If the offset didn't change, the next chars will write after the 592 // first 593 requireWrite(t, f, buf) 594 595 // We should be able to pwrite the same bytes as above 596 requirePwrite(t, f, buf, 9) 597 598 // We should also be able to pwrite past the above. 599 requirePwrite(t, f, buf, 12) 600 601 b, err := os.ReadFile(path) 602 require.NoError(t, err) 603 604 // We expect to have written the text two and a half times: 605 // 1. Write: (file offset 0) "waz" 606 // 2. Pwrite: offset 6 "waz" 607 // 3. Write: (file offset 3) "ero" 608 // 4. Pwrite: offset 9 "ero" 609 // 4. Pwrite: offset 12 "ero" 610 require.Equal(t, "wazerowazeroero", string(b)) 611 } 612 613 func requireWrite(t *testing.T, f fsapi.File, buf []byte) { 614 n, errno := f.Write(buf) 615 require.EqualErrno(t, 0, errno) 616 require.Equal(t, len(buf), n) 617 } 618 619 func requirePwrite(t *testing.T, f fsapi.File, buf []byte, off int64) { 620 n, errno := f.Pwrite(buf, off) 621 require.EqualErrno(t, 0, errno) 622 require.Equal(t, len(buf), n) 623 } 624 625 func TestFileWrite_empty(t *testing.T) { 626 // fsapi.FS doesn't support writes, and there is no other built-in 627 // implementation except os.File. 628 path := path.Join(t.TempDir(), emptyFile) 629 f := requireOpenFile(t, path, syscall.O_RDWR|os.O_CREATE, 0o600) 630 defer f.Close() 631 632 tests := []struct { 633 name string 634 fn func(fsapi.File, []byte) (int, syscall.Errno) 635 }{ 636 {name: "Write", fn: func(f fsapi.File, buf []byte) (int, syscall.Errno) { 637 return f.Write(buf) 638 }}, 639 {name: "Pwrite from zero", fn: func(f fsapi.File, buf []byte) (int, syscall.Errno) { 640 return f.Pwrite(buf, 0) 641 }}, 642 {name: "Pwrite from 3", fn: func(f fsapi.File, buf []byte) (int, syscall.Errno) { 643 return f.Pwrite(buf, 3) 644 }}, 645 } 646 647 var emptyBuf []byte 648 649 for _, tc := range tests { 650 tc := tc 651 652 t.Run(tc.name, func(t *testing.T) { 653 n, errno := tc.fn(f, emptyBuf) 654 require.EqualErrno(t, 0, errno) 655 require.Zero(t, n) 656 657 // The file should be empty 658 b, err := os.ReadFile(path) 659 require.NoError(t, err) 660 require.Zero(t, len(b)) 661 }) 662 } 663 } 664 665 func TestFileWrite_Unsupported(t *testing.T) { 666 embedFS, err := fs.Sub(testdata, "testdata") 667 require.NoError(t, err) 668 669 // Use syscall.O_RDWR so that it fails due to type not flags 670 f, errno := OpenFSFile(&maskFS{embedFS}, wazeroFile, syscall.O_RDWR, 0) 671 require.EqualErrno(t, 0, errno) 672 defer f.Close() 673 674 tests := []struct { 675 name string 676 fn func(fsapi.File, []byte) (int, syscall.Errno) 677 }{ 678 {name: "Write", fn: func(f fsapi.File, buf []byte) (int, syscall.Errno) { 679 return f.Write(buf) 680 }}, 681 {name: "Pwrite", fn: func(f fsapi.File, buf []byte) (int, syscall.Errno) { 682 return f.Pwrite(buf, 0) 683 }}, 684 } 685 686 buf := []byte("wazero") 687 688 for _, tc := range tests { 689 tc := tc 690 691 t.Run(tc.name, func(t *testing.T) { 692 _, errno := tc.fn(f, buf) 693 require.EqualErrno(t, syscall.ENOSYS, errno) 694 }) 695 } 696 } 697 698 func TestFileWrite_Errors(t *testing.T) { 699 // Create the file 700 path := path.Join(t.TempDir(), emptyFile) 701 of, err := os.Create(path) 702 require.NoError(t, err) 703 require.NoError(t, of.Close()) 704 705 // Open the file read-only 706 flag := syscall.O_RDONLY 707 f := requireOpenFile(t, path, flag, 0o600) 708 defer f.Close() 709 buf := []byte("wazero") 710 711 tests := []struct { 712 name string 713 fn func(fsapi.File) syscall.Errno 714 }{ 715 {name: "Write", fn: func(f fsapi.File) syscall.Errno { 716 _, errno := f.Write(buf) 717 return errno 718 }}, 719 {name: "Pwrite", fn: func(f fsapi.File) syscall.Errno { 720 _, errno := f.Pwrite(buf, 0) 721 return errno 722 }}, 723 } 724 725 for _, tc := range tests { 726 tc := tc 727 728 t.Run(tc.name, func(t *testing.T) { 729 t.Run("EBADF when not open for writing", func(t *testing.T) { 730 // The descriptor exists, but not open for writing 731 errno := tc.fn(f) 732 require.EqualErrno(t, syscall.EBADF, errno) 733 }) 734 testEISDIR(t, tc.fn) 735 }) 736 } 737 } 738 739 func TestFileSync_NoError(t *testing.T) { 740 testSync_NoError(t, fsapi.File.Sync) 741 } 742 743 func TestFileDatasync_NoError(t *testing.T) { 744 testSync_NoError(t, fsapi.File.Datasync) 745 } 746 747 func testSync_NoError(t *testing.T, sync func(fsapi.File) syscall.Errno) { 748 roPath := "file_test.go" 749 ro, errno := OpenFSFile(embedFS, roPath, syscall.O_RDONLY, 0) 750 require.EqualErrno(t, 0, errno) 751 defer ro.Close() 752 753 rwPath := path.Join(t.TempDir(), "datasync") 754 rw, errno := OpenOSFile(rwPath, syscall.O_CREAT|syscall.O_RDWR, 0o600) 755 require.EqualErrno(t, 0, errno) 756 defer rw.Close() 757 758 tests := []struct { 759 name string 760 f fsapi.File 761 }{ 762 {name: "UnimplementedFile", f: fsapi.UnimplementedFile{}}, 763 {name: "File of read-only fs.File", f: ro}, 764 {name: "File of os.File", f: rw}, 765 } 766 767 for _, tt := range tests { 768 tc := tt 769 770 t.Run(tc.name, func(t *testing.T) { 771 require.EqualErrno(t, 0, sync(tc.f)) 772 }) 773 } 774 } 775 776 func TestFileSync(t *testing.T) { 777 testSync(t, fsapi.File.Sync) 778 } 779 780 func TestFileDatasync(t *testing.T) { 781 testSync(t, fsapi.File.Datasync) 782 } 783 784 // testSync doesn't guarantee sync works because the operating system may 785 // sync anyway. There is no test in Go for syscall.Fdatasync, but closest is 786 // similar to below. Effectively, this only tests that things don't error. 787 func testSync(t *testing.T, sync func(fsapi.File) syscall.Errno) { 788 // Even though it is invalid, try to sync a directory 789 dPath := t.TempDir() 790 d := requireOpenFile(t, dPath, syscall.O_RDONLY, 0) 791 defer d.Close() 792 793 errno := sync(d) 794 require.EqualErrno(t, 0, errno) 795 796 fPath := path.Join(dPath, t.Name()) 797 798 f := requireOpenFile(t, fPath, syscall.O_RDWR|os.O_CREATE, 0o600) 799 defer f.Close() 800 801 expected := "hello world!" 802 803 // Write the expected data 804 _, errno = f.Write([]byte(expected)) 805 require.EqualErrno(t, 0, errno) 806 807 // Sync the data. 808 errno = sync(f) 809 require.EqualErrno(t, 0, errno) 810 811 // Rewind while the file is still open. 812 _, errno = f.Seek(0, io.SeekStart) 813 require.EqualErrno(t, 0, errno) 814 815 // Read data from the file 816 buf := make([]byte, 50) 817 n, errno := f.Read(buf) 818 require.EqualErrno(t, 0, errno) 819 820 // It may be the case that sync worked. 821 require.Equal(t, expected, string(buf[:n])) 822 823 // Windows allows you to sync a closed file 824 if runtime.GOOS != "windows" { 825 testEBADFIfFileClosed(t, sync) 826 testEBADFIfDirClosed(t, sync) 827 } 828 } 829 830 func TestFileTruncate(t *testing.T) { 831 content := []byte("123456") 832 833 tests := []struct { 834 name string 835 size int64 836 expectedContent []byte 837 expectedErr error 838 }{ 839 { 840 name: "one less", 841 size: 5, 842 expectedContent: []byte("12345"), 843 }, 844 { 845 name: "same", 846 size: 6, 847 expectedContent: content, 848 }, 849 { 850 name: "zero", 851 size: 0, 852 expectedContent: []byte(""), 853 }, 854 { 855 name: "larger", 856 size: 106, 857 expectedContent: append(content, make([]byte, 100)...), 858 }, 859 } 860 861 for _, tt := range tests { 862 tc := tt 863 t.Run(tc.name, func(t *testing.T) { 864 tmpDir := t.TempDir() 865 866 fPath := path.Join(tmpDir, tc.name) 867 f := openForWrite(t, fPath, content) 868 defer f.Close() 869 870 errno := f.Truncate(tc.size) 871 require.EqualErrno(t, 0, errno) 872 873 actual, err := os.ReadFile(fPath) 874 require.NoError(t, err) 875 require.Equal(t, tc.expectedContent, actual) 876 }) 877 } 878 879 truncateToZero := func(f fsapi.File) syscall.Errno { 880 return f.Truncate(0) 881 } 882 883 if runtime.GOOS != "windows" { 884 // TODO: os.Truncate on windows passes even when closed 885 testEBADFIfFileClosed(t, truncateToZero) 886 } 887 888 testEISDIR(t, truncateToZero) 889 890 t.Run("negative", func(t *testing.T) { 891 tmpDir := t.TempDir() 892 893 f := openForWrite(t, path.Join(tmpDir, "truncate"), content) 894 defer f.Close() 895 896 errno := f.Truncate(-1) 897 require.EqualErrno(t, syscall.EINVAL, errno) 898 }) 899 } 900 901 func TestFileUtimens(t *testing.T) { 902 switch runtime.GOOS { 903 case "linux", "darwin": // supported 904 case "freebsd": // TODO: support freebsd w/o CGO 905 case "windows": 906 if !platform.IsGo120 { 907 t.Skip("windows only works after Go 1.20") // TODO: possibly 1.19 ;) 908 } 909 default: // expect ENOSYS and callers need to fall back to Utimens 910 t.Skip("unsupported GOOS", runtime.GOOS) 911 } 912 913 testUtimens(t, true) 914 915 testEBADFIfFileClosed(t, func(f fsapi.File) syscall.Errno { 916 return f.Utimens(nil) 917 }) 918 testEBADFIfDirClosed(t, func(d fsapi.File) syscall.Errno { 919 return d.Utimens(nil) 920 }) 921 } 922 923 func TestNewStdioFile(t *testing.T) { 924 // simulate regular file attached to stdin 925 f, err := os.CreateTemp(t.TempDir(), "somefile") 926 require.NoError(t, err) 927 defer f.Close() 928 929 stdin, err := NewStdioFile(true, os.Stdin) 930 require.NoError(t, err) 931 stdinStat, err := os.Stdin.Stat() 932 require.NoError(t, err) 933 934 stdinFile, err := NewStdioFile(true, f) 935 require.NoError(t, err) 936 937 stdout, err := NewStdioFile(false, os.Stdout) 938 require.NoError(t, err) 939 stdoutStat, err := os.Stdout.Stat() 940 require.NoError(t, err) 941 942 stdoutFile, err := NewStdioFile(false, f) 943 require.NoError(t, err) 944 945 tests := []struct { 946 name string 947 f fsapi.File 948 // Depending on how the tests run, os.Stdin won't necessarily be a char 949 // device. We compare against an os.File, to account for this. 950 expectedType fs.FileMode 951 }{ 952 { 953 name: "stdin", 954 f: stdin, 955 expectedType: stdinStat.Mode().Type(), 956 }, 957 { 958 name: "stdin file", 959 f: stdinFile, 960 expectedType: 0, // normal file 961 }, 962 { 963 name: "stdout", 964 f: stdout, 965 expectedType: stdoutStat.Mode().Type(), 966 }, 967 { 968 name: "stdout file", 969 f: stdoutFile, 970 expectedType: 0, // normal file 971 }, 972 } 973 974 for _, tt := range tests { 975 tc := tt 976 977 t.Run(tc.name+" Stat", func(t *testing.T) { 978 st, errno := tc.f.Stat() 979 require.EqualErrno(t, 0, errno) 980 require.Equal(t, tc.expectedType, st.Mode&fs.ModeType) 981 require.Equal(t, uint64(1), st.Nlink) 982 983 // Fake times are needed to pass wasi-testsuite. 984 // See https://github.com/WebAssembly/wasi-testsuite/blob/af57727/tests/rust/src/bin/fd_filestat_get.rs#L1-L19 985 require.Zero(t, st.Ctim) 986 require.Zero(t, st.Mtim) 987 require.Zero(t, st.Atim) 988 }) 989 } 990 } 991 992 func testEBADFIfDirClosed(t *testing.T, fn func(fsapi.File) syscall.Errno) bool { 993 return t.Run("EBADF if dir closed", func(t *testing.T) { 994 d := requireOpenFile(t, t.TempDir(), syscall.O_RDONLY, 0o755) 995 996 // close the directory underneath 997 require.EqualErrno(t, 0, d.Close()) 998 999 require.EqualErrno(t, syscall.EBADF, fn(d)) 1000 }) 1001 } 1002 1003 func testEBADFIfFileClosed(t *testing.T, fn func(fsapi.File) syscall.Errno) bool { 1004 return t.Run("EBADF if file closed", func(t *testing.T) { 1005 tmpDir := t.TempDir() 1006 1007 f := openForWrite(t, path.Join(tmpDir, "EBADF"), []byte{1, 2, 3, 4}) 1008 1009 // close the file underneath 1010 require.EqualErrno(t, 0, f.Close()) 1011 1012 require.EqualErrno(t, syscall.EBADF, fn(f)) 1013 }) 1014 } 1015 1016 func testEISDIR(t *testing.T, fn func(fsapi.File) syscall.Errno) bool { 1017 return t.Run("EISDIR if directory", func(t *testing.T) { 1018 f := requireOpenFile(t, os.TempDir(), syscall.O_RDONLY|fsapi.O_DIRECTORY, 0o666) 1019 defer f.Close() 1020 1021 require.EqualErrno(t, syscall.EISDIR, fn(f)) 1022 }) 1023 } 1024 1025 func openForWrite(t *testing.T, path string, content []byte) fsapi.File { 1026 require.NoError(t, os.WriteFile(path, content, 0o0666)) 1027 f := requireOpenFile(t, path, syscall.O_RDWR, 0o666) 1028 _, errno := f.Write(content) 1029 require.EqualErrno(t, 0, errno) 1030 return f 1031 } 1032 1033 func requireOpenFile(t *testing.T, path string, flag int, perm fs.FileMode) fsapi.File { 1034 f, errno := OpenOSFile(path, flag, perm) 1035 require.EqualErrno(t, 0, errno) 1036 return f 1037 } 1038 1039 func dirEmbedMapFS(t *testing.T, tmpDir string) (fs.FS, fs.FS, fs.FS) { 1040 embedFS, err := fs.Sub(testdata, "testdata") 1041 require.NoError(t, err) 1042 1043 f, err := embedFS.Open(wazeroFile) 1044 require.NoError(t, err) 1045 defer f.Close() 1046 1047 bytes, err := io.ReadAll(f) 1048 require.NoError(t, err) 1049 1050 mapFS := gofstest.MapFS{ 1051 emptyFile: &gofstest.MapFile{}, 1052 wazeroFile: &gofstest.MapFile{Data: bytes}, 1053 } 1054 1055 // Write a file as can't open "testdata" in scratch tests because they 1056 // can't read the original filesystem. 1057 require.NoError(t, os.WriteFile(path.Join(tmpDir, emptyFile), nil, 0o600)) 1058 require.NoError(t, os.WriteFile(path.Join(tmpDir, wazeroFile), bytes, 0o600)) 1059 dirFS := os.DirFS(tmpDir) 1060 return dirFS, embedFS, mapFS 1061 }