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