Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ package quicktest
var (
Prefixf = prefixf
TestingVerbose = &testingVerbose

GetRunFuncSignature = getRunFuncSignature
GetRunFuncSignatureCache = getRunFuncSignatureCache
)
84 changes: 61 additions & 23 deletions quicktest.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,60 @@ var (
tbType = reflect.TypeOf(new(testing.TB)).Elem()
)

// getRunFuncSignature checks the signature of the Run method of the type (ex: *testing.T)
// and returns the signature of its function argument (func(t *T) for *testing.T).
func getRunFuncSignature(t reflect.Type) (reflect.Type, error) {
badType := func(detail string) (reflect.Type, error) {
return nil, fmt.Errorf("cannot execute Run with underlying concrete type %s (%s)", t, detail)
}
m, ok := t.MethodByName("Run")
if !ok {
// c.TB doesn't implement a Run method.
return badType("no Run method")
}
mt := m.Type
// fmt.Println(mt)
if mt.NumIn() != 3 ||
mt.In(1) != stringType ||
mt.NumOut() != 1 ||
mt.Out(0) != boolType {
// The Run method doesn't have the right argument counts and types.
return badType("wrong argument count for Run method")
}
farg := mt.In(2)
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.
return badType("bad first argument type for Run method")
}

return farg, nil
}

// Cache of getRunFuncSignature results used by getRunFuncSignatureCache.
var runFuncSigCache = []struct {
T reflect.Type
FArg reflect.Type
}{
// Prefill with common types
// Use TestCRunGetFuncSig to generate values
{T: reflect.TypeOf((*testing.T)(nil)), FArg: reflect.TypeOf((func(*testing.T))(nil))},
{T: reflect.TypeOf((*testing.B)(nil)), FArg: reflect.TypeOf((func(*testing.B))(nil))},
{T: reflect.TypeOf((*C)(nil)), FArg: reflect.TypeOf((func(*C))(nil))},
}

func getRunFuncSignatureCache(t reflect.Type) (reflect.Type, error) {
for i := 0; i < len(runFuncSigCache); i++ {
if t == runFuncSigCache[i].T {
return runFuncSigCache[i].FArg, nil
}
}

return getRunFuncSignature(t)
}

// Run runs f as a subtest of t called name. It's a wrapper around
// 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
Expand All @@ -243,30 +297,12 @@ var (
// A panic is raised when Run is called and the embedded concrete type does not
// implement a Run method with a correct signature.
func (c *C) Run(name string, f func(c *C)) bool {
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")

farg, err := getRunFuncSignatureCache(reflect.TypeOf(c.TB))
if err != nil {
panic(err.Error())
}

cFormat := c.getFormat()
fv := reflect.MakeFunc(farg, func(args []reflect.Value) []reflect.Value {
c2 := New(args[0].Interface().(testing.TB))
Expand All @@ -275,6 +311,8 @@ func (c *C) Run(name string, f func(c *C)) bool {
f(c2)
return nil
})

m := reflect.ValueOf(c.TB).MethodByName("Run")
return m.Call([]reflect.Value{reflect.ValueOf(name), fv})[0].Interface().(bool)
}

Expand Down
42 changes: 42 additions & 0 deletions quicktest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"bytes"
"errors"
"fmt"
"reflect"
"strings"
"testing"

Expand Down Expand Up @@ -527,6 +528,47 @@ want:
`)
}

// TestCRunGetFuncSig checks the internal function getRunFuncSignature which is used in Run.
func TestCRunGetFuncSig(t *testing.T) {
for _, tb := range []testing.TB{
t, // *testing.T
(*testing.B)(nil), // *testing.B
qt.New(t), // *quicktest.C
} {
tbt := reflect.TypeOf(tb)
farg, err := qt.GetRunFuncSignature(tbt)
if err != nil {
t.Error(err)
continue
}
t.Logf("(%s).Run: func(string, %s) bool", tbt, farg)
// useful to create a cache
t.Logf("{T: reflect.TypeOf((%s)(nil)), FArg: reflect.TypeOf((%s)(nil))}", tbt, farg)
}
}

func BenchmarkCRunGetFuncSig(b *testing.B) {
for _, tb := range []testing.TB{
(*testing.T)(nil), // *testing.T
b, // *testing.B
qt.New(b), // *quicktest.C
} {
tbt := reflect.TypeOf(tb)

b.Run(tbt.String()+"-no-cache", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = qt.GetRunFuncSignature(tbt)
}
})

b.Run(tbt.String()+"-with-cache", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = qt.GetRunFuncSignatureCache(tbt)
}
})
}
}

func TestHelper(t *testing.T) {
tt := &testingT{}
qt.Assert(tt, true, qt.IsFalse)
Expand Down