792 lines
23 KiB
Go
792 lines
23 KiB
Go
// Licensed under the MIT license, see LICENSE file for details.
|
|
|
|
package quicktest
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"github.com/kr/pretty"
|
|
)
|
|
|
|
// Checker is implemented by types used as part of Check/Assert invocations.
|
|
type Checker interface {
|
|
// Check checks that the obtained value (got) is correct with respect to
|
|
// the checker's arguments (args). On failure, the returned error is
|
|
// printed along with the checker arguments and any key-value pairs added
|
|
// by calling the note function. Values are pretty-printed unless they are
|
|
// of type Unquoted.
|
|
//
|
|
// When the check arguments are invalid, Check may return a BadCheck error,
|
|
// which suppresses printing of the checker arguments. Values added with
|
|
// note are still printed.
|
|
//
|
|
// If Check returns ErrSilent, neither the checker arguments nor the error
|
|
// are printed. Again, values added with note are still printed.
|
|
Check(got interface{}, args []interface{}, note func(key string, value interface{})) error
|
|
|
|
// ArgNames returns the names of all required arguments, including the
|
|
// mandatory got argument and any additional args.
|
|
ArgNames() []string
|
|
}
|
|
|
|
// Equals is a Checker checking equality of two comparable values.
|
|
//
|
|
// For instance:
|
|
//
|
|
// c.Assert(answer, qt.Equals, 42)
|
|
//
|
|
// Note that the following will fail:
|
|
//
|
|
// c.Assert((*sometype)(nil), qt.Equals, nil)
|
|
//
|
|
// Use the IsNil checker below for this kind of nil check.
|
|
var Equals Checker = &equalsChecker{
|
|
argNames: []string{"got", "want"},
|
|
}
|
|
|
|
type equalsChecker struct {
|
|
argNames
|
|
}
|
|
|
|
// Check implements Checker.Check by checking that got == args[0].
|
|
func (c *equalsChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) (err error) {
|
|
defer func() {
|
|
// A panic is raised when the provided values are not comparable.
|
|
if r := recover(); r != nil {
|
|
err = fmt.Errorf("%s", r)
|
|
}
|
|
}()
|
|
want := args[0]
|
|
if got == want {
|
|
return nil
|
|
}
|
|
|
|
// Customize error message for non-nil errors.
|
|
if _, ok := got.(error); ok && want == nil {
|
|
return errors.New("got non-nil error")
|
|
}
|
|
|
|
// Show error types when comparing errors with different types.
|
|
if got, ok := got.(error); ok {
|
|
if want, ok := want.(error); ok {
|
|
gotType := reflect.TypeOf(got)
|
|
wantType := reflect.TypeOf(want)
|
|
if gotType != wantType {
|
|
note("got type", Unquoted(gotType.String()))
|
|
note("want type", Unquoted(wantType.String()))
|
|
}
|
|
}
|
|
return errors.New("values are not equal")
|
|
}
|
|
|
|
// Show line diff when comparing different multi-line strings.
|
|
if got, ok := got.(string); ok {
|
|
if want, ok := want.(string); ok {
|
|
isMultiLine := func(s string) bool {
|
|
i := strings.Index(s, "\n")
|
|
return i != -1 && i < len(s)-1
|
|
}
|
|
if isMultiLine(got) || isMultiLine(want) {
|
|
diff := cmp.Diff(strings.SplitAfter(got, "\n"), strings.SplitAfter(want, "\n"))
|
|
note("line diff (-got +want)", Unquoted(diff))
|
|
}
|
|
}
|
|
}
|
|
|
|
return errors.New("values are not equal")
|
|
}
|
|
|
|
// CmpEquals returns a Checker checking equality of two arbitrary values
|
|
// according to the provided compare options. See DeepEquals as an example of
|
|
// such a checker, commonly used when no compare options are required.
|
|
//
|
|
// Example calls:
|
|
//
|
|
// c.Assert(list, qt.CmpEquals(cmpopts.SortSlices), []int{42, 47})
|
|
// c.Assert(got, qt.CmpEquals(), []int{42, 47}) // Same as qt.DeepEquals.
|
|
func CmpEquals(opts ...cmp.Option) Checker {
|
|
return &cmpEqualsChecker{
|
|
argNames: []string{"got", "want"},
|
|
opts: opts,
|
|
}
|
|
}
|
|
|
|
type cmpEqualsChecker struct {
|
|
argNames
|
|
opts cmp.Options
|
|
}
|
|
|
|
// Check implements Checker.Check by checking that got == args[0] according to
|
|
// the compare options stored in the checker.
|
|
func (c *cmpEqualsChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) (err error) {
|
|
defer func() {
|
|
// A panic is raised in some cases, for instance when trying to compare
|
|
// structs with unexported fields and neither AllowUnexported nor
|
|
// cmpopts.IgnoreUnexported are provided.
|
|
if r := recover(); r != nil {
|
|
err = fmt.Errorf("%s", r)
|
|
}
|
|
}()
|
|
want := args[0]
|
|
if diff := cmp.Diff(got, want, c.opts...); diff != "" {
|
|
// Only output values when the verbose flag is set.
|
|
note("error", Unquoted("values are not deep equal"))
|
|
note("diff (-got +want)", Unquoted(diff))
|
|
note("got", SuppressedIfLong{got})
|
|
note("want", SuppressedIfLong{want})
|
|
return ErrSilent
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DeepEquals is a Checker deeply checking equality of two arbitrary values.
|
|
// The comparison is done using the github.com/google/go-cmp/cmp package.
|
|
// When comparing structs, by default no exported fields are allowed. CmpEquals
|
|
// can be used when more customized compare options are required.
|
|
//
|
|
// Example call:
|
|
//
|
|
// c.Assert(got, qt.DeepEquals, []int{42, 47})
|
|
var DeepEquals = CmpEquals()
|
|
|
|
// ContentEquals is like DeepEquals but any slices in the compared values will
|
|
// be sorted before being compared.
|
|
var ContentEquals = CmpEquals(cmpopts.SortSlices(func(x, y interface{}) bool {
|
|
// TODO frankban: implement a proper sort function.
|
|
return pretty.Sprint(x) < pretty.Sprint(y)
|
|
}))
|
|
|
|
// Matches is a Checker checking that the provided string or fmt.Stringer
|
|
// matches the provided regular expression pattern.
|
|
//
|
|
// For instance:
|
|
//
|
|
// c.Assert("these are the voyages", qt.Matches, "these are .*")
|
|
// c.Assert(net.ParseIP("1.2.3.4"), qt.Matches, "1.*")
|
|
var Matches Checker = &matchesChecker{
|
|
argNames: []string{"got value", "regexp"},
|
|
}
|
|
|
|
type matchesChecker struct {
|
|
argNames
|
|
}
|
|
|
|
// Check implements Checker.Check by checking that got is a string or a
|
|
// fmt.Stringer and that it matches args[0].
|
|
func (c *matchesChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) error {
|
|
pattern := args[0]
|
|
switch v := got.(type) {
|
|
case string:
|
|
return match(v, pattern, "value does not match regexp", note)
|
|
case fmt.Stringer:
|
|
return match(v.String(), pattern, "value.String() does not match regexp", note)
|
|
}
|
|
note("value", got)
|
|
return BadCheckf("value is not a string or a fmt.Stringer")
|
|
}
|
|
|
|
func checkFirstArgIsError(got interface{}, note func(key string, value interface{})) error {
|
|
if got == nil {
|
|
return errors.New("got nil error but want non-nil")
|
|
}
|
|
_, ok := got.(error)
|
|
if !ok {
|
|
note("got", got)
|
|
return BadCheckf("first argument is not an error")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ErrorMatches is a Checker checking that the provided value is an error whose
|
|
// message matches the provided regular expression pattern.
|
|
//
|
|
// For instance:
|
|
//
|
|
// c.Assert(err, qt.ErrorMatches, "bad wolf .*")
|
|
var ErrorMatches Checker = &errorMatchesChecker{
|
|
argNames: []string{"got error", "regexp"},
|
|
}
|
|
|
|
type errorMatchesChecker struct {
|
|
argNames
|
|
}
|
|
|
|
// Check implements Checker.Check by checking that got is an error whose
|
|
// Error() matches args[0].
|
|
func (c *errorMatchesChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) error {
|
|
if err := checkFirstArgIsError(got, note); err != nil {
|
|
return err
|
|
}
|
|
|
|
gotErr := got.(error)
|
|
return match(gotErr.Error(), args[0], "error does not match regexp", note)
|
|
}
|
|
|
|
// PanicMatches is a Checker checking that the provided function panics with a
|
|
// message matching the provided regular expression pattern.
|
|
//
|
|
// For instance:
|
|
//
|
|
// c.Assert(func() {panic("bad wolf ...")}, qt.PanicMatches, "bad wolf .*")
|
|
var PanicMatches Checker = &panicMatchesChecker{
|
|
argNames: []string{"function", "regexp"},
|
|
}
|
|
|
|
type panicMatchesChecker struct {
|
|
argNames
|
|
}
|
|
|
|
// Check implements Checker.Check by checking that got is a func() that panics
|
|
// with a message matching args[0].
|
|
func (c *panicMatchesChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) (err error) {
|
|
f := reflect.ValueOf(got)
|
|
if f.Kind() != reflect.Func {
|
|
note("got", got)
|
|
return BadCheckf("first argument is not a function")
|
|
}
|
|
ftype := f.Type()
|
|
if ftype.NumIn() != 0 {
|
|
note("function", got)
|
|
return BadCheckf("cannot use a function receiving arguments")
|
|
}
|
|
|
|
defer func() {
|
|
r := recover()
|
|
if r == nil {
|
|
err = errors.New("function did not panic")
|
|
return
|
|
}
|
|
msg := fmt.Sprint(r)
|
|
note("panic value", msg)
|
|
err = match(msg, args[0], "panic value does not match regexp", note)
|
|
}()
|
|
|
|
f.Call(nil)
|
|
return nil
|
|
}
|
|
|
|
// IsNil is a Checker checking that the provided value is nil.
|
|
//
|
|
// For instance:
|
|
//
|
|
// c.Assert(got, qt.IsNil)
|
|
//
|
|
// As a special case, if the value is nil but implements the
|
|
// error interface, it is still considered to be non-nil.
|
|
// This means that IsNil will fail on an error value that happens
|
|
// to have an underlying nil value, because that's
|
|
// invariably a mistake.
|
|
// See https://golang.org/doc/faq#nil_error.
|
|
var IsNil Checker = &isNilChecker{
|
|
argNames: []string{"got"},
|
|
}
|
|
|
|
type isNilChecker struct {
|
|
argNames
|
|
}
|
|
|
|
// Check implements Checker.Check by checking that got is nil.
|
|
func (c *isNilChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) (err error) {
|
|
if got == nil {
|
|
return nil
|
|
}
|
|
value := reflect.ValueOf(got)
|
|
_, isError := got.(error)
|
|
if canBeNil(value.Kind()) && value.IsNil() {
|
|
if isError {
|
|
// It's an error with an underlying nil value.
|
|
return fmt.Errorf("error containing nil value of type %T. See https://golang.org/doc/faq#nil_error", got)
|
|
}
|
|
return nil
|
|
}
|
|
if isError {
|
|
return errors.New("got non-nil error")
|
|
}
|
|
return errors.New("got non-nil value")
|
|
}
|
|
|
|
// IsNotNil is a Checker checking that the provided value is not nil.
|
|
// IsNotNil is the equivalent of qt.Not(qt.IsNil)
|
|
//
|
|
// For instance:
|
|
//
|
|
// c.Assert(got, qt.IsNotNil)
|
|
var IsNotNil Checker = ¬Checker{
|
|
Checker: IsNil,
|
|
}
|
|
|
|
// HasLen is a Checker checking that the provided value has the given length.
|
|
//
|
|
// For instance:
|
|
//
|
|
// c.Assert([]int{42, 47}, qt.HasLen, 2)
|
|
// c.Assert(myMap, qt.HasLen, 42)
|
|
var HasLen Checker = &hasLenChecker{
|
|
argNames: []string{"got", "want length"},
|
|
}
|
|
|
|
type hasLenChecker struct {
|
|
argNames
|
|
}
|
|
|
|
// Check implements Checker.Check by checking that len(got) == args[0].
|
|
func (c *hasLenChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) (err error) {
|
|
v := reflect.ValueOf(got)
|
|
switch v.Kind() {
|
|
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
|
|
default:
|
|
note("got", got)
|
|
return BadCheckf("first argument has no length")
|
|
}
|
|
want, ok := args[0].(int)
|
|
if !ok {
|
|
note("length", args[0])
|
|
return BadCheckf("length is not an int")
|
|
}
|
|
length := v.Len()
|
|
note("len(got)", length)
|
|
if length != want {
|
|
return fmt.Errorf("unexpected length")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Implements checks that the provided value implements an interface. The
|
|
// interface is specified with a pointer to an interface variable.
|
|
//
|
|
// For instance:
|
|
//
|
|
// var rc io.ReadCloser
|
|
// c.Assert(myReader, qt.Implements, &rc)
|
|
var Implements Checker = &implementsChecker{
|
|
argNames: []string{"got", "want interface pointer"},
|
|
}
|
|
|
|
type implementsChecker struct {
|
|
argNames
|
|
}
|
|
|
|
var emptyInterface = reflect.TypeOf((*interface{})(nil)).Elem()
|
|
|
|
// Check implements Checker.Check by checking that got implements the
|
|
// interface pointed to by args[0].
|
|
func (c *implementsChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) (err error) {
|
|
if got == nil {
|
|
note("error", Unquoted("got nil value but want non-nil"))
|
|
note("got", got)
|
|
return ErrSilent
|
|
}
|
|
|
|
if args[0] == nil {
|
|
return BadCheckf("want a pointer to an interface variable but nil was provided")
|
|
}
|
|
wantType := reflect.TypeOf(args[0])
|
|
if wantType.Kind() != reflect.Ptr {
|
|
note("want", Unquoted(wantType.String()))
|
|
return BadCheckf("want a pointer to an interface variable but a non-pointer value was provided")
|
|
} else if wantType.Elem().Kind() != reflect.Interface {
|
|
note("want pointer type", Unquoted(wantType.Elem().String()))
|
|
return BadCheckf("want a pointer to an interface variable but a pointer to a concrete type was provided")
|
|
} else if wantType.Elem() == emptyInterface {
|
|
note("want pointer type", Unquoted(wantType.Elem().String()))
|
|
return BadCheckf("all types implement the empty interface, want a pointer to a variable that isn't the empty interface")
|
|
}
|
|
|
|
gotType := reflect.TypeOf(got)
|
|
if !gotType.Implements(wantType.Elem()) {
|
|
note("error", Unquoted("got value does not implement wanted interface"))
|
|
note("got", got)
|
|
note("want interface", Unquoted(wantType.Elem().String()))
|
|
return ErrSilent
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Satisfies is a Checker checking that the provided value, when used as
|
|
// argument of the provided predicate function, causes the function to return
|
|
// true. The function must be of type func(T) bool, having got assignable to T.
|
|
//
|
|
// For instance:
|
|
//
|
|
// // Check that an error from os.Open satisfies os.IsNotExist.
|
|
// c.Assert(err, qt.Satisfies, os.IsNotExist)
|
|
//
|
|
// // Check that a floating point number is a not-a-number.
|
|
// c.Assert(f, qt.Satisfies, math.IsNaN)
|
|
var Satisfies Checker = &satisfiesChecker{
|
|
argNames: []string{"arg", "predicate function"},
|
|
}
|
|
|
|
type satisfiesChecker struct {
|
|
argNames
|
|
}
|
|
|
|
// Check implements Checker.Check by checking that args[0](got) == true.
|
|
func (c *satisfiesChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) (err error) {
|
|
// Original code at
|
|
// <https://github.com/juju/testing/blob/master/checkers/bool.go>.
|
|
// Copyright 2011 Canonical Ltd.
|
|
// Licensed under the LGPLv3, see LICENSE file for details.
|
|
predicate := args[0]
|
|
f := reflect.ValueOf(predicate)
|
|
ftype := f.Type()
|
|
if ftype.Kind() != reflect.Func || ftype.NumIn() != 1 || ftype.NumOut() != 1 || ftype.Out(0).Kind() != reflect.Bool {
|
|
note("predicate function", predicate)
|
|
return BadCheckf("predicate function is not a func(T) bool")
|
|
}
|
|
v, t := reflect.ValueOf(got), ftype.In(0)
|
|
if !v.IsValid() {
|
|
if !canBeNil(t.Kind()) {
|
|
note("predicate function", predicate)
|
|
return BadCheckf("cannot use nil as type %v in argument to predicate function", t)
|
|
}
|
|
v = reflect.Zero(t)
|
|
} else if !v.Type().AssignableTo(t) {
|
|
note("arg", got)
|
|
note("predicate function", predicate)
|
|
return BadCheckf("cannot use value of type %v as type %v in argument to predicate function", v.Type(), t)
|
|
}
|
|
if f.Call([]reflect.Value{v})[0].Interface().(bool) {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("value does not satisfy predicate function")
|
|
}
|
|
|
|
// IsTrue is a Checker checking that the provided value is true.
|
|
// The value must have a boolean underlying type.
|
|
//
|
|
// For instance:
|
|
//
|
|
// c.Assert(true, qt.IsTrue)
|
|
// c.Assert(myBoolean(false), qt.IsTrue)
|
|
var IsTrue Checker = &boolChecker{
|
|
want: true,
|
|
}
|
|
|
|
// IsFalse is a Checker checking that the provided value is false.
|
|
// The value must have a boolean underlying type.
|
|
//
|
|
// For instance:
|
|
//
|
|
// c.Assert(false, qt.IsFalse)
|
|
// c.Assert(IsValid(), qt.IsFalse)
|
|
var IsFalse Checker = &boolChecker{
|
|
want: false,
|
|
}
|
|
|
|
type boolChecker struct {
|
|
want bool
|
|
}
|
|
|
|
// Check implements Checker.Check by checking that got == c.want.
|
|
func (c *boolChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) (err error) {
|
|
v := reflect.ValueOf(got)
|
|
if v.IsValid() && v.Kind() == reflect.Bool {
|
|
if v.Bool() != c.want {
|
|
return fmt.Errorf("value is not %v", c.want)
|
|
}
|
|
return nil
|
|
}
|
|
note("value", got)
|
|
return BadCheckf("value does not have a bool underlying type")
|
|
}
|
|
|
|
// ArgNames implements Checker.ArgNames.
|
|
func (c *boolChecker) ArgNames() []string {
|
|
return []string{"got"}
|
|
}
|
|
|
|
// Not returns a Checker negating the given Checker.
|
|
//
|
|
// For instance:
|
|
//
|
|
// c.Assert(got, qt.Not(qt.IsNil))
|
|
// c.Assert(answer, qt.Not(qt.Equals), 42)
|
|
func Not(checker Checker) Checker {
|
|
return ¬Checker{
|
|
Checker: checker,
|
|
}
|
|
}
|
|
|
|
type notChecker struct {
|
|
Checker
|
|
}
|
|
|
|
// Check implements Checker.Check by checking that the stored checker fails.
|
|
func (c *notChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) (err error) {
|
|
if nc, ok := c.Checker.(*notChecker); ok {
|
|
return nc.Checker.Check(got, args, note)
|
|
}
|
|
err = c.Checker.Check(got, args, note)
|
|
if IsBadCheck(err) {
|
|
return err
|
|
}
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
if c.Checker == IsNil {
|
|
return errors.New("got nil value but want non-nil")
|
|
}
|
|
return errors.New("unexpected success")
|
|
}
|
|
|
|
// Contains is a checker that checks that a map, slice, array
|
|
// or string contains a value. It's the same as using
|
|
// Any(Equals), except that it has a special case
|
|
// for strings - if the first argument is a string,
|
|
// the second argument must also be a string
|
|
// and strings.Contains will be used.
|
|
//
|
|
// For example:
|
|
//
|
|
// c.Assert("hello world", qt.Contains, "world")
|
|
// c.Assert([]int{3,5,7,99}, qt.Contains, 7)
|
|
var Contains Checker = &containsChecker{
|
|
argNames: []string{"container", "want"},
|
|
}
|
|
|
|
type containsChecker struct {
|
|
argNames
|
|
}
|
|
|
|
// Check implements Checker.Check by checking that got contains args[0].
|
|
func (c *containsChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) error {
|
|
if got, ok := got.(string); ok {
|
|
want, ok := args[0].(string)
|
|
if !ok {
|
|
return BadCheckf("strings can only contain strings, not %T", args[0])
|
|
}
|
|
if strings.Contains(got, want) {
|
|
return nil
|
|
}
|
|
return errors.New("no substring match found")
|
|
}
|
|
return Any(Equals).Check(got, args, note)
|
|
}
|
|
|
|
// Any returns a Checker that uses the given checker to check elements
|
|
// of a slice or array or the values from a map. It succeeds if any element
|
|
// passes the check.
|
|
//
|
|
// For example:
|
|
//
|
|
// c.Assert([]int{3,5,7,99}, qt.Any(qt.Equals), 7)
|
|
// c.Assert([][]string{{"a", "b"}, {"c", "d"}}, qt.Any(qt.DeepEquals), []string{"c", "d"})
|
|
//
|
|
// See also All and Contains.
|
|
func Any(c Checker) Checker {
|
|
return &anyChecker{
|
|
argNames: append([]string{"container"}, c.ArgNames()[1:]...),
|
|
elemChecker: c,
|
|
}
|
|
}
|
|
|
|
type anyChecker struct {
|
|
argNames
|
|
elemChecker Checker
|
|
}
|
|
|
|
// Check implements Checker.Check by checking that one of the elements of
|
|
// got passes the c.elemChecker check.
|
|
func (c *anyChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) error {
|
|
iter, err := newIter(got)
|
|
if err != nil {
|
|
return BadCheckf("%v", err)
|
|
}
|
|
for iter.next() {
|
|
// For the time being, discard the notes added by the sub-checker,
|
|
// because it's not clear what a good behaviour would be.
|
|
// Should we print all the failed check for all elements? If there's only
|
|
// one element in the container, the answer is probably yes,
|
|
// but let's leave it for now.
|
|
err := c.elemChecker.Check(
|
|
iter.value().Interface(),
|
|
args,
|
|
func(key string, value interface{}) {},
|
|
)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if IsBadCheck(err) {
|
|
return BadCheckf("at %s: %v", iter.key(), err)
|
|
}
|
|
}
|
|
return errors.New("no matching element found")
|
|
}
|
|
|
|
// All returns a Checker that uses the given checker to check elements
|
|
// of slice or array or the values of a map. It succeeds if all elements
|
|
// pass the check.
|
|
// On failure it prints the error from the first index that failed.
|
|
//
|
|
// For example:
|
|
//
|
|
// c.Assert([]int{3, 5, 8}, qt.All(qt.Not(qt.Equals)), 0)
|
|
// c.Assert([][]string{{"a", "b"}, {"a", "b"}}, qt.All(qt.DeepEquals), []string{"c", "d"})
|
|
//
|
|
// See also Any and Contains.
|
|
func All(c Checker) Checker {
|
|
return &allChecker{
|
|
argNames: append([]string{"container"}, c.ArgNames()[1:]...),
|
|
elemChecker: c,
|
|
}
|
|
}
|
|
|
|
type allChecker struct {
|
|
argNames
|
|
elemChecker Checker
|
|
}
|
|
|
|
// Check implement Checker.Check by checking that all the elements of got
|
|
// pass the c.elemChecker check.
|
|
func (c *allChecker) Check(got interface{}, args []interface{}, notef func(key string, value interface{})) error {
|
|
iter, err := newIter(got)
|
|
if err != nil {
|
|
return BadCheckf("%v", err)
|
|
}
|
|
for iter.next() {
|
|
// Store any notes added by the checker so
|
|
// we can add our own note at the start
|
|
// to say which element failed.
|
|
var notes []note
|
|
err := c.elemChecker.Check(
|
|
iter.value().Interface(),
|
|
args,
|
|
func(key string, val interface{}) {
|
|
notes = append(notes, note{key, val})
|
|
},
|
|
)
|
|
if err == nil {
|
|
continue
|
|
}
|
|
if IsBadCheck(err) {
|
|
return BadCheckf("at %s: %v", iter.key(), err)
|
|
}
|
|
notef("error", Unquoted("mismatch at "+iter.key()))
|
|
if err != ErrSilent {
|
|
// If the error's not silent, the checker is expecting
|
|
// the caller to print the error and the value that failed.
|
|
notef("error", Unquoted(err.Error()))
|
|
notef("first mismatched element", iter.value().Interface())
|
|
}
|
|
for _, n := range notes {
|
|
notef(n.key, n.value)
|
|
}
|
|
return ErrSilent
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// JSONEquals is a checker that checks whether a byte slice
|
|
// or string is JSON-equivalent to a Go value. See CodecEquals for
|
|
// more information.
|
|
//
|
|
// It uses DeepEquals to do the comparison. If a more sophisticated
|
|
// comparison is required, use CodecEquals directly.
|
|
//
|
|
// For instance:
|
|
//
|
|
// c.Assert(`{"First": 47.11}`, qt.JSONEquals, &MyStruct{First: 47.11})
|
|
var JSONEquals = CodecEquals(json.Marshal, json.Unmarshal)
|
|
|
|
type codecEqualChecker struct {
|
|
argNames
|
|
marshal func(interface{}) ([]byte, error)
|
|
unmarshal func([]byte, interface{}) error
|
|
deepEquals Checker
|
|
}
|
|
|
|
// CodecEquals returns a checker that checks for codec value equivalence.
|
|
//
|
|
// It expects two arguments: a byte slice or a string containing some
|
|
// codec-marshaled data, and a Go value.
|
|
//
|
|
// It uses unmarshal to unmarshal the data into an interface{} value.
|
|
// It marshals the Go value using marshal, then unmarshals the result into
|
|
// an interface{} value.
|
|
//
|
|
// It then checks that the two interface{} values are deep-equal to one
|
|
// another, using CmpEquals(opts) to perform the check.
|
|
//
|
|
// See JSONEquals for an example of this in use.
|
|
func CodecEquals(
|
|
marshal func(interface{}) ([]byte, error),
|
|
unmarshal func([]byte, interface{}) error,
|
|
opts ...cmp.Option,
|
|
) Checker {
|
|
return &codecEqualChecker{
|
|
argNames: argNames{"got", "want"},
|
|
marshal: marshal,
|
|
unmarshal: unmarshal,
|
|
deepEquals: CmpEquals(opts...),
|
|
}
|
|
}
|
|
|
|
func (c *codecEqualChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) error {
|
|
var gotContent []byte
|
|
switch got := got.(type) {
|
|
case string:
|
|
gotContent = []byte(got)
|
|
case []byte:
|
|
gotContent = got
|
|
default:
|
|
return BadCheckf("expected string or byte, got %T", got)
|
|
}
|
|
wantContent := args[0]
|
|
wantContentBytes, err := c.marshal(wantContent)
|
|
if err != nil {
|
|
return BadCheckf("cannot marshal expected contents: %v", err)
|
|
}
|
|
var wantContentVal interface{}
|
|
if err := c.unmarshal(wantContentBytes, &wantContentVal); err != nil {
|
|
return BadCheckf("cannot unmarshal expected contents: %v", err)
|
|
}
|
|
var gotContentVal interface{}
|
|
if err := c.unmarshal([]byte(gotContent), &gotContentVal); err != nil {
|
|
return fmt.Errorf("cannot unmarshal obtained contents: %v; %q", err, gotContent)
|
|
}
|
|
return c.deepEquals.Check(gotContentVal, []interface{}{wantContentVal}, note)
|
|
}
|
|
|
|
// argNames helps implementing Checker.ArgNames.
|
|
type argNames []string
|
|
|
|
// ArgNames implements Checker.ArgNames by returning the argument names.
|
|
func (a argNames) ArgNames() []string {
|
|
return a
|
|
}
|
|
|
|
// match checks that the given error message matches the given pattern.
|
|
func match(got string, pattern interface{}, msg string, note func(key string, value interface{})) error {
|
|
regex, ok := pattern.(string)
|
|
if !ok {
|
|
note("regexp", pattern)
|
|
return BadCheckf("regexp is not a string")
|
|
}
|
|
matches, err := regexp.MatchString("^("+regex+")$", got)
|
|
if err != nil {
|
|
note("regexp", regex)
|
|
return BadCheckf("cannot compile regexp: %s", err)
|
|
}
|
|
if matches {
|
|
return nil
|
|
}
|
|
return errors.New(msg)
|
|
}
|
|
|
|
// canBeNil reports whether a value or type of the given kind can be nil.
|
|
func canBeNil(k reflect.Kind) bool {
|
|
switch k {
|
|
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
|
return true
|
|
}
|
|
return false
|
|
}
|