Skip to content

Commit

Permalink
test: Add TestTcreate (partially failing)
Browse files Browse the repository at this point in the history
When a styxfile.Directory is passed as the result of a Tcreate call
and the underlying object does not have a Stat method,
statGuess does not manage to detect any underlying fs.FileInfo methods,
(e.g.: Name, Mode, ...) which means that for opened directories,
the results of Tstat are always an incorrectly guessed fallback value
  • Loading branch information
zakkor committed Sep 15, 2022
1 parent df32e67 commit aefe08b
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 12 deletions.
1 change: 1 addition & 0 deletions internal/styxfile/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ func Stat(buf []byte, file Interface, name string, qid styxproto.Qid) (styxproto
return stat, nil
}

// FIXME: When statGuess is used with a styxfile.Directory, none of the stat methods are found,
type statGuess struct {
file Interface
name string
Expand Down
111 changes: 99 additions & 12 deletions server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"time"

"aqwari.net/net/styx/internal/netutil"
"aqwari.net/net/styx/internal/styxfile"
"aqwari.net/net/styx/styxproto"
)

Expand Down Expand Up @@ -86,25 +87,48 @@ func (emptyFS) Serve9P(s *Session) {
switch req := s.Request().(type) {
case Tstat:
if req.Path() == "/" {
req.Rstat(emptyDir(req.Path()), nil)
req.Rstat(emptyDir{emptyStatDir(req.Path())}, nil)
}
case Topen:
req.Ropen(emptyDir(req.Path()), nil)
req.Ropen(emptyDir{emptyStatDir(req.Path())}, nil)
}
}
}

type emptyDir string
type emptyStatFile string

// fs.FileInfo
func (s emptyStatFile) Mode() os.FileMode { return 0222 }
func (s emptyStatFile) IsDir() bool { return s.Mode().IsDir() }
func (s emptyStatFile) Name() string { return string(s) }
func (s emptyStatFile) Sys() interface{} { return nil }
func (s emptyStatFile) Size() int64 { return 0 }
func (s emptyStatFile) ModTime() time.Time { return time.Time{} }

type emptyStatDir string

// fs.FileInfo
func (s emptyStatDir) Mode() os.FileMode { return 0222 | os.ModeDir }
func (s emptyStatDir) IsDir() bool { return s.Mode().IsDir() }
func (s emptyStatDir) Name() string { return string(s) }
func (s emptyStatDir) Sys() interface{} { return nil }
func (s emptyStatDir) Size() int64 { return 0 }
func (s emptyStatDir) ModTime() time.Time { return time.Time{} }

type emptyFile struct{ emptyStatFile }

var _ styxfile.Interface = emptyFile{}

func (f emptyFile) ReadAt(p []byte, offset int64) (written int, err error) { return 0, io.EOF }

func (f emptyFile) WriteAt(p []byte, offset int64) (int, error) { return 0, styxfile.ErrNotSupported }

func (f emptyFile) Close() error { return nil }

type emptyDir struct{ emptyStatDir }

var _ styxfile.Directory = emptyDir{}

// os.FileInfo
func (d emptyDir) Mode() os.FileMode { return os.ModeDir }
func (d emptyDir) IsDir() bool { return d.Mode().IsDir() }
func (d emptyDir) Name() string { return string(d) }
func (d emptyDir) Sys() interface{} { return nil }
func (d emptyDir) Size() int64 { return 0 }
func (d emptyDir) ModTime() time.Time { return time.Time{} }

// styx.Directory
func (d emptyDir) Readdir(int) ([]os.FileInfo, error) { return nil, nil }

func chanServer(t *testing.T, handler Handler) (in, out chan styxproto.Msg) {
Expand Down Expand Up @@ -516,6 +540,69 @@ func TestWalkNonexistent(t *testing.T) {
})
}

func TestTcreate(t *testing.T) {
srv := testServer{test: t}

type expectedstat struct {
name string
mode os.FileMode
}
fidnames := map[uint32]expectedstat{
1: {name: "dir", mode: 0222 | os.ModeDir},
2: {name: "file", mode: 0222},
}

srv.callback = func(req, rsp styxproto.Msg) {
if _, ok := rsp.(styxproto.Rerror); ok {
t.Errorf("got %T response to %T", rsp, req)
}
if req, ok := req.(styxproto.Tstat); ok {
if rsp, ok := rsp.(styxproto.Rstat); !ok {
t.Errorf("got %T response to %T", rsp, req)
} else {
expected := fidnames[req.Fid()]
name := string(rsp.Stat().Name())
if name != expected.name {
t.Errorf("expected name to be %s, instead got %s", expected.name, name)
}
mode := styxfile.ModeOS(rsp.Stat().Mode())
if mode != expected.mode {
t.Errorf("expected mode to be %s, instead got %s", expected.mode, mode)
}
}
}
}
srv.handler = HandlerFunc(func(s *Session) {
for s.Next() {
switch req := s.Request().(type) {
case Tcreate:
t.Logf("Tcreate %s %s", req.Path(), req.NewPath())
var f any
if req.Mode.IsDir() {
f = emptyDir{emptyStatDir(req.Name)}
} else {
f = emptyFile{emptyStatFile(req.Name)}
}
req.Rcreate(f, nil)
case Twalk:
// Empty walks get automatically handled, no need to handle
case Tstat:
// Because Rcreate returns an opened file, Tstat is called on styxfile.Interface or styxfile.Directory,
// so it will use styxfile.Stat to get stat, no need to handle
}
}
})

srv.runMsg(func(enc *styxproto.Encoder) {
enc.Twalk(1, 0, 1)
enc.Tcreate(1, 1, "dir", 0222|styxproto.DMDIR, styxproto.DMREAD)
enc.Tstat(1, 1)
enc.Twalk(1, 0, 2)
enc.Tcreate(1, 2, "file", 0222, styxproto.DMREAD)
enc.Tstat(1, 2)
})
}

func blankQid() styxproto.Qid {
buf := make([]byte, styxproto.QidLen)
qid, _, err := styxproto.NewQid(buf, 0, 0, 0)
Expand Down

0 comments on commit aefe08b

Please sign in to comment.