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