diff options
| author | jEzEk <[email protected]> | 2018-10-29 19:41:24 +0100 |
|---|---|---|
| committer | jEzEk <[email protected]> | 2018-10-30 18:04:49 +0100 |
| commit | 01e3ef92338ac79a57aa6542633770366db1ff52 (patch) | |
| tree | 108435e9adc85caf8b359e07a82ed41890126086 | |
| parent | 788010f11d65572ca0cea2caf65ade6db4a274de (diff) | |
refactor to testingTools.go with more tests
leak testing added
dummy X server replier fo dummy net.Conn tests
| -rw-r--r-- | testingTools.go (renamed from dummyNetConn.go) | 165 | ||||
| -rw-r--r-- | testingTools_test.go (renamed from dummyNetConn_test.go) | 79 | ||||
| -rw-r--r-- | xgb_test.go | 174 |
3 files changed, 250 insertions, 168 deletions
diff --git a/dummyNetConn.go b/testingTools.go index 91bae4f..2f73031 100644 --- a/dummyNetConn.go +++ b/testingTools.go @@ -5,9 +5,119 @@ import ( "errors" "io" "net" + "regexp" + "runtime" + "strconv" + "strings" + "testing" "time" ) +// Leaks monitor + +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 := 10 * time.Millisecond + 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 + } +} + +// dummy net.Conn + type dAddr struct { s string } @@ -259,3 +369,58 @@ func (s *dNC) ReadSuccess() error { } return s.Control(dNCCReadSuccess{}) } + +// dummy X server replier for dummy net.Conn + +type dXSEvent struct{} + +func (_ dXSEvent) Bytes() []byte { return nil } +func (_ dXSEvent) String() string { return "dummy X server event" } + +type dXSError struct { + seqId uint16 +} + +func (e dXSError) SequenceId() uint16 { return e.seqId } +func (_ dXSError) BadId() uint32 { return 0 } +func (_ dXSError) Error() string { return "dummy X server error reply" } + +func newDummyXServerReplier() func([]byte) []byte { + // register xgb error & event replies + NewErrorFuncs[255] = func(buf []byte) Error { + return dXSError{Get16(buf[2:])} + } + NewEventFuncs[128&127] = func(buf []byte) Event { + return dXSEvent{} + } + + // sequence number generator + 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++ + } + } + return func(request []byte) []byte { + res := make([]byte, 32) + switch string(request) { + case "event": + res[0] = 128 + 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" { + return nil + } + return res + } +} diff --git a/dummyNetConn_test.go b/testingTools_test.go index 94691be..518b326 100644 --- a/dummyNetConn_test.go +++ b/testingTools_test.go @@ -1,14 +1,56 @@ package xgb import ( + "bytes" "errors" "fmt" "io" "reflect" + "sync" "testing" "time" ) +func TestLeaks(t *testing.T) { + lm := leaksMonitor("lm") + if lgrs := lm.leakingGoroutines(); len(lgrs) != 0 { + t.Errorf("leakingGoroutines returned %d leaking goroutines, want 0", len(lgrs)) + } + + done := make(chan struct{}) + wg := &sync.WaitGroup{} + + wg.Add(1) + go func() { + <-done + wg.Done() + }() + + if lgrs := lm.leakingGoroutines(); len(lgrs) != 1 { + t.Errorf("leakingGoroutines returned %d leaking goroutines, want 1", len(lgrs)) + } + + wg.Add(1) + go func() { + <-done + wg.Done() + }() + + if lgrs := lm.leakingGoroutines(); len(lgrs) != 2 { + t.Errorf("leakingGoroutines returned %d leaking goroutines, want 2", len(lgrs)) + } + + close(done) + wg.Wait() + + if lgrs := lm.leakingGoroutines(); len(lgrs) != 0 { + t.Errorf("leakingGoroutines returned %d leaking goroutines, want 0", len(lgrs)) + } + + lm.checkTesting(t) + //TODO multiple leak monitors with report ignore tests +} + func TestDummyNetConn(t *testing.T) { ioStatesPairGenerator := func(writeStates, readStates []string) []func() (*dNC, error) { writeSetters := map[string]func(*dNC) error{ @@ -57,7 +99,7 @@ func TestDummyNetConn(t *testing.T) { return res } - timeout := time.Millisecond + timeout := 10 * time.Millisecond wantResponse := func(action func(*dNC) error, want, block error) func(*dNC) error { return func(s *dNC) error { actionResult := make(chan error) @@ -271,3 +313,38 @@ func TestDummyNetConn(t *testing.T) { }) } } + +func TestDummyXServerReplier(t *testing.T) { + testCases := [][][2][]byte{ + { + [2][]byte{[]byte("reply"), []byte{1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + [2][]byte{[]byte("eply"), []byte{1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + [2][]byte{[]byte("ply"), []byte{1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + [2][]byte{[]byte("event"), []byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + [2][]byte{[]byte("ly"), []byte{1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + [2][]byte{[]byte("y"), []byte{1, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + [2][]byte{[]byte(""), []byte{1, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + [2][]byte{[]byte("event"), []byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + [2][]byte{[]byte("reply"), []byte{1, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + [2][]byte{[]byte("error"), []byte{0, 255, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + [2][]byte{[]byte("ply"), []byte{1, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + [2][]byte{[]byte("event"), []byte{128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + [2][]byte{[]byte("ly"), []byte{1, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + [2][]byte{[]byte("noreply"), nil}, + [2][]byte{[]byte("error"), []byte{0, 255, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + [2][]byte{[]byte("noreply"), nil}, + [2][]byte{[]byte(""), []byte{1, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + }, + } + + for tci, tc := range testCases { + replier := newDummyXServerReplier() + for ai, ioPair := range tc { + in, want := ioPair[0], ioPair[1] + if out := replier(in); !bytes.Equal(out, want) { + t.Errorf("testCase %d, action %d, replier(%s) = %v, want %v", tci, ai, string(in), out, want) + break + } + } + } +} 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) - }) } } |
