diff options
Diffstat (limited to 'xgb_test.go')
| -rw-r--r-- | xgb_test.go | 151 |
1 files changed, 93 insertions, 58 deletions
diff --git a/xgb_test.go b/xgb_test.go index 7badfc4..45c6176 100644 --- a/xgb_test.go +++ b/xgb_test.go @@ -13,18 +13,22 @@ import ( "time" ) -type addr struct{} +type addr struct { + s string +} -func (_ addr) Network() string { return "" } -func (_ addr) String() string { return "" } +func (_ addr) Network() string { return "dummy" } +func (a addr) String() string { return a.s } -type server struct { +type serverBlocking struct { + addr addr control chan interface{} done chan struct{} } -func newServer() net.Conn { - s := &server{ +func newServerBlocking() net.Conn { + s := &serverBlocking{ + addr{"blocking server"}, make(chan interface{}), make(chan struct{}), } @@ -42,27 +46,27 @@ func newServer() net.Conn { return s } -func (_ *server) errClosed() error { +func (_ *serverBlocking) errClosed() error { return errors.New("closed") } -func (_ *server) errEOF() error { +func (_ *serverBlocking) errEOF() error { return io.EOF } -func (s *server) Write(b []byte) (int, error) { +func (s *serverBlocking) Write(b []byte) (int, error) { select { case <-s.done: } return 0, s.errClosed() } -func (s *server) Read(b []byte) (int, error) { +func (s *serverBlocking) Read(b []byte) (int, error) { select { case <-s.done: } return 0, s.errEOF() } -func (s *server) Close() error { +func (s *serverBlocking) Close() error { select { case s.control <- nil: <-s.done @@ -71,24 +75,32 @@ func (s *server) Close() error { return s.errClosed() } } -func (s *server) LocalAddr() net.Addr { return addr{} } -func (s *server) RemoteAddr() net.Addr { return addr{} } -func (s *server) SetDeadline(t time.Time) error { return nil } -func (s *server) SetReadDeadline(t time.Time) error { return nil } -func (s *server) SetWriteDeadline(t time.Time) error { return nil } +func (s *serverBlocking) LocalAddr() net.Addr { return s.addr } +func (s *serverBlocking) RemoteAddr() net.Addr { return s.addr } +func (s *serverBlocking) SetDeadline(t time.Time) error { return nil } +func (s *serverBlocking) SetReadDeadline(t time.Time) error { return nil } +func (s *serverBlocking) SetWriteDeadline(t time.Time) error { return nil } -// ispired by https://golang.org/src/runtime/debug/stack.go?s=587:606#L21 -// stack returns a formatted stack trace of all goroutines. -// It calls runtime.Stack with a large enough buffer to capture the entire trace. -func stack() []byte { - buf := make([]byte, 1024) - for { - n := runtime.Stack(buf, true) - if n < len(buf) { - return buf[:n] - } - buf = make([]byte, 2*len(buf)) +type serverWriteError struct { + *serverBlocking +} + +func newServerWriteError() net.Conn { + s := &serverWriteError{newServerBlocking().(*serverBlocking)} + s.addr.s = "server write error" + return s +} + +func (s *serverWriteError) Write(b []byte) (int, error) { + select { + case <-s.done: + return 0, s.errClosed() + default: } + return 0, s.errWrite() +} +func (_ *serverWriteError) errWrite() error { + return errors.New("write failed") } type goroutine struct { @@ -107,9 +119,23 @@ func leaksMonitor() leaks { } } -func (_ leaks) collectGoroutines() map[int]goroutine { +// ispired by https://golang.org/src/runtime/debug/stack.go?s=587:606#L21 +// stack returns a formatted stack trace of all goroutines. +// It calls runtime.Stack with a large enough buffer to capture the entire trace. +func (_ leaks) stack() []byte { + buf := make([]byte, 1024) + for { + n := runtime.Stack(buf, true) + if n < len(buf) { + return buf[:n] + } + buf = make([]byte, 2*len(buf)) + } +} + +func (l leaks) collectGoroutines() map[int]goroutine { res := make(map[int]goroutine) - stacks := bytes.Split(stack(), []byte{'\n', '\n'}) + stacks := bytes.Split(l.stack(), []byte{'\n', '\n'}) regexpId := regexp.MustCompile(`^\s*goroutine\s*(\d+)`) for _, st := range stacks { @@ -129,9 +155,14 @@ func (_ leaks) collectGoroutines() map[int]goroutine { if _, ok := res[id]; ok { panic("2 goroutines with same id: " + strconv.Itoa(id)) } + name := strings.TrimSpace(string(lines[1])) + + //filter out our stack routine + if strings.Contains(name, "xgb.leaks.stacks") { + continue + } - //TODO filter out test routines, stack routine - res[id] = goroutine{id, strings.TrimSpace(string(lines[1])), st} + res[id] = goroutine{id, name, st} } return res } @@ -144,7 +175,8 @@ func (l leaks) checkTesting(t *testing.T) { } } leakTimeout := time.Second - t.Logf("possible goroutine leakage, waiting %v", leakTimeout) + time.Sleep(leakTimeout) + //t.Logf("possible goroutine leakage, waiting %v", leakTimeout) goroutines := l.collectGoroutines() if len(l.goroutines) == len(goroutines) { return @@ -160,35 +192,38 @@ func (l leaks) checkTesting(t *testing.T) { func TestConnOpenClose(t *testing.T) { - //t.Logf("creating new dummy blocking server") - s := newServer() - defer func() { - if err := s.Close(); err != nil { - t.Errorf("server closing error: %v", err) - } - }() - //t.Logf("new server created: %v", s) + testCases := []func() net.Conn{ + // newServerBlocking, // i'm not ready to handle this yet + newServerWriteError, + } + for _, tc := range testCases { + lm := leaksMonitor() + serverConn := tc() - defer leaksMonitor().checkTesting(t) + t.Run(serverConn.LocalAddr().String(), func(t *testing.T) { + c, err := postNewConn(&Conn{conn: serverConn}) + if err != nil { + t.Fatalf("connect error: %v", err) + } + //t.Logf("connection to server created: %v", c) - c, err := postNewConn(&Conn{conn: s}) - if err != nil { - t.Fatalf("connect error: %v", err) - } - //t.Logf("connection to server created: %v", c) + closeErr := make(chan struct{}) + go func() { + //t.Logf("closing connection to server") + c.Close() + close(closeErr) + }() + closeTimeout := time.Second + select { + case <-closeErr: + //t.Logf("connection to server closed") + case <-time.After(closeTimeout): + t.Errorf("*Conn.Close() not responded for %v", closeTimeout) + } + }) - closeErr := make(chan struct{}) - go func() { - //t.Logf("closing connection to server") - c.Close() - close(closeErr) - }() - closeTimeout := time.Second - select { - case <-closeErr: - //t.Logf("connection to server closed") - case <-time.After(closeTimeout): - t.Errorf("*Conn.Close() not responded for %v", closeTimeout) + serverConn.Close() + lm.checkTesting(t) } } |
