github.com/qiniu/x@v1.11.9/ts/case.go (about) 1 /* 2 Copyright 2020 Qiniu Limited (qiniu.com) 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package ts provides Go packages testing utilities. 18 // See https://github.com/qiniu/x/wiki/How-to-write-a-TestCase for details. 19 package ts 20 21 import ( 22 "reflect" 23 "strings" 24 "testing" 25 26 "github.com/qiniu/x/errors" 27 ) 28 29 // ---------------------------------------------------------------------------- 30 31 // Testing represents a testing object. 32 type Testing struct { 33 t *testing.T 34 } 35 36 // New creates a testing object. 37 func New(t *testing.T) *Testing { 38 return &Testing{t: t} 39 } 40 41 // New creates a test case. 42 func (p *Testing) New(name string) *TestCase { 43 return &TestCase{name: name, t: p.t} 44 } 45 46 // Call creates a test case, and then calls a function. 47 func (p *Testing) Call(fn interface{}, args ...interface{}) *TestCase { 48 return p.New("").Call(fn, args...) 49 } 50 51 // Case creates a test case and sets its output parameters. 52 func (p *Testing) Case(name string, result ...interface{}) *TestCase { 53 return p.New(name).Init(result...) 54 } 55 56 // ---------------------------------------------------------------------------- 57 58 // TestCase represents a test case. 59 type TestCase struct { 60 t *testing.T 61 name string 62 msg []byte 63 rcov interface{} 64 cstk *stack 65 out []reflect.Value 66 idx int 67 } 68 69 func (p *TestCase) newMsg() []byte { 70 msg := make([]byte, 0, 16) 71 if p.name != "" { 72 msg = append(msg, p.name...) 73 msg = append(msg, ' ') 74 } 75 return msg 76 } 77 78 // Init sets output parameters. 79 func (p *TestCase) Init(result ...interface{}) *TestCase { 80 out := make([]reflect.Value, len(result)) 81 for i, ret := range result { 82 out[i] = reflect.ValueOf(ret) 83 } 84 p.msg = p.newMsg() 85 p.rcov = nil 86 p.out = out 87 p.idx = 0 88 return p 89 } 90 91 // Call calls a function. 92 func (p *TestCase) Call(fn interface{}, args ...interface{}) (e *TestCase) { 93 e = p 94 e.msg = errors.CallDetail(e.newMsg(), fn, args...) 95 defer func() { 96 if e.rcov = recover(); e.rcov != nil { 97 e.cstk = callers(3) 98 } 99 }() 100 e.rcov = nil 101 e.out = reflect.ValueOf(fn).Call(makeArgs(args)) 102 e.idx = 0 103 return 104 } 105 106 func makeArgs(args []interface{}) []reflect.Value { 107 in := make([]reflect.Value, len(args)) 108 for i, arg := range args { 109 in[i] = reflect.ValueOf(arg) 110 } 111 return in 112 } 113 114 // Next sets current output value to next output parameter. 115 func (p *TestCase) Next() *TestCase { 116 p.idx++ 117 return p 118 } 119 120 // With sets current output value to check. 121 func (p *TestCase) With(i int) *TestCase { 122 p.idx = i 123 return p 124 } 125 126 // Panic checks if function call panics or not. Panic(v) means 127 // function call panics with `v`. If v == nil, it means we don't 128 // care any detail information about panic. 129 func (p *TestCase) Panic(panicMsg ...interface{}) *TestCase { 130 if panicMsg == nil { 131 p.assertNotPanic() 132 } else { 133 assertPanic(p.t, p.msg, p.rcov, panicMsg[0]) 134 } 135 return p 136 } 137 138 func assertPanic(t *testing.T, msg []byte, rcov interface{}, panicMsg interface{}) { 139 if rcov == nil { 140 t.Fatalf("%s:\nPanic checks: no panic, expected: panic\n", string(msg)) 141 } 142 if panicMsg != nil { 143 if !reflect.DeepEqual(rcov, panicMsg) { 144 t.Fatalf("%s:\nPanic checks: %v, expected: %v\n", string(msg), rcov, panicMsg) 145 } 146 } 147 } 148 149 func (p *TestCase) assertNotPanic() { 150 if p.rcov != nil { 151 p.t.Fatalf("panic: %v\n%+v\n", p.rcov, p.cstk) 152 } 153 } 154 155 // Equal checks current output value. 156 func (p *TestCase) Equal(v interface{}) *TestCase { 157 p.assertNotPanic() 158 p.assertEq(p.out[p.idx].Interface(), v) 159 return p 160 } 161 162 // PropEqual checks property of current output value. 163 func (p *TestCase) PropEqual(prop string, v interface{}) *TestCase { 164 p.assertNotPanic() 165 o := PropVal(p.out[p.idx], prop) 166 p.assertEq(o.Interface(), v) 167 return p 168 } 169 170 func (p *TestCase) assertEq(a, b interface{}) { 171 if !reflect.DeepEqual(a, b) { 172 p.t.Fatalf("%s:\nassertEq failed: %v, expected: %v\n", string(p.msg), a, b) 173 } 174 } 175 176 // PropVal returns property value of an object. 177 func PropVal(o reflect.Value, prop string) reflect.Value { 178 start: 179 switch o.Kind() { 180 case reflect.Struct: 181 if ret := o.FieldByName(prop); ret.IsValid() { 182 return ret 183 } 184 case reflect.Interface: 185 o = o.Elem() 186 goto start 187 } 188 if m := o.MethodByName(strings.Title(prop)); m.IsValid() { 189 out := m.Call([]reflect.Value{}) 190 if len(out) != 1 { 191 panic("invalid PropVal: " + prop) 192 } 193 return out[0] 194 } 195 panic(o.Type().String() + " object hasn't property: " + prop) 196 } 197 198 // ----------------------------------------------------------------------------