suite.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. package suite
  2. import (
  3. "flag"
  4. "fmt"
  5. "os"
  6. "reflect"
  7. "regexp"
  8. "runtime/debug"
  9. "sync"
  10. "testing"
  11. "time"
  12. "github.com/stretchr/testify/assert"
  13. "github.com/stretchr/testify/require"
  14. )
  15. var allTestsFilter = func(_, _ string) (bool, error) { return true, nil }
  16. var matchMethod = flag.String("testify.m", "", "regular expression to select tests of the testify suite to run")
  17. // Suite is a basic testing suite with methods for storing and
  18. // retrieving the current *testing.T context.
  19. type Suite struct {
  20. *assert.Assertions
  21. mu sync.RWMutex
  22. require *require.Assertions
  23. t *testing.T
  24. // Parent suite to have access to the implemented methods of parent struct
  25. s TestingSuite
  26. }
  27. // T retrieves the current *testing.T context.
  28. func (suite *Suite) T() *testing.T {
  29. suite.mu.RLock()
  30. defer suite.mu.RUnlock()
  31. return suite.t
  32. }
  33. // SetT sets the current *testing.T context.
  34. func (suite *Suite) SetT(t *testing.T) {
  35. suite.mu.Lock()
  36. defer suite.mu.Unlock()
  37. suite.t = t
  38. suite.Assertions = assert.New(t)
  39. suite.require = require.New(t)
  40. }
  41. // SetS needs to set the current test suite as parent
  42. // to get access to the parent methods
  43. func (suite *Suite) SetS(s TestingSuite) {
  44. suite.s = s
  45. }
  46. // Require returns a require context for suite.
  47. func (suite *Suite) Require() *require.Assertions {
  48. suite.mu.Lock()
  49. defer suite.mu.Unlock()
  50. if suite.require == nil {
  51. panic("'Require' must not be called before 'Run' or 'SetT'")
  52. }
  53. return suite.require
  54. }
  55. // Assert returns an assert context for suite. Normally, you can call
  56. // `suite.NoError(expected, actual)`, but for situations where the embedded
  57. // methods are overridden (for example, you might want to override
  58. // assert.Assertions with require.Assertions), this method is provided so you
  59. // can call `suite.Assert().NoError()`.
  60. func (suite *Suite) Assert() *assert.Assertions {
  61. suite.mu.Lock()
  62. defer suite.mu.Unlock()
  63. if suite.Assertions == nil {
  64. panic("'Assert' must not be called before 'Run' or 'SetT'")
  65. }
  66. return suite.Assertions
  67. }
  68. func recoverAndFailOnPanic(t *testing.T) {
  69. t.Helper()
  70. r := recover()
  71. failOnPanic(t, r)
  72. }
  73. func failOnPanic(t *testing.T, r interface{}) {
  74. t.Helper()
  75. if r != nil {
  76. t.Errorf("test panicked: %v\n%s", r, debug.Stack())
  77. t.FailNow()
  78. }
  79. }
  80. // Run provides suite functionality around golang subtests. It should be
  81. // called in place of t.Run(name, func(t *testing.T)) in test suite code.
  82. // The passed-in func will be executed as a subtest with a fresh instance of t.
  83. // Provides compatibility with go test pkg -run TestSuite/TestName/SubTestName.
  84. func (suite *Suite) Run(name string, subtest func()) bool {
  85. oldT := suite.T()
  86. return oldT.Run(name, func(t *testing.T) {
  87. suite.SetT(t)
  88. defer suite.SetT(oldT)
  89. defer recoverAndFailOnPanic(t)
  90. if setupSubTest, ok := suite.s.(SetupSubTest); ok {
  91. setupSubTest.SetupSubTest()
  92. }
  93. if tearDownSubTest, ok := suite.s.(TearDownSubTest); ok {
  94. defer tearDownSubTest.TearDownSubTest()
  95. }
  96. subtest()
  97. })
  98. }
  99. // Run takes a testing suite and runs all of the tests attached
  100. // to it.
  101. func Run(t *testing.T, suite TestingSuite) {
  102. defer recoverAndFailOnPanic(t)
  103. suite.SetT(t)
  104. suite.SetS(suite)
  105. var suiteSetupDone bool
  106. var stats *SuiteInformation
  107. if _, ok := suite.(WithStats); ok {
  108. stats = newSuiteInformation()
  109. }
  110. tests := []testing.InternalTest{}
  111. methodFinder := reflect.TypeOf(suite)
  112. suiteName := methodFinder.Elem().Name()
  113. for i := 0; i < methodFinder.NumMethod(); i++ {
  114. method := methodFinder.Method(i)
  115. ok, err := methodFilter(method.Name)
  116. if err != nil {
  117. fmt.Fprintf(os.Stderr, "testify: invalid regexp for -m: %s\n", err)
  118. os.Exit(1)
  119. }
  120. if !ok {
  121. continue
  122. }
  123. if !suiteSetupDone {
  124. if stats != nil {
  125. stats.Start = time.Now()
  126. }
  127. if setupAllSuite, ok := suite.(SetupAllSuite); ok {
  128. setupAllSuite.SetupSuite()
  129. }
  130. suiteSetupDone = true
  131. }
  132. test := testing.InternalTest{
  133. Name: method.Name,
  134. F: func(t *testing.T) {
  135. parentT := suite.T()
  136. suite.SetT(t)
  137. defer recoverAndFailOnPanic(t)
  138. defer func() {
  139. t.Helper()
  140. r := recover()
  141. if stats != nil {
  142. passed := !t.Failed() && r == nil
  143. stats.end(method.Name, passed)
  144. }
  145. if afterTestSuite, ok := suite.(AfterTest); ok {
  146. afterTestSuite.AfterTest(suiteName, method.Name)
  147. }
  148. if tearDownTestSuite, ok := suite.(TearDownTestSuite); ok {
  149. tearDownTestSuite.TearDownTest()
  150. }
  151. suite.SetT(parentT)
  152. failOnPanic(t, r)
  153. }()
  154. if setupTestSuite, ok := suite.(SetupTestSuite); ok {
  155. setupTestSuite.SetupTest()
  156. }
  157. if beforeTestSuite, ok := suite.(BeforeTest); ok {
  158. beforeTestSuite.BeforeTest(methodFinder.Elem().Name(), method.Name)
  159. }
  160. if stats != nil {
  161. stats.start(method.Name)
  162. }
  163. method.Func.Call([]reflect.Value{reflect.ValueOf(suite)})
  164. },
  165. }
  166. tests = append(tests, test)
  167. }
  168. if suiteSetupDone {
  169. defer func() {
  170. if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok {
  171. tearDownAllSuite.TearDownSuite()
  172. }
  173. if suiteWithStats, measureStats := suite.(WithStats); measureStats {
  174. stats.End = time.Now()
  175. suiteWithStats.HandleStats(suiteName, stats)
  176. }
  177. }()
  178. }
  179. runTests(t, tests)
  180. }
  181. // Filtering method according to set regular expression
  182. // specified command-line argument -m
  183. func methodFilter(name string) (bool, error) {
  184. if ok, _ := regexp.MatchString("^Test", name); !ok {
  185. return false, nil
  186. }
  187. return regexp.MatchString(*matchMethod, name)
  188. }
  189. func runTests(t testing.TB, tests []testing.InternalTest) {
  190. if len(tests) == 0 {
  191. t.Log("warning: no tests to run")
  192. return
  193. }
  194. r, ok := t.(runner)
  195. if !ok { // backwards compatibility with Go 1.6 and below
  196. if !testing.RunTests(allTestsFilter, tests) {
  197. t.Fail()
  198. }
  199. return
  200. }
  201. for _, test := range tests {
  202. r.Run(test.Name, test.F)
  203. }
  204. }
  205. type runner interface {
  206. Run(name string, f func(t *testing.T)) bool
  207. }