go.undefinedlabs.com/scopeagent@v0.4.2/instrumentation/gocheck/types.go (about) 1 package gocheck 2 3 import ( 4 "fmt" 5 "io" 6 "path/filepath" 7 "reflect" 8 "runtime" 9 "strings" 10 "sync" 11 "testing" 12 "time" 13 _ "unsafe" 14 15 "github.com/undefinedlabs/go-mpatch" 16 17 "go.undefinedlabs.com/scopeagent/instrumentation" 18 scopetesting "go.undefinedlabs.com/scopeagent/instrumentation/testing" 19 "go.undefinedlabs.com/scopeagent/instrumentation/testing/config" 20 "go.undefinedlabs.com/scopeagent/reflection" 21 "go.undefinedlabs.com/scopeagent/runner" 22 "go.undefinedlabs.com/scopeagent/tags" 23 24 goerrors "github.com/go-errors/errors" 25 "github.com/opentracing/opentracing-go/log" 26 chk "gopkg.in/check.v1" 27 ) 28 29 type ( 30 methodType struct { 31 reflect.Value 32 Info reflect.Method 33 } 34 35 resultTracker struct { 36 result chk.Result 37 _lastWasProblem bool 38 _waiting int 39 _missed int 40 _expectChan chan *chk.C 41 _doneChan chan *chk.C 42 _stopChan chan bool 43 } 44 45 tempDir struct { 46 sync.Mutex 47 path string 48 counter int 49 } 50 51 outputWriter struct { 52 m sync.Mutex 53 writer io.Writer 54 wroteCallProblemLast bool 55 Stream bool 56 Verbose bool 57 } 58 59 suiteRunner struct { 60 suite interface{} 61 setUpSuite, tearDownSuite *methodType 62 setUpTest, tearDownTest *methodType 63 tests []*methodType 64 tracker *resultTracker 65 tempDir *tempDir 66 keepDir bool 67 output *outputWriter 68 reportedProblemLast bool 69 benchTime time.Duration 70 benchMem bool 71 } 72 73 timer struct { 74 start time.Time // Time test or benchmark started 75 duration time.Duration 76 N int 77 bytes int64 78 timerOn bool 79 benchTime time.Duration 80 // The initial states of memStats.Mallocs and memStats.TotalAlloc. 81 startAllocs uint64 82 startBytes uint64 83 // The net total of this test after being run. 84 netAllocs uint64 85 netBytes uint64 86 } 87 88 testStatus uint32 89 90 testData struct { 91 c *chk.C 92 fn func(*chk.C) 93 err *goerrors.Error 94 options *runner.Options 95 test *Test 96 writer io.Writer 97 } 98 99 testLogWriter struct { 100 test *Test 101 } 102 ) 103 104 const ( 105 testSucceeded testStatus = iota 106 testFailed 107 testSkipped 108 testPanicked 109 testFixturePanicked 110 testMissed 111 ) 112 113 var ( 114 testMap = map[*chk.C]*testData{} 115 testMapMutex = sync.RWMutex{} 116 ) 117 118 //go:linkname nSRunner gopkg.in/check%2ev1.newSuiteRunner 119 func nSRunner(suite interface{}, runConf *chk.RunConf) *suiteRunner 120 121 //go:linkname lTestingT gopkg.in/check%2ev1.TestingT 122 func lTestingT(testingT *testing.T) 123 124 //go:linkname writeLog gopkg.in/check%2ev1.(*C).writeLog 125 func writeLog(c *chk.C, buf []byte) 126 127 //go:linkname lreportCallDone gopkg.in/check%2ev1.(*suiteRunner).reportCallDone 128 func lreportCallDone(runner *suiteRunner, c *chk.C) 129 130 func Init() { 131 var nSRunnerPatch *mpatch.Patch 132 var err error 133 nSRunnerPatch, err = mpatch.PatchMethod(nSRunner, func(suite interface{}, runConf *chk.RunConf) *suiteRunner { 134 nSRunnerPatch.Unpatch() 135 defer nSRunnerPatch.Patch() 136 runnerOptions := runner.GetRunnerOptions() 137 138 tWriter := &testLogWriter{} 139 140 r := nSRunner(suite, runConf) 141 for idx := range r.tests { 142 item := r.tests[idx] 143 144 if strings.HasPrefix(item.Info.Name, "Benchmark") { 145 continue 146 } 147 148 tData := &testData{options: runnerOptions, writer: tWriter} 149 instTest := func(c *chk.C) { 150 if isTestCached(c) { 151 writeCachedResult(item) 152 return 153 } 154 tData.c = c 155 testMapMutex.Lock() 156 testMap[c] = tData 157 setLogWriter(c, &tData.writer) 158 testMapMutex.Unlock() 159 defer func() { 160 testMapMutex.Lock() 161 delete(testMap, c) 162 testMapMutex.Unlock() 163 }() 164 165 test := startTest(item, c) 166 tData.test = test 167 tData.writer.(*testLogWriter).test = test 168 defer test.end(c) 169 item.Call([]reflect.Value{reflect.ValueOf(c)}) 170 } 171 tData.fn = instTest 172 173 if runnerOptions != nil { 174 instTest = getRunnerTestFunc(tData) 175 } 176 177 r.tests[idx] = &methodType{reflect.ValueOf(instTest), item.Info} 178 } 179 return r 180 }) 181 logOnError(err) 182 183 var lTestingTPatch *mpatch.Patch 184 lTestingTPatch, err = mpatch.PatchMethod(lTestingT, func(testingT *testing.T) { 185 lTestingTPatch.Unpatch() 186 defer lTestingTPatch.Patch() 187 188 // We tell the runner to ignore retries on this testing.T 189 runner.IgnoreRetries(testingT) 190 191 // We get the instrumented test struct and clean it, that removes the results of that test to be sent to scope 192 *scopetesting.GetTest(testingT) = scopetesting.Test{} 193 194 // We call the original go-check TestingT func 195 lTestingT(testingT) 196 }) 197 logOnError(err) 198 199 var lreportCallDonePatch *mpatch.Patch 200 lreportCallDonePatch, err = mpatch.PatchMethod(lreportCallDone, func(runner *suiteRunner, c *chk.C) { 201 lreportCallDonePatch.Unpatch() 202 defer lreportCallDonePatch.Patch() 203 204 if ptrMethod, err := reflection.GetFieldPointerOf(c, "method"); err == nil { 205 method := *(**methodType)(ptrMethod) 206 207 if strings.HasPrefix(method.Info.Name, "Benchmark") { 208 if ptr, err := reflection.GetFieldPointerOf(c, "timer"); err == nil { 209 tm := *(*timer)(ptr) 210 writeBenchmarkResult(method, c, tm) 211 } 212 } 213 } 214 215 lreportCallDone(runner, c) 216 }) 217 logOnError(err) 218 } 219 220 func logOnError(err error) { 221 if err != nil { 222 instrumentation.Logger().Println(err) 223 } 224 } 225 226 // gets test status 227 func getTestStatus(c *chk.C) testStatus { 228 var status uint32 229 if ptr, err := reflection.GetFieldPointerOf(c, "_status"); err == nil { 230 status = *(*uint32)(ptr) 231 } 232 return testStatus(status) 233 } 234 235 // sets test status 236 func setTestStatus(c *chk.C, status testStatus) { 237 sValue := uint32(status) 238 if ptr, err := reflection.GetFieldPointerOf(c, "_status"); err == nil { 239 *(*uint32)(ptr) = sValue 240 } 241 } 242 243 // gets the test reason 244 func getTestReason(c *chk.C) string { 245 if ptr, err := reflection.GetFieldPointerOf(c, "reason"); err == nil { 246 return *(*string)(ptr) 247 } 248 return "" 249 } 250 251 // gets if the test must fail 252 func getTestMustFail(c *chk.C) bool { 253 if ptr, err := reflection.GetFieldPointerOf(c, "mustFail"); err == nil { 254 return *(*bool)(ptr) 255 } 256 return false 257 } 258 259 func setLogWriter(c *chk.C, w *io.Writer) { 260 if ptr, err := reflection.GetFieldPointerOf(c, "logw"); err == nil { 261 cWriter := *(*io.Writer)(ptr) 262 if cWriter == nil { 263 *(*io.Writer)(ptr) = *w 264 } else if cWriter != *w { 265 *w = io.MultiWriter(cWriter, *w) 266 *(*io.Writer)(ptr) = *w 267 } 268 } 269 } 270 271 // gets if the test should retry 272 func shouldRetry(c *chk.C) bool { 273 switch status := getTestStatus(c); status { 274 case testFailed, testPanicked, testFixturePanicked: 275 if getTestMustFail(c) { 276 return false 277 } 278 return true 279 } 280 if getTestMustFail(c) { 281 return true 282 } 283 return false 284 } 285 286 // gets the test func with the test runner algorithm 287 func getRunnerTestFunc(tData *testData) func(*chk.C) { 288 runnerExecution := func(td *testData) { 289 defer func() { 290 if rc := recover(); rc != nil { 291 // using go-errors to preserve stacktrace 292 td.err = goerrors.Wrap(rc, 2) 293 td.c.Fail() 294 } 295 }() 296 td.fn(td.c) 297 } 298 return func(c *chk.C) { 299 if isTestCached(c) { 300 tData.fn(c) 301 return 302 } 303 tData.c = c 304 run := 1 305 for { 306 wg := new(sync.WaitGroup) 307 wg.Add(1) 308 go func() { 309 defer wg.Done() 310 runnerExecution(tData) 311 }() 312 wg.Wait() 313 if !shouldRetry(tData.c) { 314 break 315 } 316 if run > tData.options.FailRetries { 317 break 318 } 319 if tData.err != nil { 320 if !tData.options.PanicAsFail { 321 tData.options.OnPanic(nil, tData.err) 322 panic(tData.err.ErrorStack()) 323 } 324 tData.options.Logger.Printf("test '%s' - panic recover: %v", 325 tData.c.TestName(), tData.err) 326 } 327 setTestStatus(tData.c, testSucceeded) 328 fmt.Printf("FAIL: Retrying '%s' [%d/%d]\n", c.TestName(), run, tData.options.FailRetries) 329 run++ 330 } 331 } 332 } 333 334 // gets if the test is cached 335 func isTestCached(c *chk.C) bool { 336 fqn := c.TestName() 337 cachedMap := config.GetCachedTestsMap() 338 if _, ok := cachedMap[fqn]; ok { 339 instrumentation.Logger().Printf("Test '%v' is cached.", fqn) 340 fmt.Print("[SCOPE CACHED] ") 341 return true 342 } 343 instrumentation.Logger().Printf("Test '%v' is not cached.", fqn) 344 return false 345 } 346 347 // Write data to a test event 348 func (w *testLogWriter) Write(p []byte) (n int, err error) { 349 if w.test == nil || w.test.span == nil { 350 return 0, nil 351 } 352 353 pcs := make([]uintptr, 64) 354 count := runtime.Callers(2, pcs) 355 pcs = pcs[:count] 356 frames := runtime.CallersFrames(pcs) 357 for { 358 frame, more := frames.Next() 359 name := frame.Function 360 361 // If the frame is not in the gopkg.in/check we skip it 362 if !strings.Contains(name, "gopkg.in/check") { 363 if !more { 364 break 365 } 366 continue 367 } 368 369 // we only log if in the stackframe we see the log or lof method 370 if strings.HasSuffix(name, "log") || strings.HasSuffix(name, "logf") { 371 frame, more = frames.Next() 372 if !more { 373 break 374 } 375 frame, more = frames.Next() 376 helperName := frame.Function 377 if strings.HasSuffix(helperName, "logCaller") { 378 break 379 } 380 eventType := tags.LogEvent 381 eventLevel := tags.LogLevel_INFO 382 _, file, line, _ := getCallerInsideSourceRoot(frame, more, frames) 383 source := fmt.Sprintf("%s:%d", file, line) 384 if strings.HasSuffix(helperName, "Fatal") { 385 eventType = tags.EventTestFailure 386 eventLevel = tags.LogLevel_ERROR 387 388 } else if strings.HasSuffix(helperName, "Error") || strings.HasSuffix(helperName, "Errorf") { 389 eventLevel = tags.LogLevel_ERROR 390 } 391 fields := []log.Field{ 392 log.String(tags.EventType, eventType), 393 log.String(tags.EventMessage, string(p)), 394 log.String(tags.LogEventLevel, eventLevel), 395 log.String("log.logger", "gopkg.in/check.v1"), 396 } 397 if file != "" { 398 fields = append(fields, log.String(tags.EventSource, source)) 399 } 400 w.test.span.LogFields(fields...) 401 return len(p), nil 402 } 403 if !more { 404 break 405 } 406 } 407 return 0, nil 408 } 409 410 //go:noinline 411 func getCallerInsideSourceRoot(frame runtime.Frame, more bool, frames *runtime.Frames) (pc uintptr, file string, line int, ok bool) { 412 isWindows := runtime.GOOS == "windows" 413 sourceRoot := instrumentation.GetSourceRoot() 414 if isWindows { 415 sourceRoot = strings.ToLower(sourceRoot) 416 } 417 for { 418 file := filepath.Clean(frame.File) 419 dir := filepath.Dir(file) 420 if isWindows { 421 dir = strings.ToLower(dir) 422 } 423 if strings.Index(dir, sourceRoot) != -1 { 424 return frame.PC, file, frame.Line, true 425 } 426 if !more { 427 break 428 } 429 frame, more = frames.Next() 430 } 431 return 432 }