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