// Variant of OnPattern() that uses the given subgroup of the pattern. func OnPatternGroup(pattern string, group int) func(matcher *base.Matcher) *base.Matcher { re := regexp.MustCompile(pattern) if num := re.NumSubexp(); num < group { println("Illegal group #", group, ": there are only ", num, "groups.") panic("Group index out of bounds.") } return func(matcher *base.Matcher) *base.Matcher { match := func(s string) *base.Result { loc := re.FindStringSubmatchIndex(s) if loc == nil { return base.NewResultf(false, "No occurrences of pattern \"%v\"", pattern) } start, end := loc[0], loc[1] substart, subend := loc[group*2], loc[group*2+1] prefix := s[start:substart] substring := s[substart:subend] suffix := s[subend:end] result := matcher.Match(substring) return base.NewResultf(result.Matched(), "Found substring[%v:%v], [%v:%v]=\"%v[%v]%v\" for pattern \"%v\"", substart, subend, start, end, prefix, substring, suffix, pattern).WithCauses(result) } return base.NewMatcherf(match, "OnPatternGroup[\"%v\", %v][%v]", pattern, group, matcher) } }
func Test_IfThen(t *testing.T) { yes, no := Anything(), Not(Anything()) calledSnoop := false snoop := base.NewMatcherf(func(v interface{}) *base.Result { calledSnoop = true return base.NewResultf(false, "snooped!") }, "Snoop") if result := If(yes).Then(yes).Match(0); !result.Matched() { t.Errorf("if yes then yes should match, was [%v]", result) } if result := If(yes).Then(no).Match(0); result.Matched() { t.Errorf("if yes then no should not match, was [%v]", result) } result := If(no).Then(snoop).Match(0) if calledSnoop { t.Errorf("If-no-then-snoop should short-circuit before calling snoop") } if !result.Matched() { t.Errorf("if-no-then-snoop should match on failing antecedent, was [%v]", result) } logSamples(t, If(yes).Then(yes)) logSamples(t, If(yes).Then(no)) logSamples(t, If(no).Then(yes)) logSamples(t, If(no).Then(no)) }
// Matches strings that contain the given substring. func Contains(substring string) *base.Matcher { match := func(s string) *base.Result { extra := 8 if foundStart := strings.Index(s, substring); foundStart >= 0 { foundEnd := foundStart + len(substring) start, end := foundStart-extra, foundEnd+extra prefix, suffix := "", "" if start <= 0 { start = 0 } else { prefix = "..." } if end >= len(s) { end = len(s) } else { suffix = "..." } return base.NewResultf(true, "substring \"%v\" appears in \"%v%v[%v]%v%v\"", substring, prefix, s[start:foundStart], substring, s[foundEnd:end], suffix) } return base.NewResultf(false, "substring \"%v\" does not appear in \"%v\"", substring, s) } return base.NewMatcherf(match, "Contains(\"%v\")", substring) }
// Second part of a builder for an either/xor matcher: // matcher := Either(matcher1).Xor(matcher2) // This matcher matches when exactly one of the two matchers matches // a given value; if both or neither of the matchers is successful, // xor fails to match. Note that this is *never* a short-circuiting // operation. func (self *EitherClause) Xor(matcher2 *base.Matcher) *base.Matcher { matcher1 := self.matcher match := func(actual interface{}) *base.Result { result1 := matcher1.Match(actual) result2 := matcher2.Match(actual) if result1.Matched() { if result2.Matched() { return base.NewResultf(false, "both parts of 'Either/Xor' matched [%v]", actual). WithCauses(result1, result2) } return base.NewResultf(true, "only the first part of 'Either/Xor' matched [%v]", actual). WithCauses(result1, result2) } if result2.Matched() { return base.NewResultf(true, "only the second part of 'Either/Xor' matched [%v]", actual). WithCauses(result1, result2) } return base.NewResultf(false, "neither part of 'Either/Xor' matched [%v]", actual). WithCauses(result1, result2) } return base.NewMatcherf(match, "either [%v] xor [%v]", matcher1, matcher2) }
// Check Matchers func Test_BothAnd(t *testing.T) { yes, no := Anything(), Not(Anything()) calledSnoop := false snoop := base.NewMatcherf(func(v interface{}) *base.Result { calledSnoop = true return base.NewResultf(false, "snooped!") }, "Snoop") if result := Both(yes).And(yes).Match(0); !result.Matched() { t.Errorf("yes and yes should match, was [%v]", result) } if result := Both(yes).And(no).Match(0); result.Matched() { t.Errorf("yes and no should not match, was [%v]", result) } result := Both(no).And(snoop).Match(0) if calledSnoop { t.Errorf("no and snoop should short-circuit before calling snoop") } if result.Matched() { t.Errorf("no and snoop should not match, was [%v]", result) } logSamples(t, Both(yes).And(yes)) logSamples(t, Both(yes).And(no)) logSamples(t, Both(no).And(yes)) logSamples(t, Both(no).And(no)) }
// Constructs an if-and-only-if/then matcher: // matcher := IfAndOnlyIf(AntecedentMatcher).Then(ConsequentMatcher) // that matches when both or neither of the Antecedent and the // Consequent match. Note that this is logically equivalent to: // Either(Not(AntecedentMatcher)).Xor(ConsequentMatcher) // But may be more readable in practice. func (self *IfAndOnlyIfClause) Then(consequent *base.Matcher) *base.Matcher { antecedent := self.antecedent match := func(actual interface{}) *base.Result { result1 := antecedent.Match(actual) result2 := consequent.Match(actual) if result1.Matched() { if result2.Matched() { return base.NewResultf(true, "Matched because both parts of 'Iff/Then' matched on [%v]", actual). WithCauses(result1, result2) } return base.NewResultf(false, "Failed because only the first part of 'Iff/Then' matched on [%v]", actual). WithCauses(result1, result2) } if result2.Matched() { return base.NewResultf(false, "Failed because only the second part of 'IFf/Then' matched on [%v]", actual). WithCauses(result1, result2) } return base.NewResultf(true, "Matched because neither part of 'Iff/Then' matched on [%v]", actual). WithCauses(result1, result2) } return base.NewMatcherf(match, "if and only if [%v] then [%v]", antecedent, consequent) }
// Returns a matcher that matches on any array or slice input value // if the given matcher matches every element of that array or slice. // // The returned matcher does not match any non-array-or-slice value. func EachElem(matcher *base.Matcher) *base.Matcher { match := func(actual interface{}) *base.Result { v := reflect.NewValue(actual) var value _ElemAndLen var ok bool value, ok = v.(*reflect.ArrayValue) if !ok { value, ok = v.(*reflect.SliceValue) } if !ok { return base.NewResultf(false, "Was not array or slice: was type %T", actual) } n := value.Len() for i := 0; i < n; i++ { elem := value.Elem(i).Interface() result := matcher.Match(elem) if !result.Matched() { return base.NewResultf(false, "Failed to match element %v of %v: %v", i+1, n, elem). WithCauses(result) } } return base.NewResultf(true, "Matched all of the %v elements", n) } return base.NewMatcherf(match, "EveryElement[%v]", matcher) }
// Returns a short-circuiting function that applies the matcher to each // occurrence of the pattern in an input string, until a matching pattern // is found (in which case the matcher successfully matches) or all // matching instances are exhausted (in which case the output matcher // fails to match). // // For example: // AnyPattern("x.")(EqualTo("xy")) // would match: // "six sax are sexy" (three matches of "x.", third is "xy") // but not: // "pox pix are pixelated" (three matches of "x.", none is "xy") func AnyPattern(pattern string) func(matcher *base.Matcher) *base.Matcher { re := regexp.MustCompile(pattern) return func(matcher *base.Matcher) *base.Matcher { match := func(s string) *base.Result { matches := re.FindAllStringIndex(s, -1) if matches == nil { return base.NewResultf(false, "No occurrences of pattern \"%v\"", pattern) } for index, loc := range matches { start, end := loc[0], loc[1] substring := s[start:end] result := matcher.Match(substring) if result.Matched() { return base.NewResultf(true, "matched substring[%v:%v]=\"%v\", occurrence #%v (of %v) of pattern \"%v\"", start, end, substring, index+1, len(matches), pattern) } } return base.NewResultf(false, "Did not match any occurrence (of %v) of pattern \"%v\"", len(matches), pattern) } return base.NewMatcherf(match, "AnyPattern[\"%v\"][%v]", pattern, matcher) } }
// Variant of AnyPattern() that uses the given subgroup of the pattern. func AnyPatternGroup(pattern string, group int) func(matcher *base.Matcher) *base.Matcher { re := regexp.MustCompile(pattern) if num := re.NumSubexp(); num < group { println("Illegal group #", group, ": there are only ", num, "groups.") panic("Group index out of bounds.") } return func(matcher *base.Matcher) *base.Matcher { match := func(s string) *base.Result { matches := re.FindAllStringSubmatchIndex(s, -1) if matches == nil { return base.NewResultf(false, "No occurrences of pattern \"%v\"", pattern) } for index, loc := range matches { substart, subend := loc[2*group], loc[2*group+1] substring := s[substart:subend] result := matcher.Match(substring) if result.Matched() { start, end := loc[0], loc[1] prefix, suffix := s[start:substart], s[subend:end] return base.NewResultf(true, "matched substring[%v:%v], [%v:%v]=\"%v[%v]%v\", occurrence #%v (of %v) of pattern \"%v\"", substart, subend, start, end, prefix, substring, suffix, index+1, len(matches), pattern) } } return base.NewResultf(false, "Did not match any occurrence (of %v) of pattern \"%v\"", len(matches), pattern) } return base.NewMatcherf(match, "AnyPatternGroup[\"%v\", %v][%v]", pattern, group, matcher) } }
// Creates a new matcher that applies the given matcher to the result of // converting an input string its length. (using the `len()` builtin). // If the input value is not a string, the matcher fails to match. func ToLen(matcher *base.Matcher) *base.Matcher { match := func(s string) *base.Result { length := len(s) result := matcher.Match(length) return base.NewResultf(result.Matched(), "length is %v", length). WithCauses(result) } return base.NewMatcherf(match, "ToLen(%v)", matcher) }
// Creates a new matcher that applies the given matcher to the result of // converting an input string to uppercase (using strings.ToUpper). // If the input value is not a string, the matcher fails to match. func ToUpper(matcher *base.Matcher) *base.Matcher { match := func(s string) *base.Result { upper := strings.ToUpper(s) result := matcher.Match(upper) return base.NewResultf(result.Matched(), "ToUpper is %v", upper). WithCauses(result) } return base.NewMatcherf(match, "ToUpper(%v)", matcher) }
// Returns a new matcher that applies the type of its input // element to the given matcher. func ToType(matcher *base.Matcher) *base.Matcher { match := func(actual interface{}) *base.Result { actualType := reflect.Typeof(actual) result := matcher.Match(actualType) return base.NewResultf(result.Matched(), "reflect.Typeof() returned %v", actualType). WithCauses(result) } return base.NewMatcherf(match, "ToType(%v)", matcher) }
// Matches any input element that is an empty array, slice, or map. func Empty() *base.Matcher { match := func(actual interface{}) *base.Result { value := reflect.NewValue(actual) if hasLen, ok := value.(_HasLen); ok { length := hasLen.Len() return base.NewResultf(length == 0, "Len() returned %v", length) } return base.NewResultf(false, "Can't determine length of type %T", actual) } return base.NewMatcherf(match, "Empty") }
// Applies the given matcher to the length of the input element. func ToLen(matcher *base.Matcher) *base.Matcher { match := func(actual interface{}) *base.Result { value := reflect.NewValue(actual) if hasLen, ok := value.(_HasLen); ok { length := hasLen.Len() result := matcher.Match(length) return base.NewResultf(result.Matched(), "Len() returned %v", length) } return base.NewResultf(false, "Can't determine Len() for %T", actual) } return base.NewMatcherf(match, "ToLen[%v]", matcher) }
func _TypeMatcher(name string, expectedType reflect.Type) *base.Matcher { match := func(actual interface{}) *base.Result { if actual == nil { return base.NewResultf(false, "was nil") } actualType := reflect.Typeof(actual) if reflect.DeepEqual(actualType, expectedType) { return base.NewResultf(true, "was of type %v", expectedType) } return base.NewResultf(false, "was a %v, not a %v", actualType, expectedType) } return base.NewMatcherf(match, "Typeof[%v]", expectedType) }
// Matches strings that contain the given regexp pattern, using // the same syntax as the standard regexp package. func HasPattern(pattern string) *base.Matcher { re := regexp.MustCompile(pattern) match := func(s string) *base.Result { if found := re.FindStringIndex(s); found != nil { start, end := found[0], found[1] return base.NewResultf(true, "pattern \"%v\" matched substring[%v:%v]=\"%v\"", pattern, start, end, s[start:end]) } return base.NewResultf(false, "pattern \"%v\" not found in \"%v\"", pattern, s) } return base.NewMatcherf(match, "HasPattern[\"%v\"]", pattern) }
// Returns a new matcher that, on any input that is a *reflect.SliceType, // extracts the type of element and matches it against the given matcher. // // If the given input is not an *reflect.SliceType, this fails to match. // Note: this matches slice *types*, not slices. (See SliceOf.) func SliceTypeOf(elementTypeMatcher *base.Matcher) *base.Matcher { match := func(actual interface{}) *base.Result { if sliceType, ok := actual.(*reflect.SliceType); ok { elementType := sliceType.Elem() result := elementTypeMatcher.Match(elementType) return base.NewResultf( result.Matched(), "was SliceType with elements of type %v", elementType.Name()). WithCauses(result) } return base.NewResultf(false, "was of type %T, not a slice", actual) } return base.NewMatcherf(match, "SliceTypeOf(%v)", elementTypeMatcher) }
func EqualToIgnoringCase(expected string) *base.Matcher { expectedToLower := strings.ToLower(expected) match := func(actual string) *base.Result { actualToLower := strings.ToLower(actual) if actualToLower == expectedToLower { return base.NewResultf(true, "\"%v\" matches \"%v\" (ignoring case)", actual, expected) } return base.NewResultf(false, "\"%v\" differs from \"%v\" (ignoring case)", actual, expected) } return base.NewMatcherf(match, "EqualToIgnoringCase(\"%v\")", expected) }
// Returns a new matcher that, on any input that is an array, extracts // its type and matches it against the given matcher. // // If the given input is not an array, this fails to match. // Note: this matches *arrays*, not array *types*. (See ArrayTypeOf.) func ArrayOf(elementTypeMatcher *base.Matcher) *base.Matcher { match := func(actual interface{}) *base.Result { actualType := reflect.Typeof(actual) if arrayType, ok := actualType.(*reflect.ArrayType); ok { elementType := arrayType.Elem() result := elementTypeMatcher.Match(elementType) return base.NewResultf( result.Matched(), "was array with elements of type %v", elementType). WithCauses(result) } return base.NewResultf(false, "was of type %T, not an array", actual) } return base.NewMatcherf(match, "ArrayOf(%v)", elementTypeMatcher) }
// Returns a new matcher that, on any input that is a channel, extracts // its type and matches it against the given matcher. // // If the given input is not a channel, this fails to match. // Note: this matches *channels*, not channel *types*. (See ChannelTypeOf.) func ChannelOf(elementTypeMatcher *base.Matcher) *base.Matcher { match := func(actual interface{}) *base.Result { actualType := reflect.Typeof(actual) if channelType, ok := actualType.(*reflect.ChanType); ok { elementType := channelType.Elem() result := elementTypeMatcher.Match(elementType) return base.NewResultf(result.Matched(), "was channel with elements of type %v", elementType). WithCauses(result) } return base.NewResultf(false, "was of type %T, not a channel", actual) } return base.NewMatcherf(match, "ChannelOf(%v)", elementTypeMatcher) }
// Matches strings that begin with the given prefix. func HasPrefix(prefix string) *base.Matcher { maxLength := len(prefix) + 8 // arbitrary extra amount match := func(s string) *base.Result { continued := "" if len(s) > maxLength { s, continued = s[:maxLength], "..." } if strings.HasPrefix(s, prefix) { return base.NewResultf(true, "\"%v%v\" starts with \"%v\"", s, continued, prefix) } return base.NewResultf(false, "\"%v%v\" does not start with \"%v\"", s, continued, prefix) } return base.NewMatcherf(match, "HasPrefix(\"%v\")", prefix) }
// Matches strings that end with the given prefix. func HasSuffix(suffix string) *base.Matcher { maxLength := len(suffix) + 8 // arbitrary extra amount match := func(s string) *base.Result { continued := "" if len(s) > maxLength { continued, s = "...", s[len(s)-maxLength:] } if strings.HasSuffix(s, suffix) { return base.NewResultf(true, "\"%v%v\" ends with \"%v\"", s, continued, suffix) } return base.NewResultf(false, "\"%v%v\" does not end with \"%v\"", s, continued, suffix) } return base.NewMatcherf(match, "HasSuffix(\"%v\")", suffix) }
// Returns a new matcher that, on any input that is a pointer, extracts the // type of object that it thinks it's pointing to (the "pointee") and // matches it against the given matcher. // // If the given input is not an pointer, this fails to match. // Note: this matches *pointers*, not pointer *types*. (See PtrTypeTo.) func PtrTo(pointeeTypeMatcher *base.Matcher) *base.Matcher { match := func(actual interface{}) *base.Result { actualType := reflect.Typeof(actual) if ptrType, ok := actualType.(*reflect.PtrType); ok { elementType := ptrType.Elem() result := pointeeTypeMatcher.Match(elementType) return base.NewResultf( result.Matched(), "was PtrType to type %v", elementType). WithCauses(result) } return base.NewResultf(false, "was type %T, not a pointer", actual) } return base.NewMatcherf(match, "PtrTo(%v)", pointeeTypeMatcher) }
func Test_AnyOf(t *testing.T) { we := asserter.Using(t) yes, no := Anything(), Not(Anything()) calledSnoop := false snoop := base.NewMatcherf(func(v interface{}) *base.Result { calledSnoop = true return base.NewResultf(false, "snooped!") }, "Snoop") we.CheckThat(AnyOf(no, no, no).Match(0), DidNotMatch.Comment("none matched")) we.CheckThat(AnyOf(no, no, yes).Match(0), Matched.Comment("one matched")) we.CheckThat(AnyOf(yes).Match(0), Matched.Comment("can pass one matcher")) we.CheckThat(AnyOf(no).Match(0), DidNotMatch.Comment("can fail one matcher")) we.CheckThat(AnyOf(no, yes, snoop).Match(0), Matched.Comment("can short-circuit")) we.CheckFalse(calledSnoop, "AnyOf should short-circuit on first match") logSamples(t, AnyOf(True(), Nil(), EqualTo(42))) }
// Applies the given matcher to the result of writing the input object's // to a string by using fmt.Sprintf("%#v", object). func ToGoString(matcher *base.Matcher) *base.Matcher { match := func(actual interface{}) *base.Result { if gostringer, ok := actual.(fmt.GoStringer); ok { s := gostringer.GoString() result := matcher.Match(s) return base.NewResultf(result.Matched(), "GoString() returned %v", s). WithCauses(result) } s := fmt.Sprintf("%#v", actual) result := matcher.Match(s) return base.NewResultf(result.Matched(), "Not a fmt.GoStringer, but prints as %v", s). WithCauses(result) } return base.NewMatcherf(match, "ToGoString(%v)", matcher) }
// Returns a function that applies the matcher to the first occurrence of // the pattern in an input string. // // For example: // OnPattern("h.s")(EqualTo("his")) // would match: // "hers and his" (because the first instance of "h.s" is equal to "his") // but none of: // "just hers" (no instances of "h.s") // "has chisel" (the first instance of "h.s" is not "his") func OnPattern(pattern string) func(matcher *base.Matcher) *base.Matcher { re := regexp.MustCompile(pattern) return func(matcher *base.Matcher) *base.Matcher { match := func(s string) *base.Result { matches := re.FindStringIndex(s) if matches == nil { return base.NewResultf(false, "No occurrences of pattern \"%v\"", pattern) } start, end := matches[0], matches[1] substring := s[start:end] result := matcher.Match(substring) return base.NewResultf(result.Matched(), "Found substring[%v:%v]=\"%v\" for pattern \"%v\"", start, end, substring, pattern).WithCauses(result) } return base.NewMatcherf(match, "OnPattern[\"%v\"][%v]", pattern, matcher) } }
// Second part of a builder for a short-circuiting both/and matcher. func (self *BothClause) And(matcher2 *base.Matcher) *base.Matcher { matcher1 := self.matcher match := func(actual interface{}) *base.Result { result1 := matcher1.Match(actual) if !result1.Matched() { return base.NewResultf(false, "first part of 'Both/And' did not match [%v]", actual). WithCauses(result1) } result2 := matcher2.Match(actual) if !result2.Matched() { return base.NewResultf(false, "second part of 'Both/And' did not match [%v]", actual). WithCauses(result2) } return base.NewResultf(true, "both parts of 'Both/And' matched [%v]", actual). WithCauses(result1, result2) } return base.NewMatcherf(match, "both [%v] and [%v]", matcher1, matcher2) }
// Creates a matcher that passes when neither this matcher nor the // other matcher pass. This operation is short-circuiting, so that // if the first matcher matches, the second is not attempted. // Note that this is logically equivalent to: // Both(Not(matcher1)).And(Not(matcher2)) // But may be more readable in practice. func (self *NeitherClause) Nor(matcher2 *base.Matcher) *base.Matcher { matcher1 := self.matcher match := func(actual interface{}) *base.Result { result1 := matcher1.Match(actual) if result1.Matched() { return base.NewResultf(false, "first part of 'Nor' matched [%v]", actual). WithCauses(result1) } result2 := matcher2.Match(actual) if result2.Matched() { return base.NewResultf(false, "second part of 'Nor' matched [%v]", actual). WithCauses(result2) } return base.NewResultf(true, "neither part of 'Nor' matched [%v]", actual). WithCauses(result1, result2) } return base.NewMatcherf(match, "neither [%v] nor [%v]", matcher1, matcher2) }
// Returns a matcher that matches on any array or slice input value // if the given matcher matches at least one element of that array // or slice. // // The returned matcher does not match any non-array-or-slice value. func AnyElem(matcher *base.Matcher) *base.Matcher { match := func(actual interface{}) *base.Result { v := reflect.NewValue(actual) if value, ok := v.(_ElemAndLen); ok { n := value.Len() for i := 0; i < n; i++ { elem := value.Elem(i).Interface() result := matcher.Match(elem) if result.Matched() { return base.NewResultf(true, "Matched element %v of %v: %v", i+1, n, elem). WithCauses(result) } } return base.NewResultf(false, "Matched none of the %v elements", n) } return matcher.Match(v) } return base.NewMatcherf(match, "AnyElement[%v]", matcher) }
// Returns a new matcher that, on any input that is a *reflect.MapType, // extracts the type of keys and element and matches them against two // given matchers. // // If the given input is not an *reflect.MapType, this fails to match. // Note: this matches map *types*, not maps. (See MapOf.) func MapTypeOf(keyTypeMatcher, elementTypeMatcher *base.Matcher) *base.Matcher { match := func(actual interface{}) *base.Result { if mapType, ok := actual.(*reflect.MapType); ok { keyType := mapType.Key() elementType := mapType.Elem() keyResult := keyTypeMatcher.Match(keyType) if !keyResult.Matched() { return base.NewResultf(false, "was MapType with keys of type %v", keyType). WithCauses(keyResult) } elementResult := elementTypeMatcher.Match(elementType) return base.NewResultf(elementResult.Matched(), "was MapType with keys/elements of type %v/%v", keyType, elementType). WithCauses(keyResult, elementResult) } return base.NewResultf(false, "was of type %T, not a MapType", actual) } return base.NewMatcherf(match, "MapTypeOf(%v, %v)", keyTypeMatcher, elementTypeMatcher) }