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 | 6 | "fmt" |
7 | 7 | "reflect" |
8 | 8 | "regexp" |
9 | "strings" | |
9 | 10 | "testing" |
10 | 11 | |
11 | 12 | "github.com/google/go-cmp/cmp" |
376 | 377 | return errors.New("unexpected success") |
377 | 378 | } |
378 | 379 | |
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 | ||
379 | 529 | // argNames helps implementing Checker.ArgNames. |
380 | 530 | type argNames []string |
381 | 531 |
1883 | 1883 | want args: |
1884 | 1884 | want |
1885 | 1885 | `, |
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 | `, | |
1886 | 2158 | }} |
1887 | 2159 | |
1888 | 2160 | 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 | } |