lib9p

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

commit 6df3fcb1a4f4e5b19ceaa5a37dcdbcfc2a98c247
parent 52156521b60ac65a9c5199ff0f957ed75d945de7
Author: Matsuda Kenji <info@mtkn.jp>
Date:   Tue,  2 Jan 2024 10:28:03 +0900

copy testfs to lib9p for whitebox testing

Diffstat:
Afs_test.go | 277+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mserver_test.go | 85++++++++++++++++++++++++++++++++++---------------------------------------------
2 files changed, 313 insertions(+), 49 deletions(-)

diff --git a/fs_test.go b/fs_test.go @@ -0,0 +1,277 @@ +package lib9p + +import ( + "bytes" + "io/fs" + "fmt" + "path" + "strings" + "testing" +) + +type testFile struct { + // fsys is the testFS this testFile belongs to. + fsys *testFS + // parent is the parent directory this testFile located at. + parent *testFile + // children is the child files this testFile has. + // It is nil if this testFile is not a directory + children []*testFile + // content is the content of this testFile. + // It is nil if this testFile is a directory + content []byte + // r is used if this testFile is not a directory and + // when this testFile is read. + // It contains content. + r *bytes.Reader + // stat is the Stat of this testFile. + stat Stat +} + +func (f *testFile) Stat() (*FileInfo, error) { + return &FileInfo{Stat: f.stat}, nil +} + +func (f *testFile) Close() error { + f.r = nil + return nil +} + +func (f *testFile) Read(b []byte) (int, error) { + if f.fsys.slow { + f.fsys.waitc <- struct{}{} + <-f.fsys.waitc + } + return f.r.Read(b) +} + +func (f *testFile) ReadAt(b []byte, off int64) (n int, err error) { + if f.fsys.slow { + f.fsys.waitc <- struct{}{} + <-f.fsys.waitc + } + return f.r.ReadAt(b, off) +} + +func (f *testFile) ReadDir(n int) ([]fs.DirEntry, error) { + de := make([]fs.DirEntry, len(f.children)) + for i, c := range f.children { + info, _ := c.Stat() + de[i] = info + } + return de, nil +} + +func (f *testFile) WriteAt(p []byte, off int64) (int, error) { + if f.fsys.slow { + f.fsys.waitc <- struct{}{} + } + if f.r == nil { + return 0, fmt.Errorf("not open") + } + if off < 0 || off > int64(len(f.content)) { + return 0, fmt.Errorf("bad offset") + } + if off+int64(len(p)) > int64(len(f.content)) { + newcon := make([]byte, off+int64(len(p))) + copy(newcon, f.content) + f.content = newcon + } + copy(f.content[off:], p) + f.r.Reset(f.content) + if f.fsys.slow { + <-f.fsys.waitc + } + return len(p), nil +} + +type testFS struct { + // root is the root testFile of this file system. + root *testFile + // If slow is true, each function on files of this testFS waits for + // something arrives on waitc. + slow bool + // waitc is used to wait some timing to emulate slow response. + waitc chan struct{} + // groups holds the information of groups and it leader and members. + groups map[string]group +} + +type group struct { + leader string + members map[string]bool +} + +func (fs *testFS) OpenFile(path string, omode OpenMode) (File, error) { + wnames := strings.Split(path, "/") + f, err := fs.Walk(wnames) + if err != nil { + return nil, fmt.Errorf("walk: %v", err) + } + f.r = bytes.NewReader(f.content) + return f, nil +} + +func (fs *testFS) IsGroupLeader(gid, uid string) bool { + g, ok := fs.groups[gid] + if !ok { + return false + } + return g.leader == uid +} + +func (fs *testFS) IsGroupMember(gid, uid string) bool { + g, ok := fs.groups[gid] + if !ok { + return false + } + return g.members[uid] +} + +func (fs *testFS) Walk(wnames []string) (*testFile, error) { + if len(wnames) == 1 && (wnames[0] == "." || wnames[0] == "") { + return fs.root, nil + } + cwd := fs.root +L: + for i, name := range wnames { + for _, child := range cwd.children { + if child.stat.Name == name { + cwd = child + continue L + } + } + return nil, fmt.Errorf("%s not found", strings.Join(wnames[:i+1], "/")) + } + return cwd, nil +} + +func (fs *testFS) WalkPath(pathname string) (*testFile, error) { + wnames := strings.Split(pathname, "/") + return fs.Walk(wnames) +} + +var testfs *testFS + +func init() { + testfs = &testFS{ + root: &testFile{ + stat: Stat{ + Qid: Qid{Path: 0, Type: QTDIR}, + Mode: FileMode(fs.ModeDir | 0755), + Name: "root", + Uid: "glenda", + Gid: "glenda", + Muid: "glenda", + }, + children: []*testFile{ + &testFile{ + content: []byte("a\n"), + stat: Stat{ + Qid: Qid{Path: 1, Type: QTFILE}, + Mode: FileMode(0644), + Name: "a", + Uid: "glenda", + Gid: "glenda", + Muid: "glenda", + }, + }, + &testFile{ + content: []byte("b\n"), + stat: Stat{ + Qid: Qid{Path: 2, Type: QTFILE}, + Mode: FileMode(0600), + Name: "b", + Uid: "ken", + Gid: "ken", + Muid: "ken", + }, + }, + &testFile{ + stat: Stat{ + Qid: Qid{Path: 3, Type: QTDIR}, + Mode: FileMode(fs.ModeDir | 0755), + Name: "dir", + Uid: "rob", + Gid: "rob", + Muid: "rob", + }, + children: []*testFile{ + &testFile{ + content: []byte("unko\n"), + stat: Stat{ + Qid: Qid{Path: 4, Type: QTFILE}, + Mode: FileMode(0666), + Name: "file", + Uid: "brian", + Gid: "brian", + Muid: "dennis", + }, + }, + }, + }, + }, + }, + waitc: make(chan struct{}), + groups: map[string]group{ + "bell": group{ + leader: "glenda", + members: map[string]bool{ + "glenda": true, + "ken": true, + "dennis": true, + "brian": true, + "rob": true, + }, + }, + "kessoku": group{ + leader: "ijichi", + members: map[string]bool{ + "ijichi": true, + "yamada": true, + "gotoh": true, + "kita": true, + }, + }, + }, + } + SetfsysAndparent(testfs, nil) +} + +// SetfsysAndparent sets file.fsys and file.parent for every file in the fsys. +// Pass nil as file to setup entire file system. +func SetfsysAndparent(fsys *testFS, file *testFile) { + if file == nil { + file = fsys.root + file.parent = fsys.root + file.fsys = fsys + } + for _, child := range file.children { + child.parent = file + child.fsys = fsys + SetfsysAndparent(fsys, child) + } +} + +// This function does the actual work for TestWalk(). +func testWalk(t *testing.T, fs *testFS, pathname string, file *testFile) { + t.Logf("walk %s", pathname) + f, err := fs.Walk(strings.Split(pathname, "/")) + if err != nil { + t.Errorf("open %s: %v", pathname, err) + } + if f != file { + t.Errorf("open %s: wrong file", pathname) + } + for _, child := range file.children { + childpath := path.Join(pathname, child.stat.Name) + testWalk(t, fs, childpath, child) + } +} + +// TestWalk walk through testFS in depth-first fassion, +// checks if all files can be opened without error and if +// the opened file is the same as the file accessed via testFS.child +func TestWalk(t *testing.T) { + testWalk(t, testfs, ".", testfs.root) +} diff --git a/server_test.go b/server_test.go @@ -136,6 +136,19 @@ func TestGetReq(t *testing.T) { } } +func setupConn(fs FS) (c *conn, tc, rc chan *request) { + tc = make(chan *request) + rc = make(chan *request) + c = &conn{ + s: NewServer(fs), + msize: 1024, + mSizeLock: new(sync.Mutex), + fPool: newFidPool(), + respChan: rc, + } + return +} + func TestSVersion(t *testing.T) { tests := []struct { input *request @@ -150,9 +163,7 @@ func TestSVersion(t *testing.T) { {&request{ifcall: &TVersion{Msize: 1024, Version: "unko"}}, &request{ofcall: &RVersion{Msize: 1024, Version: "unknown"}}}, } - tc := make(chan *request) - rc := make(chan *request) - c := &Conn{msize: 1024, mSizeLock: new(sync.Mutex), respChan: rc} + c, tc, rc := setupConn(nil) ctx, cancel := context.WithCancel(context.Background()) defer cancel() go sVersion(ctx, c, tc) @@ -194,12 +205,8 @@ func TestSAuth(t *testing.T) { } for _, test := range tests { func() { - tc := make(chan *request) - rc := make(chan *request) - defer close(tc) - defer close(rc) - s := &Server{Auth: test.authFunc} - c := &Conn{s: s, respChan: rc, fPool: newFidPool()} + c, tc, rc := setupConn(nil) + c.s.Auth = test.authFunc ctx, cancel := context.WithCancel(context.Background()) defer cancel() go sAuth(ctx, c, tc) @@ -237,9 +244,7 @@ func TestSFlush(t *testing.T) { {&request{ifcall: &TFlush{}, oldreq: &request{pool: rp, done: make(chan struct{})}}}, } - tc := make(chan *request) - rc := make(chan *request) - c := &Conn{msize: 1024, mSizeLock: new(sync.Mutex), respChan: rc} + c, tc, rc := setupConn(nil) ctx, cancel := context.WithCancel(context.Background()) defer cancel() go sFlush(ctx, c, tc) @@ -255,18 +260,6 @@ func TestSFlush(t *testing.T) { } func TestSAttach(t *testing.T) { - dammyAuth := func(context.Context, *Req) {} - af := &AuthFile{ - Qid: Qid{Type: QTAUTH}, - Uname: "kenji", - Aname: "", - } - fp := newFidPool() - fid, err := fp.add(0) - if err != nil { - t.Fatal(err) - } - fid.file = af tests := []struct { input Msg auth bool @@ -292,21 +285,27 @@ func TestSAttach(t *testing.T) { true, true, &RAttach{}}, // ok } - tc := make(chan *Req) - rc := make(chan *Req) - defer close(tc) - defer close(rc) - s := &Server{fs: testfs} - c := &conn{s: s, respChan: rc, fPool: fp} + c, tc, rc := setupConn(testfs) + dammyAuth := func(context.Context, *Req) {} + af := &AuthFile{ + Qid: Qid{Type: QTAUTH}, + Uname: "kenji", + Aname: "", + } + fid, err := c.fPool.add(0) + if err != nil { + t.Fatal(err) + } + fid.file = af ctx, cancel := context.WithCancel(context.Background()) defer cancel() go sAttach(ctx, c, tc) for i, test := range tests { af.AuthOK = test.authOK if test.auth { - s.Auth = dammyAuth + c.s.Auth = dammyAuth } else { - s.Auth = nil + c.s.Auth = nil } req := &request{ifcall: test.input} tc <- req @@ -350,19 +349,13 @@ func TestSWalk(t *testing.T) { {&TWalk{Fid: 0, Newfid: 5, Wnames: []string{"unko", "unko"}}, 0, errors.New("not found")}, } - tc := make(chan *Req) - rc := make(chan *Req) - defer close(tc) - defer close(rc) - fp := newFidPool() - fid, err := fp.add(0) + c, tc, rc := setupConn(testfs) + fid, err := c.fPool.add(0) if err != nil { t.Fatal(err) } fid.omode = -1 fid.path = "." - s := &Server{fs: testfs} - c := &conn{s: s, respChan: rc, fPool: fp} ctx, cancel := context.WithCancel(context.Background()) defer cancel() go sWalk(ctx, c, tc) @@ -410,19 +403,13 @@ func TestSOpen(t *testing.T) { // is a directory {"dir", "glenda", &TOpen{Fid: 0, Mode: OWRITE}, &RError{}}, } - tc := make(chan *Req) - rc := make(chan *Req) - defer close(tc) - defer close(rc) - fp := newFidPool() - s := &Server{fs: testfs} - c := &conn{s: s, respChan: rc, fPool: fp, mSizeLock: new(sync.Mutex), msize: 1024} + c, tc, rc := setupConn(testfs) ctx, cancel := context.WithCancel(context.Background()) defer cancel() go sOpen(ctx, c, tc) for i, test := range tests { - fp.delete(0) - fid, err := fp.add(0) + c.fPool.delete(0) + fid, err := c.fPool.add(0) if err != nil { t.Error(i, err) continue