github.com/qiniu/x@v1.11.9/ts/expect.go (about) 1 package ts 2 3 import ( 4 "bytes" 5 "os" 6 "reflect" 7 "runtime" 8 "strings" 9 "sync" 10 "testing" 11 "time" 12 13 "github.com/qiniu/x/errors" 14 ) 15 16 // ---------------------------------------------------------------------------- 17 18 type captureData struct { 19 target **os.File 20 old *os.File 21 exp chan interface{} 22 done chan bool 23 } 24 25 func (p *captureData) expect(t *testing.T, v interface{}) { 26 p.exp <- v 27 if equal := <-p.done; !equal { 28 t.FailNow() 29 } 30 } 31 32 // Expecting represents a expecting object. 33 type Expecting struct { 34 t *testing.T 35 cdata [2]*captureData 36 msg []byte 37 rcov interface{} 38 cstk *stack 39 } 40 41 const ( 42 // CapStdout - capture stdout 43 CapStdout = 1 44 // CapStderr - capture stderr 45 CapStderr = 2 46 // CapOutput - capture stdout and stderr 47 CapOutput = CapStdout | CapStderr 48 ) 49 50 // StartExpecting starts expecting with a capture mode. 51 func StartExpecting(t *testing.T, mode int) *Expecting { 52 p := &Expecting{t: t} 53 if mode == 0 { 54 mode = CapOutput 55 } 56 if (mode & CapStdout) != 0 { 57 p.capture(0, &os.Stdout) 58 } 59 if (mode & CapStderr) != 0 { 60 p.capture(1, &os.Stderr) 61 } 62 return p 63 } 64 65 // Call creates a test case, and then calls a function. 66 func (p *Expecting) Call(fn interface{}, args ...interface{}) (e *Expecting) { 67 e = p 68 e.msg = errors.CallDetail(nil, fn, args...) 69 defer func() { 70 if e.rcov = recover(); e.rcov != nil { 71 e.cstk = callers(3) 72 } 73 }() 74 e.rcov = nil 75 reflect.ValueOf(fn).Call(makeArgs(args)) 76 return 77 } 78 79 // Expect expects stdout ouput is equal to text. 80 func (p *Expecting) Expect(text interface{}) *Expecting { 81 p.cdata[0].expect(p.t, text) 82 return p 83 } 84 85 // ExpectErr expects stderr ouput is equal to text. 86 func (p *Expecting) ExpectErr(text interface{}) *Expecting { 87 p.cdata[1].expect(p.t, text) 88 return p 89 } 90 91 // NoPanic indicates that no panic occurs. 92 func (p *Expecting) NoPanic() { 93 if p.rcov != nil { 94 p.t.Fatalf("panic: %v\n%+v\n", p.rcov, p.cstk) 95 } 96 } 97 98 // Panic checks if function call panics or not. Panic(v) means 99 // function call panics with `v`. If v == nil, it means we don't 100 // care any detail information about panic. Panic() indicates 101 // that no panic occurs. 102 func (p *Expecting) Panic(panicMsg ...interface{}) *Expecting { 103 if panicMsg == nil { 104 p.NoPanic() 105 } else { 106 assertPanic(p.t, p.msg, p.rcov, panicMsg[0]) 107 } 108 return p 109 } 110 111 // Close stops expecting. 112 func (p *Expecting) Close() error { 113 for i, cdata := range p.cdata { 114 if cdata != nil { 115 p.cdata[i] = nil 116 w := *cdata.target 117 *cdata.target = cdata.old 118 w.Close() 119 cdata.exp <- nil // close pipe 120 } 121 } 122 return nil 123 } 124 125 func (p *Expecting) capture(idx int, target **os.File) { 126 old := *target 127 cdata := &captureData{ 128 target: target, 129 old: old, 130 exp: make(chan interface{}), 131 done: make(chan bool, 1), 132 } 133 p.cdata[idx] = cdata 134 r, w, err := os.Pipe() 135 if err != nil { 136 panic(err) 137 } 138 var mutex sync.Mutex 139 var buf = bytes.NewBuffer(nil) 140 go func() { 141 b := make([]byte, 1024) 142 for { 143 n, err := r.Read(b) 144 if err != nil { 145 return 146 } 147 if n > 0 { 148 mutex.Lock() 149 buf.Write(b[:n]) 150 old.Write(b[:n]) 151 mutex.Unlock() 152 } 153 } 154 }() 155 go func() { 156 for exp := range cdata.exp { 157 if exp == nil { // close pipe 158 r.Close() 159 return 160 } 161 deadline := time.Now().Add(time.Second / 10) // 0.1s 162 off, b := 0, toString(exp) 163 retry: 164 mutex.Lock() 165 a := string(buf.Bytes()) 166 buf.Reset() 167 mutex.Unlock() 168 equal := (a == b[off:]) 169 if !equal { 170 if strings.HasPrefix(b[off:], a) && time.Now().Before(deadline) { 171 off += len(a) 172 runtime.Gosched() 173 goto retry 174 } 175 *target = old 176 p.t.Logf("%s:\n%s\nExpect:\n%s\n", string(p.msg), b[:off]+a, b) 177 *target = w 178 } 179 cdata.done <- equal 180 } 181 }() 182 *target = w 183 } 184 185 func toString(exp interface{}) string { 186 switch v := exp.(type) { 187 case []byte: 188 return string(v) 189 case string: 190 return v 191 default: 192 panic("expect: unsupport type - " + reflect.TypeOf(exp).String()) 193 } 194 } 195 196 // ----------------------------------------------------------------------------