370 lines
10 KiB
Go
370 lines
10 KiB
Go
// Licensed under the MIT license, see LICENSE file for details.
|
|
|
|
package quicktest
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
// Check runs the given check using the provided t and continues execution in
|
|
// case of failure. For instance:
|
|
//
|
|
// qt.Check(t, answer, qt.Equals, 42)
|
|
// qt.Check(t, got, qt.IsNil, qt.Commentf("iteration %d", i))
|
|
//
|
|
// Additional args (not consumed by the checker), when provided, are included as
|
|
// comments in the failure output when the check fails.
|
|
func Check(t testing.TB, got interface{}, checker Checker, args ...interface{}) bool {
|
|
t.Helper()
|
|
return New(t).Check(got, checker, args...)
|
|
}
|
|
|
|
// Assert runs the given check using the provided t and stops execution in case
|
|
// of failure. For instance:
|
|
//
|
|
// qt.Assert(t, got, qt.DeepEquals, []int{42, 47})
|
|
// qt.Assert(t, got, qt.ErrorMatches, "bad wolf .*", qt.Commentf("a comment"))
|
|
//
|
|
// Additional args (not consumed by the checker), when provided, are included as
|
|
// comments in the failure output when the check fails.
|
|
func Assert(t testing.TB, got interface{}, checker Checker, args ...interface{}) bool {
|
|
t.Helper()
|
|
return New(t).Assert(got, checker, args...)
|
|
}
|
|
|
|
// New returns a new checker instance that uses t to fail the test when checks
|
|
// fail. It only ever calls the Fatal, Error and (when available) Run methods
|
|
// of t. For instance.
|
|
//
|
|
// func TestFoo(t *testing.T) {
|
|
// t.Run("A=42", func(t *testing.T) {
|
|
// c := qt.New(t)
|
|
// c.Assert(a, qt.Equals, 42)
|
|
// })
|
|
// }
|
|
//
|
|
// The library already provides some base checkers, and more can be added by
|
|
// implementing the Checker interface.
|
|
//
|
|
// If there is a likelihood that Defer will be called, then
|
|
// a call to Done should be deferred after calling New.
|
|
// For example:
|
|
//
|
|
// func TestFoo(t *testing.T) {
|
|
// c := qt.New(t)
|
|
// defer c.Done()
|
|
// c.Setenv("HOME", "/non-existent")
|
|
// c.Assert(os.Getenv("HOME"), qt.Equals, "/non-existent")
|
|
// })
|
|
//
|
|
// A value of C that's has a non-nil TB field but is otherwise zero is valid.
|
|
// So:
|
|
//
|
|
// c := &qt.C{TB: t}
|
|
//
|
|
// is valid a way to create a C value; it's exactly the same as:
|
|
//
|
|
// c := qt.New(t)
|
|
//
|
|
// Methods on C may be called concurrently, assuming the underlying
|
|
// `testing.TB` implementation also allows that.
|
|
func New(t testing.TB) *C {
|
|
return &C{
|
|
TB: t,
|
|
}
|
|
}
|
|
|
|
// C is a quicktest checker. It embeds a testing.TB value and provides
|
|
// additional checking functionality. If an Assert or Check operation fails, it
|
|
// uses the wrapped TB value to fail the test appropriately.
|
|
type C struct {
|
|
testing.TB
|
|
|
|
mu sync.Mutex
|
|
doneNeeded bool
|
|
deferred func()
|
|
format formatFunc
|
|
}
|
|
|
|
// cleaner is implemented by testing.TB on Go 1.14 and later.
|
|
type cleaner interface {
|
|
Cleanup(func())
|
|
}
|
|
|
|
// Defer registers a function to be called when c.Done is
|
|
// called. Deferred functions will be called in last added, first called
|
|
// order. If c.Done is not called by the end of the test, the test
|
|
// may panic. Note that if Cleanup is called, there is no
|
|
// need to call Done.
|
|
//
|
|
// Deprecated: in Go >= 1.14 use testing.TB.Cleanup instead.
|
|
func (c *C) Defer(f func()) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
if cleaner, ok := c.TB.(cleaner); ok {
|
|
// Use TB.Cleanup when available, but add a check
|
|
// that Done has been called so that we don't run
|
|
// into unexpected Go version incompatibilities.
|
|
if c.doneNeeded {
|
|
// We've already installed the wrapper func that checks for Done
|
|
// so we can avoid doing it again.
|
|
cleaner.Cleanup(f)
|
|
return
|
|
}
|
|
c.doneNeeded = true
|
|
cleaner.Cleanup(func() {
|
|
c.mu.Lock()
|
|
doneNeeded := c.doneNeeded
|
|
c.mu.Unlock()
|
|
if doneNeeded {
|
|
panic("Done not called after Defer")
|
|
}
|
|
f()
|
|
})
|
|
return
|
|
}
|
|
|
|
oldDeferred := c.deferred
|
|
c.deferred = func() {
|
|
if oldDeferred != nil {
|
|
defer oldDeferred()
|
|
}
|
|
f()
|
|
}
|
|
}
|
|
|
|
// Done calls all the functions registered by Defer in reverse
|
|
// registration order. After it's called, the functions are
|
|
// unregistered, so calling Done twice will only call them once.
|
|
//
|
|
// When a test function is called by Run, Done will be called
|
|
// automatically on the C value passed into it.
|
|
//
|
|
// Deprecated: in Go >= 1.14 this is no longer needed if using
|
|
// testing.TB.Cleanup.
|
|
func (c *C) Done() {
|
|
c.mu.Lock()
|
|
deferred := c.deferred
|
|
c.deferred = nil
|
|
c.doneNeeded = false
|
|
c.mu.Unlock()
|
|
|
|
if deferred != nil {
|
|
deferred()
|
|
}
|
|
}
|
|
|
|
// SetFormat sets the function used to print values in test failures.
|
|
// By default Format is used.
|
|
// Any subsequent subtests invoked with c.Run will also use this function by
|
|
// default.
|
|
func (c *C) SetFormat(format func(interface{}) string) {
|
|
c.mu.Lock()
|
|
c.format = format
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
// getFormat returns the format function
|
|
// safely acquired under lock.
|
|
func (c *C) getFormat() func(interface{}) string {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
return c.format
|
|
}
|
|
|
|
// Check runs the given check and continues execution in case of failure.
|
|
// For instance:
|
|
//
|
|
// c.Check(answer, qt.Equals, 42)
|
|
// c.Check(got, qt.IsNil, qt.Commentf("iteration %d", i))
|
|
//
|
|
// Additional args (not consumed by the checker), when provided, are included
|
|
// as comments in the failure output when the check fails.
|
|
func (c *C) Check(got interface{}, checker Checker, args ...interface{}) bool {
|
|
c.TB.Helper()
|
|
return check(c, checkParams{
|
|
fail: c.TB.Error,
|
|
checker: checker,
|
|
got: got,
|
|
args: args,
|
|
})
|
|
}
|
|
|
|
// Assert runs the given check and stops execution in case of failure.
|
|
// For instance:
|
|
//
|
|
// c.Assert(got, qt.DeepEquals, []int{42, 47})
|
|
// c.Assert(got, qt.ErrorMatches, "bad wolf .*", qt.Commentf("a comment"))
|
|
//
|
|
// Additional args (not consumed by the checker), when provided, are included
|
|
// as comments in the failure output when the check fails.
|
|
func (c *C) Assert(got interface{}, checker Checker, args ...interface{}) bool {
|
|
c.TB.Helper()
|
|
return check(c, checkParams{
|
|
fail: c.TB.Fatal,
|
|
checker: checker,
|
|
got: got,
|
|
args: 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
|
|
// 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.
|
|
//
|
|
// 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)
|
|
// c.Run("A=42", func(c *qt.C) {
|
|
// // This assertion only stops the current subtest.
|
|
// c.Assert(a, qt.Equals, 42)
|
|
// })
|
|
// }
|
|
//
|
|
// 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")
|
|
}
|
|
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.
|
|
// It's a wrapper around *testing.T.Parallel.
|
|
//
|
|
// A panic is raised when Parallel is called and the embedded concrete type does not
|
|
// implement Parallel, for instance if TB's concrete type is a benchmark.
|
|
func (c *C) Parallel() {
|
|
p, ok := c.TB.(interface {
|
|
Parallel()
|
|
})
|
|
if !ok {
|
|
panic(fmt.Sprintf("cannot execute Parallel with underlying concrete type %T", c.TB))
|
|
}
|
|
p.Parallel()
|
|
}
|
|
|
|
// check performs the actual check with the provided params.
|
|
// In case of failure p.fail is called. In the fail report values are formatted
|
|
// using p.format.
|
|
func check(c *C, p checkParams) bool {
|
|
c.TB.Helper()
|
|
rp := reportParams{
|
|
got: p.got,
|
|
args: p.args,
|
|
format: c.getFormat(),
|
|
}
|
|
if rp.format == nil {
|
|
// No format set; use the default: Format.
|
|
rp.format = Format
|
|
}
|
|
|
|
// Allow checkers to annotate messages.
|
|
note := func(key string, value interface{}) {
|
|
rp.notes = append(rp.notes, note{
|
|
key: key,
|
|
value: value,
|
|
})
|
|
}
|
|
|
|
// Ensure that we have a checker.
|
|
if p.checker == nil {
|
|
p.fail(report(BadCheckf("nil checker provided"), rp))
|
|
return false
|
|
}
|
|
|
|
// Extract comments if provided.
|
|
for len(p.args) > 0 {
|
|
comment, ok := p.args[len(p.args)-1].(Comment)
|
|
if !ok {
|
|
break
|
|
}
|
|
rp.comments = append([]Comment{comment}, rp.comments...)
|
|
p.args = p.args[:len(p.args)-1]
|
|
}
|
|
rp.args = p.args
|
|
|
|
// Validate that we have the correct number of arguments.
|
|
rp.argNames = p.checker.ArgNames()
|
|
wantNumArgs := len(rp.argNames) - 1
|
|
if gotNumArgs := len(rp.args); gotNumArgs != wantNumArgs {
|
|
if gotNumArgs > 0 {
|
|
note("got args", rp.args)
|
|
}
|
|
if wantNumArgs > 0 {
|
|
note("want args", Unquoted(strings.Join(rp.argNames[1:], ", ")))
|
|
}
|
|
var prefix string
|
|
if gotNumArgs > wantNumArgs {
|
|
prefix = "too many arguments provided to checker"
|
|
} else {
|
|
prefix = "not enough arguments provided to checker"
|
|
}
|
|
p.fail(report(BadCheckf("%s: got %d, want %d", prefix, gotNumArgs, wantNumArgs), rp))
|
|
return false
|
|
}
|
|
|
|
// Execute the check and report the failure if necessary.
|
|
if err := p.checker.Check(p.got, p.args, note); err != nil {
|
|
p.fail(report(err, rp))
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// checkParams holds parameters for executing a check.
|
|
type checkParams struct {
|
|
fail func(...interface{})
|
|
checker Checker
|
|
got interface{}
|
|
args []interface{}
|
|
}
|