Codebase list golang-github-frankban-quicktest / ea51502
Merge pull request #43 from rogpeppe/008-anyall implement Any, All and Contains Francesco Banconi authored 4 years ago GitHub committed 4 years ago
6 changed file(s) with 556 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
66 "fmt"
77 "reflect"
88 "regexp"
9 "strings"
910 "testing"
1011
1112 "github.com/google/go-cmp/cmp"
376377 return errors.New("unexpected success")
377378 }
378379
380 // Contains is a checker that checks that a map, slice, array
381 // or string contains a value. It's the same as using
382 // Any(Equals), except that it has a special case
383 // for strings - if the first argument is a string,
384 // the second argument must also be a string
385 // and strings.Contains will be used.
386 //
387 // For example:
388 //
389 // c.Assert("hello world", qt.Contains, "world")
390 // c.Assert([]int{3,5,7,99}, qt.Contains, 7)
391 var Contains Checker = &containsChecker{
392 argNames: []string{"got", "want"},
393 }
394
395 type containsChecker struct {
396 argNames
397 }
398
399 // Check implements Checker.Check by checking that got contains args[0].
400 func (c *containsChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) error {
401 if got, ok := got.(string); ok {
402 want, ok := args[0].(string)
403 if !ok {
404 return BadCheckf("strings can only contain strings, not %T", args[0])
405 }
406 if strings.Contains(got, want) {
407 return nil
408 }
409 return errors.New("no substring match found")
410 }
411 return Any(Equals).Check(got, args, note)
412 }
413
414 // Any returns a Checker that uses the given checker to check elements
415 // of a slice or array or the values from a map. It succeeds if any element
416 // passes the check.
417 //
418 // For example:
419 //
420 // c.Assert([]int{3,5,7,99}, qt.Any(qt.Equals), 7)
421 // c.Assert([][]string{{"a", "b"}, {"c", "d"}}, qt.Any(qt.DeepEquals), []string{"c", "d"})
422 //
423 // See also All and Contains.
424 func Any(c Checker) Checker {
425 return &anyChecker{
426 argNames: append([]string{"container"}, c.ArgNames()[1:]...),
427 elemChecker: c,
428 }
429 }
430
431 type anyChecker struct {
432 argNames
433 elemChecker Checker
434 }
435
436 // Check implements Checker.Check by checking that one of the elements of
437 // got passes the c.elemChecker check.
438 func (c *anyChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) error {
439 iter, err := newIter(got)
440 if err != nil {
441 return BadCheckf("%v", err)
442 }
443 for iter.next() {
444 // For the time being, discard the notes added by the sub-checker,
445 // because it's not clear what a good behaviour would be.
446 // Should we print all the failed check for all elements? If there's only
447 // one element in the container, the answer is probably yes,
448 // but let's leave it for now.
449 err := c.elemChecker.Check(
450 iter.value().Interface(),
451 args,
452 func(key string, value interface{}) {},
453 )
454 if err == nil {
455 return nil
456 }
457 if IsBadCheck(err) {
458 return BadCheckf("at %s: %v", iter.key(), err)
459 }
460 }
461 return errors.New("no matching element found")
462 }
463
464 // All returns a Checker that uses the given checker to check elements
465 // of slice or array or the values of a map. It succeeds if all elements
466 // pass the check.
467 // On failure it prints the error from the first index that failed.
468 //
469 // For example:
470 //
471 // c.Assert([]int{3, 5, 8}, qt.All(qt.Not(qt.Equals)), 0)
472 // c.Assert([][]string{{"a", "b"}, {"a", "b"}}, qt.All(qt.DeepEquals), []string{"c", "d"})
473 //
474 // See also Any and Contains.
475 func All(c Checker) Checker {
476 return &allChecker{
477 argNames: append([]string{"container"}, c.ArgNames()[1:]...),
478 elemChecker: c,
479 }
480 }
481
482 type allChecker struct {
483 argNames
484 elemChecker Checker
485 }
486
487 // Check implement Checker.Check by checking that all the elements of got
488 // pass the c.elemChecker check.
489 func (c *allChecker) Check(got interface{}, args []interface{}, notef func(key string, value interface{})) error {
490 iter, err := newIter(got)
491 if err != nil {
492 return BadCheckf("%v", err)
493 }
494 for iter.next() {
495 // Store any notes added by the checker so
496 // we can add our own note at the start
497 // to say which element failed.
498 var notes []note
499 err := c.elemChecker.Check(
500 iter.value().Interface(),
501 args,
502 func(key string, val interface{}) {
503 notes = append(notes, note{key, val})
504 },
505 )
506 if err == nil {
507 continue
508 }
509 if IsBadCheck(err) {
510 return BadCheckf("at %s: %v", iter.key(), err)
511 }
512 notef("error", Unquoted("mismatch at "+iter.key()))
513 // TODO should we print the whole container value in
514 // verbose mode?
515 if err != ErrSilent {
516 // If the error's not silent, the checker is expecting
517 // the caller to print the error and the value that failed.
518 notef("error", Unquoted(err.Error()))
519 notef("first mismatched element", iter.value().Interface())
520 }
521 for _, n := range notes {
522 notef(n.key, n.value)
523 }
524 return ErrSilent
525 }
526 return nil
527 }
528
379529 // argNames helps implementing Checker.ArgNames.
380530 type argNames []string
381531
18831883 want args:
18841884 want
18851885 `,
1886 }, {
1887 about: "Contains with string",
1888 checker: qt.Contains,
1889 got: "hello, world",
1890 args: []interface{}{"world"},
1891 expectedNegateFailure: `
1892 error:
1893 unexpected success
1894 got:
1895 "hello, world"
1896 want:
1897 "world"
1898 `,
1899 }, {
1900 about: "Contains with string no match",
1901 checker: qt.Contains,
1902 got: "hello, world",
1903 args: []interface{}{"worlds"},
1904 expectedCheckFailure: `
1905 error:
1906 no substring match found
1907 got:
1908 "hello, world"
1909 want:
1910 "worlds"
1911 `,
1912 }, {
1913 about: "Contains with slice",
1914 checker: qt.Contains,
1915 got: []string{"a", "b", "c"},
1916 args: []interface{}{"a"},
1917 expectedNegateFailure: `
1918 error:
1919 unexpected success
1920 got:
1921 []string{"a", "b", "c"}
1922 want:
1923 "a"
1924 `,
1925 }, {
1926 about: "Contains with map",
1927 checker: qt.Contains,
1928 // Note: we can't use more than one element here because
1929 // pretty.Print output is non-deterministic.
1930 // https://github.com/kr/pretty/issues/47
1931 got: map[string]string{"a": "d"},
1932 args: []interface{}{"d"},
1933 expectedNegateFailure: `
1934 error:
1935 unexpected success
1936 got:
1937 map[string]string{"a":"d"}
1938 want:
1939 "d"
1940 `,
1941 }, {
1942 about: "Contains with non-string",
1943 checker: qt.Contains,
1944 got: "aa",
1945 args: []interface{}{5},
1946 expectedCheckFailure: `
1947 error:
1948 bad check: strings can only contain strings, not int
1949 `,
1950 expectedNegateFailure: `
1951 error:
1952 bad check: strings can only contain strings, not int
1953 `,
1954 }, {
1955 about: "All slice equals",
1956 checker: qt.All(qt.Equals),
1957 got: []string{"a", "a"},
1958 args: []interface{}{"a"},
1959 expectedNegateFailure: `
1960 error:
1961 unexpected success
1962 container:
1963 []string{"a", "a"}
1964 want:
1965 "a"
1966 `,
1967 }, {
1968 about: "All slice match",
1969 checker: qt.All(qt.Matches),
1970 got: []string{"red", "blue", "green"},
1971 args: []interface{}{".*e.*"},
1972 expectedNegateFailure: `
1973 error:
1974 unexpected success
1975 container:
1976 []string{"red", "blue", "green"}
1977 regexp:
1978 ".*e.*"
1979 `,
1980 }, {
1981 about: "All nested match",
1982 checker: qt.All(qt.All(qt.Matches)),
1983 got: [][]string{{"hello", "goodbye"}, {"red", "blue"}, {}},
1984 args: []interface{}{".*e.*"},
1985 expectedNegateFailure: `
1986 error:
1987 unexpected success
1988 container:
1989 [][]string{
1990 {"hello", "goodbye"},
1991 {"red", "blue"},
1992 {},
1993 }
1994 regexp:
1995 ".*e.*"
1996 `,
1997 }, {
1998 about: "All nested mismatch",
1999 checker: qt.All(qt.All(qt.Matches)),
2000 got: [][]string{{"hello", "goodbye"}, {"black", "blue"}, {}},
2001 args: []interface{}{".*e.*"},
2002 expectedCheckFailure: `
2003 error:
2004 mismatch at index 1
2005 error:
2006 mismatch at index 0
2007 error:
2008 value does not match regexp
2009 first mismatched element:
2010 "black"
2011 `,
2012 }, {
2013 about: "All slice mismatch",
2014 checker: qt.All(qt.Matches),
2015 got: []string{"red", "black"},
2016 args: []interface{}{".*e.*"},
2017 expectedCheckFailure: `
2018 error:
2019 mismatch at index 1
2020 error:
2021 value does not match regexp
2022 first mismatched element:
2023 "black"
2024 `,
2025 }, {
2026 about: "All slice mismatch with DeepEqual",
2027 checker: qt.All(qt.DeepEquals),
2028 got: [][]string{{"a", "b"}, {"a", "c"}},
2029 args: []interface{}{[]string{"a", "b"}},
2030 expectedCheckFailure: `
2031 error:
2032 mismatch at index 1
2033 error:
2034 values are not deep equal
2035 diff (-got +want):
2036 ` + diff([]string{"a", "c"}, []string{"a", "b"}) + `
2037 `,
2038 }, {
2039 about: "All bad checker args count",
2040 checker: qt.All(qt.IsNil),
2041 got: []int{},
2042 args: []interface{}{5},
2043 expectedCheckFailure: `
2044 error:
2045 bad check: too many arguments provided to checker: got 1, want 0
2046 got args:
2047 []interface {}{
2048 int(5),
2049 }
2050 `,
2051 expectedNegateFailure: `
2052 error:
2053 bad check: too many arguments provided to checker: got 1, want 0
2054 got args:
2055 []interface {}{
2056 int(5),
2057 }
2058 `,
2059 }, {
2060 about: "All bad checker args",
2061 checker: qt.All(qt.Matches),
2062 got: []string{"hello"},
2063 args: []interface{}{5},
2064 expectedCheckFailure: `
2065 error:
2066 bad check: at index 0: bad check: regexp is not a string
2067 `,
2068 expectedNegateFailure: `
2069 error:
2070 bad check: at index 0: bad check: regexp is not a string
2071 `,
2072 }, {
2073 about: "All with non-container",
2074 checker: qt.All(qt.Equals),
2075 got: 5,
2076 args: []interface{}{5},
2077 expectedCheckFailure: `
2078 error:
2079 bad check: map, slice or array required
2080 `,
2081 expectedNegateFailure: `
2082 error:
2083 bad check: map, slice or array required
2084 `,
2085 }, {
2086 about: "All mismatch with map",
2087 checker: qt.All(qt.Matches),
2088 got: map[string]string{"a": "red", "b": "black"},
2089 args: []interface{}{".*e.*"},
2090 expectedCheckFailure: `
2091 error:
2092 mismatch at key "b"
2093 error:
2094 value does not match regexp
2095 first mismatched element:
2096 "black"
2097 `,
2098 }, {
2099 about: "Any with non-container",
2100 checker: qt.Any(qt.Equals),
2101 got: 5,
2102 args: []interface{}{5},
2103 expectedCheckFailure: `
2104 error:
2105 bad check: map, slice or array required
2106 `,
2107 expectedNegateFailure: `
2108 error:
2109 bad check: map, slice or array required
2110 `,
2111 }, {
2112 about: "Any no match",
2113 checker: qt.Any(qt.Equals),
2114 got: []int{},
2115 args: []interface{}{5},
2116 expectedCheckFailure: `
2117 error:
2118 no matching element found
2119 container:
2120 []int{}
2121 want:
2122 int(5)
2123 `,
2124 }, {
2125 about: "Any bad checker arg count",
2126 checker: qt.Any(qt.IsNil),
2127 got: []int{},
2128 args: []interface{}{5},
2129 expectedCheckFailure: `
2130 error:
2131 bad check: too many arguments provided to checker: got 1, want 0
2132 got args:
2133 []interface {}{
2134 int(5),
2135 }
2136 `,
2137 expectedNegateFailure: `
2138 error:
2139 bad check: too many arguments provided to checker: got 1, want 0
2140 got args:
2141 []interface {}{
2142 int(5),
2143 }
2144 `,
2145 }, {
2146 about: "Any bad checker args",
2147 checker: qt.Any(qt.Matches),
2148 got: []string{"hello"},
2149 args: []interface{}{5},
2150 expectedCheckFailure: `
2151 error:
2152 bad check: at index 0: bad check: regexp is not a string
2153 `,
2154 expectedNegateFailure: `
2155 error:
2156 bad check: at index 0: bad check: regexp is not a string
2157 `,
18862158 }}
18872159
18882160 func TestCheckers(t *testing.T) {
0 // Licensed under the MIT license, see LICENCE file for details.
1
2 package quicktest
3
4 import (
5 "fmt"
6 "reflect"
7 )
8
9 // containerIter provides an interface for iterating over a container
10 // (map, slice or array).
11 type containerIter interface {
12 // next advances to the next item in the container.
13 next() bool
14 // key returns the current key as a string.
15 key() string
16 // value returns the current value.
17 value() reflect.Value
18 }
19
20 // newIter returns an iterator over x which must be a map, slice
21 // or array.
22 func newIter(x interface{}) (containerIter, error) {
23 v := reflect.ValueOf(x)
24 switch v.Kind() {
25 case reflect.Map:
26 return newMapIter(v), nil
27 case reflect.Slice, reflect.Array:
28 return &sliceIter{
29 index: -1,
30 v: v,
31 }, nil
32 default:
33 return nil, fmt.Errorf("map, slice or array required")
34 }
35 }
36
37 // sliceIter implements containerIter for slices and arrays.
38 type sliceIter struct {
39 v reflect.Value
40 index int
41 }
42
43 func (i *sliceIter) next() bool {
44 i.index++
45 return i.index < i.v.Len()
46 }
47
48 func (i *sliceIter) value() reflect.Value {
49 return i.v.Index(i.index)
50 }
51
52 func (i *sliceIter) key() string {
53 return fmt.Sprintf("index %d", i.index)
54 }
0 // Licensed under the MIT license, see LICENCE file for details.
1
2 // +build go1.12
3
4 package quicktest
5
6 import (
7 "fmt"
8 "reflect"
9 )
10
11 func newMapIter(v reflect.Value) containerIter {
12 return mapIter{v.MapRange()}
13 }
14
15 // mapIter implements containerIter for maps.
16 type mapIter struct {
17 iter *reflect.MapIter
18 }
19
20 func (i mapIter) next() bool {
21 return i.iter.Next()
22 }
23
24 func (i mapIter) key() string {
25 return fmt.Sprintf("key %#v", i.iter.Key())
26 }
27
28 func (i mapIter) value() reflect.Value {
29 return i.iter.Value()
30 }
0 // Licensed under the MIT license, see LICENCE file for details.
1
2 // +build !go1.12
3
4 package quicktest
5
6 import (
7 "fmt"
8 "reflect"
9 )
10
11 func newMapIter(v reflect.Value) containerIter {
12 return &mapIter{
13 v: v,
14 keys: v.MapKeys(),
15 index: -1,
16 }
17 }
18
19 // mapIter implements containerIter for maps prior to the
20 // introduction of reflect.Value.MapRange in Go 1.12.
21 type mapIter struct {
22 v reflect.Value
23 keys []reflect.Value
24 index int
25 }
26
27 func (i *mapIter) next() bool {
28 i.index++
29 return i.index < len(i.keys)
30 }
31
32 func (i *mapIter) value() reflect.Value {
33 v := i.v.MapIndex(i.keys[i.index])
34 if !v.IsValid() {
35 // We've probably got a NaN key; we can't
36 // get NaN keys from maps with reflect,
37 // so just return the zero value.
38 return reflect.Zero(i.v.Type().Elem())
39 }
40 return v
41 }
42
43 func (i *mapIter) key() string {
44 return fmt.Sprintf("key %#v", i.keys[i.index])
45 }
0 // Licensed under the MIT license, see LICENCE file for details.
1
02 package quicktest_test
13
24 import (