zorldo

Goofing around with Ebiten
git clone git://bsandro.tech/zorldo
Log | Files | Refs | README

testingTools.go (11721B)


      1 package xgb
      2 
      3 import (
      4 	"bytes"
      5 	"errors"
      6 	"io"
      7 	"net"
      8 	"regexp"
      9 	"runtime"
     10 	"strconv"
     11 	"strings"
     12 	"testing"
     13 	"time"
     14 )
     15 
     16 // Leaks monitor
     17 
     18 type goroutine struct {
     19 	id    int
     20 	name  string
     21 	stack []byte
     22 }
     23 
     24 type leaks struct {
     25 	name       string
     26 	goroutines map[int]goroutine
     27 	report     []*leaks
     28 }
     29 
     30 func leaksMonitor(name string, monitors ...*leaks) *leaks {
     31 	return &leaks{
     32 		name,
     33 		leaks{}.collectGoroutines(),
     34 		monitors,
     35 	}
     36 }
     37 
     38 // ispired by https://golang.org/src/runtime/debug/stack.go?s=587:606#L21
     39 // stack returns a formatted stack trace of all goroutines.
     40 // It calls runtime.Stack with a large enough buffer to capture the entire trace.
     41 func (_ leaks) stack() []byte {
     42 	buf := make([]byte, 1024)
     43 	for {
     44 		n := runtime.Stack(buf, true)
     45 		if n < len(buf) {
     46 			return buf[:n]
     47 		}
     48 		buf = make([]byte, 2*len(buf))
     49 	}
     50 }
     51 
     52 func (l leaks) collectGoroutines() map[int]goroutine {
     53 	res := make(map[int]goroutine)
     54 	stacks := bytes.Split(l.stack(), []byte{'\n', '\n'})
     55 
     56 	regexpId := regexp.MustCompile(`^\s*goroutine\s*(\d+)`)
     57 	for _, st := range stacks {
     58 		lines := bytes.Split(st, []byte{'\n'})
     59 		if len(lines) < 2 {
     60 			panic("routine stach has less tnan two lines: " + string(st))
     61 		}
     62 
     63 		idMatches := regexpId.FindSubmatch(lines[0])
     64 		if len(idMatches) < 2 {
     65 			panic("no id found in goroutine stack's first line: " + string(lines[0]))
     66 		}
     67 		id, err := strconv.Atoi(string(idMatches[1]))
     68 		if err != nil {
     69 			panic("converting goroutine id to number error: " + err.Error())
     70 		}
     71 		if _, ok := res[id]; ok {
     72 			panic("2 goroutines with same id: " + strconv.Itoa(id))
     73 		}
     74 		name := strings.TrimSpace(string(lines[1]))
     75 
     76 		//filter out our stack routine
     77 		if strings.Contains(name, "xgb.leaks.stack") {
     78 			continue
     79 		}
     80 
     81 		res[id] = goroutine{id, name, st}
     82 	}
     83 	return res
     84 }
     85 
     86 func (l leaks) leakingGoroutines() []goroutine {
     87 	goroutines := l.collectGoroutines()
     88 	res := []goroutine{}
     89 	for id, gr := range goroutines {
     90 		if _, ok := l.goroutines[id]; ok {
     91 			continue
     92 		}
     93 		res = append(res, gr)
     94 	}
     95 	return res
     96 }
     97 func (l leaks) checkTesting(t *testing.T) {
     98 	if len(l.leakingGoroutines()) == 0 {
     99 		return
    100 	}
    101 	leakTimeout := 10 * time.Millisecond
    102 	time.Sleep(leakTimeout)
    103 	//t.Logf("possible goroutine leakage, waiting %v", leakTimeout)
    104 	grs := l.leakingGoroutines()
    105 	for _, gr := range grs {
    106 		t.Errorf("%s: %s is leaking", l.name, gr.name)
    107 		//t.Errorf("%s: %s is leaking\n%v", l.name, gr.name, string(gr.stack))
    108 	}
    109 	for _, rl := range l.report {
    110 		rl.ignoreLeak(grs...)
    111 	}
    112 }
    113 func (l *leaks) ignoreLeak(grs ...goroutine) {
    114 	for _, gr := range grs {
    115 		l.goroutines[gr.id] = gr
    116 	}
    117 }
    118 
    119 // dummy net.Conn
    120 
    121 type dAddr struct {
    122 	s string
    123 }
    124 
    125 func (_ dAddr) Network() string { return "dummy" }
    126 func (a dAddr) String() string  { return a.s }
    127 
    128 var (
    129 	dNCErrNotImplemented = errors.New("command not implemented")
    130 	dNCErrClosed         = errors.New("server closed")
    131 	dNCErrWrite          = errors.New("server write failed")
    132 	dNCErrRead           = errors.New("server read failed")
    133 	dNCErrResponse       = errors.New("server response error")
    134 )
    135 
    136 type dNCIoResult struct {
    137 	n   int
    138 	err error
    139 }
    140 type dNCIo struct {
    141 	b      []byte
    142 	result chan dNCIoResult
    143 }
    144 
    145 type dNCCWriteLock struct{}
    146 type dNCCWriteUnlock struct{}
    147 type dNCCWriteError struct{}
    148 type dNCCWriteSuccess struct{}
    149 type dNCCReadLock struct{}
    150 type dNCCReadUnlock struct{}
    151 type dNCCReadError struct{}
    152 type dNCCReadSuccess struct{}
    153 
    154 // dummy net.Conn interface. Needs to be constructed via newDummyNetConn([...]) function.
    155 type dNC struct {
    156 	reply   func([]byte) []byte
    157 	addr    dAddr
    158 	in, out chan dNCIo
    159 	control chan interface{}
    160 	done    chan struct{}
    161 }
    162 
    163 // Results running dummy server, satisfying net.Conn interface for test purposes.
    164 // 'name' parameter will be returned via (*dNC).Local/RemoteAddr().String()
    165 // 'reply' parameter function will be runned only on successful (*dNC).Write(b) with 'b' as parameter to 'reply'. The result will be stored in internal buffer and can be retrieved later via (*dNC).Read([...]) method.
    166 // It is users responsibility to stop and clean up resources with (*dNC).Close, if not needed anymore.
    167 // By default, the (*dNC).Write([...]) and (*dNC).Read([...]) methods are unlocked and will not result in error.
    168 //TODO make (*dNC).SetDeadline, (*dNC).SetReadDeadline, (*dNC).SetWriteDeadline work proprely.
    169 func newDummyNetConn(name string, reply func([]byte) []byte) *dNC {
    170 
    171 	s := &dNC{
    172 		reply,
    173 		dAddr{name},
    174 		make(chan dNCIo), make(chan dNCIo),
    175 		make(chan interface{}),
    176 		make(chan struct{}),
    177 	}
    178 
    179 	in, out := s.in, chan dNCIo(nil)
    180 	buf := &bytes.Buffer{}
    181 	errorRead, errorWrite := false, false
    182 	lockRead := false
    183 
    184 	go func() {
    185 		defer close(s.done)
    186 		for {
    187 			select {
    188 			case dxsio := <-in:
    189 				if errorWrite {
    190 					dxsio.result <- dNCIoResult{0, dNCErrWrite}
    191 					break
    192 				}
    193 
    194 				response := s.reply(dxsio.b)
    195 
    196 				buf.Write(response)
    197 				dxsio.result <- dNCIoResult{len(dxsio.b), nil}
    198 
    199 				if !lockRead && buf.Len() > 0 && out == nil {
    200 					out = s.out
    201 				}
    202 			case dxsio := <-out:
    203 				if errorRead {
    204 					dxsio.result <- dNCIoResult{0, dNCErrRead}
    205 					break
    206 				}
    207 
    208 				n, err := buf.Read(dxsio.b)
    209 				dxsio.result <- dNCIoResult{n, err}
    210 
    211 				if buf.Len() == 0 {
    212 					out = nil
    213 				}
    214 			case ci := <-s.control:
    215 				if ci == nil {
    216 					return
    217 				}
    218 				switch ci.(type) {
    219 				case dNCCWriteLock:
    220 					in = nil
    221 				case dNCCWriteUnlock:
    222 					in = s.in
    223 				case dNCCWriteError:
    224 					errorWrite = true
    225 				case dNCCWriteSuccess:
    226 					errorWrite = false
    227 				case dNCCReadLock:
    228 					out = nil
    229 					lockRead = true
    230 				case dNCCReadUnlock:
    231 					lockRead = false
    232 					if buf.Len() > 0 && out == nil {
    233 						out = s.out
    234 					}
    235 				case dNCCReadError:
    236 					errorRead = true
    237 				case dNCCReadSuccess:
    238 					errorRead = false
    239 				default:
    240 				}
    241 			}
    242 		}
    243 	}()
    244 	return s
    245 }
    246 
    247 // Shuts down dummy net.Conn server. Every blocking or future method calls will do nothing and result in error.
    248 // Result will be dNCErrClosed if server was allready closed.
    249 // Server can not be unclosed.
    250 func (s *dNC) Close() error {
    251 	select {
    252 	case s.control <- nil:
    253 		<-s.done
    254 		return nil
    255 	case <-s.done:
    256 	}
    257 	return dNCErrClosed
    258 }
    259 
    260 // Performs a write action to server.
    261 // If not locked by (*dNC).WriteLock, it results in error or success. If locked, this method will block until unlocked, or closed.
    262 //
    263 // This method can be set to result in error or success, via (*dNC).WriteError() or (*dNC).WriteSuccess() methods.
    264 //
    265 // If setted to result in error, the 'reply' function will NOT be called and internal buffer will NOT increasethe.
    266 // Result will be (0, dNCErrWrite).
    267 //
    268 // If setted to result in success, the 'reply' function will be called and its result will be writen to internal buffer.
    269 // If there is something in the internal buffer, the (*dNC).Read([...]) will be unblocked (if not previously locked with (*dNC).ReadLock).
    270 // Result will be (len(b), nil)
    271 //
    272 // If server was closed previously, result will be (0, dNCErrClosed).
    273 func (s *dNC) Write(b []byte) (int, error) {
    274 	resChan := make(chan dNCIoResult)
    275 	select {
    276 	case s.in <- dNCIo{b, resChan}:
    277 		res := <-resChan
    278 		return res.n, res.err
    279 	case <-s.done:
    280 	}
    281 	return 0, dNCErrClosed
    282 }
    283 
    284 // Performs a read action from server.
    285 // If locked by (*dNC).ReadLock(), this method will block until unlocked with (*dNC).ReadUnlock(), or server closes.
    286 //
    287 // If not locked, this method can be setted to result imidiatly in error, will block if internal buffer is empty or will perform an read operation from internal buffer.
    288 //
    289 // If setted to result in error via (*dNC).ReadError(), the result will be (0, dNCErrWrite).
    290 //
    291 // If not locked and not setted to result in error via (*dNC).ReadSuccess(), this method will block until internall buffer is not empty, than it returns the result of the buffer read operation via (*bytes.Buffer).Read([...]).
    292 // If the internal buffer is empty after this method, all follwing (*dNC).Read([...]), requests will block until internall buffer is filled after successful write requests.
    293 //
    294 // If server was closed previously, result will be (0, io.EOF).
    295 func (s *dNC) Read(b []byte) (int, error) {
    296 	resChan := make(chan dNCIoResult)
    297 	select {
    298 	case s.out <- dNCIo{b, resChan}:
    299 		res := <-resChan
    300 		return res.n, res.err
    301 	case <-s.done:
    302 	}
    303 	return 0, io.EOF
    304 }
    305 func (s *dNC) LocalAddr() net.Addr                { return s.addr }
    306 func (s *dNC) RemoteAddr() net.Addr               { return s.addr }
    307 func (s *dNC) SetDeadline(t time.Time) error      { return dNCErrNotImplemented }
    308 func (s *dNC) SetReadDeadline(t time.Time) error  { return dNCErrNotImplemented }
    309 func (s *dNC) SetWriteDeadline(t time.Time) error { return dNCErrNotImplemented }
    310 
    311 func (s *dNC) Control(i interface{}) error {
    312 	select {
    313 	case s.control <- i:
    314 		return nil
    315 	case <-s.done:
    316 	}
    317 	return dNCErrClosed
    318 }
    319 
    320 // Locks writing. All write requests will be blocked until write is unlocked with (*dNC).WriteUnlock, or server closes.
    321 func (s *dNC) WriteLock() error {
    322 	return s.Control(dNCCWriteLock{})
    323 }
    324 
    325 // Unlocks writing. All blocked write requests until now will be accepted.
    326 func (s *dNC) WriteUnlock() error {
    327 	return s.Control(dNCCWriteUnlock{})
    328 }
    329 
    330 // Unlocks writing and makes (*dNC).Write to result (0, dNCErrWrite).
    331 func (s *dNC) WriteError() error {
    332 	if err := s.WriteUnlock(); err != nil {
    333 		return err
    334 	}
    335 	return s.Control(dNCCWriteError{})
    336 }
    337 
    338 // Unlocks writing and makes (*dNC).Write([...]) not result in error. See (*dNC).Write for details.
    339 func (s *dNC) WriteSuccess() error {
    340 	if err := s.WriteUnlock(); err != nil {
    341 		return err
    342 	}
    343 	return s.Control(dNCCWriteSuccess{})
    344 }
    345 
    346 // Locks reading. All read requests will be blocked until read is unlocked with (*dNC).ReadUnlock, or server closes.
    347 // (*dNC).Read([...]) wil block even after successful write.
    348 func (s *dNC) ReadLock() error {
    349 	return s.Control(dNCCReadLock{})
    350 }
    351 
    352 // Unlocks reading. If the internall buffer is not empty, next read will not block.
    353 func (s *dNC) ReadUnlock() error {
    354 	return s.Control(dNCCReadUnlock{})
    355 }
    356 
    357 // Unlocks read and makes every blocked and following (*dNC).Read([...]) imidiatly result in error. See (*dNC).Read for details.
    358 func (s *dNC) ReadError() error {
    359 	if err := s.ReadUnlock(); err != nil {
    360 		return err
    361 	}
    362 	return s.Control(dNCCReadError{})
    363 }
    364 
    365 // Unlocks read and makes every blocked and following (*dNC).Read([...]) requests be handled, if according to internal buffer. See (*dNC).Read for details.
    366 func (s *dNC) ReadSuccess() error {
    367 	if err := s.ReadUnlock(); err != nil {
    368 		return err
    369 	}
    370 	return s.Control(dNCCReadSuccess{})
    371 }
    372 
    373 // dummy X server replier for dummy net.Conn
    374 
    375 type dXSEvent struct{}
    376 
    377 func (_ dXSEvent) Bytes() []byte  { return nil }
    378 func (_ dXSEvent) String() string { return "dummy X server event" }
    379 
    380 type dXSError struct {
    381 	seqId uint16
    382 }
    383 
    384 func (e dXSError) SequenceId() uint16 { return e.seqId }
    385 func (_ dXSError) BadId() uint32      { return 0 }
    386 func (_ dXSError) Error() string      { return "dummy X server error reply" }
    387 
    388 func newDummyXServerReplier() func([]byte) []byte {
    389 	// register xgb error & event replies
    390 	NewErrorFuncs[255] = func(buf []byte) Error {
    391 		return dXSError{Get16(buf[2:])}
    392 	}
    393 	NewEventFuncs[128&127] = func(buf []byte) Event {
    394 		return dXSEvent{}
    395 	}
    396 
    397 	// sequence number generator
    398 	seqId := uint16(1)
    399 	incrementSequenceId := func() {
    400 		// this has to be the same algorithm as in (*Conn).generateSeqIds
    401 		if seqId == uint16((1<<16)-1) {
    402 			seqId = 0
    403 		} else {
    404 			seqId++
    405 		}
    406 	}
    407 	return func(request []byte) []byte {
    408 		res := make([]byte, 32)
    409 		switch string(request) {
    410 		case "event":
    411 			res[0] = 128
    412 			return res
    413 		case "error":
    414 			res[0] = 0   // error
    415 			res[1] = 255 // error function
    416 		default:
    417 			res[0] = 1 // reply
    418 		}
    419 		Put16(res[2:], seqId) // sequence number
    420 		incrementSequenceId()
    421 		if string(request) == "noreply" {
    422 			return nil
    423 		}
    424 		return res
    425 	}
    426 }