package xgb import ( "errors" "fmt" "testing" "time" ) func TestConnOnNonBlockingDummyXServer(t *testing.T) { timeout := 10 * time.Millisecond checkedReply := func(wantError bool) func(*Conn) error { request := "reply" if wantError { request = "error" } return func(c *Conn) error { cookie := c.NewCookie(true, true) c.NewRequest([]byte(request), cookie) _, err := cookie.Reply() if wantError && err == nil { return errors.New(fmt.Sprintf("checked request \"%v\" with reply resulted in nil error, want some error", request)) } if !wantError && err != nil { return errors.New(fmt.Sprintf("checked request \"%v\" with reply resulted in error %v, want nil error", request, err)) } return nil } } checkedNoreply := func(wantError bool) func(*Conn) error { request := "noreply" if wantError { request = "error" } return func(c *Conn) error { cookie := c.NewCookie(true, false) c.NewRequest([]byte(request), cookie) err := cookie.Check() if wantError && err == nil { return errors.New(fmt.Sprintf("checked request \"%v\" with no reply resulted in nil error, want some error", request)) } if !wantError && err != nil { return errors.New(fmt.Sprintf("checked request \"%v\" with no reply resulted in error %v, want nil error", request, err)) } return nil } } uncheckedReply := func(wantError bool) func(*Conn) error { request := "reply" if wantError { request = "error" } return func(c *Conn) error { cookie := c.NewCookie(false, true) c.NewRequest([]byte(request), cookie) _, err := cookie.Reply() if err != nil { return errors.New(fmt.Sprintf("unchecked request \"%v\" with reply resulted in %v, want nil", request, err)) } return nil } } uncheckedNoreply := func(wantError bool) func(*Conn) error { request := "noreply" if wantError { request = "error" } return func(c *Conn) error { cookie := c.NewCookie(false, false) c.NewRequest([]byte(request), cookie) return nil } } event := func() func(*Conn) error { return func(c *Conn) error { _, err := c.conn.Write([]byte("event")) if err != nil { return errors.New(fmt.Sprintf("asked dummy server to send event, but resulted in error: %v\n", err)) } return err } } waitEvent := func(wantError bool) func(*Conn) error { return func(c *Conn) error { _, err := c.WaitForEvent() if wantError && err == nil { return errors.New(fmt.Sprintf("wait for event resulted in nil error, want some error")) } if !wantError && err != nil { return errors.New(fmt.Sprintf("wait for event resulted in error %v, want nil error", err)) } return nil } } checkClosed := func(c *Conn) error { select { case eoe, ok := <-c.eventChan: if ok { return fmt.Errorf("(*Conn).eventChan should be closed, but is not and returns %v", eoe) } case <-time.After(timeout): return fmt.Errorf("(*Conn).eventChan should be closed, but is not and was blocking for %v", timeout) } return nil } testCases := []struct { description string actions []func(*Conn) error }{ {"close", []func(*Conn) error{}, }, {"double close", []func(*Conn) error{ func(c *Conn) error { c.Close() return nil }, }, }, {"checked requests with reply", []func(*Conn) error{ checkedReply(false), checkedReply(true), checkedReply(false), checkedReply(true), }, }, {"checked requests no reply", []func(*Conn) error{ checkedNoreply(false), checkedNoreply(true), checkedNoreply(false), checkedNoreply(true), }, }, {"unchecked requests with reply", []func(*Conn) error{ uncheckedReply(false), uncheckedReply(true), waitEvent(true), uncheckedReply(false), event(), waitEvent(false), }, }, {"unchecked requests no reply", []func(*Conn) error{ uncheckedNoreply(false), uncheckedNoreply(true), waitEvent(true), uncheckedNoreply(false), event(), waitEvent(false), }, }, {"close with pending requests", []func(*Conn) error{ func(c *Conn) error { c.conn.(*dNC).ReadLock() defer c.conn.(*dNC).ReadUnlock() c.NewRequest([]byte("reply"), c.NewCookie(false, true)) c.Close() return nil }, checkClosed, }, }, {"unexpected conn close", []func(*Conn) error{ func(c *Conn) error { c.conn.Close() if ev, err := c.WaitForEvent(); ev != nil || err != nil { return fmt.Errorf("WaitForEvent() = (%v, %v), want (nil, nil)", ev, err) } return nil }, checkClosed, }, }, } for _, tc := range testCases { t.Run(tc.description, func(t *testing.T) { sclm := leaksMonitor("after server close, before testcase exit") defer sclm.checkTesting(t) s := newDummyNetConn("dummyX", newDummyXServerReplier()) defer s.Close() c, err := postNewConn(&Conn{conn: s}) if err != nil { t.Errorf("connect to dummy server error: %v", err) return } defer leaksMonitor("after actions end", sclm).checkTesting(t) for _, action := range tc.actions { if err := action(c); err != nil { t.Error(err) break } } recovered := false func() { defer func() { if err := recover(); err != nil { t.Errorf("(*Conn).Close() panic recover: %v", err) recovered = true } }() c.Close() }() if !recovered { if err := checkClosed(c); err != nil { t.Error(err) } } }) } }