lib9p

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

commit 394806bf11cc201fdb8abcc731966460e485d3dd
parent 8a667df91124da092c26a83a212afbbdd731d72c
Author: Matsuda Kenji <info@mtkn.jp>
Date:   Thu,  4 Jan 2024 14:00:02 +0900

delete testfs (wip)

Diffstat:
Mclient/client_test.go | 83+++++++++++++++++--------------------------------------------------------------
Aclient/conn_test.go | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Mclient/file_test.go | 59++++++++++++++++++++++-------------------------------------
Aclient/fs_test.go | 254+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dtestfs/conn.go | 51---------------------------------------------------
Dtestfs/fs.go | 254-------------------------------------------------------------------------------
6 files changed, 346 insertions(+), 407 deletions(-)

diff --git a/client/client_test.go b/client/client_test.go @@ -1,4 +1,4 @@ -package client_test +package client import ( "context" @@ -6,97 +6,50 @@ import ( "testing" "git.mtkn.jp/lib9p" - "git.mtkn.jp/lib9p/client" - "git.mtkn.jp/lib9p/testfs" ) -const ( - mSize = 8192 - uname = "kenji" -) - -// the following tests are test for both client and server. -// should be moved to propper file. -func TestTransaction(t *testing.T) { - conn := testfs.SetupConn() - defer conn.Close() - bg := context.Background() - rmsize, rversion, err := conn.C.Version(bg, 0, mSize, "9P2000") - if err != nil { - t.Log(err) - } else { - t.Log(&lib9p.RVersion{Msize: rmsize, Version: rversion}) - } - rauth, err := conn.C.Auth(bg, 0, 0, "kenji", "") - if err != nil { - t.Log(err) - } else { - t.Log(rauth) - } - rattach, err := conn.C.Attach(bg, 0, 0, lib9p.NOFID, "kenji", "") - if err != nil { - t.Log(err) - } else { - t.Log(rattach) - } -} - -func TestClient2(t *testing.T) { +func setupClient(fs lib9p.FS) (*Client, context.CancelFunc) { cr, sw := io.Pipe() sr, cw := io.Pipe() - server := lib9p.NewServer(testfs.Fsys) - //server.Chatty() - go server.Serve(context.Background(), sr, sw) - fs, err := client.Mount(cr, cw, "kenji", "") - if err != nil { - t.Errorf("mount: %v", err) - } - a0, err := fs.OpenFile("a", lib9p.OREAD) - if err != nil { - t.Errorf("open: %v", err) - } - t.Log(a0) - a := a0.(*client.File) - b := make([]byte, 64) - n, err := a.Read(b) - if err != nil { - t.Errorf("read: %v", err) - } - t.Log(string(b[:n])) + s := lib9p.NewServer(fs) + ctx, cancel := context.WithCancel(context.Background()) + go s.Serve(ctx, sr, sw) + c := NewClient(8*1024, "kenji", cr, cw) + return c, cancel } func TestDupTag(t *testing.T) { - conn := testfs.SetupConn() - defer conn.Close() - testfs.Fsys.Slow = true - defer func() { testfs.Fsys.Slow = false }() + c, cancel := setupClient(testfs) + defer cancel() + testfs.slow = true + defer func() { testfs.slow = false }() bg := context.Background() - _, _, err := conn.C.Version(bg, 0, 8*1024, "9P2000") + _, _, err := c.Version(bg, 0, 8*1024, "9P2000") if err != nil { t.Fatal(err) } - _, err = conn.C.Attach(bg, 0, 0, lib9p.NOFID, "kenji", "") + _, err = c.Attach(bg, 0, 0, lib9p.NOFID, "kenji", "") if err != nil { t.Fatal(err) } wname := []string{"a"} - wqid, err := conn.C.Walk(bg, 0, 0, 1, wname) + wqid, err := c.Walk(bg, 0, 0, 1, wname) if err != nil { t.Fatal(err) } else if len(wqid) != len(wname) { t.Fatal("file not found") } - _, _, err = conn.C.Open(bg, 0, 1, lib9p.OREAD) + _, _, err = c.Open(bg, 0, 1, lib9p.OREAD) if err != nil { t.Fatal(err) } ctx, cancel := context.WithCancel(bg) defer cancel() go func() { - _, err = conn.C.Read(ctx, 0, 1, 0, 1024) + _, err = c.Read(ctx, 0, 1, 0, 1024) }() - <-testfs.Fsys.Waitc - _, err = conn.C.Stat(bg, 0, 1) + <-testfs.waitc + _, err = c.Stat(bg, 0, 1) if err == nil { t.Error("dup tag not reported") } diff --git a/client/conn_test.go b/client/conn_test.go @@ -0,0 +1,51 @@ +package client +/* +import ( + "context" + "io" + + "git.mtkn.jp/lib9p" +) + +type Conn struct { + S *lib9p.Server + C *Client + cr, sr *io.PipeReader + cw, sw *io.PipeWriter + cancel context.CancelFunc +} + +// SetupTestConn setups a connection between a server and a client and +// returns those. +func SetupConn() *Conn { + const ( + mSize = 8 * 1024 + uname = "kenji" + ) + cr, sw := io.Pipe() + sr, cw := io.Pipe() + // TODO: fix the inconsistency of server and client api. + s := lib9p.NewServer(Fsys) + clnt := NewClient(mSize, uname, cr, cw) + ctx, cancel := context.WithCancel(context.Background()) + go s.Serve(ctx, sr, sw) + return &Conn{ + S: s, + C: clnt, + cr: cr, + cw: cw, + sr: sr, + sw: sw, + cancel: cancel, + } +} + +func (conn *Conn) Close() { + conn.cancel() + conn.C.Stop() + conn.cw.Close() + conn.cr.Close() + conn.sw.Close() + conn.sr.Close() +} +*/ +\ No newline at end of file diff --git a/client/file_test.go b/client/file_test.go @@ -1,65 +1,50 @@ -package client_test +package client import ( "context" + "fmt" "io" "path" "testing" "git.mtkn.jp/lib9p" - "git.mtkn.jp/lib9p/client" - "git.mtkn.jp/lib9p/testfs" ) -type FS struct { - *client.FS - cr, sr *io.PipeReader - cw, sw *io.PipeWriter - cancel context.CancelFunc -} - -func mountTestFS(t *testing.T) *FS { +// cancel is to stop the server. +func mount(fs lib9p.FS) (cfs *FS, cancel context.CancelFunc, err error) { cr, sw := io.Pipe() sr, cw := io.Pipe() - s := lib9p.NewServer(testfs.Fsys) + s := lib9p.NewServer(fs) ctx, cancel := context.WithCancel(context.Background()) go s.Serve(ctx, sr, sw) - fsys, err := client.Mount(cr, cw, "ken", "") + cfs, err = Mount(cr, cw, "glenda", "") if err != nil { - t.Fatal(err) + return nil, nil, fmt.Errorf("mount: %v", err) } - return &FS{fsys, cr, sr, cw, sw, cancel} -} - -func (fsys *FS) unmount() { - fsys.Unmount() - fsys.cancel() - fsys.cr.Close() - fsys.cw.Close() - fsys.sr.Close() - fsys.sw.Close() + return cfs, cancel, nil } // TestStat tests whether Stat returns the same lib9p.Stat as testfs.Fsys defines. // TODO: work in progress. func TestStat(t *testing.T) { - fsys := mountTestFS(t) - defer fsys.unmount() - } // TestReadDir tests whether ReadDir returns the same dir entries as testfs.Fsys // has. func TestReadDir(t *testing.T) { - fsys := mountTestFS(t) - defer fsys.unmount() - testReadDir(t, fsys, testfs.Fsys, ".") + cfs, cancel, err := mount(testfs) + if err != nil { + t.Fatal(err) + } + defer cancel() + defer cfs.Unmount() + testReadDir(t, cfs, testfs, ".") } -func testReadDir(t *testing.T, cfs *FS, tfs *testfs.FS, cwd string) { +func testReadDir(t *testing.T, cfs *FS, tfs *testFS, cwd string) { cf, err := cfs.Open(cwd) if err != nil { - t.Fatalf("open: %v", err) + t.Fatalf("open %s: %v", cwd, err) } defer cf.Close() st, err := cf.Stat() @@ -69,7 +54,7 @@ func testReadDir(t *testing.T, cfs *FS, tfs *testfs.FS, cwd string) { if !st.IsDir() { return } - de, err := cf.(*client.File).ReadDir(-1) + de, err := cf.(*File).ReadDir(-1) if err != nil && err != io.EOF { t.Fatalf("readdir: %v", err) } @@ -77,14 +62,14 @@ func testReadDir(t *testing.T, cfs *FS, tfs *testfs.FS, cwd string) { if err != nil { t.Fatalf("open: %v", err) } - if len(de) != len(tf.(*testfs.File).Children) { + if len(de) != len(tf.(*testFile).children) { t.Fatalf("number of directory entries does not match.: %d != %d", - len(de), len(tf.(*testfs.File).Children)) + len(de), len(tf.(*testFile).children)) } L: for _, f := range de { - for _, c := range tf.(*testfs.File).Children { - if c.St.Name == f.Name() { + for _, c := range tf.(*testFile).children { + if c.stat.Name == f.Name() { childPath := path.Join(cwd, f.Name()) testReadDir(t, cfs, tfs, childPath) continue L diff --git a/client/fs_test.go b/client/fs_test.go @@ -0,0 +1,254 @@ +package client + +import ( + "bytes" + "fmt" + "io/fs" + "strings" + + "git.mtkn.jp/lib9p" +) + +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 lib9p.Stat +} + +func (f *testFile) Stat() (*lib9p.FileInfo, error) { + return &lib9p.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 lib9p.OpenMode) (lib9p.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: lib9p.Stat{ + Qid: lib9p.Qid{Path: 0, Type: lib9p.QTDIR}, + Mode: lib9p.FileMode(fs.ModeDir | 0755), + Name: "root", + Uid: "glenda", + Gid: "glenda", + Muid: "glenda", + }, + children: []*testFile{ + &testFile{ + content: []byte("a\n"), + stat: lib9p.Stat{ + Qid: lib9p.Qid{Path: 1, Type: lib9p.QTFILE}, + Mode: lib9p.FileMode(0644), + Name: "a", + Uid: "glenda", + Gid: "glenda", + Muid: "glenda", + }, + }, + &testFile{ + content: []byte("b\n"), + stat: lib9p.Stat{ + Qid: lib9p.Qid{Path: 2, Type: lib9p.QTFILE}, + Mode: lib9p.FileMode(0600), + Name: "b", + Uid: "ken", + Gid: "ken", + Muid: "ken", + }, + }, + &testFile{ + stat: lib9p.Stat{ + Qid: lib9p.Qid{Path: 3, Type: lib9p.QTDIR}, + Mode: lib9p.FileMode(fs.ModeDir | 0755), + Name: "dir", + Uid: "rob", + Gid: "rob", + Muid: "rob", + }, + children: []*testFile{ + &testFile{ + content: []byte("unko\n"), + stat: lib9p.Stat{ + Qid: lib9p.Qid{Path: 4, Type: lib9p.QTFILE}, + Mode: lib9p.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) + } +} diff --git a/testfs/conn.go b/testfs/conn.go @@ -1,51 +0,0 @@ -package testfs - -import ( - "context" - "io" - - "git.mtkn.jp/lib9p" - "git.mtkn.jp/lib9p/client" -) - -type Conn struct { - S *lib9p.Server - C *client.Client - cr, sr *io.PipeReader - cw, sw *io.PipeWriter - cancel context.CancelFunc -} - -// SetupTestConn setups a connection between a server and a client and -// returns those. -func SetupConn() *Conn { - const ( - mSize = 8 * 1024 - uname = "kenji" - ) - cr, sw := io.Pipe() - sr, cw := io.Pipe() - // TODO: fix the inconsistency of server and client api. - s := lib9p.NewServer(Fsys) - clnt := client.NewClient(mSize, uname, cr, cw) - ctx, cancel := context.WithCancel(context.Background()) - go s.Serve(ctx, sr, sw) - return &Conn{ - S: s, - C: clnt, - cr: cr, - cw: cw, - sr: sr, - sw: sw, - cancel: cancel, - } -} - -func (conn *Conn) Close() { - conn.cancel() - conn.C.Stop() - conn.cw.Close() - conn.cr.Close() - conn.sw.Close() - conn.sr.Close() -} diff --git a/testfs/fs.go b/testfs/fs.go @@ -1,254 +0,0 @@ -package testfs - -import ( - "bytes" - "fmt" - "io/fs" - "strings" - - "git.mtkn.jp/lib9p" -) - -type File struct { - // Fsys is the FS this File belongs to. - Fsys *FS - // Parent is the parent directory this File located at. - Parent *File - // Children is the child files this File has. - // It is nil if this File is not a directory - Children []*File - // Content is the content of this File. - // It is nil if this File is a directory - Content []byte - // Reader is used if this File is not a directory and - // when this File is read. - // It contains Content. - Reader *bytes.Reader - // St is the Stat of this File. - St lib9p.Stat -} - -func (f *File) Stat() (*lib9p.FileInfo, error) { - return &lib9p.FileInfo{Stat: f.St}, nil -} - -func (f *File) Close() error { - f.Reader = nil - return nil -} - -func (f *File) Read(b []byte) (int, error) { - if f.Fsys.Slow { - f.Fsys.Waitc <- struct{}{} - <-f.Fsys.Waitc - } - return f.Reader.Read(b) -} - -func (f *File) ReadAt(b []byte, off int64) (n int, err error) { - if f.Fsys.Slow { - f.Fsys.Waitc <- struct{}{} - <-f.Fsys.Waitc - } - return f.Reader.ReadAt(b, off) -} - -func (f *File) 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 *File) WriteAt(p []byte, off int64) (int, error) { - if f.Fsys.Slow { - f.Fsys.Waitc <- struct{}{} - } - if f.Reader == 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.Reader.Reset(f.Content) - if f.Fsys.Slow { - <-f.Fsys.Waitc - } - return len(p), nil -} - -type FS struct { - // Root is the root File of this file system. - Root *File - // If Slow is true, each function on files of this FS 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 *FS) OpenFile(path string, omode lib9p.OpenMode) (lib9p.File, error) { - wnames := strings.Split(path, "/") - f, err := fs.Walk(wnames) - if err != nil { - return nil, fmt.Errorf("walk: %v", err) - } - f.Reader = bytes.NewReader(f.Content) - return f, nil -} - -func (fs *FS) IsGroupLeader(gid, uid string) bool { - g, ok := fs.groups[gid] - if !ok { - return false - } - return g.leader == uid -} - -func (fs *FS) IsGroupMember(gid, uid string) bool { - g, ok := fs.groups[gid] - if !ok { - return false - } - return g.members[uid] -} - -func (fs *FS) Walk(wnames []string) (*File, 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.St.Name == name { - cwd = child - continue L - } - } - return nil, fmt.Errorf("%s not found", strings.Join(wnames[:i+1], "/")) - } - return cwd, nil -} - -func (fs *FS) WalkPath(pathname string) (*File, error) { - wnames := strings.Split(pathname, "/") - return fs.Walk(wnames) -} - -var Fsys *FS - -func init() { - Fsys = &FS{ - Root: &File{ - St: lib9p.Stat{ - Qid: lib9p.Qid{Path: 0, Type: lib9p.QTDIR}, - Mode: lib9p.FileMode(fs.ModeDir | 0755), - Name: "root", - Uid: "glenda", - Gid: "glenda", - Muid: "glenda", - }, - Children: []*File{ - &File{ - Content: []byte("a\n"), - St: lib9p.Stat{ - Qid: lib9p.Qid{Path: 1, Type: lib9p.QTFILE}, - Mode: lib9p.FileMode(0644), - Name: "a", - Uid: "glenda", - Gid: "glenda", - Muid: "glenda", - }, - }, - &File{ - Content: []byte("b\n"), - St: lib9p.Stat{ - Qid: lib9p.Qid{Path: 2, Type: lib9p.QTFILE}, - Mode: lib9p.FileMode(0600), - Name: "b", - Uid: "ken", - Gid: "ken", - Muid: "ken", - }, - }, - &File{ - St: lib9p.Stat{ - Qid: lib9p.Qid{Path: 3, Type: lib9p.QTDIR}, - Mode: lib9p.FileMode(fs.ModeDir | 0755), - Name: "dir", - Uid: "rob", - Gid: "rob", - Muid: "rob", - }, - Children: []*File{ - &File{ - Content: []byte("unko\n"), - St: lib9p.Stat{ - Qid: lib9p.Qid{Path: 4, Type: lib9p.QTFILE}, - Mode: lib9p.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(Fsys, 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 *FS, file *File) { - 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) - } -}