fs_test.go (7196B)
1 package lib9p 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/fs" 8 "path" 9 "strings" 10 "sync" 11 "testing" 12 "time" 13 14 "git.mtkn.jp/lib9p/testdata" 15 ) 16 17 // SetupConn sets up a conn struct to serve fs. 18 // rc is the channel from which reply *requests are derived. 19 func setupConn(fs FS) (c *conn, rc chan *request) { 20 rc = make(chan *request) 21 c = &conn{ 22 s: NewServer(fs), 23 msize: 1024, 24 mSizeLock: new(sync.Mutex), 25 fPool: newFidPool(), 26 respChan: rc, 27 } 28 return 29 } 30 31 type testFile struct { 32 // fsys is the testFS this testFile belongs to. 33 fsys *testFS 34 // parent is the parent directory this testFile located at. 35 parent *testFile 36 // children is the child files this testFile has. 37 // It is nil if this testFile is not a directory 38 children []*testFile 39 // content is the content of this testFile. 40 // It is nil if this testFile is a directory 41 content []byte 42 // r is used if this testFile is not a directory and 43 // when this testFile is read. 44 // It contains content. 45 r *bytes.Reader 46 offset int64 47 // stat is the Stat of this testFile. 48 stat Stat 49 } 50 51 func (f *testFile) Stat() (*FileInfo, error) { 52 return &FileInfo{Stat: f.stat}, nil 53 } 54 55 func (f *testFile) Close() error { 56 f.r = nil 57 f.offset = 0 58 return nil 59 } 60 61 func (f *testFile) Read(b []byte) (int, error) { 62 if f.fsys.slow { 63 f.fsys.waitc <- struct{}{} 64 <-f.fsys.waitc 65 } 66 if f.r == nil { 67 f.r = bytes.NewReader(f.content) 68 } 69 n, err := f.r.ReadAt(b, f.offset) 70 f.offset += int64(n) 71 return n, err 72 } 73 74 func (f *testFile) ReadAt(b []byte, off int64) (n int, err error) { 75 if f.fsys.slow { 76 f.fsys.waitc <- struct{}{} 77 <-f.fsys.waitc 78 } 79 // ReadAt shoul not affect nor be affected by the underlying seek offset. 80 return f.r.ReadAt(b, off) 81 } 82 83 func (f *testFile) ReadDir(n int) ([]fs.DirEntry, error) { 84 de := make([]fs.DirEntry, len(f.children)) 85 for i, c := range f.children { 86 info, _ := c.Stat() 87 de[i] = info 88 } 89 return de, nil 90 } 91 92 func (f *testFile) WriteAt(p []byte, off int64) (int, error) { 93 if f.fsys.slow { 94 f.fsys.waitc <- struct{}{} 95 } 96 if off < 0 || off > int64(len(f.content)) { 97 return 0, fmt.Errorf("bad offset") 98 } 99 if off+int64(len(p)) > int64(len(f.content)) { 100 newcon := make([]byte, off+int64(len(p))) 101 copy(newcon, f.content) 102 f.content = newcon 103 } 104 copy(f.content[off:], p) 105 if f.r != nil { 106 f.r.Reset(f.content) 107 f.r.Seek(f.offset, io.SeekStart) 108 } 109 if f.fsys.slow { 110 <-f.fsys.waitc 111 } 112 return len(p), nil 113 } 114 115 // permission check is intentionally skipped. 116 func (f *testFile) WStat(stat *Stat) error { 117 f.stat = *stat 118 return nil 119 } 120 121 type testFS struct { 122 // root is the root testFile of this file system. 123 root *testFile 124 // If slow is true, each function on files of this testFS waits for 125 // something arrives on waitc. 126 slow bool 127 nextQid uint64 128 // waitc is used to wait some timing to emulate slow response. 129 waitc chan struct{} 130 // groups holds the information of groups and it leader and members. 131 groups map[string]group 132 } 133 134 type group struct { 135 leader string 136 members map[string]bool 137 } 138 139 func (fs *testFS) OpenFile(path string, flag int) (File, error) { 140 wnames := strings.Split(path, "/") 141 f, err := fs.walk(wnames) 142 if err != nil { 143 return nil, fmt.Errorf("walk: %v", err) 144 } 145 f.r = bytes.NewReader(f.content) 146 return f, nil 147 } 148 149 func (fs *testFS) IsGroupLeader(gid, uid string) bool { 150 g, ok := fs.groups[gid] 151 if !ok { 152 return false 153 } 154 return g.leader == uid 155 } 156 157 func (fs *testFS) IsGroupMember(gid, uid string) bool { 158 g, ok := fs.groups[gid] 159 if !ok { 160 return false 161 } 162 return g.members[uid] 163 } 164 165 // Permission check is intentionally skipped. 166 func (fs *testFS) Create(name string, uid string, mode OpenMode, perm FileMode) (File, error) { 167 dirname, filename := path.Split(name) 168 if dirname == "" && (filename == "." || filename == "") { 169 return nil, fmt.Errorf("cant create root") 170 } 171 dir, err := fs.walkPath(path.Dir(dirname)) 172 if err != nil { 173 return nil, fmt.Errorf("not found") 174 } 175 for _, c := range dir.children { 176 if c.stat.Name == filename { 177 return nil, fmt.Errorf("file already exists") 178 } 179 } 180 f := &testFile{ 181 fsys: fs, 182 parent: dir, 183 stat: Stat{ 184 Qid: Qid{ 185 Type: FSModeToQidType(perm), 186 Path: fs.nextQid, 187 }, 188 Mode: perm, 189 Atime: uint32(time.Now().Unix()), 190 Mtime: uint32(time.Now().Unix()), 191 Name: filename, 192 Uid: uid, 193 Gid: dir.stat.Gid, 194 Muid: uid, 195 }, 196 } 197 fs.nextQid++ 198 dir.children = append(dir.children, f) 199 return f, nil 200 } 201 202 func (fs *testFS) Remove(name string) error { 203 ds, filename := path.Split(name) 204 dirname := path.Dir(ds) 205 dir, err := fs.walkPath(dirname) 206 if err != nil { 207 return fmt.Errorf("not found") 208 } 209 for i, c := range dir.children { 210 if c.stat.Name == filename { 211 if len(c.children) != 0 { 212 return fmt.Errorf("directory not empty") 213 } 214 dir.children = append(dir.children[:i], dir.children[i+1:]...) 215 // TODO: should close the file? 216 return nil 217 } 218 } 219 return fmt.Errorf("not found") 220 } 221 222 func (fs *testFS) walk(wnames []string) (*testFile, error) { 223 if len(wnames) == 1 && (wnames[0] == "." || wnames[0] == "") { 224 return fs.root, nil 225 } 226 cwd := fs.root 227 L: 228 for i, name := range wnames { 229 for _, child := range cwd.children { 230 if child.stat.Name == name { 231 cwd = child 232 continue L 233 } 234 } 235 return nil, fmt.Errorf("%s not found", strings.Join(wnames[:i+1], "/")) 236 } 237 return cwd, nil 238 } 239 240 func (fs *testFS) walkPath(pathname string) (*testFile, error) { 241 wnames := strings.Split(pathname, "/") 242 return fs.walk(wnames) 243 } 244 245 var testfs *testFS 246 247 func init() { 248 fileTree := testdata.FileTree 249 testfs = new(testFS) 250 for i, f := range fileTree { 251 var ff *testFile 252 if i == 0 { // root of the testfs 253 testfs.root = new(testFile) 254 ff = testfs.root 255 ff.parent = testfs.root 256 } else { 257 d, err := testfs.walkPath(path.Dir(f.Path)) 258 if err != nil { 259 panic(err) 260 } 261 ff = new(testFile) 262 d.children = append(d.children, ff) 263 ff.parent = d 264 } 265 ff.content = []byte(f.Content) 266 var qt QidType 267 if f.Mode&fs.ModeDir != 0 { 268 qt = QTDIR 269 } else { 270 qt = QTFILE 271 } 272 ff.stat = Stat{ 273 Qid: Qid{Path: uint64(i), Type: qt}, 274 Mode: f.Mode, 275 Name: path.Base(f.Path), 276 Uid: f.Uid, 277 Gid: f.Gid, 278 Muid: f.Muid, 279 } 280 ff.fsys = testfs 281 testfs.nextQid++ 282 } 283 testfs.waitc = make(chan struct{}) 284 testfs.groups = make(map[string]group) 285 for _, g := range testdata.Groups { 286 testfs.groups[g.Name] = group{ 287 leader: g.Leader, 288 members: make(map[string]bool), 289 } 290 for _, u := range g.Members { 291 testfs.groups[g.Name].members[u] = true 292 } 293 } 294 } 295 296 // This function does the actual work for TestWalk(). 297 func testWalk(t *testing.T, fs *testFS, pathname string, file *testFile) { 298 t.Logf("walk %s", pathname) 299 f, err := fs.walk(strings.Split(pathname, "/")) 300 if err != nil { 301 t.Errorf("open %s: %v", pathname, err) 302 } 303 if f != file { 304 t.Errorf("open %s: wrong file", pathname) 305 } 306 for _, child := range file.children { 307 childpath := path.Join(pathname, child.stat.Name) 308 testWalk(t, fs, childpath, child) 309 } 310 } 311 312 // TestWalk walk through testFS in depth-first fassion, 313 // checks if all files can be opened without error and if 314 // the opened file is the same as the file accessed via testFS.child 315 func TestWalk(t *testing.T) { 316 testWalk(t, testfs, ".", testfs.root) 317 }