summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjEzEk <[email protected]>2018-10-29 19:41:24 +0100
committerjEzEk <[email protected]>2018-10-30 18:04:49 +0100
commit01e3ef92338ac79a57aa6542633770366db1ff52 (patch)
tree108435e9adc85caf8b359e07a82ed41890126086
parent788010f11d65572ca0cea2caf65ade6db4a274de (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.go174
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)
-
})
}
}