go-tg-screenshot-bot
426 строк · 11.4 Кб
1package xgb
2
3import (
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
18type goroutine struct {
19id int
20name string
21stack []byte
22}
23
24type leaks struct {
25name string
26goroutines map[int]goroutine
27report []*leaks
28}
29
30func leaksMonitor(name string, monitors ...*leaks) *leaks {
31return &leaks{
32name,
33leaks{}.collectGoroutines(),
34monitors,
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.
41func (_ leaks) stack() []byte {
42buf := make([]byte, 1024)
43for {
44n := runtime.Stack(buf, true)
45if n < len(buf) {
46return buf[:n]
47}
48buf = make([]byte, 2*len(buf))
49}
50}
51
52func (l leaks) collectGoroutines() map[int]goroutine {
53res := make(map[int]goroutine)
54stacks := bytes.Split(l.stack(), []byte{'\n', '\n'})
55
56regexpId := regexp.MustCompile(`^\s*goroutine\s*(\d+)`)
57for _, st := range stacks {
58lines := bytes.Split(st, []byte{'\n'})
59if len(lines) < 2 {
60panic("routine stach has less tnan two lines: " + string(st))
61}
62
63idMatches := regexpId.FindSubmatch(lines[0])
64if len(idMatches) < 2 {
65panic("no id found in goroutine stack's first line: " + string(lines[0]))
66}
67id, err := strconv.Atoi(string(idMatches[1]))
68if err != nil {
69panic("converting goroutine id to number error: " + err.Error())
70}
71if _, ok := res[id]; ok {
72panic("2 goroutines with same id: " + strconv.Itoa(id))
73}
74name := strings.TrimSpace(string(lines[1]))
75
76//filter out our stack routine
77if strings.Contains(name, "xgb.leaks.stack") {
78continue
79}
80
81res[id] = goroutine{id, name, st}
82}
83return res
84}
85
86func (l leaks) leakingGoroutines() []goroutine {
87goroutines := l.collectGoroutines()
88res := []goroutine{}
89for id, gr := range goroutines {
90if _, ok := l.goroutines[id]; ok {
91continue
92}
93res = append(res, gr)
94}
95return res
96}
97func (l leaks) checkTesting(t *testing.T) {
98if len(l.leakingGoroutines()) == 0 {
99return
100}
101leakTimeout := 10 * time.Millisecond
102time.Sleep(leakTimeout)
103//t.Logf("possible goroutine leakage, waiting %v", leakTimeout)
104grs := l.leakingGoroutines()
105for _, gr := range grs {
106t.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}
109for _, rl := range l.report {
110rl.ignoreLeak(grs...)
111}
112}
113func (l *leaks) ignoreLeak(grs ...goroutine) {
114for _, gr := range grs {
115l.goroutines[gr.id] = gr
116}
117}
118
119// dummy net.Conn
120
121type dAddr struct {
122s string
123}
124
125func (_ dAddr) Network() string { return "dummy" }
126func (a dAddr) String() string { return a.s }
127
128var (
129dNCErrNotImplemented = errors.New("command not implemented")
130dNCErrClosed = errors.New("server closed")
131dNCErrWrite = errors.New("server write failed")
132dNCErrRead = errors.New("server read failed")
133dNCErrResponse = errors.New("server response error")
134)
135
136type dNCIoResult struct {
137n int
138err error
139}
140type dNCIo struct {
141b []byte
142result chan dNCIoResult
143}
144
145type dNCCWriteLock struct{}
146type dNCCWriteUnlock struct{}
147type dNCCWriteError struct{}
148type dNCCWriteSuccess struct{}
149type dNCCReadLock struct{}
150type dNCCReadUnlock struct{}
151type dNCCReadError struct{}
152type dNCCReadSuccess struct{}
153
154// dummy net.Conn interface. Needs to be constructed via newDummyNetConn([...]) function.
155type dNC struct {
156reply func([]byte) []byte
157addr dAddr
158in, out chan dNCIo
159control chan interface{}
160done 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.
169func newDummyNetConn(name string, reply func([]byte) []byte) *dNC {
170
171s := &dNC{
172reply,
173dAddr{name},
174make(chan dNCIo), make(chan dNCIo),
175make(chan interface{}),
176make(chan struct{}),
177}
178
179in, out := s.in, chan dNCIo(nil)
180buf := &bytes.Buffer{}
181errorRead, errorWrite := false, false
182lockRead := false
183
184go func() {
185defer close(s.done)
186for {
187select {
188case dxsio := <-in:
189if errorWrite {
190dxsio.result <- dNCIoResult{0, dNCErrWrite}
191break
192}
193
194response := s.reply(dxsio.b)
195
196buf.Write(response)
197dxsio.result <- dNCIoResult{len(dxsio.b), nil}
198
199if !lockRead && buf.Len() > 0 && out == nil {
200out = s.out
201}
202case dxsio := <-out:
203if errorRead {
204dxsio.result <- dNCIoResult{0, dNCErrRead}
205break
206}
207
208n, err := buf.Read(dxsio.b)
209dxsio.result <- dNCIoResult{n, err}
210
211if buf.Len() == 0 {
212out = nil
213}
214case ci := <-s.control:
215if ci == nil {
216return
217}
218switch ci.(type) {
219case dNCCWriteLock:
220in = nil
221case dNCCWriteUnlock:
222in = s.in
223case dNCCWriteError:
224errorWrite = true
225case dNCCWriteSuccess:
226errorWrite = false
227case dNCCReadLock:
228out = nil
229lockRead = true
230case dNCCReadUnlock:
231lockRead = false
232if buf.Len() > 0 && out == nil {
233out = s.out
234}
235case dNCCReadError:
236errorRead = true
237case dNCCReadSuccess:
238errorRead = false
239default:
240}
241}
242}
243}()
244return 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.
250func (s *dNC) Close() error {
251select {
252case s.control <- nil:
253<-s.done
254return nil
255case <-s.done:
256}
257return 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).
273func (s *dNC) Write(b []byte) (int, error) {
274resChan := make(chan dNCIoResult)
275select {
276case s.in <- dNCIo{b, resChan}:
277res := <-resChan
278return res.n, res.err
279case <-s.done:
280}
281return 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).
295func (s *dNC) Read(b []byte) (int, error) {
296resChan := make(chan dNCIoResult)
297select {
298case s.out <- dNCIo{b, resChan}:
299res := <-resChan
300return res.n, res.err
301case <-s.done:
302}
303return 0, io.EOF
304}
305func (s *dNC) LocalAddr() net.Addr { return s.addr }
306func (s *dNC) RemoteAddr() net.Addr { return s.addr }
307func (s *dNC) SetDeadline(t time.Time) error { return dNCErrNotImplemented }
308func (s *dNC) SetReadDeadline(t time.Time) error { return dNCErrNotImplemented }
309func (s *dNC) SetWriteDeadline(t time.Time) error { return dNCErrNotImplemented }
310
311func (s *dNC) Control(i interface{}) error {
312select {
313case s.control <- i:
314return nil
315case <-s.done:
316}
317return dNCErrClosed
318}
319
320// Locks writing. All write requests will be blocked until write is unlocked with (*dNC).WriteUnlock, or server closes.
321func (s *dNC) WriteLock() error {
322return s.Control(dNCCWriteLock{})
323}
324
325// Unlocks writing. All blocked write requests until now will be accepted.
326func (s *dNC) WriteUnlock() error {
327return s.Control(dNCCWriteUnlock{})
328}
329
330// Unlocks writing and makes (*dNC).Write to result (0, dNCErrWrite).
331func (s *dNC) WriteError() error {
332if err := s.WriteUnlock(); err != nil {
333return err
334}
335return s.Control(dNCCWriteError{})
336}
337
338// Unlocks writing and makes (*dNC).Write([...]) not result in error. See (*dNC).Write for details.
339func (s *dNC) WriteSuccess() error {
340if err := s.WriteUnlock(); err != nil {
341return err
342}
343return 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.
348func (s *dNC) ReadLock() error {
349return s.Control(dNCCReadLock{})
350}
351
352// Unlocks reading. If the internall buffer is not empty, next read will not block.
353func (s *dNC) ReadUnlock() error {
354return 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.
358func (s *dNC) ReadError() error {
359if err := s.ReadUnlock(); err != nil {
360return err
361}
362return 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.
366func (s *dNC) ReadSuccess() error {
367if err := s.ReadUnlock(); err != nil {
368return err
369}
370return s.Control(dNCCReadSuccess{})
371}
372
373// dummy X server replier for dummy net.Conn
374
375type dXSEvent struct{}
376
377func (_ dXSEvent) Bytes() []byte { return nil }
378func (_ dXSEvent) String() string { return "dummy X server event" }
379
380type dXSError struct {
381seqId uint16
382}
383
384func (e dXSError) SequenceId() uint16 { return e.seqId }
385func (_ dXSError) BadId() uint32 { return 0 }
386func (_ dXSError) Error() string { return "dummy X server error reply" }
387
388func newDummyXServerReplier() func([]byte) []byte {
389// register xgb error & event replies
390NewErrorFuncs[255] = func(buf []byte) Error {
391return dXSError{Get16(buf[2:])}
392}
393NewEventFuncs[128&127] = func(buf []byte) Event {
394return dXSEvent{}
395}
396
397// sequence number generator
398seqId := uint16(1)
399incrementSequenceId := func() {
400// this has to be the same algorithm as in (*Conn).generateSeqIds
401if seqId == uint16((1<<16)-1) {
402seqId = 0
403} else {
404seqId++
405}
406}
407return func(request []byte) []byte {
408res := make([]byte, 32)
409switch string(request) {
410case "event":
411res[0] = 128
412return res
413case "error":
414res[0] = 0 // error
415res[1] = 255 // error function
416default:
417res[0] = 1 // reply
418}
419Put16(res[2:], seqId) // sequence number
420incrementSequenceId()
421if string(request) == "noreply" {
422return nil
423}
424return res
425}
426}
427