// Package tt supports table-driven tests with little boilerplate. // // See the test case for this package for example usage.
package tt import ( ) // Table represents a test table. type Table []*Case // Case represents a test case. It is created by the C function, and offers // setters that augment and return itself; those calls can be chained like // C(...).Rets(...). type Case struct { args []interface{} retsMatchers [][]interface{} } // Args returns a new Case with the given arguments. func ( ...interface{}) *Case { return &Case{args: } } // Rets modifies the test case so that it requires the return values to match // the given values. It returns the receiver. The arguments may implement the // Matcher interface, in which case its Match method is called with the actual // return value. Otherwise, reflect.DeepEqual is used to determine matches. func ( *Case) ( ...interface{}) *Case { .retsMatchers = append(.retsMatchers, ) return } // FnToTest describes a function to test. type FnToTest struct { name string body interface{} argsFmt string retsFmt string } // Fn makes a new FnToTest with the given function name and body. func ( string, interface{}) *FnToTest { return &FnToTest{name: , body: } } // ArgsFmt sets the string for formatting arguments in test error messages, and // return fn itself. func ( *FnToTest) ( string) *FnToTest { .argsFmt = return } // RetsFmt sets the string for formatting return values in test error messages, // and return fn itself. func ( *FnToTest) ( string) *FnToTest { .retsFmt = return } // T is the interface for accessing testing.T. type T interface { Helper() Errorf(format string, args ...interface{}) } // Test tests a function against test cases. func ( T, *FnToTest, Table) { .Helper() for , := range { := call(.body, .args) for , := range .retsMatchers { if !match(, ) { var , , string if .argsFmt == "" { = sprintArgs(.args...) } else { = fmt.Sprintf(.argsFmt, .args...) } if .retsFmt == "" { = sprintRets(...) = sprintRets(...) } else { = fmt.Sprintf(.retsFmt, ...) = fmt.Sprintf(.retsFmt, ...) } .Errorf("%s(%s) -> %s, want %s", .name, , , ) } } } } // RetValue is an empty interface used in the Matcher interface. type RetValue interface{} // Matcher wraps the Match method. type Matcher interface { // Match reports whether a return value is considered a match. The argument // is of type RetValue so that it cannot be implemented accidentally. Match(RetValue) bool } // Any is a Matcher that matches any value. var Any Matcher = anyMatcher{} type anyMatcher struct{} func (anyMatcher) (RetValue) bool { return true } func (, []interface{}) bool { for , := range { if !matchOne(, []) { return false } } return true } func (, interface{}) bool { if , := .(Matcher); { return .Match() } return reflect.DeepEqual(, ) } func ( ...interface{}) string { return sprintCommaDelimited(...) } func ( ...interface{}) string { if len() == 1 { return fmt.Sprint([0]) } return "(" + sprintCommaDelimited(...) + ")" } func ( ...interface{}) string { var bytes.Buffer for , := range { if > 0 { .WriteString(", ") } fmt.Fprint(&, ) } return .String() } func ( interface{}, []interface{}) []interface{} { := make([]reflect.Value, len()) for , := range { if == nil { // reflect.ValueOf(nil) returns a zero Value, but this is not what // we want. Work around this by taking the ValueOf a pointer to nil // and then get the Elem. // TODO(xiaq): This is now always using a nil value with type // interface{}. For more usability, inspect the type of fn to see // which type of nil this argument should be. var interface{} [] = reflect.ValueOf(&).Elem() } else { [] = reflect.ValueOf() } } := reflect.ValueOf().Call() := make([]interface{}, len()) for , := range { [] = .Interface() } return }