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 }