github.com/zaolin/u-root@v0.0.0-20200428085104-64aaafd46c6d/cmds/core/elvish/tt/tt.go (about) 1 // Package tt supports table-driven tests with little boilerplate. 2 // 3 // See the test case for this package for example usage. 4 package tt 5 6 import ( 7 "bytes" 8 "fmt" 9 "reflect" 10 ) 11 12 // Table represents a test table. 13 type Table []*Case 14 15 // Case represents a test case. It is created by the C function, and offers 16 // setters that augment and return itself; those calls can be chained like 17 // C(...).Rets(...). 18 type Case struct { 19 args []interface{} 20 retsMatchers [][]interface{} 21 } 22 23 // Args returns a new Case with the given arguments. 24 func Args(args ...interface{}) *Case { 25 return &Case{args: args} 26 } 27 28 // Rets modifies the test case so that it requires the return values to match 29 // the given values. It returns the receiver. The arguments may implement the 30 // Matcher interface, in which case its Match method is called with the actual 31 // return value. Otherwise, reflect.DeepEqual is used to determine matches. 32 func (c *Case) Rets(matchers ...interface{}) *Case { 33 c.retsMatchers = append(c.retsMatchers, matchers) 34 return c 35 } 36 37 // FnToTest describes a function to test. 38 type FnToTest struct { 39 name string 40 body interface{} 41 argsFmt string 42 retsFmt string 43 } 44 45 // Fn makes a new FnToTest with the given function name and body. 46 func Fn(name string, body interface{}) *FnToTest { 47 return &FnToTest{name: name, body: body} 48 } 49 50 // ArgsFmt sets the string for formatting arguments in test error messages, and 51 // return fn itself. 52 func (fn *FnToTest) ArgsFmt(s string) *FnToTest { 53 fn.argsFmt = s 54 return fn 55 } 56 57 // RetsFmt sets the string for formatting return values in test error messages, 58 // and return fn itself. 59 func (fn *FnToTest) RetsFmt(s string) *FnToTest { 60 fn.retsFmt = s 61 return fn 62 } 63 64 // T is the interface for accessing testing.T. 65 type T interface { 66 Errorf(format string, args ...interface{}) 67 } 68 69 // Test tests a function against test cases. 70 func Test(t T, fn *FnToTest, tests Table) { 71 for _, test := range tests { 72 rets := call(fn.body, test.args) 73 for _, retsMatcher := range test.retsMatchers { 74 if !match(retsMatcher, rets) { 75 var argsString, retsString, wantRetsString string 76 if fn.argsFmt == "" { 77 argsString = sprintArgs(test.args...) 78 } else { 79 argsString = fmt.Sprintf(fn.argsFmt, test.args...) 80 } 81 if fn.retsFmt == "" { 82 retsString = sprintRets(rets...) 83 wantRetsString = sprintRets(retsMatcher...) 84 } else { 85 retsString = fmt.Sprintf(fn.retsFmt, rets...) 86 wantRetsString = fmt.Sprintf(fn.retsFmt, retsMatcher...) 87 } 88 t.Errorf("%s(%s) -> %s, want %s", fn.name, argsString, retsString, wantRetsString) 89 } 90 } 91 } 92 } 93 94 // RetValue is an empty interface used in the Matcher interface. 95 type RetValue interface{} 96 97 // Matcher wraps the Match method. 98 type Matcher interface { 99 // Match reports whether a return value is considered a match. The argument 100 // is of type RetValue so that it cannot be implemented accidentally. 101 Match(RetValue) bool 102 } 103 104 func match(matchers, actual []interface{}) bool { 105 for i, matcher := range matchers { 106 if !matchOne(matcher, actual[i]) { 107 return false 108 } 109 } 110 return true 111 } 112 113 func matchOne(m, a interface{}) bool { 114 if m, ok := m.(Matcher); ok { 115 return m.Match(a) 116 } 117 return reflect.DeepEqual(m, a) 118 } 119 120 func sprintArgs(args ...interface{}) string { 121 return sprintCommaDelimited(args...) 122 } 123 124 func sprintRets(rets ...interface{}) string { 125 if len(rets) == 1 { 126 return fmt.Sprint(rets[0]) 127 } 128 return "(" + sprintCommaDelimited(rets...) + ")" 129 } 130 131 func sprintCommaDelimited(args ...interface{}) string { 132 var b bytes.Buffer 133 for i, arg := range args { 134 if i > 0 { 135 b.WriteString(", ") 136 } 137 fmt.Fprint(&b, arg) 138 } 139 return b.String() 140 } 141 142 func call(fn interface{}, args []interface{}) []interface{} { 143 argsReflect := make([]reflect.Value, len(args)) 144 for i, arg := range args { 145 argsReflect[i] = reflect.ValueOf(arg) 146 } 147 retsReflect := reflect.ValueOf(fn).Call(argsReflect) 148 rets := make([]interface{}, len(retsReflect)) 149 for i, retReflect := range retsReflect { 150 rets[i] = retReflect.Interface() 151 } 152 return rets 153 }