summaryrefslogtreecommitdiff
path: root/xgb_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'xgb_test.go')
-rw-r--r--xgb_test.go174
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)
-
})
}
}