Unverified Commit ea51502d authored by Francesco Banconi's avatar Francesco Banconi Committed by GitHub

Merge pull request #43 from rogpeppe/008-anyall

implement Any, All and Contains
parents 67b6636e 52c5d834
......@@ -7,6 +7,7 @@ import (
"fmt"
"reflect"
"regexp"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
......@@ -377,6 +378,155 @@ func (c *notChecker) Check(got interface{}, args []interface{}, note func(key st
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{"got", "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()))
// TODO should we print the whole container value in
// verbose mode?
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
}
// argNames helps implementing Checker.ArgNames.
type argNames []string
......
......@@ -1884,6 +1884,278 @@ got args:
want args:
want
`,
}, {
about: "Contains with string",
checker: qt.Contains,
got: "hello, world",
args: []interface{}{"world"},
expectedNegateFailure: `
error:
unexpected success
got:
"hello, world"
want:
"world"
`,
}, {
about: "Contains with string no match",
checker: qt.Contains,
got: "hello, world",
args: []interface{}{"worlds"},
expectedCheckFailure: `
error:
no substring match found
got:
"hello, world"
want:
"worlds"
`,
}, {
about: "Contains with slice",
checker: qt.Contains,
got: []string{"a", "b", "c"},
args: []interface{}{"a"},
expectedNegateFailure: `
error:
unexpected success
got:
[]string{"a", "b", "c"}
want:
"a"
`,
}, {
about: "Contains with map",
checker: qt.Contains,
// Note: we can't use more than one element here because
// pretty.Print output is non-deterministic.
// https://github.com/kr/pretty/issues/47
got: map[string]string{"a": "d"},
args: []interface{}{"d"},
expectedNegateFailure: `
error:
unexpected success
got:
map[string]string{"a":"d"}
want:
"d"
`,
}, {
about: "Contains with non-string",
checker: qt.Contains,
got: "aa",
args: []interface{}{5},
expectedCheckFailure: `
error:
bad check: strings can only contain strings, not int
`,
expectedNegateFailure: `
error:
bad check: strings can only contain strings, not int
`,
}, {
about: "All slice equals",
checker: qt.All(qt.Equals),
got: []string{"a", "a"},
args: []interface{}{"a"},
expectedNegateFailure: `
error:
unexpected success
container:
[]string{"a", "a"}
want:
"a"
`,
}, {
about: "All slice match",
checker: qt.All(qt.Matches),
got: []string{"red", "blue", "green"},
args: []interface{}{".*e.*"},
expectedNegateFailure: `
error:
unexpected success
container:
[]string{"red", "blue", "green"}
regexp:
".*e.*"
`,
}, {
about: "All nested match",
checker: qt.All(qt.All(qt.Matches)),
got: [][]string{{"hello", "goodbye"}, {"red", "blue"}, {}},
args: []interface{}{".*e.*"},
expectedNegateFailure: `
error:
unexpected success
container:
[][]string{
{"hello", "goodbye"},
{"red", "blue"},
{},
}
regexp:
".*e.*"
`,
}, {
about: "All nested mismatch",
checker: qt.All(qt.All(qt.Matches)),
got: [][]string{{"hello", "goodbye"}, {"black", "blue"}, {}},
args: []interface{}{".*e.*"},
expectedCheckFailure: `
error:
mismatch at index 1
error:
mismatch at index 0
error:
value does not match regexp
first mismatched element:
"black"
`,
}, {
about: "All slice mismatch",
checker: qt.All(qt.Matches),
got: []string{"red", "black"},
args: []interface{}{".*e.*"},
expectedCheckFailure: `
error:
mismatch at index 1
error:
value does not match regexp
first mismatched element:
"black"
`,
}, {
about: "All slice mismatch with DeepEqual",
checker: qt.All(qt.DeepEquals),
got: [][]string{{"a", "b"}, {"a", "c"}},
args: []interface{}{[]string{"a", "b"}},
expectedCheckFailure: `
error:
mismatch at index 1
error:
values are not deep equal
diff (-got +want):
` + diff([]string{"a", "c"}, []string{"a", "b"}) + `
`,
}, {
about: "All bad checker args count",
checker: qt.All(qt.IsNil),
got: []int{},
args: []interface{}{5},
expectedCheckFailure: `
error:
bad check: too many arguments provided to checker: got 1, want 0
got args:
[]interface {}{
int(5),
}
`,
expectedNegateFailure: `
error:
bad check: too many arguments provided to checker: got 1, want 0
got args:
[]interface {}{
int(5),
}
`,
}, {
about: "All bad checker args",
checker: qt.All(qt.Matches),
got: []string{"hello"},
args: []interface{}{5},
expectedCheckFailure: `
error:
bad check: at index 0: bad check: regexp is not a string
`,
expectedNegateFailure: `
error:
bad check: at index 0: bad check: regexp is not a string
`,
}, {
about: "All with non-container",
checker: qt.All(qt.Equals),
got: 5,
args: []interface{}{5},
expectedCheckFailure: `
error:
bad check: map, slice or array required
`,
expectedNegateFailure: `
error:
bad check: map, slice or array required
`,
}, {
about: "All mismatch with map",
checker: qt.All(qt.Matches),
got: map[string]string{"a": "red", "b": "black"},
args: []interface{}{".*e.*"},
expectedCheckFailure: `
error:
mismatch at key "b"
error:
value does not match regexp
first mismatched element:
"black"
`,
}, {
about: "Any with non-container",
checker: qt.Any(qt.Equals),
got: 5,
args: []interface{}{5},
expectedCheckFailure: `
error:
bad check: map, slice or array required
`,
expectedNegateFailure: `
error:
bad check: map, slice or array required
`,
}, {
about: "Any no match",
checker: qt.Any(qt.Equals),
got: []int{},
args: []interface{}{5},
expectedCheckFailure: `
error:
no matching element found
container:
[]int{}
want:
int(5)
`,
}, {
about: "Any bad checker arg count",
checker: qt.Any(qt.IsNil),
got: []int{},
args: []interface{}{5},
expectedCheckFailure: `
error:
bad check: too many arguments provided to checker: got 1, want 0
got args:
[]interface {}{
int(5),
}
`,
expectedNegateFailure: `
error:
bad check: too many arguments provided to checker: got 1, want 0
got args:
[]interface {}{
int(5),
}
`,
}, {
about: "Any bad checker args",
checker: qt.Any(qt.Matches),
got: []string{"hello"},
args: []interface{}{5},
expectedCheckFailure: `
error:
bad check: at index 0: bad check: regexp is not a string
`,
expectedNegateFailure: `
error:
bad check: at index 0: bad check: regexp is not a string
`,
}}
func TestCheckers(t *testing.T) {
......
// Licensed under the MIT license, see LICENCE file for details.
package quicktest
import (
"fmt"
"reflect"
)
// containerIter provides an interface for iterating over a container
// (map, slice or array).
type containerIter interface {
// next advances to the next item in the container.
next() bool
// key returns the current key as a string.
key() string
// value returns the current value.
value() reflect.Value
}
// newIter returns an iterator over x which must be a map, slice
// or array.
func newIter(x interface{}) (containerIter, error) {
v := reflect.ValueOf(x)
switch v.Kind() {
case reflect.Map:
return newMapIter(v), nil
case reflect.Slice, reflect.Array:
return &sliceIter{
index: -1,
v: v,
}, nil
default:
return nil, fmt.Errorf("map, slice or array required")
}
}
// sliceIter implements containerIter for slices and arrays.
type sliceIter struct {
v reflect.Value
index int
}
func (i *sliceIter) next() bool {
i.index++
return i.index < i.v.Len()
}
func (i *sliceIter) value() reflect.Value {
return i.v.Index(i.index)
}
func (i *sliceIter) key() string {
return fmt.Sprintf("index %d", i.index)
}
// Licensed under the MIT license, see LICENCE file for details.
// +build go1.12
package quicktest
import (
"fmt"
"reflect"
)
func newMapIter(v reflect.Value) containerIter {
return mapIter{v.MapRange()}
}
// mapIter implements containerIter for maps.
type mapIter struct {
iter *reflect.MapIter
}
func (i mapIter) next() bool {
return i.iter.Next()
}
func (i mapIter) key() string {
return fmt.Sprintf("key %#v", i.iter.Key())
}
func (i mapIter) value() reflect.Value {
return i.iter.Value()
}
// Licensed under the MIT license, see LICENCE file for details.
// +build !go1.12
package quicktest
import (
"fmt"
"reflect"
)
func newMapIter(v reflect.Value) containerIter {
return &mapIter{
v: v,
keys: v.MapKeys(),
index: -1,
}
}
// mapIter implements containerIter for maps prior to the
// introduction of reflect.Value.MapRange in Go 1.12.
type mapIter struct {
v reflect.Value
keys []reflect.Value
index int
}
func (i *mapIter) next() bool {
i.index++
return i.index < len(i.keys)
}
func (i *mapIter) value() reflect.Value {
v := i.v.MapIndex(i.keys[i.index])
if !v.IsValid() {
// We've probably got a NaN key; we can't
// get NaN keys from maps with reflect,
// so just return the zero value.
return reflect.Zero(i.v.Type().Elem())
}
return v
}
func (i *mapIter) key() string {
return fmt.Sprintf("key %#v", i.keys[i.index])
}
// Licensed under the MIT license, see LICENCE file for details.
package quicktest_test
import (
......
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