Skip to content

Commit 05fe863

Browse files
committed
test: Add TestTcreate (partially failing)
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
1 parent 7e817ef commit 05fe863

File tree

2 files changed

+102
-12
lines changed

2 files changed

+102
-12
lines changed

internal/styxfile/file.go

+2
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ func Stat(buf []byte, file Interface, name string, qid styxproto.Qid) (styxproto
130130
return stat, nil
131131
}
132132

133+
// FIXME: When statGuess is used with a styxfile.Directory, none of the stat methods are found,
134+
// and we fall back on incorrectly using guessed values every time.
133135
type statGuess struct {
134136
file Interface
135137
name string

server_test.go

+100-12
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"time"
1515

1616
"aqwari.net/net/styx/internal/netutil"
17+
"aqwari.net/net/styx/internal/styxfile"
1718
"aqwari.net/net/styx/styxproto"
1819
)
1920

@@ -86,25 +87,48 @@ func (emptyFS) Serve9P(s *Session) {
8687
switch req := s.Request().(type) {
8788
case Tstat:
8889
if req.Path() == "/" {
89-
req.Rstat(emptyDir(req.Path()), nil)
90+
req.Rstat(emptyDir{emptyStatDir(req.Path())}, nil)
9091
}
9192
case Topen:
92-
req.Ropen(emptyDir(req.Path()), nil)
93+
req.Ropen(emptyDir{emptyStatDir(req.Path())}, nil)
9394
}
9495
}
9596
}
9697

97-
type emptyDir string
98+
type emptyStatFile string
99+
100+
// fs.FileInfo
101+
func (s emptyStatFile) Mode() os.FileMode { return 0222 }
102+
func (s emptyStatFile) IsDir() bool { return s.Mode().IsDir() }
103+
func (s emptyStatFile) Name() string { return string(s) }
104+
func (s emptyStatFile) Sys() interface{} { return nil }
105+
func (s emptyStatFile) Size() int64 { return 0 }
106+
func (s emptyStatFile) ModTime() time.Time { return time.Time{} }
107+
108+
type emptyStatDir string
109+
110+
// fs.FileInfo
111+
func (s emptyStatDir) Mode() os.FileMode { return 0222 | os.ModeDir }
112+
func (s emptyStatDir) IsDir() bool { return s.Mode().IsDir() }
113+
func (s emptyStatDir) Name() string { return string(s) }
114+
func (s emptyStatDir) Sys() interface{} { return nil }
115+
func (s emptyStatDir) Size() int64 { return 0 }
116+
func (s emptyStatDir) ModTime() time.Time { return time.Time{} }
117+
118+
type emptyFile struct{ emptyStatFile }
119+
120+
var _ styxfile.Interface = emptyFile{}
121+
122+
func (f emptyFile) ReadAt(p []byte, offset int64) (written int, err error) { return 0, io.EOF }
123+
124+
func (f emptyFile) WriteAt(p []byte, offset int64) (int, error) { return 0, styxfile.ErrNotSupported }
125+
126+
func (f emptyFile) Close() error { return nil }
127+
128+
type emptyDir struct{ emptyStatDir }
129+
130+
var _ styxfile.Directory = emptyDir{}
98131

99-
// os.FileInfo
100-
func (d emptyDir) Mode() os.FileMode { return os.ModeDir }
101-
func (d emptyDir) IsDir() bool { return d.Mode().IsDir() }
102-
func (d emptyDir) Name() string { return string(d) }
103-
func (d emptyDir) Sys() interface{} { return nil }
104-
func (d emptyDir) Size() int64 { return 0 }
105-
func (d emptyDir) ModTime() time.Time { return time.Time{} }
106-
107-
// styx.Directory
108132
func (d emptyDir) Readdir(int) ([]os.FileInfo, error) { return nil, nil }
109133

110134
func chanServer(t *testing.T, handler Handler) (in, out chan styxproto.Msg) {
@@ -516,6 +540,70 @@ func TestWalkNonexistent(t *testing.T) {
516540
})
517541
}
518542

543+
func TestTcreate(t *testing.T) {
544+
srv := testServer{test: t}
545+
546+
type expectedstat struct {
547+
name string
548+
mode os.FileMode
549+
}
550+
fidnames := map[uint32]expectedstat{
551+
1: {name: "dir", mode: 0222 | os.ModeDir},
552+
2: {name: "file", mode: 0222},
553+
}
554+
555+
srv.callback = func(req, rsp styxproto.Msg) {
556+
if _, ok := rsp.(styxproto.Rerror); ok {
557+
t.Errorf("got %T response to %T", rsp, req)
558+
}
559+
if req, ok := req.(styxproto.Tstat); ok {
560+
if rsp, ok := rsp.(styxproto.Rstat); !ok {
561+
t.Errorf("got %T response to %T", rsp, req)
562+
} else {
563+
expected := fidnames[req.Fid()]
564+
name := string(rsp.Stat().Name())
565+
if name != expected.name {
566+
t.Errorf("expected name to be %s, instead got %s", expected.name, name)
567+
}
568+
// FIXME: For directories, the mode does not match
569+
mode := styxfile.ModeOS(rsp.Stat().Mode())
570+
if mode != expected.mode {
571+
t.Errorf("expected mode to be %s, instead got %s", expected.mode, mode)
572+
}
573+
}
574+
}
575+
}
576+
srv.handler = HandlerFunc(func(s *Session) {
577+
for s.Next() {
578+
switch req := s.Request().(type) {
579+
case Tcreate:
580+
t.Logf("Tcreate %s %s", req.Path(), req.NewPath())
581+
var f any
582+
if req.Mode.IsDir() {
583+
f = emptyDir{emptyStatDir(req.Name)}
584+
} else {
585+
f = emptyFile{emptyStatFile(req.Name)}
586+
}
587+
req.Rcreate(f, nil)
588+
case Twalk:
589+
// Empty walks get automatically handled, no need to handle
590+
case Tstat:
591+
// Because Rcreate returns an opened file, Tstat is called on styxfile.Interface or styxfile.Directory,
592+
// so it will use styxfile.Stat to get stat, no need to handle
593+
}
594+
}
595+
})
596+
597+
srv.runMsg(func(enc *styxproto.Encoder) {
598+
enc.Twalk(1, 0, 1)
599+
enc.Tcreate(1, 1, "dir", 0222|styxproto.DMDIR, styxproto.DMREAD)
600+
enc.Tstat(1, 1)
601+
enc.Twalk(1, 0, 2)
602+
enc.Tcreate(1, 2, "file", 0222, styxproto.DMREAD)
603+
enc.Tstat(1, 2)
604+
})
605+
}
606+
519607
func blankQid() styxproto.Qid {
520608
buf := make([]byte, styxproto.QidLen)
521609
qid, _, err := styxproto.NewQid(buf, 0, 0, 0)

0 commit comments

Comments
 (0)