From aca3c331854ec3b75db4f795e2dc29e299c893f8 Mon Sep 17 00:00:00 2001 From: Michael Whatcott Date: Fri, 31 Jan 2014 10:02:18 -0700 Subject: [PATCH] More thread-safe execution; Reporting no longer reports stats to the console; --- convey/context.go | 91 +++++++++++++++++++++++++++++++++++++++ convey/doc.go | 27 +++--------- convey/wiring.go | 12 +----- execution/registration.go | 14 +----- gotest/utils.go | 29 ------------- reporting/init.go | 6 +-- reporting/problems.go | 1 - reporting/story.go | 1 + 8 files changed, 104 insertions(+), 77 deletions(-) create mode 100644 convey/context.go diff --git a/convey/context.go b/convey/context.go new file mode 100644 index 00000000..08d93787 --- /dev/null +++ b/convey/context.go @@ -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 = "" // panic? + } + return fmt.Sprintf("%s---%s", testName, file) +} + +const maxStackDepth = 100 // This had better be enough... + +var callStack []uintptr = make([]uintptr, maxStackDepth, maxStackDepth) diff --git a/convey/doc.go b/convey/doc.go index 37fcc64f..d0203847 100644 --- a/convey/doc.go +++ b/convey/doc.go @@ -5,7 +5,6 @@ package convey import ( "github.com/smartystreets/goconvey/execution" - "github.com/smartystreets/goconvey/gotest" "github.com/smartystreets/goconvey/reporting" ) @@ -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. @@ -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)) } } diff --git a/convey/wiring.go b/convey/wiring.go index 21c79e41..3c72940d 100644 --- a/convey/wiring.go +++ b/convey/wiring.go @@ -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, @@ -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 @@ -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 diff --git a/execution/registration.go b/execution/registration.go index 60bd5b86..d23737f0 100644 --- a/execution/registration.go +++ b/execution/registration.go @@ -1,10 +1,6 @@ package execution -import ( - "fmt" - - "github.com/smartystreets/goconvey/gotest" -) +import "github.com/smartystreets/goconvey/gotest" type Registration struct { Situation string @@ -12,25 +8,19 @@ type Registration struct { 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 } diff --git a/gotest/utils.go b/gotest/utils.go index 67e7ea26..546f18c9 100644 --- a/gotest/utils.go +++ b/gotest/utils.go @@ -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 = "" - return // panic? -} - const maxStackDepth = 100 // This had better be enough... var callStack []uintptr = make([]uintptr, maxStackDepth, maxStackDepth) diff --git a/reporting/init.go b/reporting/init.go index 4380996b..0b296220 100644 --- a/reporting/init.go +++ b/reporting/init.go @@ -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 ( diff --git a/reporting/problems.go b/reporting/problems.go index 91f13832..329f57a3 100644 --- a/reporting/problems.go +++ b/reporting/problems.go @@ -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() diff --git a/reporting/story.go b/reporting/story.go index b1cf3d40..7356adfc 100644 --- a/reporting/story.go +++ b/reporting/story.go @@ -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 {