commit f6789a34b394239544179747522339eca903913b
parent 6eb207b31e815193514fe458de7ef118741621f5
Author: Matsuda Kenji <info@mtkn.jp>
Date: Tue, 26 Dec 2023 09:52:41 +0900
add test for listener and speaker goroutines
Diffstat:
5 files changed, 143 insertions(+), 26 deletions(-)
diff --git a/auth_test.go b/auth_test.go
@@ -23,7 +23,6 @@ func TestAuth(t *testing.T) {
asr.Close()
asw.Close()
}()
- authDone := make(chan struct{})
conn.S.Auth = func(ctx context.Context, respc chan<- *lib9p.Req) chan<- *lib9p.Req {
authc := make(chan *lib9p.Req)
go func() {
@@ -44,7 +43,7 @@ func TestAuth(t *testing.T) {
W: acw,
R: acr,
}
- runAuth(ctx, t, r.Afid.File.(*lib9p.AuthFile), asr, asw, authDone)
+ runAuth(ctx, t, r.Afid.File.(*lib9p.AuthFile), asr, asw)
r.Ofcall = &lib9p.RAuth{Tag: ifcall.Tag, Aqid: aqid}
respc <- r
}
@@ -69,15 +68,14 @@ func TestAuth(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- _, err = conn.C.Write(ctx, 0, 0, 0, 5, []byte("kenji"))
+ _, err = conn.C.Write(ctx, 0, 0, 0, 8, []byte("password"))
if err != nil {
t.Fatal(err)
}
- _, err = conn.C.Write(ctx, 0, 0, 0, 8, []byte("password"))
+ _, err = conn.C.Read(ctx, 0, 0, 0, 1024)
if err != nil {
t.Fatal(err)
}
- <-authDone
_, err = conn.C.Attach(ctx, 0, 1, 0, "kenji", "")
if err != nil {
t.Fatal(err)
@@ -89,20 +87,39 @@ func TestAuth(t *testing.T) {
}
// Dumb state machine...
-func runAuth(ctx context.Context, t *testing.T, afile *lib9p.AuthFile, r io.Reader, w io.Writer, authDone chan<- struct{}) {
+func runAuth(ctx context.Context, t *testing.T, afile *lib9p.AuthFile, r io.Reader, w io.Writer) {
go func() {
buf := make([]byte, 10)
- r.Read(buf)
- t.Logf("read username: %s", string(buf))
- r.Read(buf)
- t.Logf("read password: %s", string(buf))
- afile.AuthOK = true
- close(authDone)
- t.Log("authenticated")
+ uname := "kenji"
+ password := "password"
+ state := 0
for {
- _, err := r.Read(buf)
- if err != nil {
- return
+ switch state {
+ case 0: // start
+ if afile.Uname != uname {
+ state = 2
+ break
+ }
+ n, err := r.Read(buf)
+ if err != nil {
+ t.Log(err)
+ break
+ }
+ if string(buf[:n]) == password {
+ state = 1
+ break
+ }
+ case 1: // accepted
+ afile.AuthOK = true
+ go func() { for { r.Read(buf) } }()
+ for {
+ w.Write([]byte("ok"))
+ }
+ case 2: // unknown user
+ go func() { for { r.Read(buf) } }()
+ for {
+ w.Write([]byte("unknown user"))
+ }
}
}
}()
diff --git a/fcall_test.go b/fcall_test.go
@@ -34,7 +34,7 @@ func TestFcallParse(t *testing.T) {
t.Errorf("unmarshal: %v", err)
continue
}
- t.Log(m)
+ //t.Log(m)
if !bytes.Equal(m.marshal(), mb) {
t.Errorf("data mismatch: %v, %v != %v", m, m.marshal(), mb)
}
diff --git a/req.go b/req.go
@@ -7,8 +7,8 @@ import (
// Req represents each requests.
type Req struct {
- Tag uint16
- Srv *Server
+ Tag uint16
+ Srv *Server
// Ifcall is incomming 9P message
Ifcall Msg
// Ofcall is response message to Ifcall.
@@ -21,7 +21,7 @@ type Req struct {
Cancel context.CancelFunc
// Done is used by time consuming goroutines to check whether the request
// is canceled.
- Done <-chan struct{}
+ Done <-chan struct{}
// listenErr is any error encountered while waiting for new 9P message.
listenErr error
// speakErrChan is used to report any error encountered while sending
@@ -57,6 +57,10 @@ func newReqPool() *ReqPool {
// Add allocates a Req with the specified tag in ReqPool rp.
// It returns (nil, ErrDupTag) if there is already a Req with the specified tag.
func (rp *ReqPool) add(tag uint16) (*Req, error) {
+ return reqPoolAdd(rp, tag)
+}
+
+var reqPoolAdd = func(rp *ReqPool, tag uint16) (*Req, error) {
rp.lock.Lock()
defer rp.lock.Unlock()
if _, ok := rp.m[tag]; ok {
diff --git a/server.go b/server.go
@@ -109,9 +109,8 @@ func (s *Server) setMSize(mSize uint32) {
}
// runListener runs the listener goroutine.
-// Listener goroutine reads 9P messages from r and allocats Req for each
-// of them, and sends it to the returned chan of *Req.
-// It reports any error to the returned chan of error.
+// Listener goroutine reads 9P messages from s.r by calling getReq
+// and allocats Req for each of them, and sends it to the server's listenChan.
func (s *Server) runListener(ctx context.Context) {
rc := make(chan *Req)
s.listenChan = rc
@@ -129,7 +128,7 @@ func (s *Server) runListener(ctx context.Context) {
// runSpeaker runs the speaker goroutine.
// Speaker goroutine wait for reply Requests from the returned channel,
-// and marshalls each of them into 9P messages and writes it to w.
+// and marshalls each of them into 9P messages and writes it to s.w.
func (s *Server) runSpeaker(ctx context.Context) {
rc := make(chan *Req)
s.speakChan = rc
@@ -153,10 +152,10 @@ func (s *Server) runSpeaker(ctx context.Context) {
}()
}
-// GetReq reads 9P message from r, allocates Req and returns it.
+// GetReq reads 9P message from s.r, allocates Req and returns it.
// Any error it encounters is embedded into the Req struct.
// This function is called only by the server's listener goroutine,
-// and does not need to lock r.
+// and does not need to lock s.r.
// The argument ctx is used to set the request's context.
func (s *Server) getReq(ctx context.Context) *Req {
ifcall, err := RecvMsg(s.r)
diff --git a/server2_test.go b/server2_test.go
@@ -0,0 +1,97 @@
+package lib9p
+
+import (
+ "bytes"
+ "context"
+ "io"
+ "os"
+ "reflect"
+ "testing"
+)
+
+// TestRunListener tests if the listener goroutine receives 9P messages
+// and send them through the server's listenChan channel.
+// It also checks whether the listener goroutine returns by canceling
+// ctx, by checking if the server's listenChan is closed.
+func TestRunListener(t *testing.T) {
+ tFile, err := os.Open("testdata/test_Tmsg.dat")
+ if err != nil {
+ t.Fatalf("open file: %v", err)
+ }
+ defer tFile.Close()
+ tFile2, err := os.Open("testdata/test_Tmsg.dat")
+ if err != nil {
+ t.Fatalf("open file: %v", err)
+ }
+ defer tFile2.Close()
+ s := &Server{
+ rPool: newReqPool(),
+ r: tFile,
+ }
+ oldReqPoolAdd := reqPoolAdd
+ defer func() { reqPoolAdd = oldReqPoolAdd }()
+ reqPoolAdd = func(*ReqPool, uint16) (*Req, error) { return &Req{}, nil }
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ s.runListener(ctx)
+ for {
+ want, err := RecvMsg(tFile2)
+ if err == io.EOF {
+ break
+ } else if err != nil {
+ t.Fatalf("recvmsg: %v", err)
+ }
+ r := <-s.listenChan
+ if r.listenErr != nil {
+ t.Fatalf("listenErr: %v", r.listenErr)
+ }
+ got := r.Ifcall
+ if !reflect.DeepEqual(want, got) {
+ t.Errorf("listener modified message:\n\twant: %v\n\tgot: %v",
+ want, got)
+ }
+ }
+ cancel()
+ if _, ok := <-s.listenChan; ok {
+ t.Error("listenChan not closed")
+ }
+}
+
+// TestRunSpeaker tests if the speaker goroutine receives 9P messages
+// and send them through the server's speakChan channel.
+func TestRunSpeaker(t *testing.T) {
+ rFile, err := os.Open("testdata/test_Rmsg.dat")
+ if err != nil {
+ t.Fatalf("open file: %v", err)
+ }
+ defer rFile.Close()
+ r, w := io.Pipe()
+ s := &Server{w: w}
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ s.runSpeaker(ctx)
+ for {
+ want, err := readMsg(rFile)
+ if err == io.EOF {
+ break
+ } else if err != nil {
+ t.Fatalf("readmsg: %v", err)
+ }
+ msg, err := unmarshal(want)
+ if err != nil {
+ t.Fatalf("unmarshal %v", err)
+ }
+ s.speakChan <- &Req{Ofcall: msg, speakErrChan: make(chan error)}
+ got := make([]byte, len(want))
+ _, err = io.ReadFull(r, got)
+ if err != nil {
+ t.Fatalf("readfull: %v", err)
+ }
+ if !bytes.Equal(want, got) {
+ t.Errorf("speaker modified message:\n\twant: %v\n\tgot: %v",
+ want, got)
+ }
+ }
+}
+
+func TestGetReq(t *testing.T) {}