diff --git a/bench_test.go b/bench_test.go new file mode 100644 index 0000000..e971268 --- /dev/null +++ b/bench_test.go @@ -0,0 +1,29 @@ +package quicktest_test + +import ( + "testing" + + qt "github.com/frankban/quicktest" +) + +func BenchmarkCNewAndRunWithCustomType(b *testing.B) { + for i := 0; i < b.N; i++ { + c := qt.New(customTForBenchmark{}) + c.Run("test", func(c *qt.C) {}) + } +} + +func BenchmarkCRunWithCustomType(b *testing.B) { + c := qt.New(customTForBenchmark{}) + for i := 0; i < b.N; i++ { + c.Run("test", func(c *qt.C) {}) + } +} + +type customTForBenchmark struct { + testing.TB +} + +func (customTForBenchmark) Run(name string, f func(testing.TB)) bool { + return true +} diff --git a/go.mod b/go.mod index d5ba542..21f2cd5 100644 --- a/go.mod +++ b/go.mod @@ -5,4 +5,12 @@ require ( github.com/kr/pretty v0.3.1 ) -go 1.13 +require ( + github.com/kr/text v0.2.0 // indirect + github.com/rogpeppe/go-internal v1.9.0 // indirect +) + +// We do actually support go 1.14, but until go 1.21 +// we can't have any generics code even if guarded +// by a build tag. +go 1.18 diff --git a/quicktest.go b/quicktest.go index 65243cf..e944435 100644 --- a/quicktest.go +++ b/quicktest.go @@ -243,6 +243,10 @@ 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 { + r, ok := fastRun(c, name, f) + if ok { + return r + } badType := func(m string) { panic(fmt.Sprintf("cannot execute Run with underlying concrete type %T (%s)", c.TB, m)) } diff --git a/run_go1.18.go b/run_go1.18.go new file mode 100644 index 0000000..76cab73 --- /dev/null +++ b/run_go1.18.go @@ -0,0 +1,41 @@ +// Licensed under the MIT license, see LICENSE file for details. + +//go:build go1.18 +// +build go1.18 + +package quicktest + +import "testing" + +// fastRun implements c.Run for some known types. +// It returns the result of calling c.Run and also reports +// whether it was able to do so. +func fastRun(c *C, name string, f func(c *C)) (bool, bool) { + switch t := c.TB.(type) { + case runner[*testing.T]: + return fastRun1(c, name, f, t), true + case runner[*testing.B]: + return fastRun1(c, name, f, t), true + case runner[*C]: + return fastRun1(c, name, f, t), true + case runner[testing.TB]: + // This case is here mostly for benchmarking, because + // it's hard to create a working concrete instance of *testing.T + // that isn't part of the outer tests. + return fastRun1(c, name, f, t), true + } + return false, false +} + +type runner[T any] interface { + Run(name string, f func(T)) bool +} + +func fastRun1[T testing.TB](c *C, name string, f func(*C), t runner[T]) bool { + return t.Run(name, func(t2 T) { + c2 := New(t2) + defer c2.Done() + c2.SetFormat(c.getFormat()) + f(c2) + }) +} diff --git a/run_go1.18_test.go b/run_go1.18_test.go new file mode 100644 index 0000000..847e87a --- /dev/null +++ b/run_go1.18_test.go @@ -0,0 +1,60 @@ +// Licensed under the MIT license, see LICENSE file for details. + +//go:build go1.18 +// +build go1.18 + +package quicktest_test + +import ( + "reflect" + "testing" + + qt "github.com/frankban/quicktest" +) + +type customT2[T testing.TB] struct { + testing.TB +} + +func (t *customT2[T]) Run(name string, f func(T)) bool { + f(*new(T)) + return true +} + +func (t *customT2[T]) rtype() reflect.Type { + return reflect.TypeOf((*T)(nil)).Elem() +} + +type otherTB struct { + testing.TB +} + +func TestCRunCustomTypeWithNonMatchingRunSignature(t *testing.T) { + // Note: this test runs only on >=go1.18 because there isn't any + // code that specializes on this types that's enabled on versions before that. + tests := []interface { + testing.TB + rtype() reflect.Type + }{ + &customT2[*testing.T]{}, + &customT2[*testing.B]{}, + &customT2[*qt.C]{}, + &customT2[testing.TB]{}, + &customT2[otherTB]{}, + } + for _, test := range tests { + t.Run(test.rtype().String(), func(t *testing.T) { + c := qt.New(test) + called := 0 + c.Run("test", func(c *qt.C) { + called++ + if test.rtype().Kind() != reflect.Interface && reflect.TypeOf(c.TB) != test.rtype() { + t.Errorf("TB isn't expected type (want %v got %T)", test.rtype(), c.TB) + } + }) + if got, want := called, 1; got != want { + t.Errorf("subtest was called %d times, not once", called) + } + }) + } +} diff --git a/run_legacy.go b/run_legacy.go new file mode 100644 index 0000000..d4eddfd --- /dev/null +++ b/run_legacy.go @@ -0,0 +1,10 @@ +// Licensed under the MIT license, see LICENSE file for details. + +//go:build !go1.18 +// +build !go1.18 + +package quicktest + +func fastRun(c *C, name string, f func(c *C)) (bool, bool) { + return false, false +}