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:
A | fs_test.go | | | 277 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | server_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