1 package suite
2
3 import (
4 "flag"
5 "fmt"
6 "os"
7 "reflect"
8 "regexp"
9 "runtime/debug"
10 "sync"
11 "testing"
12 "time"
13
14 "github.com/stretchr/testify/assert"
15 "github.com/stretchr/testify/require"
16 )
17
18 var allTestsFilter = func(_, _ string) (bool, error) { return true, nil }
19 var matchMethod = flag.String("testify.m", "", "regular expression to select tests of the testify suite to run")
20
21
22
23 type Suite struct {
24 *assert.Assertions
25
26 mu sync.RWMutex
27 require *require.Assertions
28 t *testing.T
29
30
31 s TestingSuite
32 }
33
34
35 func (suite *Suite) T() *testing.T {
36 suite.mu.RLock()
37 defer suite.mu.RUnlock()
38 return suite.t
39 }
40
41
42 func (suite *Suite) SetT(t *testing.T) {
43 suite.mu.Lock()
44 defer suite.mu.Unlock()
45 suite.t = t
46 suite.Assertions = assert.New(t)
47 suite.require = require.New(t)
48 }
49
50
51
52 func (suite *Suite) SetS(s TestingSuite) {
53 suite.s = s
54 }
55
56
57 func (suite *Suite) Require() *require.Assertions {
58 suite.mu.Lock()
59 defer suite.mu.Unlock()
60 if suite.require == nil {
61 panic("'Require' must not be called before 'Run' or 'SetT'")
62 }
63 return suite.require
64 }
65
66
67
68
69
70
71 func (suite *Suite) Assert() *assert.Assertions {
72 suite.mu.Lock()
73 defer suite.mu.Unlock()
74 if suite.Assertions == nil {
75 panic("'Assert' must not be called before 'Run' or 'SetT'")
76 }
77 return suite.Assertions
78 }
79
80 func recoverAndFailOnPanic(t *testing.T) {
81 t.Helper()
82 r := recover()
83 failOnPanic(t, r)
84 }
85
86 func failOnPanic(t *testing.T, r interface{}) {
87 t.Helper()
88 if r != nil {
89 t.Errorf("test panicked: %v\n%s", r, debug.Stack())
90 t.FailNow()
91 }
92 }
93
94
95
96
97
98 func (suite *Suite) Run(name string, subtest func()) bool {
99 oldT := suite.T()
100
101 return oldT.Run(name, func(t *testing.T) {
102 suite.SetT(t)
103 defer suite.SetT(oldT)
104
105 defer recoverAndFailOnPanic(t)
106
107 if setupSubTest, ok := suite.s.(SetupSubTest); ok {
108 setupSubTest.SetupSubTest()
109 }
110
111 if tearDownSubTest, ok := suite.s.(TearDownSubTest); ok {
112 defer tearDownSubTest.TearDownSubTest()
113 }
114
115 subtest()
116 })
117 }
118
119
120
121 func Run(t *testing.T, suite TestingSuite) {
122 defer recoverAndFailOnPanic(t)
123
124 suite.SetT(t)
125 suite.SetS(suite)
126
127 var suiteSetupDone bool
128
129 var stats *SuiteInformation
130 if _, ok := suite.(WithStats); ok {
131 stats = newSuiteInformation()
132 }
133
134 tests := []testing.InternalTest{}
135 methodFinder := reflect.TypeOf(suite)
136 suiteName := methodFinder.Elem().Name()
137
138 for i := 0; i < methodFinder.NumMethod(); i++ {
139 method := methodFinder.Method(i)
140
141 ok, err := methodFilter(method.Name)
142 if err != nil {
143 fmt.Fprintf(os.Stderr, "testify: invalid regexp for -m: %s\n", err)
144 os.Exit(1)
145 }
146
147 if !ok {
148 continue
149 }
150
151 if !suiteSetupDone {
152 if stats != nil {
153 stats.Start = time.Now()
154 }
155
156 if setupAllSuite, ok := suite.(SetupAllSuite); ok {
157 setupAllSuite.SetupSuite()
158 }
159
160 suiteSetupDone = true
161 }
162
163 test := testing.InternalTest{
164 Name: method.Name,
165 F: func(t *testing.T) {
166 parentT := suite.T()
167 suite.SetT(t)
168 defer recoverAndFailOnPanic(t)
169 defer func() {
170 t.Helper()
171
172 r := recover()
173
174 if stats != nil {
175 passed := !t.Failed() && r == nil
176 stats.end(method.Name, passed)
177 }
178
179 if afterTestSuite, ok := suite.(AfterTest); ok {
180 afterTestSuite.AfterTest(suiteName, method.Name)
181 }
182
183 if tearDownTestSuite, ok := suite.(TearDownTestSuite); ok {
184 tearDownTestSuite.TearDownTest()
185 }
186
187 suite.SetT(parentT)
188 failOnPanic(t, r)
189 }()
190
191 if setupTestSuite, ok := suite.(SetupTestSuite); ok {
192 setupTestSuite.SetupTest()
193 }
194 if beforeTestSuite, ok := suite.(BeforeTest); ok {
195 beforeTestSuite.BeforeTest(methodFinder.Elem().Name(), method.Name)
196 }
197
198 if stats != nil {
199 stats.start(method.Name)
200 }
201
202 method.Func.Call([]reflect.Value{reflect.ValueOf(suite)})
203 },
204 }
205 tests = append(tests, test)
206 }
207 if suiteSetupDone {
208 defer func() {
209 if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok {
210 tearDownAllSuite.TearDownSuite()
211 }
212
213 if suiteWithStats, measureStats := suite.(WithStats); measureStats {
214 stats.End = time.Now()
215 suiteWithStats.HandleStats(suiteName, stats)
216 }
217 }()
218 }
219
220 runTests(t, tests)
221 }
222
223
224
225 func methodFilter(name string) (bool, error) {
226 if ok, _ := regexp.MatchString("^Test", name); !ok {
227 return false, nil
228 }
229 return regexp.MatchString(*matchMethod, name)
230 }
231
232 func runTests(t testing.TB, tests []testing.InternalTest) {
233 if len(tests) == 0 {
234 t.Log("warning: no tests to run")
235 return
236 }
237
238 r, ok := t.(runner)
239 if !ok {
240 if !testing.RunTests(allTestsFilter, tests) {
241 t.Fail()
242 }
243 return
244 }
245
246 for _, test := range tests {
247 r.Run(test.Name, test.F)
248 }
249 }
250
251 type runner interface {
252 Run(name string, f func(t *testing.T)) bool
253 }
254
View as plain text