github.com/tetratelabs/wazero@v1.2.1/imports/wasi_snapshot_preview1/fs_unit_test.go (about) 1 package wasi_snapshot_preview1 2 3 import ( 4 "os" 5 "syscall" 6 "testing" 7 8 "github.com/tetratelabs/wazero/internal/fsapi" 9 "github.com/tetratelabs/wazero/internal/fstest" 10 "github.com/tetratelabs/wazero/internal/sys" 11 "github.com/tetratelabs/wazero/internal/sysfs" 12 "github.com/tetratelabs/wazero/internal/testing/require" 13 "github.com/tetratelabs/wazero/internal/wasip1" 14 ) 15 16 func Test_maxDirents(t *testing.T) { 17 tests := []struct { 18 name string 19 dirents []fsapi.Dirent 20 maxLen uint32 21 expectedCount uint32 22 expectedwriteTruncatedEntry bool 23 expectedBufused uint32 24 }{ 25 { 26 name: "no entries", 27 }, 28 { 29 name: "can't fit one", 30 dirents: testDirents, 31 maxLen: 23, 32 expectedBufused: 23, 33 expectedwriteTruncatedEntry: false, 34 }, 35 { 36 name: "only fits header", 37 dirents: testDirents, 38 maxLen: 24, 39 expectedBufused: 24, 40 expectedwriteTruncatedEntry: true, 41 }, 42 { 43 name: "one", 44 dirents: testDirents, 45 maxLen: 25, 46 expectedCount: 1, 47 expectedBufused: 25, 48 }, 49 { 50 name: "one but not room for two's name", 51 dirents: testDirents, 52 maxLen: 25 + 25, 53 expectedCount: 1, 54 expectedwriteTruncatedEntry: true, // can write DirentSize 55 expectedBufused: 25 + 25, 56 }, 57 { 58 name: "two", 59 dirents: testDirents, 60 maxLen: 25 + 26, 61 expectedCount: 2, 62 expectedBufused: 25 + 26, 63 }, 64 { 65 name: "two but not three's dirent", 66 dirents: testDirents, 67 maxLen: 25 + 26 + 20, 68 expectedCount: 2, 69 expectedwriteTruncatedEntry: false, // 20 + 4 == DirentSize 70 expectedBufused: 25 + 26 + 20, 71 }, 72 { 73 name: "two but not three's name", 74 dirents: testDirents, 75 maxLen: 25 + 26 + 26, 76 expectedCount: 2, 77 expectedwriteTruncatedEntry: true, // can write DirentSize 78 expectedBufused: 25 + 26 + 26, 79 }, 80 { 81 name: "three", 82 dirents: testDirents, 83 maxLen: 25 + 26 + 27, 84 expectedCount: 3, 85 expectedwriteTruncatedEntry: false, // end of dir 86 expectedBufused: 25 + 26 + 27, 87 }, 88 { 89 name: "max", 90 dirents: testDirents, 91 maxLen: 100, 92 expectedCount: 3, 93 expectedwriteTruncatedEntry: false, // end of dir 94 expectedBufused: 25 + 26 + 27, 95 }, 96 } 97 98 for _, tt := range tests { 99 tc := tt 100 101 t.Run(tc.name, func(t *testing.T) { 102 readdir, _ := sys.NewReaddir( 103 func() ([]fsapi.Dirent, syscall.Errno) { 104 return tc.dirents, 0 105 }, 106 func(n uint64) ([]fsapi.Dirent, syscall.Errno) { 107 return nil, 0 108 }, 109 ) 110 _, bufused, direntCount, writeTruncatedEntry := maxDirents(readdir, tc.maxLen) 111 require.Equal(t, tc.expectedCount, direntCount) 112 require.Equal(t, tc.expectedwriteTruncatedEntry, writeTruncatedEntry) 113 require.Equal(t, tc.expectedBufused, bufused) 114 }) 115 } 116 } 117 118 var ( 119 testDirents = func() []fsapi.Dirent { 120 dPath := "dir" 121 d, errno := sysfs.OpenFSFile(fstest.FS, dPath, syscall.O_RDONLY, 0) 122 if errno != 0 { 123 panic(errno) 124 } 125 defer d.Close() 126 dirents, errno := d.Readdir(-1) 127 if errno != 0 { 128 panic(errno) 129 } 130 return dirents 131 }() 132 133 dirent1 = []byte{ 134 1, 0, 0, 0, 0, 0, 0, 0, // d_next = 1 135 0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0 136 1, 0, 0, 0, // d_namlen = 1 character 137 4, 0, 0, 0, // d_type = regular_file 138 '-', // name 139 } 140 dirent2 = []byte{ 141 2, 0, 0, 0, 0, 0, 0, 0, // d_next = 2 142 0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0 143 2, 0, 0, 0, // d_namlen = 1 character 144 3, 0, 0, 0, // d_type = directory 145 'a', '-', // name 146 } 147 dirent3 = []byte{ 148 3, 0, 0, 0, 0, 0, 0, 0, // d_next = 3 149 0, 0, 0, 0, 0, 0, 0, 0, // d_ino = 0 150 3, 0, 0, 0, // d_namlen = 3 characters 151 4, 0, 0, 0, // d_type = regular_file 152 'a', 'b', '-', // name 153 } 154 ) 155 156 func Test_writeDirents(t *testing.T) { 157 tests := []struct { 158 name string 159 entries []fsapi.Dirent 160 entryCount uint32 161 writeTruncatedEntry bool 162 expectedEntriesBuf []byte 163 }{ 164 { 165 name: "none", 166 entries: testDirents, 167 }, 168 { 169 name: "one", 170 entries: testDirents, 171 entryCount: 1, 172 expectedEntriesBuf: dirent1, 173 }, 174 { 175 name: "two", 176 entries: testDirents, 177 entryCount: 2, 178 expectedEntriesBuf: append(dirent1, dirent2...), 179 }, 180 { 181 name: "two with truncated", 182 entries: testDirents, 183 entryCount: 2, 184 writeTruncatedEntry: true, 185 expectedEntriesBuf: append(append(dirent1, dirent2...), dirent3[0:10]...), 186 }, 187 { 188 name: "three", 189 entries: testDirents, 190 entryCount: 3, 191 expectedEntriesBuf: append(append(dirent1, dirent2...), dirent3...), 192 }, 193 } 194 195 for _, tt := range tests { 196 tc := tt 197 198 t.Run(tc.name, func(t *testing.T) { 199 cookie := uint64(1) 200 entriesBuf := make([]byte, len(tc.expectedEntriesBuf)) 201 writeDirents(tc.entries, tc.entryCount, tc.writeTruncatedEntry, entriesBuf, cookie) 202 require.Equal(t, tc.expectedEntriesBuf, entriesBuf) 203 }) 204 } 205 } 206 207 func Test_openFlags(t *testing.T) { 208 tests := []struct { 209 name string 210 dirflags, oflags, fdflags uint16 211 rights uint32 212 expectedOpenFlags int 213 }{ 214 { 215 name: "oflags=0", 216 expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDONLY, 217 }, 218 { 219 name: "oflags=O_CREAT", 220 oflags: wasip1.O_CREAT, 221 expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDWR | syscall.O_CREAT, 222 }, 223 { 224 name: "oflags=O_DIRECTORY", 225 oflags: wasip1.O_DIRECTORY, 226 expectedOpenFlags: fsapi.O_NOFOLLOW | fsapi.O_DIRECTORY, 227 }, 228 { 229 name: "oflags=O_EXCL", 230 oflags: wasip1.O_EXCL, 231 expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDONLY | syscall.O_EXCL, 232 }, 233 { 234 name: "oflags=O_TRUNC", 235 oflags: wasip1.O_TRUNC, 236 expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDWR | syscall.O_TRUNC, 237 }, 238 { 239 name: "fdflags=FD_APPEND", 240 fdflags: wasip1.FD_APPEND, 241 expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDWR | syscall.O_APPEND, 242 }, 243 { 244 name: "oflags=O_TRUNC|O_CREAT", 245 oflags: wasip1.O_TRUNC | wasip1.O_CREAT, 246 expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDWR | syscall.O_TRUNC | syscall.O_CREAT, 247 }, 248 { 249 name: "dirflags=LOOKUP_SYMLINK_FOLLOW", 250 dirflags: wasip1.LOOKUP_SYMLINK_FOLLOW, 251 expectedOpenFlags: syscall.O_RDONLY, 252 }, 253 { 254 name: "rights=FD_READ", 255 rights: wasip1.RIGHT_FD_READ, 256 expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDONLY, 257 }, 258 { 259 name: "rights=FD_WRITE", 260 rights: wasip1.RIGHT_FD_WRITE, 261 expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_WRONLY, 262 }, 263 { 264 name: "rights=FD_READ|FD_WRITE", 265 rights: wasip1.RIGHT_FD_READ | wasip1.RIGHT_FD_WRITE, 266 expectedOpenFlags: fsapi.O_NOFOLLOW | syscall.O_RDWR, 267 }, 268 } 269 270 for _, tt := range tests { 271 tc := tt 272 273 t.Run(tc.name, func(t *testing.T) { 274 openFlags := openFlags(tc.dirflags, tc.oflags, tc.fdflags, tc.rights) 275 require.Equal(t, tc.expectedOpenFlags, openFlags) 276 }) 277 } 278 } 279 280 func Test_getWasiFiletype_DevNull(t *testing.T) { 281 st, err := os.Stat(os.DevNull) 282 require.NoError(t, err) 283 284 ft := getWasiFiletype(st.Mode()) 285 286 // Should be a character device, and not contain permissions 287 require.Equal(t, wasip1.FILETYPE_CHARACTER_DEVICE, ft) 288 } 289 290 func Test_isPreopenedStdio(t *testing.T) { 291 tests := []struct { 292 name string 293 fd int32 294 f *sys.FileEntry 295 expected bool 296 }{ 297 { 298 name: "stdin", 299 fd: sys.FdStdin, 300 f: &sys.FileEntry{IsPreopen: true}, 301 expected: true, 302 }, 303 { 304 name: "stdin re-opened", 305 fd: sys.FdStdin, 306 f: &sys.FileEntry{IsPreopen: false}, 307 expected: false, 308 }, 309 { 310 name: "stdout", 311 fd: sys.FdStdout, 312 f: &sys.FileEntry{IsPreopen: true}, 313 expected: true, 314 }, 315 { 316 name: "stdout re-opened", 317 fd: sys.FdStdout, 318 f: &sys.FileEntry{IsPreopen: false}, 319 expected: false, 320 }, 321 { 322 name: "stderr", 323 fd: sys.FdStderr, 324 f: &sys.FileEntry{IsPreopen: true}, 325 expected: true, 326 }, 327 { 328 name: "stderr re-opened", 329 fd: sys.FdStderr, 330 f: &sys.FileEntry{IsPreopen: false}, 331 expected: false, 332 }, 333 { 334 name: "not stdio pre-open", 335 fd: sys.FdPreopen, 336 f: &sys.FileEntry{IsPreopen: true}, 337 expected: false, 338 }, 339 { 340 name: "random file", 341 fd: 42, 342 f: &sys.FileEntry{}, 343 expected: false, 344 }, 345 } 346 347 for _, tt := range tests { 348 tc := tt 349 350 t.Run(tc.name, func(t *testing.T) { 351 ok := isPreopenedStdio(tc.fd, tc.f) 352 require.Equal(t, tc.expected, ok) 353 }) 354 } 355 }