package check import ( "fmt" "reflect" "regexp" "strings" "github.com/kr/pretty" ) // ----------------------------------------------------------------------- // CommentInterface and Commentf helper, to attach extra information to checks. type comment struct { format string args []interface{} } // Commentf returns an infomational value to use with Assert or Check calls. // If the checker test fails, the provided arguments will be passed to // fmt.Sprintf, and will be presented next to the logged failure. // // For example: // // c.Assert(v, Equals, 42, Commentf("Iteration #%d failed.", i)) // // Note that if the comment is constant, a better option is to // simply use a normal comment right above or next to the line, as // it will also get printed with any errors: // // c.Assert(l, Equals, 8192) // Ensure buffer size is correct (bug #123) // func Commentf(format string, args ...interface{}) CommentInterface { return &comment{format, args} } // CommentInterface must be implemented by types that attach extra // information to failed checks. See the Commentf function for details. type CommentInterface interface { CheckCommentString() string } func (c *comment) CheckCommentString() string { return fmt.Sprintf(c.format, c.args...) } // ----------------------------------------------------------------------- // The Checker interface. // The Checker interface must be provided by checkers used with // the Assert and Check verification methods. type Checker interface { Info() *CheckerInfo Check(params []interface{}, names []string) (result bool, error string) } // See the Checker interface. type CheckerInfo struct { Name string Params []string } func (info *CheckerInfo) Info() *CheckerInfo { return info } // ----------------------------------------------------------------------- // Not checker logic inverter. // The Not checker inverts the logic of the provided checker. The // resulting checker will succeed where the original one failed, and // vice-versa. // // For example: // // c.Assert(a, Not(Equals), b) // func Not(checker Checker) Checker { return ¬Checker{checker} } type notChecker struct { sub Checker } func (checker *notChecker) Info() *CheckerInfo { info := *checker.sub.Info() info.Name = "Not(" + info.Name + ")" return &info } func (checker *notChecker) Check(params []interface{}, names []string) (result bool, error string) { result, error = checker.sub.Check(params, names) result = !result if result { // clear error message if the new result is true error = "" } return } // ----------------------------------------------------------------------- // IsNil checker. type isNilChecker struct { *CheckerInfo } // The IsNil checker tests whether the obtained value is nil. // // For example: // // c.Assert(err, IsNil) // var IsNil Checker = &isNilChecker{ &CheckerInfo{Name: "IsNil", Params: []string{"value"}}, } func (checker *isNilChecker) Check(params []interface{}, names []string) (result bool, error string) { return isNil(params[0]), "" } func isNil(obtained interface{}) (result bool) { if obtained == nil { result = true } else { switch v := reflect.ValueOf(obtained); v.Kind() { case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: return v.IsNil() } } return } // ----------------------------------------------------------------------- // NotNil checker. Alias for Not(IsNil), since it's so common. type notNilChecker struct { *CheckerInfo } // The NotNil checker verifies that the obtained value is not nil. // // For example: // // c.Assert(iface, NotNil) // // This is an alias for Not(IsNil), made available since it's a // fairly common check. // var NotNil Checker = ¬NilChecker{ &CheckerInfo{Name: "NotNil", Params: []string{"value"}}, } func (checker *notNilChecker) Check(params []interface{}, names []string) (result bool, error string) { return !isNil(params[0]), "" } // ----------------------------------------------------------------------- // Equals checker. func diffworthy(a interface{}) bool { t := reflect.TypeOf(a) switch t.Kind() { case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct, reflect.String, reflect.Ptr: return true } return false } // formatUnequal will dump the actual and expected values into a textual // representation and return an error message containing a diff. func formatUnequal(obtained interface{}, expected interface{}) string { // We do not do diffs for basic types because go-check already // shows them very cleanly. if !diffworthy(obtained) || !diffworthy(expected) { return "" } // Handle strings, short strings are ignored (go-check formats // them very nicely already). We do multi-line strings by // generating two string slices and using kr.Diff to compare // those (kr.Diff does not do string diffs by itself). aStr, aOK := obtained.(string) bStr, bOK := expected.(string) if aOK && bOK { l1 := strings.Split(aStr, "\n") l2 := strings.Split(bStr, "\n") // the "2" here is a bit arbitrary if len(l1) > 2 && len(l2) > 2 { diff := pretty.Diff(l1, l2) return fmt.Sprintf(`String difference: %s`, formatMultiLine(strings.Join(diff, "\n"), false)) } // string too short return "" } // generic diff diff := pretty.Diff(obtained, expected) if len(diff) == 0 { // No diff, this happens when e.g. just struct // pointers are different but the structs have // identical values. return "" } return fmt.Sprintf(`Difference: %s`, formatMultiLine(strings.Join(diff, "\n"), false)) } type equalsChecker struct { *CheckerInfo } // The Equals checker verifies that the obtained value is equal to // the expected value, according to usual Go semantics for ==. // // For example: // // c.Assert(value, Equals, 42) // var Equals Checker = &equalsChecker{ &CheckerInfo{Name: "Equals", Params: []string{"obtained", "expected"}}, } func (checker *equalsChecker) Check(params []interface{}, names []string) (result bool, error string) { defer func() { if v := recover(); v != nil { result = false error = fmt.Sprint(v) } }() result = params[0] == params[1] if !result { error = formatUnequal(params[0], params[1]) } return } // ----------------------------------------------------------------------- // DeepEquals checker. type deepEqualsChecker struct { *CheckerInfo } // The DeepEquals checker verifies that the obtained value is deep-equal to // the expected value. The check will work correctly even when facing // slices, interfaces, and values of different types (which always fail // the test). // // For example: // // c.Assert(value, DeepEquals, 42) // c.Assert(array, DeepEquals, []string{"hi", "there"}) // var DeepEquals Checker = &deepEqualsChecker{ &CheckerInfo{Name: "DeepEquals", Params: []string{"obtained", "expected"}}, } func (checker *deepEqualsChecker) Check(params []interface{}, names []string) (result bool, error string) { result = reflect.DeepEqual(params[0], params[1]) if !result { error = formatUnequal(params[0], params[1]) } return } // ----------------------------------------------------------------------- // HasLen checker. type hasLenChecker struct { *CheckerInfo } // The HasLen checker verifies that the obtained value has the // provided length. In many cases this is superior to using Equals // in conjunction with the len function because in case the check // fails the value itself will be printed, instead of its length, // providing more details for figuring the problem. // // For example: // // c.Assert(list, HasLen, 5) // var HasLen Checker = &hasLenChecker{ &CheckerInfo{Name: "HasLen", Params: []string{"obtained", "n"}}, } func (checker *hasLenChecker) Check(params []interface{}, names []string) (result bool, error string) { n, ok := params[1].(int) if !ok { return false, "n must be an int" } value := reflect.ValueOf(params[0]) switch value.Kind() { case reflect.Map, reflect.Array, reflect.Slice, reflect.Chan, reflect.String: default: return false, "obtained value type has no length" } return value.Len() == n, "" } // ----------------------------------------------------------------------- // ErrorMatches checker. type errorMatchesChecker struct { *CheckerInfo } // The ErrorMatches checker verifies that the error value // is non nil and matches the regular expression provided. // // For example: // // c.Assert(err, ErrorMatches, "perm.*denied") // var ErrorMatches Checker = errorMatchesChecker{ &CheckerInfo{Name: "ErrorMatches", Params: []string{"value", "regex"}}, } func (checker errorMatchesChecker) Check(params []interface{}, names []string) (result bool, errStr string) { if params[0] == nil { return false, "Error value is nil" } err, ok := params[0].(error) if !ok { return false, "Value is not an error" } params[0] = err.Error() names[0] = "error" return matches(params[0], params[1]) } // ----------------------------------------------------------------------- // Matches checker. type matchesChecker struct { *CheckerInfo } // The Matches checker verifies that the string provided as the obtained // value (or the string resulting from obtained.String()) matches the // regular expression provided. // // For example: // // c.Assert(err, Matches, "perm.*denied") // var Matches Checker = &matchesChecker{ &CheckerInfo{Name: "Matches", Params: []string{"value", "regex"}}, } func (checker *matchesChecker) Check(params []interface{}, names []string) (result bool, error string) { return matches(params[0], params[1]) } func matches(value, regex interface{}) (result bool, error string) { reStr, ok := regex.(string) if !ok { return false, "Regex must be a string" } valueStr, valueIsStr := value.(string) if !valueIsStr { if valueWithStr, valueHasStr := value.(fmt.Stringer); valueHasStr { valueStr, valueIsStr = valueWithStr.String(), true } } if valueIsStr { matches, err := regexp.MatchString("^"+reStr+"$", valueStr) if err != nil { return false, "Can't compile regex: " + err.Error() } return matches, "" } return false, "Obtained value is not a string and has no .String()" } // ----------------------------------------------------------------------- // Panics checker. type panicsChecker struct { *CheckerInfo } // The Panics checker verifies that calling the provided zero-argument // function will cause a panic which is deep-equal to the provided value. // // For example: // // c.Assert(func() { f(1, 2) }, Panics, &SomeErrorType{"BOOM"}). // // var Panics Checker = &panicsChecker{ &CheckerInfo{Name: "Panics", Params: []string{"function", "expected"}}, } func (checker *panicsChecker) Check(params []interface{}, names []string) (result bool, error string) { f := reflect.ValueOf(params[0]) if f.Kind() != reflect.Func || f.Type().NumIn() != 0 { return false, "Function must take zero arguments" } defer func() { // If the function has not panicked, then don't do the check. if error != "" { return } params[0] = recover() names[0] = "panic" result = reflect.DeepEqual(params[0], params[1]) }() f.Call(nil) return false, "Function has not panicked" } type panicMatchesChecker struct { *CheckerInfo } // The PanicMatches checker verifies that calling the provided zero-argument // function will cause a panic with an error value matching // the regular expression provided. // // For example: // // c.Assert(func() { f(1, 2) }, PanicMatches, `open.*: no such file or directory`). // // var PanicMatches Checker = &panicMatchesChecker{ &CheckerInfo{Name: "PanicMatches", Params: []string{"function", "expected"}}, } func (checker *panicMatchesChecker) Check(params []interface{}, names []string) (result bool, errmsg string) { f := reflect.ValueOf(params[0]) if f.Kind() != reflect.Func || f.Type().NumIn() != 0 { return false, "Function must take zero arguments" } defer func() { // If the function has not panicked, then don't do the check. if errmsg != "" { return } obtained := recover() names[0] = "panic" if e, ok := obtained.(error); ok { params[0] = e.Error() } else if _, ok := obtained.(string); ok { params[0] = obtained } else { errmsg = "Panic value is not a string or an error" return } result, errmsg = matches(params[0], params[1]) }() f.Call(nil) return false, "Function has not panicked" } // ----------------------------------------------------------------------- // FitsTypeOf checker. type fitsTypeChecker struct { *CheckerInfo } // The FitsTypeOf checker verifies that the obtained value is // assignable to a variable with the same type as the provided // sample value. // // For example: // // c.Assert(value, FitsTypeOf, int64(0)) // c.Assert(value, FitsTypeOf, os.Error(nil)) // var FitsTypeOf Checker = &fitsTypeChecker{ &CheckerInfo{Name: "FitsTypeOf", Params: []string{"obtained", "sample"}}, } func (checker *fitsTypeChecker) Check(params []interface{}, names []string) (result bool, error string) { obtained := reflect.ValueOf(params[0]) sample := reflect.ValueOf(params[1]) if !obtained.IsValid() { return false, "" } if !sample.IsValid() { return false, "Invalid sample value" } return obtained.Type().AssignableTo(sample.Type()), "" } // ----------------------------------------------------------------------- // Implements checker. type implementsChecker struct { *CheckerInfo } // The Implements checker verifies that the obtained value // implements the interface specified via a pointer to an interface // variable. // // For example: // // var e os.Error // c.Assert(err, Implements, &e) // var Implements Checker = &implementsChecker{ &CheckerInfo{Name: "Implements", Params: []string{"obtained", "ifaceptr"}}, } func (checker *implementsChecker) Check(params []interface{}, names []string) (result bool, error string) { obtained := reflect.ValueOf(params[0]) ifaceptr := reflect.ValueOf(params[1]) if !obtained.IsValid() { return false, "" } if !ifaceptr.IsValid() || ifaceptr.Kind() != reflect.Ptr || ifaceptr.Elem().Kind() != reflect.Interface { return false, "ifaceptr should be a pointer to an interface variable" } return obtained.Type().Implements(ifaceptr.Elem().Type()), "" }