Skip to content

Commit

Permalink
More thread-safe execution; Reporting no longer reports stats to the …
Browse files Browse the repository at this point in the history
…console;
  • Loading branch information
mdwhatcott committed Jan 31, 2014
1 parent 29375dc commit aca3c33
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 77 deletions.
91 changes: 91 additions & 0 deletions convey/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package convey

import (
"fmt"
"runtime"
"strings"
"sync"

"github.com/smartystreets/goconvey/execution"
"github.com/smartystreets/goconvey/reporting"
)

type SuiteContext struct {
runners map[string]execution.Runner
reporters map[string]reporting.Reporter
lock sync.Mutex
}

func (self *SuiteContext) Assign() execution.Runner {
key := resolveExternalCallerWithTestName()
reporter := buildReporter()
runner := execution.NewRunner()
runner.UpgradeReporter(reporter)

self.lock.Lock()
self.runners[key] = runner
self.reporters[key] = reporter
self.lock.Unlock()

return runner
}

func (self *SuiteContext) CurrentRunner() execution.Runner {
key := resolveExternalCallerWithTestName()
self.lock.Lock()
defer self.lock.Unlock()
return self.runners[key]
}

func (self *SuiteContext) CurrentReporter() reporting.Reporter {
key := resolveExternalCallerWithTestName()
self.lock.Lock()
defer self.lock.Unlock()
return self.reporters[key]
}

func NewSuiteContext() *SuiteContext {
self := new(SuiteContext)
self.runners = make(map[string]execution.Runner)
self.reporters = make(map[string]reporting.Reporter)
return self
}

func resolveExternalCallerWithTestName() string {
// TODO: It turns out the more robust solution is to manually parse the debug.Stack()
// because we can then filter out non-test methods that start with "Test".

var (
caller_id uintptr
testName string
file string
)
callers := runtime.Callers(0, callStack)

var x int
for ; x < callers; x++ {
caller_id, file, _, _ = runtime.Caller(x)
if strings.HasSuffix(file, "test.go") {
break
}
}

for ; x < callers; x++ {
caller_id, _, _, _ = runtime.Caller(x)
packageAndTestName := runtime.FuncForPC(caller_id).Name()
parts := strings.Split(packageAndTestName, ".")
testName = parts[len(parts)-1]
if strings.HasPrefix(testName, "Test") {
break
}
}

if testName == "" {
testName = "<unkown test method name>" // panic?
}
return fmt.Sprintf("%s---%s", testName, file)
}

const maxStackDepth = 100 // This had better be enough...

var callStack []uintptr = make([]uintptr, maxStackDepth, maxStackDepth)
27 changes: 6 additions & 21 deletions convey/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package convey

import (
"github.com/smartystreets/goconvey/execution"
"github.com/smartystreets/goconvey/gotest"
"github.com/smartystreets/goconvey/reporting"
)

Expand Down Expand Up @@ -42,33 +41,22 @@ func SkipConvey(items ...interface{}) {

func register(entry *execution.Registration) {
if entry.IsTopLevel() {
reporter := buildReporter()
runner := execution.NewRunner()
runner.UpgradeReporter(reporter)

runners[entry.File+entry.TestName] = runner
reporters[entry.File+entry.TestName] = reporter

runner := suites.Assign()
runner.Begin(entry)
runner.Run()
} else {
runner := runners[entry.File+entry.TestName]
runner.Register(entry)
suites.CurrentRunner().Register(entry)
}
}

func skipReport() {
file, _, testName := gotest.ResolveExternalCallerWithTestName()
reporter := reporters[file+testName]
reporter.Report(reporting.NewSkipReport())
suites.CurrentReporter().Report(reporting.NewSkipReport())
}

// Reset registers a cleanup function to be run after each Convey()
// in the same scope. See the examples package for a simple use case.
func Reset(action func()) {
file, _, testName := gotest.ResolveExternalCallerWithTestName()
runner := runners[file+testName]
runner.RegisterReset(execution.NewAction(action))
suites.CurrentRunner().RegisterReset(execution.NewAction(action))
}

// So is the means by which assertions are made against the system under test.
Expand All @@ -79,13 +67,10 @@ func Reset(action func()) {
// See the examples package for use cases and the assertions package for
// documentation on specific assertion methods.
func So(actual interface{}, assert assertion, expected ...interface{}) {
file, _, testName := gotest.ResolveExternalCallerWithTestName()
reporter := reporters[file+testName]

if result := assert(actual, expected...); result == assertionSuccess {
reporter.Report(reporting.NewSuccessReport())
suites.CurrentReporter().Report(reporting.NewSuccessReport())
} else {
reporter.Report(reporting.NewFailureReport(result))
suites.CurrentReporter().Report(reporting.NewFailureReport(result))
}
}

Expand Down
12 changes: 2 additions & 10 deletions convey/wiring.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ package convey
import (
"os"

"github.com/smartystreets/goconvey/execution"
"github.com/smartystreets/goconvey/reporting"
)

func init() {
parseFlags()
initializeState()
suites = NewSuiteContext()
}

// parseFlags parses the command line args manually because the go test tool,
Expand All @@ -30,11 +29,6 @@ func flagFound(flagValue string) bool {
return false
}

func initializeState() {
runners = make(map[string]execution.Runner)
reporters = make(map[string]reporting.Reporter)
}

func buildReporter() reporting.Reporter {
if testReporter != nil {
return testReporter
Expand All @@ -48,9 +42,7 @@ func buildReporter() reporting.Reporter {
}

var (
// both keyed by concat(fileName, testName)
runners map[string]execution.Runner
reporters map[string]reporting.Reporter
suites *SuiteContext

// only set by internal tests
testReporter reporting.Reporter
Expand Down
14 changes: 2 additions & 12 deletions execution/registration.go
Original file line number Diff line number Diff line change
@@ -1,36 +1,26 @@
package execution

import (
"fmt"

"github.com/smartystreets/goconvey/gotest"
)
import "github.com/smartystreets/goconvey/gotest"

type Registration struct {
Situation string
Action *Action
Test gotest.T
File string
Line int
TestName string
}

func (self *Registration) IsTopLevel() bool {
return self.Test != nil
}

func (self *Registration) KeyName() string {
return fmt.Sprintf("%s:%s", self.File, self.TestName)
}

func NewRegistration(situation string, action *Action, test gotest.T) *Registration {
file, line, testName := gotest.ResolveExternalCallerWithTestName()
file, line, _ := gotest.ResolveExternalCaller()
self := &Registration{}
self.Situation = situation
self.Action = action
self.Test = test
self.File = file
self.Line = line
self.TestName = testName
return self
}
29 changes: 0 additions & 29 deletions gotest/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,35 +29,6 @@ func ResolveExternalCaller() (file string, line int, name string) {
return // panic?
}

// Much like ResolveExternalCaller, but goes a bit deeper to get the test method name.
func ResolveExternalCallerWithTestName() (file string, line int, testName string) {
// TODO: It turns out the more robust solution is to manually parse the debug.Stack()

var caller_id uintptr
callers := runtime.Callers(0, callStack)

var x int
for ; x < callers; x++ {
caller_id, file, line, _ = runtime.Caller(x)
if strings.HasSuffix(file, "test.go") {
break
}
}

for ; x < callers; x++ {
caller_id, _, _, _ = runtime.Caller(x)
packageAndTestName := runtime.FuncForPC(caller_id).Name()
parts := strings.Split(packageAndTestName, ".")
testName = parts[len(parts)-1]
if strings.HasPrefix(testName, "Test") {
return
}
}

testName = "<unkown test method name>"
return // panic?
}

const maxStackDepth = 100 // This had better be enough...

var callStack []uintptr = make([]uintptr, maxStackDepth, maxStackDepth)
6 changes: 2 additions & 4 deletions reporting/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,14 @@ func BuildDotReporter() Reporter {
return NewReporters(
NewGoTestReporter(),
NewDotReporter(out),
NewProblemReporter(out),
NewStatisticsReporter(out))
NewProblemReporter(out))
}
func BuildStoryReporter() Reporter {
out := printing.NewPrinter(printing.NewConsole())
return NewReporters(
NewGoTestReporter(),
NewStoryReporter(out),
NewProblemReporter(out),
NewStatisticsReporter(out))
NewProblemReporter(out))
}

var (
Expand Down
1 change: 0 additions & 1 deletion reporting/problems.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ func (self *problem) Report(report *AssertionResult) {
func (self *problem) Exit() {}

func (self *problem) EndStory() {
self.out.Println("")
self.show(self.showErrors, redColor)
self.show(self.showFailures, yellowColor)
self.prepareForNextStory()
Expand Down
1 change: 1 addition & 0 deletions reporting/story.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func (self *story) Exit() {

func (self *story) EndStory() {
self.titlesById = make(map[string]string)
self.out.Println("\n")
}

func NewStoryReporter(out *printing.Printer) *story {
Expand Down

0 comments on commit aca3c33

Please sign in to comment.