tt.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  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. import (
  6. "fmt"
  7. "reflect"
  8. "strings"
  9. "github.com/google/go-cmp/cmp"
  10. )
  11. // Table represents a test table.
  12. type Table []*Case
  13. // Case represents a test case. It is created by the C function, and offers
  14. // setters that augment and return itself; those calls can be chained like
  15. // C(...).Rets(...).
  16. type Case struct {
  17. args []interface{}
  18. retsMatchers [][]interface{}
  19. }
  20. // Args returns a new Case with the given arguments.
  21. func Args(args ...interface{}) *Case {
  22. return &Case{args: args}
  23. }
  24. // Rets modifies the test case so that it requires the return values to match
  25. // the given values. It returns the receiver. The arguments may implement the
  26. // Matcher interface, in which case its Match method is called with the actual
  27. // return value. Otherwise, reflect.DeepEqual is used to determine matches.
  28. func (c *Case) Rets(matchers ...interface{}) *Case {
  29. c.retsMatchers = append(c.retsMatchers, matchers)
  30. return c
  31. }
  32. // FnToTest describes a function to test.
  33. type FnToTest struct {
  34. name string
  35. body interface{}
  36. argsFmt string
  37. retsFmt string
  38. }
  39. // Fn makes a new FnToTest with the given function name and body.
  40. func Fn(name string, body interface{}) *FnToTest {
  41. return &FnToTest{name: name, body: body}
  42. }
  43. // ArgsFmt sets the string for formatting arguments in test error messages, and
  44. // return fn itself.
  45. func (fn *FnToTest) ArgsFmt(s string) *FnToTest {
  46. fn.argsFmt = s
  47. return fn
  48. }
  49. // RetsFmt sets the string for formatting return values in test error messages,
  50. // and return fn itself.
  51. func (fn *FnToTest) RetsFmt(s string) *FnToTest {
  52. fn.retsFmt = s
  53. return fn
  54. }
  55. // T is the interface for accessing testing.T.
  56. type T interface {
  57. Helper()
  58. Errorf(format string, args ...interface{})
  59. }
  60. // Test tests a function against test cases.
  61. func Test(t T, fn *FnToTest, tests Table) {
  62. t.Helper()
  63. for _, test := range tests {
  64. rets := call(fn.body, test.args)
  65. for _, retsMatcher := range test.retsMatchers {
  66. if !match(retsMatcher, rets) {
  67. var args string
  68. if fn.argsFmt == "" {
  69. args = sprintArgs(test.args...)
  70. } else {
  71. args = fmt.Sprintf(fn.argsFmt, test.args...)
  72. }
  73. var diff string
  74. if len(retsMatcher) == 1 && len(rets) == 1 {
  75. diff = cmp.Diff(retsMatcher[0], rets[0], cmpopt)
  76. } else {
  77. diff = cmp.Diff(retsMatcher, rets, cmpopt)
  78. }
  79. t.Errorf("%s(%s) returns (-want +got):\n%s", fn.name, args, diff)
  80. }
  81. }
  82. }
  83. }
  84. // RetValue is an empty interface used in the Matcher interface.
  85. type RetValue interface{}
  86. // Matcher wraps the Match method.
  87. type Matcher interface {
  88. // Match reports whether a return value is considered a match. The argument
  89. // is of type RetValue so that it cannot be implemented accidentally.
  90. Match(RetValue) bool
  91. }
  92. // Any is a Matcher that matches any value.
  93. var Any Matcher = anyMatcher{}
  94. type anyMatcher struct{}
  95. func (anyMatcher) Match(RetValue) bool { return true }
  96. func match(matchers, actual []interface{}) bool {
  97. for i, matcher := range matchers {
  98. if !matchOne(matcher, actual[i]) {
  99. return false
  100. }
  101. }
  102. return true
  103. }
  104. func matchOne(m, a interface{}) bool {
  105. if m, ok := m.(Matcher); ok {
  106. return m.Match(a)
  107. }
  108. return reflect.DeepEqual(m, a)
  109. }
  110. func sprintArgs(args ...interface{}) string {
  111. var b strings.Builder
  112. for i, arg := range args {
  113. if i > 0 {
  114. b.WriteString(", ")
  115. }
  116. fmt.Fprint(&b, arg)
  117. }
  118. return b.String()
  119. }
  120. func call(fn interface{}, args []interface{}) []interface{} {
  121. argsReflect := make([]reflect.Value, len(args))
  122. for i, arg := range args {
  123. if arg == nil {
  124. // reflect.ValueOf(nil) returns a zero Value, but this is not what
  125. // we want. Work around this by taking the ValueOf a pointer to nil
  126. // and then get the Elem.
  127. // TODO(xiaq): This is now always using a nil value with type
  128. // interface{}. For more usability, inspect the type of fn to see
  129. // which type of nil this argument should be.
  130. var v interface{}
  131. argsReflect[i] = reflect.ValueOf(&v).Elem()
  132. } else {
  133. argsReflect[i] = reflect.ValueOf(arg)
  134. }
  135. }
  136. retsReflect := reflect.ValueOf(fn).Call(argsReflect)
  137. rets := make([]interface{}, len(retsReflect))
  138. for i, retReflect := range retsReflect {
  139. rets[i] = retReflect.Interface()
  140. }
  141. return rets
  142. }