launchpad.net/gocheck@v0.0.0-20140225173054-000000000087/gocheck.go (about) 1 package gocheck 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "math/rand" 9 "os" 10 "path" 11 "path/filepath" 12 "reflect" 13 "regexp" 14 "runtime" 15 "strconv" 16 "strings" 17 "sync" 18 "time" 19 ) 20 21 // ----------------------------------------------------------------------- 22 // Internal type which deals with suite method calling. 23 24 const ( 25 fixtureKd = iota 26 testKd 27 ) 28 29 type funcKind int 30 31 const ( 32 succeededSt = iota 33 failedSt 34 skippedSt 35 panickedSt 36 fixturePanickedSt 37 missedSt 38 ) 39 40 type funcStatus int 41 42 // A method value can't reach its own Method structure. 43 type methodType struct { 44 reflect.Value 45 Info reflect.Method 46 } 47 48 func newMethod(receiver reflect.Value, i int) *methodType { 49 return &methodType{receiver.Method(i), receiver.Type().Method(i)} 50 } 51 52 func (method *methodType) PC() uintptr { 53 return method.Info.Func.Pointer() 54 } 55 56 func (method *methodType) suiteName() string { 57 t := method.Info.Type.In(0) 58 if t.Kind() == reflect.Ptr { 59 t = t.Elem() 60 } 61 return t.Name() 62 } 63 64 func (method *methodType) String() string { 65 return method.suiteName() + "." + method.Info.Name 66 } 67 68 func (method *methodType) matches(re *regexp.Regexp) bool { 69 return (re.MatchString(method.Info.Name) || 70 re.MatchString(method.suiteName()) || 71 re.MatchString(method.String())) 72 } 73 74 type C struct { 75 method *methodType 76 kind funcKind 77 status funcStatus 78 logb *logger 79 logw io.Writer 80 done chan *C 81 reason string 82 mustFail bool 83 tempDir *tempDir 84 startTime time.Time 85 timer 86 } 87 88 func (c *C) stopNow() { 89 runtime.Goexit() 90 } 91 92 // logger is a concurrency safe byte.Buffer 93 type logger struct { 94 sync.Mutex 95 writer bytes.Buffer 96 } 97 98 func (l *logger) Write(buf []byte) (int, error) { 99 l.Lock() 100 defer l.Unlock() 101 return l.writer.Write(buf) 102 } 103 104 func (l *logger) WriteTo(w io.Writer) (int64, error) { 105 l.Lock() 106 defer l.Unlock() 107 return l.writer.WriteTo(w) 108 } 109 110 func (l *logger) String() string { 111 l.Lock() 112 defer l.Unlock() 113 return l.writer.String() 114 } 115 116 // ----------------------------------------------------------------------- 117 // Handling of temporary files and directories. 118 119 type tempDir struct { 120 sync.Mutex 121 _path string 122 _counter int 123 } 124 125 func (td *tempDir) newPath() string { 126 td.Lock() 127 defer td.Unlock() 128 if td._path == "" { 129 var err error 130 for i := 0; i != 100; i++ { 131 path := fmt.Sprintf("%s/gocheck-%d", os.TempDir(), rand.Int()) 132 if err = os.Mkdir(path, 0700); err == nil { 133 td._path = path 134 break 135 } 136 } 137 if td._path == "" { 138 panic("Couldn't create temporary directory: " + err.Error()) 139 } 140 } 141 result := path.Join(td._path, strconv.Itoa(td._counter)) 142 td._counter += 1 143 return result 144 } 145 146 func (td *tempDir) removeAll() { 147 td.Lock() 148 defer td.Unlock() 149 if td._path != "" { 150 err := os.RemoveAll(td._path) 151 if err != nil { 152 fmt.Fprintf(os.Stderr, "WARNING: Error cleaning up temporaries: "+err.Error()) 153 } 154 } 155 } 156 157 // Create a new temporary directory which is automatically removed after 158 // the suite finishes running. 159 func (c *C) MkDir() string { 160 path := c.tempDir.newPath() 161 if err := os.Mkdir(path, 0700); err != nil { 162 panic(fmt.Sprintf("Couldn't create temporary directory %s: %s", path, err.Error())) 163 } 164 return path 165 } 166 167 // ----------------------------------------------------------------------- 168 // Low-level logging functions. 169 170 func (c *C) log(args ...interface{}) { 171 c.writeLog([]byte(fmt.Sprint(args...) + "\n")) 172 } 173 174 func (c *C) logf(format string, args ...interface{}) { 175 c.writeLog([]byte(fmt.Sprintf(format+"\n", args...))) 176 } 177 178 func (c *C) logNewLine() { 179 c.writeLog([]byte{'\n'}) 180 } 181 182 func (c *C) writeLog(buf []byte) { 183 c.logb.Write(buf) 184 if c.logw != nil { 185 c.logw.Write(buf) 186 } 187 } 188 189 func hasStringOrError(x interface{}) (ok bool) { 190 _, ok = x.(fmt.Stringer) 191 if ok { 192 return 193 } 194 _, ok = x.(error) 195 return 196 } 197 198 func (c *C) logValue(label string, value interface{}) { 199 if label == "" { 200 if hasStringOrError(value) { 201 c.logf("... %#v (%q)", value, value) 202 } else { 203 c.logf("... %#v", value) 204 } 205 } else if value == nil { 206 c.logf("... %s = nil", label) 207 } else { 208 if hasStringOrError(value) { 209 fv := fmt.Sprintf("%#v", value) 210 qv := fmt.Sprintf("%q", value) 211 if fv != qv { 212 c.logf("... %s %s = %s (%s)", label, reflect.TypeOf(value), fv, qv) 213 return 214 } 215 } 216 if s, ok := value.(string); ok && isMultiLine(s) { 217 c.logf(`... %s %s = "" +`, label, reflect.TypeOf(value)) 218 c.logMultiLine(s) 219 } else { 220 c.logf("... %s %s = %#v", label, reflect.TypeOf(value), value) 221 } 222 } 223 } 224 225 func (c *C) logMultiLine(s string) { 226 b := make([]byte, 0, len(s)*2) 227 i := 0 228 n := len(s) 229 for i < n { 230 j := i + 1 231 for j < n && s[j-1] != '\n' { 232 j++ 233 } 234 b = append(b, "... "...) 235 b = strconv.AppendQuote(b, s[i:j]) 236 if j < n { 237 b = append(b, " +"...) 238 } 239 b = append(b, '\n') 240 i = j 241 } 242 c.writeLog(b) 243 } 244 245 func isMultiLine(s string) bool { 246 for i := 0; i+1 < len(s); i++ { 247 if s[i] == '\n' { 248 return true 249 } 250 } 251 return false 252 } 253 254 func (c *C) logString(issue string) { 255 c.log("... ", issue) 256 } 257 258 func (c *C) logCaller(skip int) { 259 // This is a bit heavier than it ought to be. 260 skip += 1 // Our own frame. 261 pc, callerFile, callerLine, ok := runtime.Caller(skip) 262 if !ok { 263 return 264 } 265 var testFile string 266 var testLine int 267 testFunc := runtime.FuncForPC(c.method.PC()) 268 if runtime.FuncForPC(pc) != testFunc { 269 for { 270 skip += 1 271 if pc, file, line, ok := runtime.Caller(skip); ok { 272 // Note that the test line may be different on 273 // distinct calls for the same test. Showing 274 // the "internal" line is helpful when debugging. 275 if runtime.FuncForPC(pc) == testFunc { 276 testFile, testLine = file, line 277 break 278 } 279 } else { 280 break 281 } 282 } 283 } 284 if testFile != "" && (testFile != callerFile || testLine != callerLine) { 285 c.logCode(testFile, testLine) 286 } 287 c.logCode(callerFile, callerLine) 288 } 289 290 func (c *C) logCode(path string, line int) { 291 c.logf("%s:%d:", nicePath(path), line) 292 code, err := printLine(path, line) 293 if code == "" { 294 code = "..." // XXX Open the file and take the raw line. 295 if err != nil { 296 code += err.Error() 297 } 298 } 299 c.log(indent(code, " ")) 300 } 301 302 var valueGo = filepath.Join("reflect", "value.go") 303 304 func (c *C) logPanic(skip int, value interface{}) { 305 skip += 1 // Our own frame. 306 initialSkip := skip 307 for { 308 if pc, file, line, ok := runtime.Caller(skip); ok { 309 if skip == initialSkip { 310 c.logf("... Panic: %s (PC=0x%X)\n", value, pc) 311 } 312 name := niceFuncName(pc) 313 path := nicePath(file) 314 if name == "Value.call" && strings.HasSuffix(path, valueGo) { 315 break 316 } 317 c.logf("%s:%d\n in %s", nicePath(file), line, name) 318 } else { 319 break 320 } 321 skip += 1 322 } 323 } 324 325 func (c *C) logSoftPanic(issue string) { 326 c.log("... Panic: ", issue) 327 } 328 329 func (c *C) logArgPanic(method *methodType, expectedType string) { 330 c.logf("... Panic: %s argument should be %s", 331 niceFuncName(method.PC()), expectedType) 332 } 333 334 // ----------------------------------------------------------------------- 335 // Some simple formatting helpers. 336 337 var initWD, initWDErr = os.Getwd() 338 339 func init() { 340 if initWDErr == nil { 341 initWD = strings.Replace(initWD, "\\", "/", -1) + "/" 342 } 343 } 344 345 func nicePath(path string) string { 346 if initWDErr == nil { 347 if strings.HasPrefix(path, initWD) { 348 return path[len(initWD):] 349 } 350 } 351 return path 352 } 353 354 func niceFuncPath(pc uintptr) string { 355 function := runtime.FuncForPC(pc) 356 if function != nil { 357 filename, line := function.FileLine(pc) 358 return fmt.Sprintf("%s:%d", nicePath(filename), line) 359 } 360 return "<unknown path>" 361 } 362 363 func niceFuncName(pc uintptr) string { 364 function := runtime.FuncForPC(pc) 365 if function != nil { 366 name := path.Base(function.Name()) 367 if i := strings.Index(name, "."); i > 0 { 368 name = name[i+1:] 369 } 370 if strings.HasPrefix(name, "(*") { 371 if i := strings.Index(name, ")"); i > 0 { 372 name = name[2:i] + name[i+1:] 373 } 374 } 375 if i := strings.LastIndex(name, ".*"); i != -1 { 376 name = name[:i] + "." + name[i+2:] 377 } 378 if i := strings.LastIndex(name, "ยท"); i != -1 { 379 name = name[:i] + "." + name[i+2:] 380 } 381 return name 382 } 383 return "<unknown function>" 384 } 385 386 // ----------------------------------------------------------------------- 387 // Result tracker to aggregate call results. 388 389 type Result struct { 390 Succeeded int 391 Failed int 392 Skipped int 393 Panicked int 394 FixturePanicked int 395 ExpectedFailures int 396 Missed int // Not even tried to run, related to a panic in the fixture. 397 RunError error // Houston, we've got a problem. 398 } 399 400 type resultTracker struct { 401 result Result 402 _lastWasProblem bool 403 _waiting int 404 _missed int 405 _expectChan chan *C 406 _doneChan chan *C 407 _stopChan chan bool 408 } 409 410 func newResultTracker() *resultTracker { 411 return &resultTracker{_expectChan: make(chan *C), // Synchronous 412 _doneChan: make(chan *C, 32), // Asynchronous 413 _stopChan: make(chan bool)} // Synchronous 414 } 415 416 func (tracker *resultTracker) start() { 417 go tracker._loopRoutine() 418 } 419 420 func (tracker *resultTracker) waitAndStop() { 421 <-tracker._stopChan 422 } 423 424 func (tracker *resultTracker) expectCall(c *C) { 425 tracker._expectChan <- c 426 } 427 428 func (tracker *resultTracker) callDone(c *C) { 429 tracker._doneChan <- c 430 } 431 432 func (tracker *resultTracker) _loopRoutine() { 433 for { 434 var c *C 435 if tracker._waiting > 0 { 436 // Calls still running. Can't stop. 437 select { 438 // XXX Reindent this (not now to make diff clear) 439 case c = <-tracker._expectChan: 440 tracker._waiting += 1 441 case c = <-tracker._doneChan: 442 tracker._waiting -= 1 443 switch c.status { 444 case succeededSt: 445 if c.kind == testKd { 446 if c.mustFail { 447 tracker.result.ExpectedFailures++ 448 } else { 449 tracker.result.Succeeded++ 450 } 451 } 452 case failedSt: 453 tracker.result.Failed++ 454 case panickedSt: 455 if c.kind == fixtureKd { 456 tracker.result.FixturePanicked++ 457 } else { 458 tracker.result.Panicked++ 459 } 460 case fixturePanickedSt: 461 // Track it as missed, since the panic 462 // was on the fixture, not on the test. 463 tracker.result.Missed++ 464 case missedSt: 465 tracker.result.Missed++ 466 case skippedSt: 467 if c.kind == testKd { 468 tracker.result.Skipped++ 469 } 470 } 471 } 472 } else { 473 // No calls. Can stop, but no done calls here. 474 select { 475 case tracker._stopChan <- true: 476 return 477 case c = <-tracker._expectChan: 478 tracker._waiting += 1 479 case c = <-tracker._doneChan: 480 panic("Tracker got an unexpected done call.") 481 } 482 } 483 } 484 } 485 486 // ----------------------------------------------------------------------- 487 // The underlying suite runner. 488 489 type suiteRunner struct { 490 suite interface{} 491 setUpSuite, tearDownSuite *methodType 492 setUpTest, tearDownTest *methodType 493 tests []*methodType 494 tracker *resultTracker 495 tempDir *tempDir 496 output *outputWriter 497 reportedProblemLast bool 498 benchTime time.Duration 499 } 500 501 type RunConf struct { 502 Output io.Writer 503 Stream bool 504 Verbose bool 505 Filter string 506 Benchmark bool 507 BenchmarkTime time.Duration // Defaults to 1 second 508 } 509 510 // Create a new suiteRunner able to run all methods in the given suite. 511 func newSuiteRunner(suite interface{}, runConf *RunConf) *suiteRunner { 512 var conf RunConf 513 if runConf != nil { 514 conf = *runConf 515 } 516 if conf.Output == nil { 517 conf.Output = os.Stdout 518 } 519 if conf.Benchmark { 520 conf.Verbose = true 521 } 522 523 suiteType := reflect.TypeOf(suite) 524 suiteNumMethods := suiteType.NumMethod() 525 suiteValue := reflect.ValueOf(suite) 526 527 runner := &suiteRunner{ 528 suite: suite, 529 output: newOutputWriter(conf.Output, conf.Stream, conf.Verbose), 530 tracker: newResultTracker(), 531 benchTime: conf.BenchmarkTime, 532 } 533 runner.tests = make([]*methodType, 0, suiteNumMethods) 534 runner.tempDir = new(tempDir) 535 if runner.benchTime == 0 { 536 runner.benchTime = 1 * time.Second 537 } 538 539 var filterRegexp *regexp.Regexp 540 if conf.Filter != "" { 541 if regexp, err := regexp.Compile(conf.Filter); err != nil { 542 msg := "Bad filter expression: " + err.Error() 543 runner.tracker.result.RunError = errors.New(msg) 544 return runner 545 } else { 546 filterRegexp = regexp 547 } 548 } 549 550 for i := 0; i != suiteNumMethods; i++ { 551 method := newMethod(suiteValue, i) 552 switch method.Info.Name { 553 case "SetUpSuite": 554 runner.setUpSuite = method 555 case "TearDownSuite": 556 runner.tearDownSuite = method 557 case "SetUpTest": 558 runner.setUpTest = method 559 case "TearDownTest": 560 runner.tearDownTest = method 561 default: 562 prefix := "Test" 563 if conf.Benchmark { 564 prefix = "Benchmark" 565 } 566 if !strings.HasPrefix(method.Info.Name, prefix) { 567 continue 568 } 569 if filterRegexp == nil || method.matches(filterRegexp) { 570 runner.tests = append(runner.tests, method) 571 } 572 } 573 } 574 return runner 575 } 576 577 // Run all methods in the given suite. 578 func (runner *suiteRunner) run() *Result { 579 if runner.tracker.result.RunError == nil && len(runner.tests) > 0 { 580 runner.tracker.start() 581 if runner.checkFixtureArgs() { 582 c := runner.runFixture(runner.setUpSuite, nil) 583 if c == nil || c.status == succeededSt { 584 for i := 0; i != len(runner.tests); i++ { 585 c := runner.runTest(runner.tests[i]) 586 if c.status == fixturePanickedSt { 587 runner.skipTests(missedSt, runner.tests[i+1:]) 588 break 589 } 590 } 591 } else if c != nil && c.status == skippedSt { 592 runner.skipTests(skippedSt, runner.tests) 593 } else { 594 runner.skipTests(missedSt, runner.tests) 595 } 596 runner.runFixture(runner.tearDownSuite, nil) 597 } else { 598 runner.skipTests(missedSt, runner.tests) 599 } 600 runner.tracker.waitAndStop() 601 runner.tempDir.removeAll() 602 } 603 return &runner.tracker.result 604 } 605 606 // Create a call object with the given suite method, and fork a 607 // goroutine with the provided dispatcher for running it. 608 func (runner *suiteRunner) forkCall(method *methodType, kind funcKind, logb *logger, dispatcher func(c *C)) *C { 609 var logw io.Writer 610 if runner.output.Stream { 611 logw = runner.output 612 } 613 if logb == nil { 614 logb = new(logger) 615 } 616 c := &C{ 617 method: method, 618 kind: kind, 619 logb: logb, 620 logw: logw, 621 tempDir: runner.tempDir, 622 done: make(chan *C, 1), 623 timer: timer{benchTime: runner.benchTime}, 624 startTime: time.Now(), 625 } 626 runner.tracker.expectCall(c) 627 go (func() { 628 runner.reportCallStarted(c) 629 defer runner.callDone(c) 630 dispatcher(c) 631 })() 632 return c 633 } 634 635 // Same as forkCall(), but wait for call to finish before returning. 636 func (runner *suiteRunner) runFunc(method *methodType, kind funcKind, logb *logger, dispatcher func(c *C)) *C { 637 c := runner.forkCall(method, kind, logb, dispatcher) 638 <-c.done 639 return c 640 } 641 642 // Handle a finished call. If there were any panics, update the call status 643 // accordingly. Then, mark the call as done and report to the tracker. 644 func (runner *suiteRunner) callDone(c *C) { 645 value := recover() 646 if value != nil { 647 switch v := value.(type) { 648 case *fixturePanic: 649 if v.status == skippedSt { 650 c.status = skippedSt 651 } else { 652 c.logSoftPanic("Fixture has panicked (see related PANIC)") 653 c.status = fixturePanickedSt 654 } 655 default: 656 c.logPanic(1, value) 657 c.status = panickedSt 658 } 659 } 660 if c.mustFail { 661 switch c.status { 662 case failedSt: 663 c.status = succeededSt 664 case succeededSt: 665 c.status = failedSt 666 c.logString("Error: Test succeeded, but was expected to fail") 667 c.logString("Reason: " + c.reason) 668 } 669 } 670 671 runner.reportCallDone(c) 672 c.done <- c 673 } 674 675 // Runs a fixture call synchronously. The fixture will still be run in a 676 // goroutine like all suite methods, but this method will not return 677 // while the fixture goroutine is not done, because the fixture must be 678 // run in a desired order. 679 func (runner *suiteRunner) runFixture(method *methodType, logb *logger) *C { 680 if method != nil { 681 c := runner.runFunc(method, fixtureKd, logb, func(c *C) { 682 c.ResetTimer() 683 c.StartTimer() 684 defer c.StopTimer() 685 c.method.Call([]reflect.Value{reflect.ValueOf(c)}) 686 }) 687 return c 688 } 689 return nil 690 } 691 692 // Run the fixture method with runFixture(), but panic with a fixturePanic{} 693 // in case the fixture method panics. This makes it easier to track the 694 // fixture panic together with other call panics within forkTest(). 695 func (runner *suiteRunner) runFixtureWithPanic(method *methodType, logb *logger, skipped *bool) *C { 696 if skipped != nil && *skipped { 697 return nil 698 } 699 c := runner.runFixture(method, logb) 700 if c != nil && c.status != succeededSt { 701 if skipped != nil { 702 *skipped = c.status == skippedSt 703 } 704 panic(&fixturePanic{c.status, method}) 705 } 706 return c 707 } 708 709 type fixturePanic struct { 710 status funcStatus 711 method *methodType 712 } 713 714 // Run the suite test method, together with the test-specific fixture, 715 // asynchronously. 716 func (runner *suiteRunner) forkTest(method *methodType) *C { 717 return runner.forkCall(method, testKd, nil, func(c *C) { 718 var skipped bool 719 defer runner.runFixtureWithPanic(runner.tearDownTest, nil, &skipped) 720 defer c.StopTimer() 721 benchN := 1 722 for { 723 runner.runFixtureWithPanic(runner.setUpTest, c.logb, &skipped) 724 mt := c.method.Type() 725 if mt.NumIn() != 1 || mt.In(0) != reflect.TypeOf(c) { 726 // Rather than a plain panic, provide a more helpful message when 727 // the argument type is incorrect. 728 c.status = panickedSt 729 c.logArgPanic(c.method, "*gocheck.C") 730 return 731 } 732 if strings.HasPrefix(c.method.Info.Name, "Test") { 733 c.ResetTimer() 734 c.StartTimer() 735 c.method.Call([]reflect.Value{reflect.ValueOf(c)}) 736 return 737 } 738 if !strings.HasPrefix(c.method.Info.Name, "Benchmark") { 739 panic("unexpected method prefix: " + c.method.Info.Name) 740 } 741 742 runtime.GC() 743 c.N = benchN 744 c.ResetTimer() 745 c.StartTimer() 746 c.method.Call([]reflect.Value{reflect.ValueOf(c)}) 747 c.StopTimer() 748 if c.status != succeededSt || c.duration >= c.benchTime || benchN >= 1e9 { 749 return 750 } 751 perOpN := int(1e9) 752 if c.nsPerOp() != 0 { 753 perOpN = int(c.benchTime.Nanoseconds() / c.nsPerOp()) 754 } 755 756 // Logic taken from the stock testing package: 757 // - Run more iterations than we think we'll need for a second (1.5x). 758 // - Don't grow too fast in case we had timing errors previously. 759 // - Be sure to run at least one more than last time. 760 benchN = max(min(perOpN+perOpN/2, 100*benchN), benchN+1) 761 benchN = roundUp(benchN) 762 763 skipped = true // Don't run the deferred one if this panics. 764 runner.runFixtureWithPanic(runner.tearDownTest, nil, nil) 765 skipped = false 766 } 767 }) 768 } 769 770 // Same as forkTest(), but wait for the test to finish before returning. 771 func (runner *suiteRunner) runTest(method *methodType) *C { 772 c := runner.forkTest(method) 773 <-c.done 774 return c 775 } 776 777 // Helper to mark tests as skipped or missed. A bit heavy for what 778 // it does, but it enables homogeneous handling of tracking, including 779 // nice verbose output. 780 func (runner *suiteRunner) skipTests(status funcStatus, methods []*methodType) { 781 for _, method := range methods { 782 runner.runFunc(method, testKd, nil, func(c *C) { 783 c.status = status 784 }) 785 } 786 } 787 788 // Verify if the fixture arguments are *gocheck.C. In case of errors, 789 // log the error as a panic in the fixture method call, and return false. 790 func (runner *suiteRunner) checkFixtureArgs() bool { 791 succeeded := true 792 argType := reflect.TypeOf(&C{}) 793 for _, method := range []*methodType{runner.setUpSuite, runner.tearDownSuite, runner.setUpTest, runner.tearDownTest} { 794 if method != nil { 795 mt := method.Type() 796 if mt.NumIn() != 1 || mt.In(0) != argType { 797 succeeded = false 798 runner.runFunc(method, fixtureKd, nil, func(c *C) { 799 c.logArgPanic(method, "*gocheck.C") 800 c.status = panickedSt 801 }) 802 } 803 } 804 } 805 return succeeded 806 } 807 808 func (runner *suiteRunner) reportCallStarted(c *C) { 809 runner.output.WriteCallStarted("START", c) 810 } 811 812 func (runner *suiteRunner) reportCallDone(c *C) { 813 runner.tracker.callDone(c) 814 switch c.status { 815 case succeededSt: 816 if c.mustFail { 817 runner.output.WriteCallSuccess("FAIL EXPECTED", c) 818 } else { 819 runner.output.WriteCallSuccess("PASS", c) 820 } 821 case skippedSt: 822 runner.output.WriteCallSuccess("SKIP", c) 823 case failedSt: 824 runner.output.WriteCallProblem("FAIL", c) 825 case panickedSt: 826 runner.output.WriteCallProblem("PANIC", c) 827 case fixturePanickedSt: 828 // That's a testKd call reporting that its fixture 829 // has panicked. The fixture call which caused the 830 // panic itself was tracked above. We'll report to 831 // aid debugging. 832 runner.output.WriteCallProblem("PANIC", c) 833 case missedSt: 834 runner.output.WriteCallSuccess("MISS", c) 835 } 836 } 837 838 // ----------------------------------------------------------------------- 839 // Output writer manages atomic output writing according to settings. 840 841 type outputWriter struct { 842 m sync.Mutex 843 writer io.Writer 844 wroteCallProblemLast bool 845 Stream bool 846 Verbose bool 847 } 848 849 func newOutputWriter(writer io.Writer, stream, verbose bool) *outputWriter { 850 return &outputWriter{writer: writer, Stream: stream, Verbose: verbose} 851 } 852 853 func (ow *outputWriter) Write(content []byte) (n int, err error) { 854 ow.m.Lock() 855 n, err = ow.writer.Write(content) 856 ow.m.Unlock() 857 return 858 } 859 860 func (ow *outputWriter) WriteCallStarted(label string, c *C) { 861 if ow.Stream { 862 header := renderCallHeader(label, c, "", "\n") 863 ow.m.Lock() 864 ow.writer.Write([]byte(header)) 865 ow.m.Unlock() 866 } 867 } 868 869 func (ow *outputWriter) WriteCallProblem(label string, c *C) { 870 var prefix string 871 if !ow.Stream { 872 prefix = "\n-----------------------------------" + 873 "-----------------------------------\n" 874 } 875 header := renderCallHeader(label, c, prefix, "\n\n") 876 ow.m.Lock() 877 ow.wroteCallProblemLast = true 878 ow.writer.Write([]byte(header)) 879 if !ow.Stream { 880 c.logb.WriteTo(ow.writer) 881 } 882 ow.m.Unlock() 883 } 884 885 func (ow *outputWriter) WriteCallSuccess(label string, c *C) { 886 if ow.Stream || (ow.Verbose && c.kind == testKd) { 887 // TODO Use a buffer here. 888 var suffix string 889 if c.reason != "" { 890 suffix = " (" + c.reason + ")" 891 } 892 if c.status == succeededSt { 893 suffix += "\t" + c.timerString() 894 } 895 suffix += "\n" 896 if ow.Stream { 897 suffix += "\n" 898 } 899 header := renderCallHeader(label, c, "", suffix) 900 ow.m.Lock() 901 // Resist temptation of using line as prefix above due to race. 902 if !ow.Stream && ow.wroteCallProblemLast { 903 header = "\n-----------------------------------" + 904 "-----------------------------------\n" + 905 header 906 } 907 ow.wroteCallProblemLast = false 908 ow.writer.Write([]byte(header)) 909 ow.m.Unlock() 910 } 911 } 912 913 func renderCallHeader(label string, c *C, prefix, suffix string) string { 914 pc := c.method.PC() 915 return fmt.Sprintf("%s%s: %s: %s%s", prefix, label, niceFuncPath(pc), 916 niceFuncName(pc), suffix) 917 }