github.com/camlistore/go4@v0.0.0-20200104003542-c7e774b10ea0/testing/functest/functest.go (about) 1 /* 2 Copyright 2016 The go4.org Authors 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 functest contains utilities to ease writing table-driven 18 // tests for pure functions and method. 19 // 20 // Example: 21 // 22 // func square(v int) int { return v * v } 23 // 24 // func TestFunc(t *testing.T) { 25 // f := functest.New(square) 26 // f.Test(t, 27 // f.In(0).Want(0), 28 // f.In(1).Want(1), 29 // f.In(2).Want(4), 30 // f.In(3).Want(9), 31 // ) 32 // } 33 // 34 // It can test whether things panic: 35 // 36 // f := functest.New(condPanic) 37 // f.Test(t, 38 // f.In(false, nil), 39 // f.In(true, "boom").Check(func(res functest.Result) error { 40 // if res.Panic != "boom" { 41 // return fmt.Errorf("panic = %v; want boom", res.Panic) 42 // } 43 // return nil 44 // }), 45 // f.In(true, nil).Check(func(res functest.Result) error { 46 // if res.Panic != nil || res.Paniked { 47 // return fmt.Errorf("expected panic with nil value, got: %+v", res) 48 // } 49 // return nil 50 // }), 51 // ) 52 // 53 // If a test fails, functest does its best to format a useful error message. You can also 54 // name test cases: 55 // 56 // f := functest.New(square) 57 // f.Test(t, 58 // f.In(0).Want(0), 59 // f.In(1).Want(111), 60 // f.In(2).Want(4), 61 // f.Case("three").In(3).Want(999), 62 // ) 63 // 64 // Which would fail like: 65 // 66 // --- FAIL: TestSquare (0.00s) 67 // functest.go:304: square(1) = 1; want 111 68 // functest.go:304: three: square(3) = 9; want 999 69 // FAIL 70 // 71 package functest 72 73 import ( 74 "bytes" 75 "fmt" 76 "reflect" 77 "runtime" 78 "strings" 79 "testing" 80 ) 81 82 // Func is a wrapper around a func to test. 83 // It must be created with New. 84 type Func struct { 85 // Name is the name of the function to use in error messages. 86 // In most cases it is initialized by New, unless the function 87 // being tested is an anonymous function. 88 Name string 89 90 f interface{} // the func 91 fv reflect.Value // of f 92 } 93 94 var removePunc = strings.NewReplacer("(", "", ")", "", "*", "") 95 96 // New wraps a function for testing. 97 // The provided value f must be a function or method. 98 func New(f interface{}) *Func { 99 fv := reflect.ValueOf(f) 100 if fv.Kind() != reflect.Func { 101 panic("argument to New must be a func") 102 } 103 var name string 104 rf := runtime.FuncForPC(fv.Pointer()) 105 if rf != nil { 106 name = rf.Name() 107 if methType := strings.LastIndex(name, ".("); methType != -1 { 108 name = removePunc.Replace(name[methType+2:]) 109 } else if lastDot := strings.LastIndex(name, "."); lastDot != -1 { 110 name = name[lastDot+1:] 111 if strings.HasPrefix(name, "func") { 112 // Looks like some anonymous function. Prefer naming it "f". 113 name = "f" 114 } 115 } 116 } else { 117 name = "f" 118 } 119 120 return &Func{ 121 f: f, 122 fv: fv, 123 Name: name, 124 } 125 } 126 127 // Result is the result of a function call, for use with Check. 128 type Result struct { 129 // Result is the return value(s) of the function. 130 Result []interface{} 131 132 // Panic is the panic value of the function. 133 Panic interface{} 134 135 // Panicked is whether the function paniced. 136 // It can be used to determine whether a function 137 // called panic(nil). 138 Panicked bool 139 } 140 141 // Case is a test case to run. 142 // 143 // Test cases can be either named or unnamed, depending on how they're 144 // created. Naming cases is optional; all failures messages aim to 145 // have useful output and include the input to the function. 146 // 147 // Unless the function's arity is zero, all cases should have their input 148 // set with In. 149 // 150 // The case's expected output can be set with Want and/or Check. 151 type Case struct { 152 f *Func 153 in []interface{} 154 name string // optional 155 want []interface{} // non-nil if we check args 156 checkRes []func(Result) error 157 } 158 159 // Case returns a new named case. It should be modified before use. 160 func (f *Func) Case(name string) *Case { 161 return &Case{f: f, name: name} 162 } 163 164 // In returns a new unnamed test case. It will be identified by its arguments 165 // only. 166 func (f *Func) In(args ...interface{}) *Case { 167 return &Case{f: f, in: args} 168 } 169 170 // In sets the arguments of c used to call f. 171 func (c *Case) In(args ...interface{}) *Case { 172 c.in = args 173 return c 174 } 175 176 // Want sets the expected result values of the test case. 177 // Want modifies and returns c. 178 // Callers my use both Want and Check. 179 func (c *Case) Want(result ...interface{}) *Case { 180 if c.want != nil { 181 panic("duplicate Want declared on functest.Case") 182 } 183 c.want = result 184 numOut := c.f.fv.Type().NumOut() 185 if len(result) != numOut { 186 // TODO: let caller providing only interesting result values, or 187 // provide matchers. 188 panic(fmt.Sprintf("Want called with %d values; function returns %d values", len(result), numOut)) 189 } 190 return c 191 } 192 193 // Check adds a function to check the result of the case's function 194 // call. It is a low-level function when Want is insufficient. 195 // For instance, it allows checking whether a function panics. 196 // If no checker functions are registered, function panics are considered 197 // a test failure. 198 // 199 // Check modifies and returns c. 200 // Callers my use both Want and Check, and may use Check multiple times. 201 func (c *Case) Check(checker func(Result) error) *Case { 202 c.checkRes = append(c.checkRes, checker) 203 return c 204 } 205 206 // Test runs the provided test cases against f. 207 // If any test cases fail, t.Errorf is called. 208 func (f *Func) Test(t testing.TB, cases ...*Case) { 209 for _, tc := range cases { 210 f.testCase(t, tc) 211 } 212 } 213 214 func (f *Func) checkCall(in []reflect.Value) (out []reflect.Value, didPanic bool, panicValue interface{}) { 215 defer func() { panicValue = recover() }() 216 didPanic = true 217 out = f.fv.Call(in) 218 didPanic = false 219 return 220 } 221 222 var nilEmptyInterface = reflect.Zero(reflect.TypeOf((*interface{})(nil)).Elem()) 223 224 func (f *Func) testCase(t testing.TB, c *Case) { 225 // Non-variadic: 226 ft := f.fv.Type() 227 inReg := ft.NumIn() 228 if ft.IsVariadic() { 229 inReg-- 230 if len(c.in) < inReg { 231 c.errorf(t, ": input has %d arguments; func requires at least %d", len(c.in), inReg) 232 return 233 } 234 } else if len(c.in) != ft.NumIn() { 235 c.errorf(t, ": input has %d arguments; func takes %d", len(c.in), ft.NumIn()) 236 return 237 } 238 239 inv := make([]reflect.Value, len(c.in)) 240 for i, v := range c.in { 241 if v == nil { 242 inv[i] = nilEmptyInterface 243 } else { 244 inv[i] = reflect.ValueOf(v) 245 } 246 } 247 got, didPanic, panicValue := f.checkCall(inv) 248 249 var goti []interface{} 250 if !didPanic { 251 goti = make([]interface{}, len(got)) 252 for i, rv := range got { 253 goti[i] = rv.Interface() 254 } 255 } 256 257 if c.want != nil { 258 if !reflect.DeepEqual(goti, c.want) { 259 c.errorf(t, " = %v; want %v", formatRes(goti), formatRes(c.want)) 260 } 261 } 262 for _, checkRes := range c.checkRes { 263 err := checkRes(Result{ 264 Result: goti, 265 Panic: panicValue, 266 Panicked: didPanic, 267 }) 268 if err != nil { 269 c.errorf(t, ": %v", err) 270 } 271 } 272 if didPanic && (c.checkRes == nil) { 273 c.errorf(t, ": panicked with %v", panicValue) 274 } 275 } 276 277 func formatRes(res []interface{}) string { 278 var buf bytes.Buffer 279 if len(res) != 1 { 280 buf.WriteByte('(') 281 } 282 formatValues(&buf, res) 283 if len(res) != 1 { 284 buf.WriteByte(')') 285 } 286 return buf.String() 287 } 288 289 func formatValues(buf *bytes.Buffer, vals []interface{}) { 290 for i, v := range vals { 291 if i != 0 { 292 buf.WriteString(", ") 293 } 294 fmt.Fprintf(buf, "%#v", v) 295 } 296 } 297 298 func (c *Case) errorf(t testing.TB, format string, args ...interface{}) { 299 var buf bytes.Buffer 300 if c.name != "" { 301 fmt.Fprintf(&buf, "%s: ", c.name) 302 } 303 buf.WriteString(c.f.Name) 304 buf.WriteString("(") 305 formatValues(&buf, c.in) 306 buf.WriteString(")") 307 fmt.Fprintf(&buf, format, args...) 308 t.Errorf("%s", buf.Bytes()) 309 }