// Copyright 2011 Aaron Jacobs. All Rights Reserved. // Author: aaronjjacobs@gmail.com (Aaron Jacobs) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oglematchers import ( "strings" ) // AllOf accepts a set of matchers S and returns a matcher that follows the // algorithm below when considering a candidate c: // // 1. Return true if for every Matcher m in S, m matches c. // // 2. Otherwise, if there is a matcher m in S such that m returns a fatal // error for c, return that matcher's error message. // // 3. Otherwise, return false with the error from some wrapped matcher. // // This is akin to a logical AND operation for matchers. func AllOf(matchers ...Matcher) Matcher { return &allOfMatcher{matchers} } type allOfMatcher struct { wrappedMatchers []Matcher } func (m *allOfMatcher) Description() string { // Special case: the empty set. if len(m.wrappedMatchers) == 0 { return "is anything" } // Join the descriptions for the wrapped matchers. wrappedDescs := make([]string, len(m.wrappedMatchers)) for i, wrappedMatcher := range m.wrappedMatchers { wrappedDescs[i] = wrappedMatcher.Description() } return strings.Join(wrappedDescs, ", and ") } func (m *allOfMatcher) Matches(c interface{}) (err error) { for _, wrappedMatcher := range m.wrappedMatchers { if wrappedErr := wrappedMatcher.Matches(c); wrappedErr != nil { err = wrappedErr // If the error is fatal, return immediately with this error. _, ok := wrappedErr.(*FatalError) if ok { return } } } return }
// Copyright 2011 Aaron Jacobs. All Rights Reserved. // Author: aaronjjacobs@gmail.com (Aaron Jacobs) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oglematchers // Any returns a matcher that matches any value. func Any() Matcher { return &anyMatcher{} } type anyMatcher struct { } func (m *anyMatcher) Description() string { return "is anything" } func (m *anyMatcher) Matches(c interface{}) error { return nil }
// Copyright 2011 Aaron Jacobs. All Rights Reserved. // Author: aaronjjacobs@gmail.com (Aaron Jacobs) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oglematchers import ( "errors" "fmt" "reflect" "strings" ) // AnyOf accepts a set of values S and returns a matcher that follows the // algorithm below when considering a candidate c: // // 1. If there exists a value m in S such that m implements the Matcher // interface and m matches c, return true. // // 2. Otherwise, if there exists a value v in S such that v does not implement // the Matcher interface and the matcher Equals(v) matches c, return true. // // 3. Otherwise, if there is a value m in S such that m implements the Matcher // interface and m returns a fatal error for c, return that fatal error. // // 4. Otherwise, return false. // // This is akin to a logical OR operation for matchers, with non-matchers x // being treated as Equals(x). func AnyOf(vals ...interface{}) Matcher { // Get ahold of a type variable for the Matcher interface. var dummy *Matcher matcherType := reflect.TypeOf(dummy).Elem() // Create a matcher for each value, or use the value itself if it's already a // matcher. wrapped := make([]Matcher, len(vals)) for i, v := range vals { if reflect.TypeOf(v).Implements(matcherType) { wrapped[i] = v.(Matcher) } else { wrapped[i] = Equals(v) } } return &anyOfMatcher{wrapped} } type anyOfMatcher struct { wrapped []Matcher } func (m *anyOfMatcher) Description() string { wrappedDescs := make([]string, len(m.wrapped)) for i, matcher := range m.wrapped { wrappedDescs[i] = matcher.Description() } return fmt.Sprintf("or(%s)", strings.Join(wrappedDescs, ", ")) } func (m *anyOfMatcher) Matches(c interface{}) (err error) { err = errors.New("") // Try each matcher in turn. for _, matcher := range m.wrapped { wrappedErr := matcher.Matches(c) // Return immediately if there's a match. if wrappedErr == nil { err = nil return } // Note the fatal error, if any. if _, isFatal := wrappedErr.(*FatalError); isFatal { err = wrappedErr } } return }
// Copyright 2012 Aaron Jacobs. All Rights Reserved. // Author: aaronjjacobs@gmail.com (Aaron Jacobs) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oglematchers import ( "fmt" "reflect" ) // Return a matcher that matches arrays slices with at least one element that // matches the supplied argument. If the argument x is not itself a Matcher, // this is equivalent to Contains(Equals(x)). func Contains(x interface{}) Matcher { var result containsMatcher var ok bool if result.elementMatcher, ok = x.(Matcher); !ok { result.elementMatcher = Equals(x) } return &result } type containsMatcher struct { elementMatcher Matcher } func (m *containsMatcher) Description() string { return fmt.Sprintf("contains: %s", m.elementMatcher.Description()) } func (m *containsMatcher) Matches(candidate interface{}) error { // The candidate must be a slice or an array. v := reflect.ValueOf(candidate) if v.Kind() != reflect.Slice && v.Kind() != reflect.Array { return NewFatalError("which is not a slice or array") } // Check each element. for i := 0; i < v.Len(); i++ { elem := v.Index(i) if matchErr := m.elementMatcher.Matches(elem.Interface()); matchErr == nil { return nil } } return fmt.Errorf("") }
// Copyright 2012 Aaron Jacobs. All Rights Reserved. // Author: aaronjjacobs@gmail.com (Aaron Jacobs) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oglematchers import ( "bytes" "errors" "fmt" "reflect" ) var byteSliceType reflect.Type = reflect.TypeOf([]byte{}) // DeepEquals returns a matcher that matches based on 'deep equality', as // defined by the reflect package. This matcher requires that values have // identical types to x. func DeepEquals(x interface{}) Matcher { return &deepEqualsMatcher{x} } type deepEqualsMatcher struct { x interface{} } func (m *deepEqualsMatcher) Description() string { xDesc := fmt.Sprintf("%v", m.x) xValue := reflect.ValueOf(m.x) // Special case: fmt.Sprintf presents nil slices as "[]", but // reflect.DeepEqual makes a distinction between nil and empty slices. Make // this less confusing. if xValue.Kind() == reflect.Slice && xValue.IsNil() { xDesc = "<nil slice>" } return fmt.Sprintf("deep equals: %s", xDesc) } func (m *deepEqualsMatcher) Matches(c interface{}) error { // Make sure the types match. ct := reflect.TypeOf(c) xt := reflect.TypeOf(m.x) if ct != xt { return NewFatalError(fmt.Sprintf("which is of type %v", ct)) } // Special case: handle byte slices more efficiently. cValue := reflect.ValueOf(c) xValue := reflect.ValueOf(m.x) if ct == byteSliceType && !cValue.IsNil() && !xValue.IsNil() { xBytes := m.x.([]byte) cBytes := c.([]byte) if bytes.Equal(cBytes, xBytes) { return nil } return errors.New("") } // Defer to the reflect package. if reflect.DeepEqual(m.x, c) { return nil } // Special case: if the comparison failed because c is the nil slice, given // an indication of this (since its value is printed as "[]"). if cValue.Kind() == reflect.Slice && cValue.IsNil() { return errors.New("which is nil") } return errors.New("") }
// Copyright 2012 Aaron Jacobs. All Rights Reserved. // Author: aaronjjacobs@gmail.com (Aaron Jacobs) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oglematchers import ( "errors" "fmt" "reflect" "strings" ) // Given a list of arguments M, ElementsAre returns a matcher that matches // arrays and slices A where all of the following hold: // // * A is the same length as M. // // * For each i < len(A) where M[i] is a matcher, A[i] matches M[i]. // // * For each i < len(A) where M[i] is not a matcher, A[i] matches // Equals(M[i]). // func ElementsAre(M ...interface{}) Matcher { // Copy over matchers, or convert to Equals(x) for non-matcher x. subMatchers := make([]Matcher, len(M)) for i, x := range M { if matcher, ok := x.(Matcher); ok { subMatchers[i] = matcher continue } subMatchers[i] = Equals(x) } return &elementsAreMatcher{subMatchers} } type elementsAreMatcher struct { subMatchers []Matcher } func (m *elementsAreMatcher) Description() string { subDescs := make([]string, len(m.subMatchers)) for i, sm := range m.subMatchers { subDescs[i] = sm.Description() } return fmt.Sprintf("elements are: [%s]", strings.Join(subDescs, ", ")) } func (m *elementsAreMatcher) Matches(candidates interface{}) error { // The candidate must be a slice or an array. v := reflect.ValueOf(candidates) if v.Kind() != reflect.Slice && v.Kind() != reflect.Array { return NewFatalError("which is not a slice or array") } // The length must be correct. if v.Len() != len(m.subMatchers) { return errors.New(fmt.Sprintf("which is of length %d", v.Len())) } // Check each element. for i, subMatcher := range m.subMatchers { c := v.Index(i) if matchErr := subMatcher.Matches(c.Interface()); matchErr != nil { // Return an errors indicating which element doesn't match. If the // matcher error was fatal, make this one fatal too. err := errors.New(fmt.Sprintf("whose element %d doesn't match", i)) if _, isFatal := matchErr.(*FatalError); isFatal { err = NewFatalError(err.Error()) } return err } } return nil }
// Copyright 2011 Aaron Jacobs. All Rights Reserved. // Author: aaronjjacobs@gmail.com (Aaron Jacobs) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oglematchers import ( "errors" "fmt" "math" "reflect" ) // Equals(x) returns a matcher that matches values v such that v and x are // equivalent. This includes the case when the comparison v == x using Go's // built-in comparison operator is legal, but for convenience the following // rules also apply: // // * Type checking is done based on underlying types rather than actual // types, so that e.g. two aliases for string can be compared: // // type stringAlias1 string // type stringAlias2 string // // a := "taco" // b := stringAlias1("taco") // c := stringAlias2("taco") // // ExpectTrue(a == b) // Legal, passes // ExpectTrue(b == c) // Illegal, doesn't compile // // ExpectThat(a, Equals(b)) // Passes // ExpectThat(b, Equals(c)) // Passes // // * Values of numeric type are treated as if they were abstract numbers, and // compared accordingly. Therefore Equals(17) will match int(17), // int16(17), uint(17), float32(17), complex64(17), and so on. // // If you want a stricter matcher that contains no such cleverness, see // IdenticalTo instead. func Equals(x interface{}) Matcher { v := reflect.ValueOf(x) // The == operator is not defined for array or struct types. if v.Kind() == reflect.Array || v.Kind() == reflect.Struct { panic(fmt.Sprintf("oglematchers.Equals: unsupported kind %v", v.Kind())) } // The == operator is not defined for non-nil slices. if v.Kind() == reflect.Slice && v.Pointer() != uintptr(0) { panic(fmt.Sprintf("oglematchers.Equals: non-nil slice")) } return &equalsMatcher{v} } type equalsMatcher struct { expectedValue reflect.Value } //////////////////////////////////////////////////////////////////////// // Numeric types //////////////////////////////////////////////////////////////////////// func isSignedInteger(v reflect.Value) bool { k := v.Kind() return k >= reflect.Int && k <= reflect.Int64 } func isUnsignedInteger(v reflect.Value) bool { k := v.Kind() return k >= reflect.Uint && k <= reflect.Uint64 } func isInteger(v reflect.Value) bool { return isSignedInteger(v) || isUnsignedInteger(v) } func isFloat(v reflect.Value) bool { k := v.Kind() return k == reflect.Float32 || k == reflect.Float64 } func isComplex(v reflect.Value) bool { k := v.Kind() return k == reflect.Complex64 || k == reflect.Complex128 } func checkAgainstInt64(e int64, c reflect.Value) (err error) { err = errors.New("") switch { case isSignedInteger(c): if c.Int() == e { err = nil } case isUnsignedInteger(c): u := c.Uint() if u <= math.MaxInt64 && int64(u) == e { err = nil } // Turn around the various floating point types so that the checkAgainst* // functions for them can deal with precision issues. case isFloat(c), isComplex(c): return Equals(c.Interface()).Matches(e) default: err = NewFatalError("which is not numeric") } return } func checkAgainstUint64(e uint64, c reflect.Value) (err error) { err = errors.New("") switch { case isSignedInteger(c): i := c.Int() if i >= 0 && uint64(i) == e { err = nil } case isUnsignedInteger(c): if c.Uint() == e { err = nil } // Turn around the various floating point types so that the checkAgainst* // functions for them can deal with precision issues. case isFloat(c), isComplex(c): return Equals(c.Interface()).Matches(e) default: err = NewFatalError("which is not numeric") } return } func checkAgainstFloat32(e float32, c reflect.Value) (err error) { err = errors.New("") switch { case isSignedInteger(c): if float32(c.Int()) == e { err = nil } case isUnsignedInteger(c): if float32(c.Uint()) == e { err = nil } case isFloat(c): // Compare using float32 to avoid a false sense of precision; otherwise // e.g. Equals(float32(0.1)) won't match float32(0.1). if float32(c.Float()) == e { err = nil } case isComplex(c): comp := c.Complex() rl := real(comp) im := imag(comp) // Compare using float32 to avoid a false sense of precision; otherwise // e.g. Equals(float32(0.1)) won't match (0.1 + 0i). if im == 0 && float32(rl) == e { err = nil } default: err = NewFatalError("which is not numeric") } return } func checkAgainstFloat64(e float64, c reflect.Value) (err error) { err = errors.New("") ck := c.Kind() switch { case isSignedInteger(c): if float64(c.Int()) == e { err = nil } case isUnsignedInteger(c): if float64(c.Uint()) == e { err = nil } // If the actual value is lower precision, turn the comparison around so we // apply the low-precision rules. Otherwise, e.g. Equals(0.1) may not match // float32(0.1). case ck == reflect.Float32 || ck == reflect.Complex64: return Equals(c.Interface()).Matches(e) // Otherwise, compare with double precision. case isFloat(c): if c.Float() == e { err = nil } case isComplex(c): comp := c.Complex() rl := real(comp) im := imag(comp) if im == 0 && rl == e { err = nil } default: err = NewFatalError("which is not numeric") } return } func checkAgainstComplex64(e complex64, c reflect.Value) (err error) { err = errors.New("") realPart := real(e) imaginaryPart := imag(e) switch { case isInteger(c) || isFloat(c): // If we have no imaginary part, then we should just compare against the // real part. Otherwise, we can't be equal. if imaginaryPart != 0 { return } return checkAgainstFloat32(realPart, c) case isComplex(c): // Compare using complex64 to avoid a false sense of precision; otherwise // e.g. Equals(0.1 + 0i) won't match float32(0.1). if complex64(c.Complex()) == e { err = nil } default: err = NewFatalError("which is not numeric") } return } func checkAgainstComplex128(e complex128, c reflect.Value) (err error) { err = errors.New("") realPart := real(e) imaginaryPart := imag(e) switch { case isInteger(c) || isFloat(c): // If we have no imaginary part, then we should just compare against the // real part. Otherwise, we can't be equal. if imaginaryPart != 0 { return } return checkAgainstFloat64(realPart, c) case isComplex(c): if c.Complex() == e { err = nil } default: err = NewFatalError("which is not numeric") } return } //////////////////////////////////////////////////////////////////////// // Other types //////////////////////////////////////////////////////////////////////// func checkAgainstBool(e bool, c reflect.Value) (err error) { if c.Kind() != reflect.Bool { err = NewFatalError("which is not a bool") return } err = errors.New("") if c.Bool() == e { err = nil } return } func checkAgainstUintptr(e uintptr, c reflect.Value) (err error) { if c.Kind() != reflect.Uintptr { err = NewFatalError("which is not a uintptr") return } err = errors.New("") if uintptr(c.Uint()) == e { err = nil } return } func checkAgainstChan(e reflect.Value, c reflect.Value) (err error) { // Create a description of e's type, e.g. "chan int". typeStr := fmt.Sprintf("%s %s", e.Type().ChanDir(), e.Type().Elem()) // Make sure c is a chan of the correct type. if c.Kind() != reflect.Chan || c.Type().ChanDir() != e.Type().ChanDir() || c.Type().Elem() != e.Type().Elem() { err = NewFatalError(fmt.Sprintf("which is not a %s", typeStr)) return } err = errors.New("") if c.Pointer() == e.Pointer() { err = nil } return } func checkAgainstFunc(e reflect.Value, c reflect.Value) (err error) { // Make sure c is a function. if c.Kind() != reflect.Func { err = NewFatalError("which is not a function") return } err = errors.New("") if c.Pointer() == e.Pointer() { err = nil } return } func checkAgainstMap(e reflect.Value, c reflect.Value) (err error) { // Make sure c is a map. if c.Kind() != reflect.Map { err = NewFatalError("which is not a map") return } err = errors.New("") if c.Pointer() == e.Pointer() { err = nil } return } func checkAgainstPtr(e reflect.Value, c reflect.Value) (err error) { // Create a description of e's type, e.g. "*int". typeStr := fmt.Sprintf("*%v", e.Type().Elem()) // Make sure c is a pointer of the correct type. if c.Kind() != reflect.Ptr || c.Type().Elem() != e.Type().Elem() { err = NewFatalError(fmt.Sprintf("which is not a %s", typeStr)) return } err = errors.New("") if c.Pointer() == e.Pointer() { err = nil } return } func checkAgainstSlice(e reflect.Value, c reflect.Value) (err error) { // Create a description of e's type, e.g. "[]int". typeStr := fmt.Sprintf("[]%v", e.Type().Elem()) // Make sure c is a slice of the correct type. if c.Kind() != reflect.Slice || c.Type().Elem() != e.Type().Elem() { err = NewFatalError(fmt.Sprintf("which is not a %s", typeStr)) return } err = errors.New("") if c.Pointer() == e.Pointer() { err = nil } return } func checkAgainstString(e reflect.Value, c reflect.Value) (err error) { // Make sure c is a string. if c.Kind() != reflect.String { err = NewFatalError("which is not a string") return } err = errors.New("") if c.String() == e.String() { err = nil } return } func checkAgainstUnsafePointer(e reflect.Value, c reflect.Value) (err error) { // Make sure c is a pointer. if c.Kind() != reflect.UnsafePointer { err = NewFatalError("which is not a unsafe.Pointer") return } err = errors.New("") if c.Pointer() == e.Pointer() { err = nil } return } func checkForNil(c reflect.Value) (err error) { err = errors.New("") // Make sure it is legal to call IsNil. switch c.Kind() { case reflect.Invalid: case reflect.Chan: case reflect.Func: case reflect.Interface: case reflect.Map: case reflect.Ptr: case reflect.Slice: default: err = NewFatalError("which cannot be compared to nil") return } // Ask whether the value is nil. Handle a nil literal (kind Invalid) // specially, since it's not legal to call IsNil there. if c.Kind() == reflect.Invalid || c.IsNil() { err = nil } return } //////////////////////////////////////////////////////////////////////// // Public implementation //////////////////////////////////////////////////////////////////////// func (m *equalsMatcher) Matches(candidate interface{}) error { e := m.expectedValue c := reflect.ValueOf(candidate) ek := e.Kind() switch { case ek == reflect.Bool: return checkAgainstBool(e.Bool(), c) case isSignedInteger(e): return checkAgainstInt64(e.Int(), c) case isUnsignedInteger(e): return checkAgainstUint64(e.Uint(), c) case ek == reflect.Uintptr: return checkAgainstUintptr(uintptr(e.Uint()), c) case ek == reflect.Float32: return checkAgainstFloat32(float32(e.Float()), c) case ek == reflect.Float64: return checkAgainstFloat64(e.Float(), c) case ek == reflect.Complex64: return checkAgainstComplex64(complex64(e.Complex()), c) case ek == reflect.Complex128: return checkAgainstComplex128(complex128(e.Complex()), c) case ek == reflect.Chan: return checkAgainstChan(e, c) case ek == reflect.Func: return checkAgainstFunc(e, c) case ek == reflect.Map: return checkAgainstMap(e, c) case ek == reflect.Ptr: return checkAgainstPtr(e, c) case ek == reflect.Slice: return checkAgainstSlice(e, c) case ek == reflect.String: return checkAgainstString(e, c) case ek == reflect.UnsafePointer: return checkAgainstUnsafePointer(e, c) case ek == reflect.Invalid: return checkForNil(c) } panic(fmt.Sprintf("equalsMatcher.Matches: unexpected kind: %v", ek)) } func (m *equalsMatcher) Description() string { // Special case: handle nil. if !m.expectedValue.IsValid() { return "is nil" } return fmt.Sprintf("%v", m.expectedValue.Interface()) }
// Copyright 2011 Aaron Jacobs. All Rights Reserved. // Author: aaronjjacobs@gmail.com (Aaron Jacobs) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oglematchers // Error returns a matcher that matches non-nil values implementing the // built-in error interface for whom the return value of Error() matches the // supplied matcher. // // For example: // // err := errors.New("taco burrito") // // Error(Equals("taco burrito")) // matches err // Error(HasSubstr("taco")) // matches err // Error(HasSubstr("enchilada")) // doesn't match err // func Error(m Matcher) Matcher { return &errorMatcher{m} } type errorMatcher struct { wrappedMatcher Matcher } func (m *errorMatcher) Description() string { return "error " + m.wrappedMatcher.Description() } func (m *errorMatcher) Matches(c interface{}) error { // Make sure that c is an error. e, ok := c.(error) if !ok { return NewFatalError("which is not an error") } // Pass on the error text to the wrapped matcher. return m.wrappedMatcher.Matches(e.Error()) }
// Copyright 2011 Aaron Jacobs. All Rights Reserved. // Author: aaronjjacobs@gmail.com (Aaron Jacobs) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oglematchers import ( "fmt" "reflect" ) // GreaterOrEqual returns a matcher that matches integer, floating point, or // strings values v such that v >= x. Comparison is not defined between numeric // and string types, but is defined between all integer and floating point // types. // // x must itself be an integer, floating point, or string type; otherwise, // GreaterOrEqual will panic. func GreaterOrEqual(x interface{}) Matcher { desc := fmt.Sprintf("greater than or equal to %v", x) // Special case: make it clear that strings are strings. if reflect.TypeOf(x).Kind() == reflect.String { desc = fmt.Sprintf("greater than or equal to \"%s\"", x) } return transformDescription(Not(LessThan(x)), desc) }
// Copyright 2011 Aaron Jacobs. All Rights Reserved. // Author: aaronjjacobs@gmail.com (Aaron Jacobs) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oglematchers import ( "fmt" "reflect" ) // GreaterThan returns a matcher that matches integer, floating point, or // strings values v such that v > x. Comparison is not defined between numeric // and string types, but is defined between all integer and floating point // types. // // x must itself be an integer, floating point, or string type; otherwise, // GreaterThan will panic. func GreaterThan(x interface{}) Matcher { desc := fmt.Sprintf("greater than %v", x) // Special case: make it clear that strings are strings. if reflect.TypeOf(x).Kind() == reflect.String { desc = fmt.Sprintf("greater than \"%s\"", x) } return transformDescription(Not(LessOrEqual(x)), desc) }
// Copyright 2011 Aaron Jacobs. All Rights Reserved. // Author: aaronjjacobs@gmail.com (Aaron Jacobs) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oglematchers import ( "errors" "fmt" "reflect" "strings" ) // HasSubstr returns a matcher that matches strings containing s as a // substring. func HasSubstr(s string) Matcher { return &hasSubstrMatcher{s} } type hasSubstrMatcher struct { needle string } func (m *hasSubstrMatcher) Description() string { return fmt.Sprintf("has substring \"%s\"", m.needle) } func (m *hasSubstrMatcher) Matches(c interface{}) error { v := reflect.ValueOf(c) if v.Kind() != reflect.String { return NewFatalError("which is not a string") } // Perform the substring search. haystack := v.String() if strings.Contains(haystack, m.needle) { return nil } return errors.New("") }
// Copyright 2012 Aaron Jacobs. All Rights Reserved. // Author: aaronjjacobs@gmail.com (Aaron Jacobs) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oglematchers import ( "errors" "fmt" "reflect" ) // Is the type comparable according to the definition here? // // http://weekly.golang.org/doc/go_spec.html#Comparison_operators // func isComparable(t reflect.Type) bool { switch t.Kind() { case reflect.Array: return isComparable(t.Elem()) case reflect.Struct: for i := 0; i < t.NumField(); i++ { if !isComparable(t.Field(i).Type) { return false } } return true case reflect.Slice, reflect.Map, reflect.Func: return false } return true } // Should the supplied type be allowed as an argument to IdenticalTo? func isLegalForIdenticalTo(t reflect.Type) (bool, error) { // Allow the zero type. if t == nil { return true, nil } // Reference types are always okay; we compare pointers. switch t.Kind() { case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan: return true, nil } // Reject other non-comparable types. if !isComparable(t) { return false, errors.New(fmt.Sprintf("%v is not comparable", t)) } return true, nil } // IdenticalTo(x) returns a matcher that matches values v with type identical // to x such that: // // 1. If v and x are of a reference type (slice, map, function, channel), then // they are either both nil or are references to the same object. // // 2. Otherwise, if v and x are not of a reference type but have a valid type, // then v == x. // // If v and x are both the invalid type (which results from the predeclared nil // value, or from nil interface variables), then the matcher is satisfied. // // This function will panic if x is of a value type that is not comparable. For // example, x cannot be an array of functions. func IdenticalTo(x interface{}) Matcher { t := reflect.TypeOf(x) // Reject illegal arguments. if ok, err := isLegalForIdenticalTo(t); !ok { panic("IdenticalTo: " + err.Error()) } return &identicalToMatcher{x} } type identicalToMatcher struct { x interface{} } func (m *identicalToMatcher) Description() string { t := reflect.TypeOf(m.x) return fmt.Sprintf("identical to <%v> %v", t, m.x) } func (m *identicalToMatcher) Matches(c interface{}) error { // Make sure the candidate's type is correct. t := reflect.TypeOf(m.x) if ct := reflect.TypeOf(c); t != ct { return NewFatalError(fmt.Sprintf("which is of type %v", ct)) } // Special case: two values of the invalid type are always identical. if t == nil { return nil } // Handle reference types. switch t.Kind() { case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan: xv := reflect.ValueOf(m.x) cv := reflect.ValueOf(c) if xv.Pointer() == cv.Pointer() { return nil } return errors.New("which is not an identical reference") } // Are the values equal? if m.x == c { return nil } return errors.New("") }
// Copyright 2011 Aaron Jacobs. All Rights Reserved. // Author: aaronjjacobs@gmail.com (Aaron Jacobs) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oglematchers import ( "fmt" "reflect" ) // LessOrEqual returns a matcher that matches integer, floating point, or // strings values v such that v <= x. Comparison is not defined between numeric // and string types, but is defined between all integer and floating point // types. // // x must itself be an integer, floating point, or string type; otherwise, // LessOrEqual will panic. func LessOrEqual(x interface{}) Matcher { desc := fmt.Sprintf("less than or equal to %v", x) // Special case: make it clear that strings are strings. if reflect.TypeOf(x).Kind() == reflect.String { desc = fmt.Sprintf("less than or equal to \"%s\"", x) } // Put LessThan last so that its error messages will be used in the event of // failure. return transformDescription(AnyOf(Equals(x), LessThan(x)), desc) }
// Copyright 2011 Aaron Jacobs. All Rights Reserved. // Author: aaronjjacobs@gmail.com (Aaron Jacobs) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oglematchers import ( "errors" "fmt" "math" "reflect" ) // LessThan returns a matcher that matches integer, floating point, or strings // values v such that v < x. Comparison is not defined between numeric and // string types, but is defined between all integer and floating point types. // // x must itself be an integer, floating point, or string type; otherwise, // LessThan will panic. func LessThan(x interface{}) Matcher { v := reflect.ValueOf(x) kind := v.Kind() switch { case isInteger(v): case isFloat(v): case kind == reflect.String: default: panic(fmt.Sprintf("LessThan: unexpected kind %v", kind)) } return &lessThanMatcher{v} } type lessThanMatcher struct { limit reflect.Value } func (m *lessThanMatcher) Description() string { // Special case: make it clear that strings are strings. if m.limit.Kind() == reflect.String { return fmt.Sprintf("less than \"%s\"", m.limit.String()) } return fmt.Sprintf("less than %v", m.limit.Interface()) } func compareIntegers(v1, v2 reflect.Value) (err error) { err = errors.New("") switch { case isSignedInteger(v1) && isSignedInteger(v2): if v1.Int() < v2.Int() { err = nil } return case isSignedInteger(v1) && isUnsignedInteger(v2): if v1.Int() < 0 || uint64(v1.Int()) < v2.Uint() { err = nil } return case isUnsignedInteger(v1) && isSignedInteger(v2): if v1.Uint() <= math.MaxInt64 && int64(v1.Uint()) < v2.Int() { err = nil } return case isUnsignedInteger(v1) && isUnsignedInteger(v2): if v1.Uint() < v2.Uint() { err = nil } return } panic(fmt.Sprintf("compareIntegers: %v %v", v1, v2)) } func getFloat(v reflect.Value) float64 { switch { case isSignedInteger(v): return float64(v.Int()) case isUnsignedInteger(v): return float64(v.Uint()) case isFloat(v): return v.Float() } panic(fmt.Sprintf("getFloat: %v", v)) } func (m *lessThanMatcher) Matches(c interface{}) (err error) { v1 := reflect.ValueOf(c) v2 := m.limit err = errors.New("") // Handle strings as a special case. if v1.Kind() == reflect.String && v2.Kind() == reflect.String { if v1.String() < v2.String() { err = nil } return } // If we get here, we require that we are dealing with integers or floats. v1Legal := isInteger(v1) || isFloat(v1) v2Legal := isInteger(v2) || isFloat(v2) if !v1Legal || !v2Legal { err = NewFatalError("which is not comparable") return } // Handle the various comparison cases. switch { // Both integers case isInteger(v1) && isInteger(v2): return compareIntegers(v1, v2) // At least one float32 case v1.Kind() == reflect.Float32 || v2.Kind() == reflect.Float32: if float32(getFloat(v1)) < float32(getFloat(v2)) { err = nil } return // At least one float64 case v1.Kind() == reflect.Float64 || v2.Kind() == reflect.Float64: if getFloat(v1) < getFloat(v2) { err = nil } return } // We shouldn't get here. panic(fmt.Sprintf("lessThanMatcher.Matches: Shouldn't get here: %v %v", v1, v2)) }
// Copyright 2011 Aaron Jacobs. All Rights Reserved. // Author: aaronjjacobs@gmail.com (Aaron Jacobs) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package oglematchers provides a set of matchers useful in a testing or // mocking framework. These matchers are inspired by and mostly compatible with // Google Test for C++ and Google JS Test. // // This package is used by github.com/smartystreets/goconvey/convey/assertions/ogletest and // github.com/smartystreets/goconvey/convey/assertions/oglemock, which may be more directly useful if you're not // writing your own testing package or defining your own matchers. package oglematchers // A Matcher is some predicate implicitly defining a set of values that it // matches. For example, GreaterThan(17) matches all numeric values greater // than 17, and HasSubstr("taco") matches all strings with the substring // "taco". type Matcher interface { // Check whether the supplied value belongs to the the set defined by the // matcher. Return a non-nil error if and only if it does not. // // The error describes why the value doesn't match. The error text is a // relative clause that is suitable for being placed after the value. For // example, a predicate that matches strings with a particular substring may, // when presented with a numerical value, return the following error text: // // "which is not a string" // // Then the failure message may look like: // // Expected: has substring "taco" // Actual: 17, which is not a string // // If the error is self-apparent based on the description of the matcher, the // error text may be empty (but the error still non-nil). For example: // // Expected: 17 // Actual: 19 // // If you are implementing a new matcher, see also the documentation on // FatalError. Matches(candidate interface{}) error // Description returns a string describing the property that values matching // this matcher have, as a verb phrase where the subject is the value. For // example, "is greather than 17" or "has substring "taco"". Description() string } // FatalError is an implementation of the error interface that may be returned // from matchers, indicating the error should be propagated. Returning a // *FatalError indicates that the matcher doesn't process values of the // supplied type, or otherwise doesn't know how to handle the value. // // For example, if GreaterThan(17) returned false for the value "taco" without // a fatal error, then Not(GreaterThan(17)) would return true. This is // technically correct, but is surprising and may mask failures where the wrong // sort of matcher is accidentally used. Instead, GreaterThan(17) can return a // fatal error, which will be propagated by Not(). type FatalError struct { errorText string } // NewFatalError creates a FatalError struct with the supplied error text. func NewFatalError(s string) *FatalError { return &FatalError{s} } func (e *FatalError) Error() string { return e.errorText }
// Copyright 2011 Aaron Jacobs. All Rights Reserved. // Author: aaronjjacobs@gmail.com (Aaron Jacobs) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oglematchers import ( "errors" "fmt" "reflect" "regexp" ) // MatchesRegexp returns a matcher that matches strings and byte slices whose // contents match the supplide regular expression. The semantics are those of // regexp.Match. In particular, that means the match is not implicitly anchored // to the ends of the string: MatchesRegexp("bar") will match "foo bar baz". func MatchesRegexp(pattern string) Matcher { re, err := regexp.Compile(pattern) if err != nil { panic("MatchesRegexp: " + err.Error()) } return &matchesRegexpMatcher{re} } type matchesRegexpMatcher struct { re *regexp.Regexp } func (m *matchesRegexpMatcher) Description() string { return fmt.Sprintf("matches regexp \"%s\"", m.re.String()) } func (m *matchesRegexpMatcher) Matches(c interface{}) (err error) { v := reflect.ValueOf(c) isString := v.Kind() == reflect.String isByteSlice := v.Kind() == reflect.Slice && v.Elem().Kind() == reflect.Uint8 err = errors.New("") switch { case isString: if m.re.MatchString(v.String()) { err = nil } case isByteSlice: if m.re.Match(v.Bytes()) { err = nil } default: err = NewFatalError("which is not a string or []byte") } return }
// Copyright 2011 Aaron Jacobs. All Rights Reserved. // Author: aaronjjacobs@gmail.com (Aaron Jacobs) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oglematchers import ( "errors" "fmt" ) // Not returns a matcher that inverts the set of values matched by the wrapped // matcher. It does not transform the result for values for which the wrapped // matcher returns a fatal error. func Not(m Matcher) Matcher { return ¬Matcher{m} } type notMatcher struct { wrapped Matcher } func (m *notMatcher) Matches(c interface{}) (err error) { err = m.wrapped.Matches(c) // Did the wrapped matcher say yes? if err == nil { return errors.New("") } // Did the wrapped matcher return a fatal error? if _, isFatal := err.(*FatalError); isFatal { return err } // The wrapped matcher returned a non-fatal error. return nil } func (m *notMatcher) Description() string { return fmt.Sprintf("not(%s)", m.wrapped.Description()) }
// Copyright 2011 Aaron Jacobs. All Rights Reserved. // Author: aaronjjacobs@gmail.com (Aaron Jacobs) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oglematchers import ( "errors" "fmt" "reflect" ) // Panics matches zero-arg functions which, when invoked, panic with an error // that matches the supplied matcher. // // NOTE(jacobsa): This matcher cannot detect the case where the function panics // using panic(nil), by design of the language. See here for more info: // // http://goo.gl/9aIQL // func Panics(m Matcher) Matcher { return &panicsMatcher{m} } type panicsMatcher struct { wrappedMatcher Matcher } func (m *panicsMatcher) Description() string { return "panics with: " + m.wrappedMatcher.Description() } func (m *panicsMatcher) Matches(c interface{}) (err error) { // Make sure c is a zero-arg function. v := reflect.ValueOf(c) if v.Kind() != reflect.Func || v.Type().NumIn() != 0 { err = NewFatalError("which is not a zero-arg function") return } // Call the function and check its panic error. defer func() { if e := recover(); e != nil { err = m.wrappedMatcher.Matches(e) // Set a clearer error message if the matcher said no. if err != nil { wrappedClause := "" if err.Error() != "" { wrappedClause = ", " + err.Error() } err = errors.New(fmt.Sprintf("which panicked with: %v%s", e, wrappedClause)) } } }() v.Call([]reflect.Value{}) // If we get here, the function didn't panic. err = errors.New("which didn't panic") return }
// Copyright 2012 Aaron Jacobs. All Rights Reserved. // Author: aaronjjacobs@gmail.com (Aaron Jacobs) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oglematchers import ( "errors" "fmt" "reflect" ) // Return a matcher that matches non-nil pointers whose pointee matches the // wrapped matcher. func Pointee(m Matcher) Matcher { return &pointeeMatcher{m} } type pointeeMatcher struct { wrapped Matcher } func (m *pointeeMatcher) Matches(c interface{}) (err error) { // Make sure the candidate is of the appropriate type. cv := reflect.ValueOf(c) if !cv.IsValid() || cv.Kind() != reflect.Ptr { return NewFatalError("which is not a pointer") } // Make sure the candidate is non-nil. if cv.IsNil() { return NewFatalError("") } // Defer to the wrapped matcher. Fix up empty errors so that failure messages // are more helpful than just printing a pointer for "Actual". pointee := cv.Elem().Interface() err = m.wrapped.Matches(pointee) if err != nil && err.Error() == "" { s := fmt.Sprintf("whose pointee is %v", pointee) if _, ok := err.(*FatalError); ok { err = NewFatalError(s) } else { err = errors.New(s) } } return err } func (m *pointeeMatcher) Description() string { return fmt.Sprintf("pointee(%s)", m.wrapped.Description()) }
// Copyright 2011 Aaron Jacobs. All Rights Reserved. // Author: aaronjjacobs@gmail.com (Aaron Jacobs) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oglematchers // transformDescription returns a matcher that is equivalent to the supplied // one, except that it has the supplied description instead of the one attached // to the existing matcher. func transformDescription(m Matcher, newDesc string) Matcher { return &transformDescriptionMatcher{newDesc, m} } type transformDescriptionMatcher struct { desc string wrappedMatcher Matcher } func (m *transformDescriptionMatcher) Description() string { return m.desc } func (m *transformDescriptionMatcher) Matches(c interface{}) error { return m.wrappedMatcher.Matches(c) }