wm/vend/xgb/testingTools_test.go

351 lines
10 KiB
Go
Raw Permalink Normal View History

2023-06-11 14:21:08 +00:00
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{
"lock": (*dNC).WriteLock,
"error": (*dNC).WriteError,
"success": (*dNC).WriteSuccess,
}
readSetters := map[string]func(*dNC) error{
"lock": (*dNC).ReadLock,
"error": (*dNC).ReadError,
"success": (*dNC).ReadSuccess,
}
res := []func() (*dNC, error){}
for _, writeState := range writeStates {
writeState, writeSetter := writeState, writeSetters[writeState]
if writeSetter == nil {
panic("unknown write state: " + writeState)
continue
}
for _, readState := range readStates {
readState, readSetter := readState, readSetters[readState]
if readSetter == nil {
panic("unknown read state: " + readState)
continue
}
res = append(res, func() (*dNC, error) {
// loopback server
s := newDummyNetConn("w:"+writeState+";r:"+readState, func(b []byte) []byte { return b })
if err := readSetter(s); err != nil {
s.Close()
return nil, errors.New("set read " + readState + " error: " + err.Error())
}
if err := writeSetter(s); err != nil {
s.Close()
return nil, errors.New("set write " + writeState + " error: " + err.Error())
}
return s, nil
})
}
}
return res
}
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)
timedOut := make(chan struct{})
go func() {
err := action(s)
select {
case <-timedOut:
if err != block {
t.Errorf("after unblocking, action result=%v, want %v", err, block)
}
case actionResult <- err:
}
}()
select {
case err := <-actionResult:
if err != want {
return errors.New(fmt.Sprintf("action result=%v, want %v", err, want))
}
case <-time.After(timeout):
close(timedOut)
return errors.New(fmt.Sprintf("action did not respond for %v, result want %v", timeout, want))
}
return nil
}
}
wantBlock := func(action func(*dNC) error, unblock error) func(*dNC) error {
return func(s *dNC) error {
actionResult := make(chan error)
timedOut := make(chan struct{})
go func() {
err := action(s)
select {
case <-timedOut:
if err != unblock {
t.Errorf("after unblocking, action result=%v, want %v", err, unblock)
}
case actionResult <- err:
}
}()
select {
case err := <-actionResult:
return errors.New(fmt.Sprintf("action result=%v, want to be blocked", err))
case <-time.After(timeout):
close(timedOut)
}
return nil
}
}
write := func(b string) func(*dNC) error {
return func(s *dNC) error {
n, err := s.Write([]byte(b))
if err == nil && n != len(b) {
return errors.New("Write returned nil error, but not everything was written")
}
return err
}
}
read := func(b string) func(*dNC) error {
return func(s *dNC) error {
r := make([]byte, len(b))
n, err := s.Read(r)
if err == nil {
if n != len(b) {
return errors.New("Read returned nil error, but not everything was read")
}
if !reflect.DeepEqual(r, []byte(b)) {
return errors.New("Read=\"" + string(r) + "\", want \"" + string(b) + "\"")
}
}
return err
}
}
testCases := []struct {
description string
servers []func() (*dNC, error)
actions []func(*dNC) error // actions per server
}{
{"close,close",
ioStatesPairGenerator(
[]string{"lock", "error", "success"},
[]string{"lock", "error", "success"},
),
[]func(*dNC) error{
wantResponse((*dNC).Close, nil, dNCErrClosed),
wantResponse((*dNC).Close, dNCErrClosed, dNCErrClosed),
},
},
{"write,close,write",
ioStatesPairGenerator(
[]string{"lock"},
[]string{"lock", "error", "success"},
),
[]func(*dNC) error{
wantBlock(write(""), dNCErrClosed),
wantResponse((*dNC).Close, nil, dNCErrClosed),
wantResponse(write(""), dNCErrClosed, dNCErrClosed),
},
},
{"write,close,write",
ioStatesPairGenerator(
[]string{"error"},
[]string{"lock", "error", "success"},
),
[]func(*dNC) error{
wantResponse(write(""), dNCErrWrite, dNCErrClosed),
wantResponse((*dNC).Close, nil, dNCErrClosed),
wantResponse(write(""), dNCErrClosed, dNCErrClosed),
},
},
{"write,close,write",
ioStatesPairGenerator(
[]string{"success"},
[]string{"lock", "error", "success"},
),
[]func(*dNC) error{
wantResponse(write(""), nil, dNCErrClosed),
wantResponse((*dNC).Close, nil, dNCErrClosed),
wantResponse(write(""), dNCErrClosed, dNCErrClosed),
},
},
{"read,close,read",
ioStatesPairGenerator(
[]string{"lock", "error", "success"},
[]string{"lock", "error", "success"},
),
[]func(*dNC) error{
wantBlock(read(""), io.EOF),
wantResponse((*dNC).Close, nil, dNCErrClosed),
wantResponse(read(""), io.EOF, io.EOF),
},
},
{"write,read",
ioStatesPairGenerator(
[]string{"lock"},
[]string{"lock", "error", "success"},
),
[]func(*dNC) error{
wantBlock(write("1"), dNCErrClosed),
wantBlock(read("1"), io.EOF),
},
},
{"write,read",
ioStatesPairGenerator(
[]string{"error"},
[]string{"lock", "error", "success"},
),
[]func(*dNC) error{
wantResponse(write("1"), dNCErrWrite, dNCErrClosed),
wantBlock(read("1"), io.EOF),
},
},
{"write,read",
ioStatesPairGenerator(
[]string{"success"},
[]string{"lock"},
),
[]func(*dNC) error{
wantResponse(write("1"), nil, dNCErrClosed),
wantBlock(read("1"), io.EOF),
},
},
{"write,read",
ioStatesPairGenerator(
[]string{"success"},
[]string{"error"},
),
[]func(*dNC) error{
wantResponse(write("1"), nil, dNCErrClosed),
wantResponse(read("1"), dNCErrRead, io.EOF),
},
},
{"write,read",
ioStatesPairGenerator(
[]string{"success"},
[]string{"success"},
),
[]func(*dNC) error{
wantResponse(write("1"), nil, dNCErrClosed),
wantResponse(read("1"), nil, io.EOF),
},
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
defer leaksMonitor(tc.description).checkTesting(t)
for _, server := range tc.servers {
s, err := server()
if err != nil {
t.Error(err)
continue
}
if s == nil {
t.Error("nil server in testcase")
continue
}
t.Run(s.LocalAddr().String(), func(t *testing.T) {
defer leaksMonitor(s.LocalAddr().String()).checkTesting(t)
for _, action := range tc.actions {
if err := action(s); err != nil {
t.Error(err)
break
}
}
s.Close()
})
}
})
}
}
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
}
}
}
}