github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/sysfs/dir_test.go (about) 1 package sysfs_test 2 3 import ( 4 "io" 5 "io/fs" 6 "os" 7 "runtime" 8 "sort" 9 "testing" 10 11 "github.com/tetratelabs/wazero/experimental/sys" 12 "github.com/tetratelabs/wazero/internal/fstest" 13 "github.com/tetratelabs/wazero/internal/sysfs" 14 "github.com/tetratelabs/wazero/internal/testing/require" 15 ) 16 17 func TestFSFileReaddir(t *testing.T) { 18 t.Parallel() 19 20 tmpDir := t.TempDir() 21 require.NoError(t, fstest.WriteTestFiles(tmpDir)) 22 dirFS := os.DirFS(tmpDir) 23 maskFS := &sysfs.MaskOsFS{Fs: dirFS} 24 maskFSZeroIno := &sysfs.MaskOsFS{Fs: os.DirFS(tmpDir), ZeroIno: true} 25 26 expectIno := runtime.GOOS != "windows" 27 28 tests := []struct { 29 name string 30 fs fs.FS 31 expectIno bool 32 }{ 33 {name: "os.DirFS", fs: dirFS, expectIno: expectIno}, // To test readdirFile 34 {name: "mask(os.DirFS)", fs: maskFS, expectIno: expectIno}, // To prove no reliance on os.File 35 {name: "mask(os.DirFS) ZeroIno", fs: maskFSZeroIno, expectIno: false}, // To prove Stat_t overrides 36 {name: "fstest.MapFS", fs: fstest.FS, expectIno: false}, // To test adaptation of ReadDirFile 37 } 38 39 for _, tc := range tests { 40 tc := tc 41 42 t.Run(tc.name, func(t *testing.T) { 43 dotF, errno := sysfs.OpenFSFile(tc.fs, ".", sys.O_RDONLY, 0) 44 require.EqualErrno(t, 0, errno) 45 defer dotF.Close() 46 47 t.Run("dir", func(t *testing.T) { 48 testReaddirAll(t, dotF, tc.expectIno) 49 50 // read again even though it is exhausted 51 dirents, errno := dotF.Readdir(100) 52 require.EqualErrno(t, 0, errno) 53 require.Zero(t, len(dirents)) 54 55 // rewind via seek to zero 56 newOffset, errno := dotF.Seek(0, io.SeekStart) 57 require.EqualErrno(t, 0, errno) 58 require.Zero(t, newOffset) 59 60 // redundantly seek to zero again 61 newOffset, errno = dotF.Seek(0, io.SeekStart) 62 require.EqualErrno(t, 0, errno) 63 require.Zero(t, newOffset) 64 65 // We should be able to read again 66 testReaddirAll(t, dotF, tc.expectIno) 67 }) 68 69 // Err if the caller closed the directory while reading. This is 70 // different from something else deleting it. 71 t.Run("closed dir", func(t *testing.T) { 72 require.EqualErrno(t, 0, dotF.Close()) 73 _, errno := dotF.Readdir(-1) 74 require.EqualErrno(t, sys.EBADF, errno) 75 }) 76 77 fileF, errno := sysfs.OpenFSFile(tc.fs, "empty.txt", sys.O_RDONLY, 0) 78 require.EqualErrno(t, 0, errno) 79 defer fileF.Close() 80 81 t.Run("file", func(t *testing.T) { 82 _, errno := fileF.Readdir(-1) 83 require.EqualErrno(t, sys.EBADF, errno) 84 }) 85 86 dirF, errno := sysfs.OpenFSFile(tc.fs, "dir", sys.O_RDONLY, 0) 87 require.EqualErrno(t, 0, errno) 88 defer dirF.Close() 89 90 t.Run("partial", func(t *testing.T) { 91 dirents1, errno := dirF.Readdir(1) 92 require.EqualErrno(t, 0, errno) 93 require.Equal(t, 1, len(dirents1)) 94 95 dirents2, errno := dirF.Readdir(1) 96 require.EqualErrno(t, 0, errno) 97 require.Equal(t, 1, len(dirents2)) 98 99 // read exactly the last entry 100 dirents3, errno := dirF.Readdir(1) 101 require.EqualErrno(t, 0, errno) 102 require.Equal(t, 1, len(dirents3)) 103 104 dirents := []sys.Dirent{dirents1[0], dirents2[0], dirents3[0]} 105 sort.Slice(dirents, func(i, j int) bool { return dirents[i].Name < dirents[j].Name }) 106 107 requireIno(t, dirents, tc.expectIno) 108 109 // Scrub inodes so we can compare expectations without them. 110 for i := range dirents { 111 dirents[i].Ino = 0 112 } 113 114 require.Equal(t, []sys.Dirent{ 115 {Name: "-", Type: 0}, 116 {Name: "a-", Type: fs.ModeDir}, 117 {Name: "ab-", Type: 0}, 118 }, dirents) 119 120 // no error reading an exhausted directory 121 _, errno = dirF.Readdir(1) 122 require.EqualErrno(t, 0, errno) 123 }) 124 125 subdirF, errno := sysfs.OpenFSFile(tc.fs, "sub", sys.O_RDONLY, 0) 126 require.EqualErrno(t, 0, errno) 127 defer subdirF.Close() 128 129 t.Run("subdir", func(t *testing.T) { 130 dirents, errno := subdirF.Readdir(-1) 131 require.EqualErrno(t, 0, errno) 132 sort.Slice(dirents, func(i, j int) bool { return dirents[i].Name < dirents[j].Name }) 133 134 require.Equal(t, 1, len(dirents)) 135 require.Equal(t, "test.txt", dirents[0].Name) 136 require.Zero(t, dirents[0].Type) 137 }) 138 }) 139 } 140 } 141 142 func testReaddirAll(t *testing.T, dotF sys.File, expectDirIno bool) { 143 dirents, errno := dotF.Readdir(-1) 144 require.EqualErrno(t, 0, errno) // no io.EOF when -1 is used 145 sort.Slice(dirents, func(i, j int) bool { return dirents[i].Name < dirents[j].Name }) 146 147 requireIno(t, dirents, expectDirIno) 148 149 // Scrub inodes so we can compare expectations without them. 150 for i := range dirents { 151 dirents[i].Ino = 0 152 } 153 154 require.Equal(t, []sys.Dirent{ 155 {Name: "animals.txt", Type: 0}, 156 {Name: "dir", Type: fs.ModeDir}, 157 {Name: "empty.txt", Type: 0}, 158 {Name: "emptydir", Type: fs.ModeDir}, 159 {Name: "sub", Type: fs.ModeDir}, 160 }, dirents) 161 } 162 163 func requireIno(t *testing.T, dirents []sys.Dirent, expectDirIno bool) { 164 for _, e := range dirents { 165 if expectDirIno { 166 require.NotEqual(t, uint64(0), e.Ino, "%+v", e) 167 e.Ino = 0 168 } else { 169 require.Zero(t, e.Ino, "%+v", e) 170 } 171 } 172 }