lib9p

Go 9P library.
Log | Files | Refs | LICENSE

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 }