1
// Package check is a rich testing extension for Go's testing package.
3
// For details about the project, see:
5
// http://labix.org/gocheck
28
// -----------------------------------------------------------------------
29
// Internal type which deals with suite method calling.
47
type funcStatus uint32
49
// A method value can't reach its own Method structure.
50
type methodType struct {
55
func newMethod(receiver reflect.Value, i int) *methodType {
56
return &methodType{receiver.Method(i), receiver.Type().Method(i)}
59
func (method *methodType) PC() uintptr {
60
return method.Info.Func.Pointer()
63
func (method *methodType) suiteName() string {
64
t := method.Info.Type.In(0)
65
if t.Kind() == reflect.Ptr {
71
func (method *methodType) String() string {
72
return method.suiteName() + "." + method.Info.Name
75
func (method *methodType) matches(re *regexp.Regexp) bool {
76
return (re.MatchString(method.Info.Name) ||
77
re.MatchString(method.suiteName()) ||
78
re.MatchString(method.String()))
97
func (c *C) status() funcStatus {
98
return funcStatus(atomic.LoadUint32((*uint32)(&c._status)))
101
func (c *C) setStatus(s funcStatus) {
102
atomic.StoreUint32((*uint32)(&c._status), uint32(s))
105
func (c *C) stopNow() {
109
// logger is a concurrency safe byte.Buffer
115
func (l *logger) Write(buf []byte) (int, error) {
118
return l.writer.Write(buf)
121
func (l *logger) WriteTo(w io.Writer) (int64, error) {
124
return l.writer.WriteTo(w)
127
func (l *logger) String() string {
130
return l.writer.String()
133
// -----------------------------------------------------------------------
134
// Handling of temporary files and directories.
136
type tempDir struct {
142
func (td *tempDir) newPath() string {
147
for i := 0; i != 100; i++ {
148
path := fmt.Sprintf("%s%ccheck-%d", os.TempDir(), os.PathSeparator, rand.Int())
149
if err = os.Mkdir(path, 0700); err == nil {
155
panic("Couldn't create temporary directory: " + err.Error())
158
result := filepath.Join(td.path, strconv.Itoa(td.counter))
163
func (td *tempDir) removeAll() {
167
err := os.RemoveAll(td.path)
169
fmt.Fprintf(os.Stderr, "WARNING: Error cleaning up temporaries: "+err.Error())
174
// Create a new temporary directory which is automatically removed after
175
// the suite finishes running.
176
func (c *C) MkDir() string {
177
path := c.tempDir.newPath()
178
if err := os.Mkdir(path, 0700); err != nil {
179
panic(fmt.Sprintf("Couldn't create temporary directory %s: %s", path, err.Error()))
184
// -----------------------------------------------------------------------
185
// Low-level logging functions.
187
func (c *C) log(args ...interface{}) {
188
c.writeLog([]byte(fmt.Sprint(args...) + "\n"))
191
func (c *C) logf(format string, args ...interface{}) {
192
c.writeLog([]byte(fmt.Sprintf(format+"\n", args...)))
195
func (c *C) logNewLine() {
196
c.writeLog([]byte{'\n'})
199
func (c *C) writeLog(buf []byte) {
206
func hasStringOrError(x interface{}) (ok bool) {
207
_, ok = x.(fmt.Stringer)
215
func (c *C) logValue(label string, value interface{}) {
217
if hasStringOrError(value) {
218
c.logf("... %#v (%q)", value, value)
220
c.logf("... %#v", value)
222
} else if value == nil {
223
c.logf("... %s = nil", label)
225
if hasStringOrError(value) {
226
fv := fmt.Sprintf("%#v", value)
227
qv := fmt.Sprintf("%q", value)
229
c.logf("... %s %s = %s (%s)", label, reflect.TypeOf(value), fv, qv)
233
if s, ok := value.(string); ok && isMultiLine(s) {
234
c.logf(`... %s %s = "" +`, label, reflect.TypeOf(value))
237
c.logf("... %s %s = %#v", label, reflect.TypeOf(value), value)
242
func (c *C) logMultiLine(s string) {
243
b := make([]byte, 0, len(s)*2)
248
for j < n && s[j-1] != '\n' {
251
b = append(b, "... "...)
252
b = strconv.AppendQuote(b, s[i:j])
254
b = append(b, " +"...)
262
func isMultiLine(s string) bool {
263
for i := 0; i+1 < len(s); i++ {
271
func (c *C) logString(issue string) {
275
func (c *C) logCaller(skip int) {
276
// This is a bit heavier than it ought to be.
277
skip += 1 // Our own frame.
278
pc, callerFile, callerLine, ok := runtime.Caller(skip)
284
testFunc := runtime.FuncForPC(c.method.PC())
285
if runtime.FuncForPC(pc) != testFunc {
288
if pc, file, line, ok := runtime.Caller(skip); ok {
289
// Note that the test line may be different on
290
// distinct calls for the same test. Showing
291
// the "internal" line is helpful when debugging.
292
if runtime.FuncForPC(pc) == testFunc {
293
testFile, testLine = file, line
301
if testFile != "" && (testFile != callerFile || testLine != callerLine) {
302
c.logCode(testFile, testLine)
304
c.logCode(callerFile, callerLine)
307
func (c *C) logCode(path string, line int) {
308
c.logf("%s:%d:", nicePath(path), line)
309
code, err := printLine(path, line)
311
code = "..." // XXX Open the file and take the raw line.
316
c.log(indent(code, " "))
319
var valueGo = filepath.Join("reflect", "value.go")
320
var asmGo = filepath.Join("runtime", "asm_")
322
func (c *C) logPanic(skip int, value interface{}) {
323
skip++ // Our own frame.
326
if pc, file, line, ok := runtime.Caller(skip); ok {
327
if skip == initialSkip {
328
c.logf("... Panic: %s (PC=0x%X)\n", value, pc)
330
name := niceFuncName(pc)
331
path := nicePath(file)
332
if strings.Contains(path, "/gopkg.in/check.v") {
335
if name == "Value.call" && strings.HasSuffix(path, valueGo) {
338
if (name == "call16" || name == "call32") && strings.Contains(path, asmGo) {
341
c.logf("%s:%d\n in %s", nicePath(file), line, name)
348
func (c *C) logSoftPanic(issue string) {
349
c.log("... Panic: ", issue)
352
func (c *C) logArgPanic(method *methodType, expectedType string) {
353
c.logf("... Panic: %s argument should be %s",
354
niceFuncName(method.PC()), expectedType)
357
// -----------------------------------------------------------------------
358
// Some simple formatting helpers.
360
var initWD, initWDErr = os.Getwd()
363
if initWDErr == nil {
364
initWD = strings.Replace(initWD, "\\", "/", -1) + "/"
368
func nicePath(path string) string {
369
if initWDErr == nil {
370
if strings.HasPrefix(path, initWD) {
371
return path[len(initWD):]
377
func niceFuncPath(pc uintptr) string {
378
function := runtime.FuncForPC(pc)
380
filename, line := function.FileLine(pc)
381
return fmt.Sprintf("%s:%d", nicePath(filename), line)
383
return "<unknown path>"
386
func niceFuncName(pc uintptr) string {
387
function := runtime.FuncForPC(pc)
389
name := path.Base(function.Name())
390
if i := strings.Index(name, "."); i > 0 {
393
if strings.HasPrefix(name, "(*") {
394
if i := strings.Index(name, ")"); i > 0 {
395
name = name[2:i] + name[i+1:]
398
if i := strings.LastIndex(name, ".*"); i != -1 {
399
name = name[:i] + "." + name[i+2:]
401
if i := strings.LastIndex(name, "·"); i != -1 {
402
name = name[:i] + "." + name[i+2:]
406
return "<unknown function>"
409
// -----------------------------------------------------------------------
410
// Result tracker to aggregate call results.
419
Missed int // Not even tried to run, related to a panic in the fixture.
420
RunError error // Houston, we've got a problem.
421
WorkDir string // If KeepWorkDir is true
424
type resultTracker struct {
434
func newResultTracker() *resultTracker {
435
return &resultTracker{_expectChan: make(chan *C), // Synchronous
436
_doneChan: make(chan *C, 32), // Asynchronous
437
_stopChan: make(chan bool)} // Synchronous
440
func (tracker *resultTracker) start() {
441
go tracker._loopRoutine()
444
func (tracker *resultTracker) waitAndStop() {
448
func (tracker *resultTracker) expectCall(c *C) {
449
tracker._expectChan <- c
452
func (tracker *resultTracker) callDone(c *C) {
453
tracker._doneChan <- c
456
func (tracker *resultTracker) _loopRoutine() {
459
if tracker._waiting > 0 {
460
// Calls still running. Can't stop.
462
// XXX Reindent this (not now to make diff clear)
463
case c = <-tracker._expectChan:
464
tracker._waiting += 1
465
case c = <-tracker._doneChan:
466
tracker._waiting -= 1
469
if c.kind == testKd {
471
tracker.result.ExpectedFailures++
473
tracker.result.Succeeded++
477
tracker.result.Failed++
479
if c.kind == fixtureKd {
480
tracker.result.FixturePanicked++
482
tracker.result.Panicked++
484
case fixturePanickedSt:
485
// Track it as missed, since the panic
486
// was on the fixture, not on the test.
487
tracker.result.Missed++
489
tracker.result.Missed++
491
if c.kind == testKd {
492
tracker.result.Skipped++
497
// No calls. Can stop, but no done calls here.
499
case tracker._stopChan <- true:
501
case c = <-tracker._expectChan:
502
tracker._waiting += 1
503
case c = <-tracker._doneChan:
504
panic("Tracker got an unexpected done call.")
510
// -----------------------------------------------------------------------
511
// The underlying suite runner.
513
type suiteRunner struct {
515
setUpSuite, tearDownSuite *methodType
516
setUpTest, tearDownTest *methodType
518
tracker *resultTracker
522
reportedProblemLast bool
523
benchTime time.Duration
527
type RunConf struct {
533
BenchmarkTime time.Duration // Defaults to 1 second
538
// Create a new suiteRunner able to run all methods in the given suite.
539
func newSuiteRunner(suite interface{}, runConf *RunConf) *suiteRunner {
544
if conf.Output == nil {
545
conf.Output = os.Stdout
551
suiteType := reflect.TypeOf(suite)
552
suiteNumMethods := suiteType.NumMethod()
553
suiteValue := reflect.ValueOf(suite)
555
runner := &suiteRunner{
557
output: newOutputWriter(conf.Output, conf.Stream, conf.Verbose),
558
tracker: newResultTracker(),
559
benchTime: conf.BenchmarkTime,
560
benchMem: conf.BenchmarkMem,
562
keepDir: conf.KeepWorkDir,
563
tests: make([]*methodType, 0, suiteNumMethods),
565
if runner.benchTime == 0 {
566
runner.benchTime = 1 * time.Second
569
var filterRegexp *regexp.Regexp
570
if conf.Filter != "" {
571
if regexp, err := regexp.Compile(conf.Filter); err != nil {
572
msg := "Bad filter expression: " + err.Error()
573
runner.tracker.result.RunError = errors.New(msg)
576
filterRegexp = regexp
580
for i := 0; i != suiteNumMethods; i++ {
581
method := newMethod(suiteValue, i)
582
switch method.Info.Name {
584
runner.setUpSuite = method
585
case "TearDownSuite":
586
runner.tearDownSuite = method
588
runner.setUpTest = method
590
runner.tearDownTest = method
596
if !strings.HasPrefix(method.Info.Name, prefix) {
599
if filterRegexp == nil || method.matches(filterRegexp) {
600
runner.tests = append(runner.tests, method)
607
// Run all methods in the given suite.
608
func (runner *suiteRunner) run() *Result {
609
if runner.tracker.result.RunError == nil && len(runner.tests) > 0 {
610
runner.tracker.start()
611
if runner.checkFixtureArgs() {
612
c := runner.runFixture(runner.setUpSuite, "", nil)
613
if c == nil || c.status() == succeededSt {
614
for i := 0; i != len(runner.tests); i++ {
615
c := runner.runTest(runner.tests[i])
616
if c.status() == fixturePanickedSt {
617
runner.skipTests(missedSt, runner.tests[i+1:])
621
} else if c != nil && c.status() == skippedSt {
622
runner.skipTests(skippedSt, runner.tests)
624
runner.skipTests(missedSt, runner.tests)
626
runner.runFixture(runner.tearDownSuite, "", nil)
628
runner.skipTests(missedSt, runner.tests)
630
runner.tracker.waitAndStop()
632
runner.tracker.result.WorkDir = runner.tempDir.path
634
runner.tempDir.removeAll()
637
return &runner.tracker.result
640
// Create a call object with the given suite method, and fork a
641
// goroutine with the provided dispatcher for running it.
642
func (runner *suiteRunner) forkCall(method *methodType, kind funcKind, testName string, logb *logger, dispatcher func(c *C)) *C {
644
if runner.output.Stream {
656
tempDir: runner.tempDir,
657
done: make(chan *C, 1),
658
timer: timer{benchTime: runner.benchTime},
659
startTime: time.Now(),
660
benchMem: runner.benchMem,
662
runner.tracker.expectCall(c)
664
runner.reportCallStarted(c)
665
defer runner.callDone(c)
671
// Same as forkCall(), but wait for call to finish before returning.
672
func (runner *suiteRunner) runFunc(method *methodType, kind funcKind, testName string, logb *logger, dispatcher func(c *C)) *C {
673
c := runner.forkCall(method, kind, testName, logb, dispatcher)
678
// Handle a finished call. If there were any panics, update the call status
679
// accordingly. Then, mark the call as done and report to the tracker.
680
func (runner *suiteRunner) callDone(c *C) {
683
switch v := value.(type) {
685
if v.status == skippedSt {
686
c.setStatus(skippedSt)
688
c.logSoftPanic("Fixture has panicked (see related PANIC)")
689
c.setStatus(fixturePanickedSt)
693
c.setStatus(panickedSt)
699
c.setStatus(succeededSt)
701
c.setStatus(failedSt)
702
c.logString("Error: Test succeeded, but was expected to fail")
703
c.logString("Reason: " + c.reason)
707
runner.reportCallDone(c)
711
// Runs a fixture call synchronously. The fixture will still be run in a
712
// goroutine like all suite methods, but this method will not return
713
// while the fixture goroutine is not done, because the fixture must be
714
// run in a desired order.
715
func (runner *suiteRunner) runFixture(method *methodType, testName string, logb *logger) *C {
717
c := runner.runFunc(method, fixtureKd, testName, logb, func(c *C) {
721
c.method.Call([]reflect.Value{reflect.ValueOf(c)})
728
// Run the fixture method with runFixture(), but panic with a fixturePanic{}
729
// in case the fixture method panics. This makes it easier to track the
730
// fixture panic together with other call panics within forkTest().
731
func (runner *suiteRunner) runFixtureWithPanic(method *methodType, testName string, logb *logger, skipped *bool) *C {
732
if skipped != nil && *skipped {
735
c := runner.runFixture(method, testName, logb)
736
if c != nil && c.status() != succeededSt {
738
*skipped = c.status() == skippedSt
740
panic(&fixturePanic{c.status(), method})
745
type fixturePanic struct {
750
// Run the suite test method, together with the test-specific fixture,
752
func (runner *suiteRunner) forkTest(method *methodType) *C {
753
testName := method.String()
754
return runner.forkCall(method, testKd, testName, nil, func(c *C) {
756
defer runner.runFixtureWithPanic(runner.tearDownTest, testName, nil, &skipped)
760
runner.runFixtureWithPanic(runner.setUpTest, testName, c.logb, &skipped)
761
mt := c.method.Type()
762
if mt.NumIn() != 1 || mt.In(0) != reflect.TypeOf(c) {
763
// Rather than a plain panic, provide a more helpful message when
764
// the argument type is incorrect.
765
c.setStatus(panickedSt)
766
c.logArgPanic(c.method, "*check.C")
769
if strings.HasPrefix(c.method.Info.Name, "Test") {
772
c.method.Call([]reflect.Value{reflect.ValueOf(c)})
775
if !strings.HasPrefix(c.method.Info.Name, "Benchmark") {
776
panic("unexpected method prefix: " + c.method.Info.Name)
783
c.method.Call([]reflect.Value{reflect.ValueOf(c)})
785
if c.status() != succeededSt || c.duration >= c.benchTime || benchN >= 1e9 {
789
if c.nsPerOp() != 0 {
790
perOpN = int(c.benchTime.Nanoseconds() / c.nsPerOp())
793
// Logic taken from the stock testing package:
794
// - Run more iterations than we think we'll need for a second (1.5x).
795
// - Don't grow too fast in case we had timing errors previously.
796
// - Be sure to run at least one more than last time.
797
benchN = max(min(perOpN+perOpN/2, 100*benchN), benchN+1)
798
benchN = roundUp(benchN)
800
skipped = true // Don't run the deferred one if this panics.
801
runner.runFixtureWithPanic(runner.tearDownTest, testName, nil, nil)
807
// Same as forkTest(), but wait for the test to finish before returning.
808
func (runner *suiteRunner) runTest(method *methodType) *C {
809
c := runner.forkTest(method)
814
// Helper to mark tests as skipped or missed. A bit heavy for what
815
// it does, but it enables homogeneous handling of tracking, including
816
// nice verbose output.
817
func (runner *suiteRunner) skipTests(status funcStatus, methods []*methodType) {
818
for _, method := range methods {
819
runner.runFunc(method, testKd, "", nil, func(c *C) {
825
// Verify if the fixture arguments are *check.C. In case of errors,
826
// log the error as a panic in the fixture method call, and return false.
827
func (runner *suiteRunner) checkFixtureArgs() bool {
829
argType := reflect.TypeOf(&C{})
830
for _, method := range []*methodType{runner.setUpSuite, runner.tearDownSuite, runner.setUpTest, runner.tearDownTest} {
833
if mt.NumIn() != 1 || mt.In(0) != argType {
835
runner.runFunc(method, fixtureKd, "", nil, func(c *C) {
836
c.logArgPanic(method, "*check.C")
837
c.setStatus(panickedSt)
845
func (runner *suiteRunner) reportCallStarted(c *C) {
846
runner.output.WriteCallStarted("START", c)
849
func (runner *suiteRunner) reportCallDone(c *C) {
850
runner.tracker.callDone(c)
854
runner.output.WriteCallSuccess("FAIL EXPECTED", c)
856
runner.output.WriteCallSuccess("PASS", c)
859
runner.output.WriteCallSuccess("SKIP", c)
861
runner.output.WriteCallProblem("FAIL", c)
863
runner.output.WriteCallProblem("PANIC", c)
864
case fixturePanickedSt:
865
// That's a testKd call reporting that its fixture
866
// has panicked. The fixture call which caused the
867
// panic itself was tracked above. We'll report to
869
runner.output.WriteCallProblem("PANIC", c)
871
runner.output.WriteCallSuccess("MISS", c)