Unverified Commit 67b6636e authored by Francesco Banconi's avatar Francesco Banconi Committed by GitHub

Merge pull request #40 from rogpeppe/007-more-general-run

make C.Run more general
parents e5c096ee 08b59ac9
......@@ -4,6 +4,7 @@ package quicktest
import (
"fmt"
"reflect"
"strings"
"sync"
"testing"
......@@ -150,12 +151,26 @@ func (c *C) Assert(got interface{}, checker Checker, args ...interface{}) bool {
return c.check(c.TB.Fatal, checker, got, args)
}
var (
stringType = reflect.TypeOf("")
boolType = reflect.TypeOf(true)
tbType = reflect.TypeOf(new(testing.TB)).Elem()
)
// Run runs f as a subtest of t called name. It's a wrapper around
// *testing.T.Run that provides the quicktest checker to f. When
// the Run method of c.TB that provides the quicktest checker to f. When
// the function completes, c.Done will be called to run any
// functions registered with c.Defer.
//
// For instance:
// c.TB must implement a Run method of the following form:
//
// Run(string, func(T)) bool
//
// where T is any type that is assignable to testing.TB.
// Implementations include *testing.T, *testing.B and *C itself.
//
// The TB field in the subtest will hold the value passed
// by Run to its argument function.
//
// func TestFoo(t *testing.T) {
// c := qt.New(t)
......@@ -166,20 +181,40 @@ func (c *C) Assert(got interface{}, checker Checker, args ...interface{}) bool {
// }
//
// A panic is raised when Run is called and the embedded concrete type does not
// implement Run, for instance if TB's concrete type is a benchmark.
// implement a Run method with a correct signature.
func (c *C) Run(name string, f func(c *C)) bool {
r, ok := c.TB.(interface {
Run(string, func(*testing.T)) bool
})
if !ok {
panic(fmt.Sprintf("cannot execute Run with underlying concrete type %T", c.TB))
badType := func(m string) {
panic(fmt.Sprintf("cannot execute Run with underlying concrete type %T (%s)", c.TB, m))
}
m := reflect.ValueOf(c.TB).MethodByName("Run")
if !m.IsValid() {
// c.TB doesn't implement a Run method.
badType("no Run method")
}
mt := m.Type()
if mt.NumIn() != 2 ||
mt.In(0) != stringType ||
mt.NumOut() != 1 ||
mt.Out(0) != boolType {
// The Run method doesn't have the right argument counts and types.
badType("wrong argument count for Run method")
}
farg := mt.In(1)
if farg.Kind() != reflect.Func ||
farg.NumIn() != 1 ||
farg.NumOut() != 0 ||
!farg.In(0).AssignableTo(tbType) {
// The first argument to the Run function arg isn't right.
badType("bad first argument type for Run method")
}
return r.Run(name, func(t *testing.T) {
c2 := New(t)
fv := reflect.MakeFunc(farg, func(args []reflect.Value) []reflect.Value {
c2 := New(args[0].Interface().(testing.TB))
defer c2.Done()
c2.SetFormat(c.getFormat())
f(c2)
return nil
})
return m.Call([]reflect.Value{reflect.ValueOf(name), fv})[0].Interface().(bool)
}
// Parallel signals that this test is to be run in parallel with (and only with) other parallel tests.
......
......@@ -381,17 +381,89 @@ func TestCRunSuccess(t *testing.T) {
assertBool(t, ok, true)
}
func TestCRunOnBenchmark(t *testing.T) {
called := false
testing.Benchmark(func(b *testing.B) {
c := qt.New(b)
c.Run("c", func(c *qt.C) {
b1, ok := c.TB.(*testing.B)
if !ok {
t.Errorf("c.TB is type %T not *testing.B", c.TB)
return
}
if b1 == b {
t.Errorf("c.TB hasn't been given a new B value")
return
}
called = true
})
})
if !called {
t.Fatalf("sub-benchmark was never called")
}
}
// wrongRun1 has Run method with wrong arg count.
type wrongRun1 struct {
testing.TB
}
func (wrongRun1) Run() {}
// wrongRun2 has no Run method.
type wrongRun2 struct {
testing.TB
}
// wrongRun3 has Run method that takes a type not
// assignable to testing.TB.
type wrongRun3 struct {
testing.TB
}
func (wrongRun3) Run(string, func(string)) bool { return false }
// wrongRun4 has Run method that doesn't return bool.
type wrongRun4 struct {
testing.TB
}
func (wrongRun4) Run(string, func(*testing.T)) {}
var CRunPanicTests = []struct {
tb testing.TB
expectPanic string
}{{
tb: wrongRun1{},
expectPanic: "wrong argument count for Run method",
}, {
tb: wrongRun2{},
expectPanic: "no Run method",
}, {
tb: wrongRun3{},
expectPanic: "bad first argument type for Run method",
}, {
tb: wrongRun4{},
expectPanic: "wrong argument count for Run method",
}}
func TestCRunPanic(t *testing.T) {
c := qt.New(&testing.B{})
var run bool
defer func() {
r := recover()
if r != "cannot execute Run with underlying concrete type *testing.B" {
t.Fatalf("unexpected panic recover: %v", r)
}
}()
c.Run("panic", func(innerC *qt.C) {})
assertBool(t, run, true)
for _, test := range CRunPanicTests {
t.Run(fmt.Sprintf("%T", test.tb), func(t *testing.T) {
c := qt.New(test.tb)
defer func() {
got := recover()
want := fmt.Sprintf(
"cannot execute Run with underlying concrete type %T (%s)",
test.tb, test.expectPanic,
)
if got != want {
t.Fatalf("unexpected panic recover message; got %q want %q", got, want)
}
}()
c.Run("panic", func(innerC *qt.C) {})
})
}
}
func TestCRunFormat(t *testing.T) {
......@@ -511,6 +583,39 @@ func TestCRunDefer(t *testing.T) {
c.Assert(outerDefer, qt.Equals, 0)
}
type customT struct {
*testing.T
data int
}
func (t *customT) Run(name string, f func(*customT)) bool {
return t.T.Run(name, func(t1 *testing.T) {
f(&customT{t1, t.data})
})
}
func TestCRunCustomType(t *testing.T) {
ct := &customT{t, 99}
c := qt.New(ct)
called := 0
c.Run("test", func(c *qt.C) {
called++
ct1, ok := c.TB.(*customT)
if !ok {
t.Error("TB isn't expected type")
}
if ct1.data != ct.data {
t.Errorf("data not copied correctly; got %v want %v", ct1.data, ct.data)
}
if ct1 == ct {
t.Errorf("old instance passed, not new")
}
})
if called != 1 {
t.Fatalf("subtest was called %d times, not once", called)
}
}
func checkResult(t *testing.T, ok bool, got, want string) {
if want != "" {
assertPrefix(t, got, want+"stack:\n")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment