diff options
Diffstat (limited to 'xgb_test.go')
| -rw-r--r-- | xgb_test.go | 174 |
1 files changed, 7 insertions, 167 deletions
diff --git a/xgb_test.go b/xgb_test.go index 6931c3d..19ed307 100644 --- a/xgb_test.go +++ b/xgb_test.go @@ -1,139 +1,14 @@ package xgb import ( - "bytes" "errors" "fmt" - "regexp" - "runtime" - "strconv" - "strings" "testing" "time" ) -type goroutine struct { - id int - name string - stack []byte -} - -type leaks struct { - name string - goroutines map[int]goroutine - report []*leaks -} - -func leaksMonitor(name string, monitors ...*leaks) *leaks { - return &leaks{ - name, - leaks{}.collectGoroutines(), - monitors, - } -} - -// 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(l.stack(), []byte{'\n', '\n'}) - - regexpId := regexp.MustCompile(`^\s*goroutine\s*(\d+)`) - for _, st := range stacks { - lines := bytes.Split(st, []byte{'\n'}) - if len(lines) < 2 { - panic("routine stach has less tnan two lines: " + string(st)) - } - - idMatches := regexpId.FindSubmatch(lines[0]) - if len(idMatches) < 2 { - panic("no id found in goroutine stack's first line: " + string(lines[0])) - } - id, err := strconv.Atoi(string(idMatches[1])) - if err != nil { - panic("converting goroutine id to number error: " + err.Error()) - } - 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.stack") { - continue - } - - res[id] = goroutine{id, name, st} - } - return res -} - -func (l leaks) leakingGoroutines() []goroutine { - goroutines := l.collectGoroutines() - res := []goroutine{} - for id, gr := range goroutines { - if _, ok := l.goroutines[id]; ok { - continue - } - res = append(res, gr) - } - return res -} -func (l leaks) checkTesting(t *testing.T) { - if len(l.leakingGoroutines()) == 0 { - return - } - leakTimeout := time.Second - time.Sleep(leakTimeout) - //t.Logf("possible goroutine leakage, waiting %v", leakTimeout) - grs := l.leakingGoroutines() - for _, gr := range grs { - t.Errorf("%s: %s is leaking", l.name, gr.name) - //t.Errorf("%s: %s is leaking\n%v", l.name, gr.name, string(gr.stack)) - } - for _, rl := range l.report { - rl.ignoreLeak(grs...) - } -} -func (l *leaks) ignoreLeak(grs ...goroutine) { - for _, gr := range grs { - l.goroutines[gr.id] = gr - } -} - -type dNCEvent struct{} - -func (_ dNCEvent) Bytes() []byte { return nil } -func (_ dNCEvent) String() string { return "dummy X server event" } - -type dNCError struct { - seqId uint16 -} - -func (e dNCError) SequenceId() uint16 { return e.seqId } -func (_ dNCError) BadId() uint32 { return 0 } -func (_ dNCError) Error() string { return "dummy X server error reply" } - func TestConnOnNonBlockingDummyXServer(t *testing.T) { - timeout := time.Millisecond - NewErrorFuncs[255] = func(buf []byte) Error { - return dNCError{Get16(buf[2:])} - } - NewEventFuncs[128&127] = func(buf []byte) Event { - return dNCEvent{} - } + timeout := 10 * time.Millisecond checkedReply := func(wantError bool) func(*Conn) error { request := "reply" if wantError { @@ -307,45 +182,10 @@ func TestConnOnNonBlockingDummyXServer(t *testing.T) { } for _, tc := range testCases { t.Run(tc.description, func(t *testing.T) { - tclm := leaksMonitor("test case " + tc.description) - defer tclm.checkTesting(t) - - seqId := uint16(1) - incrementSequenceId := func() { - // this has to be the same algorithm as in (*Conn).generateSeqIds - if seqId == uint16((1<<16)-1) { - seqId = 0 - } else { - seqId++ - } - } - dummyXreplyer := func(request []byte) []byte { - //fmt.Printf("dummyXreplyer got request: %s\n", string(request)) - res := make([]byte, 32) - switch string(request) { - case "event": - res[0] = 128 - //fmt.Printf("dummyXreplyer sent response: %v\n", res) - return res - case "error": - res[0] = 0 // error - res[1] = 255 // error function - default: - res[0] = 1 // reply - } - Put16(res[2:], seqId) // sequence number - incrementSequenceId() - if string(request) == "noreply" { - //fmt.Printf("dummyXreplyer no response sent\n") - return nil - } - //fmt.Printf("dummyXreplyer sent response: %v\n", res) - return res - } - - sclm := leaksMonitor("after server close", tclm) + sclm := leaksMonitor("after server close, before testcase exit") defer sclm.checkTesting(t) - s := newDummyNetConn("dummX", dummyXreplyer) + + s := newDummyNetConn("dummyX", newDummyXServerReplier()) defer s.Close() c, err := postNewConn(&Conn{conn: s}) @@ -354,7 +194,8 @@ func TestConnOnNonBlockingDummyXServer(t *testing.T) { return } - rlm := leaksMonitor("after actions end") + defer leaksMonitor("after actions end", sclm).checkTesting(t) + for _, action := range tc.actions { if err := action(c); err != nil { t.Error(err) @@ -370,6 +211,7 @@ func TestConnOnNonBlockingDummyXServer(t *testing.T) { recovered = true } }() + c.Close() }() if !recovered { @@ -378,8 +220,6 @@ func TestConnOnNonBlockingDummyXServer(t *testing.T) { } } - rlm.checkTesting(t) - }) } } |
