Codebase list golang-github-frankban-quicktest / 67b6636
Merge pull request #40 from rogpeppe/007-more-general-run make C.Run more general Francesco Banconi authored 4 years ago GitHub committed 4 years ago
2 changed file(s) with 161 addition(s) and 21 deletion(s). Raw diff Collapse all Expand all
33
44 import (
55 "fmt"
6 "reflect"
67 "strings"
78 "sync"
89 "testing"
149150 return c.check(c.TB.Fatal, checker, got, args)
150151 }
151152
153 var (
154 stringType = reflect.TypeOf("")
155 boolType = reflect.TypeOf(true)
156 tbType = reflect.TypeOf(new(testing.TB)).Elem()
157 )
158
152159 // Run runs f as a subtest of t called name. It's a wrapper around
153 // *testing.T.Run that provides the quicktest checker to f. When
160 // the Run method of c.TB that provides the quicktest checker to f. When
154161 // the function completes, c.Done will be called to run any
155162 // functions registered with c.Defer.
156163 //
157 // For instance:
164 // c.TB must implement a Run method of the following form:
165 //
166 // Run(string, func(T)) bool
167 //
168 // where T is any type that is assignable to testing.TB.
169 // Implementations include *testing.T, *testing.B and *C itself.
170 //
171 // The TB field in the subtest will hold the value passed
172 // by Run to its argument function.
158173 //
159174 // func TestFoo(t *testing.T) {
160175 // c := qt.New(t)
165180 // }
166181 //
167182 // A panic is raised when Run is called and the embedded concrete type does not
168 // implement Run, for instance if TB's concrete type is a benchmark.
183 // implement a Run method with a correct signature.
169184 func (c *C) Run(name string, f func(c *C)) bool {
170 r, ok := c.TB.(interface {
171 Run(string, func(*testing.T)) bool
172 })
173 if !ok {
174 panic(fmt.Sprintf("cannot execute Run with underlying concrete type %T", c.TB))
175 }
176 return r.Run(name, func(t *testing.T) {
177 c2 := New(t)
185 badType := func(m string) {
186 panic(fmt.Sprintf("cannot execute Run with underlying concrete type %T (%s)", c.TB, m))
187 }
188 m := reflect.ValueOf(c.TB).MethodByName("Run")
189 if !m.IsValid() {
190 // c.TB doesn't implement a Run method.
191 badType("no Run method")
192 }
193 mt := m.Type()
194 if mt.NumIn() != 2 ||
195 mt.In(0) != stringType ||
196 mt.NumOut() != 1 ||
197 mt.Out(0) != boolType {
198 // The Run method doesn't have the right argument counts and types.
199 badType("wrong argument count for Run method")
200 }
201 farg := mt.In(1)
202 if farg.Kind() != reflect.Func ||
203 farg.NumIn() != 1 ||
204 farg.NumOut() != 0 ||
205 !farg.In(0).AssignableTo(tbType) {
206 // The first argument to the Run function arg isn't right.
207 badType("bad first argument type for Run method")
208 }
209 fv := reflect.MakeFunc(farg, func(args []reflect.Value) []reflect.Value {
210 c2 := New(args[0].Interface().(testing.TB))
178211 defer c2.Done()
179212 c2.SetFormat(c.getFormat())
180213 f(c2)
214 return nil
181215 })
216 return m.Call([]reflect.Value{reflect.ValueOf(name), fv})[0].Interface().(bool)
182217 }
183218
184219 // Parallel signals that this test is to be run in parallel with (and only with) other parallel tests.
380380 assertBool(t, ok, true)
381381 }
382382
383 func TestCRunOnBenchmark(t *testing.T) {
384 called := false
385 testing.Benchmark(func(b *testing.B) {
386 c := qt.New(b)
387 c.Run("c", func(c *qt.C) {
388 b1, ok := c.TB.(*testing.B)
389 if !ok {
390 t.Errorf("c.TB is type %T not *testing.B", c.TB)
391 return
392 }
393 if b1 == b {
394 t.Errorf("c.TB hasn't been given a new B value")
395 return
396 }
397 called = true
398 })
399 })
400 if !called {
401 t.Fatalf("sub-benchmark was never called")
402 }
403 }
404
405 // wrongRun1 has Run method with wrong arg count.
406 type wrongRun1 struct {
407 testing.TB
408 }
409
410 func (wrongRun1) Run() {}
411
412 // wrongRun2 has no Run method.
413 type wrongRun2 struct {
414 testing.TB
415 }
416
417 // wrongRun3 has Run method that takes a type not
418 // assignable to testing.TB.
419 type wrongRun3 struct {
420 testing.TB
421 }
422
423 func (wrongRun3) Run(string, func(string)) bool { return false }
424
425 // wrongRun4 has Run method that doesn't return bool.
426 type wrongRun4 struct {
427 testing.TB
428 }
429
430 func (wrongRun4) Run(string, func(*testing.T)) {}
431
432 var CRunPanicTests = []struct {
433 tb testing.TB
434 expectPanic string
435 }{{
436 tb: wrongRun1{},
437 expectPanic: "wrong argument count for Run method",
438 }, {
439 tb: wrongRun2{},
440 expectPanic: "no Run method",
441 }, {
442 tb: wrongRun3{},
443 expectPanic: "bad first argument type for Run method",
444 }, {
445 tb: wrongRun4{},
446 expectPanic: "wrong argument count for Run method",
447 }}
448
383449 func TestCRunPanic(t *testing.T) {
384 c := qt.New(&testing.B{})
385 var run bool
386 defer func() {
387 r := recover()
388 if r != "cannot execute Run with underlying concrete type *testing.B" {
389 t.Fatalf("unexpected panic recover: %v", r)
390 }
391 }()
392 c.Run("panic", func(innerC *qt.C) {})
393 assertBool(t, run, true)
450 for _, test := range CRunPanicTests {
451 t.Run(fmt.Sprintf("%T", test.tb), func(t *testing.T) {
452 c := qt.New(test.tb)
453 defer func() {
454 got := recover()
455 want := fmt.Sprintf(
456 "cannot execute Run with underlying concrete type %T (%s)",
457 test.tb, test.expectPanic,
458 )
459 if got != want {
460 t.Fatalf("unexpected panic recover message; got %q want %q", got, want)
461 }
462 }()
463 c.Run("panic", func(innerC *qt.C) {})
464 })
465 }
394466 }
395467
396468 func TestCRunFormat(t *testing.T) {
510582 c.Assert(outerDefer, qt.Equals, 0)
511583 }
512584
585 type customT struct {
586 *testing.T
587 data int
588 }
589
590 func (t *customT) Run(name string, f func(*customT)) bool {
591 return t.T.Run(name, func(t1 *testing.T) {
592 f(&customT{t1, t.data})
593 })
594 }
595
596 func TestCRunCustomType(t *testing.T) {
597 ct := &customT{t, 99}
598 c := qt.New(ct)
599 called := 0
600 c.Run("test", func(c *qt.C) {
601 called++
602 ct1, ok := c.TB.(*customT)
603 if !ok {
604 t.Error("TB isn't expected type")
605 }
606 if ct1.data != ct.data {
607 t.Errorf("data not copied correctly; got %v want %v", ct1.data, ct.data)
608 }
609 if ct1 == ct {
610 t.Errorf("old instance passed, not new")
611 }
612 })
613 if called != 1 {
614 t.Fatalf("subtest was called %d times, not once", called)
615 }
616 }
617
513618 func checkResult(t *testing.T, ok bool, got, want string) {
514619 if want != "" {
515620 assertPrefix(t, got, want+"stack:\n")