Skip to content

Commit

Permalink
Using additional, alternate correlation method for assertions. Fixes #…
Browse files Browse the repository at this point in the history
  • Loading branch information
mdwhatcott committed Feb 12, 2014
1 parent 2cd8258 commit 93e7460
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 23 deletions.
99 changes: 76 additions & 23 deletions convey/context.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package convey

import (
"errors"
"fmt"
"runtime"
"strconv"
"strings"
"sync"

"github.com/smartystreets/goconvey/execution"
Expand All @@ -13,31 +17,36 @@ import (
// that each test function has its own runner and reporter, and routes all live registrations
// to the appropriate runner/reporter.
type suiteContext struct {
runners map[string]execution.Runner
reporters map[string]reporting.Reporter
locations map[string]string // key: file:line; value: testName
runners map[string]execution.Runner // key: testName;
reporters map[string]reporting.Reporter // key: testName;
lock sync.Mutex
}

func (self *suiteContext) Run(entry *execution.Registration) {
key := resolveTestPackageAndFunctionName()
testName, location, _ := resolveAnchorConvey()

if self.currentRunner() != nil {
panic(execution.ExtraGoTest)
}

reporter := buildReporter()
runner := execution.NewRunner()
runner.UpgradeReporter(reporter)

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

runner.Begin(entry)
runner.Run()

self.lock.Lock()
delete(self.runners, key)
delete(self.reporters, key)
delete(self.locations, location)
delete(self.runners, testName)
delete(self.reporters, testName)
self.lock.Unlock()
}

Expand All @@ -53,46 +62,90 @@ func (self *suiteContext) CurrentRunner() execution.Runner {
func (self *suiteContext) currentRunner() execution.Runner {
self.lock.Lock()
defer self.lock.Unlock()
return self.runners[resolveTestPackageAndFunctionName()]
testName, _, _ := resolveAnchorConvey()
return self.runners[testName]
}

func (self *suiteContext) CurrentReporter() reporting.Reporter {
self.lock.Lock()
defer self.lock.Unlock()
return self.reporters[resolveTestPackageAndFunctionName()]
testName, _, err := resolveAnchorConvey()

if err != nil {
file, line := resolveTestFileAndLine()
closest := -1
for location, registeredTestName := range self.locations {
parts := strings.Split(location, ":")
locationFile := parts[0]
if locationFile != file {
continue
}

locationLine, err := strconv.Atoi(parts[1])
if err != nil || locationLine < line {
continue
}

if closest == -1 || locationLine < closest {
closest = locationLine
testName = registeredTestName
}
}
}

return self.reporters[testName]
}

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

// resolveTestPackageAndFunctionName traverses the call stack in reverse, looking for
// resolveAnchorConvey traverses the call stack in reverse, looking for
// the go testing harnass call ("testing.tRunner") and then grabs the very next entry,
// which represents the package under test and the test function name. Voila!
func resolveTestPackageAndFunctionName() string {
var (
callerId uintptr
found bool
)
// which represents the test function name, including the package name as a prefix.
// It also returns the file:line combo of the top-level Convey. Voila!
func resolveAnchorConvey() (testName, location string, err error) {
callers := runtime.Callers(0, callStack)

for y := callers; y > 0; y-- {
callerId, file, conveyLine, found := runtime.Caller(y)
if !found {
continue
}

if name := runtime.FuncForPC(callerId).Name(); name != goTestHarness {
continue
}

callerId, file, conveyLine, _ = runtime.Caller(y - 1)
testName = runtime.FuncForPC(callerId).Name()
location = fmt.Sprintf("%s:%d", file, conveyLine)
return
}
return "", "", errors.New("Can't resolve test method name! Are you calling Convey() from a `*_test.go` file and a `Test*` method (because you should be)?")
}

// resolveTestFileAndLine is used as a last-ditch effort to correlate an
// assertion with the right executor and runner.
func resolveTestFileAndLine() (file string, line int) {
callers := runtime.Callers(0, callStack)
var found bool

for y := callers; y > 0; y-- {
callerId, _, _, found = runtime.Caller(y)
_, file, line, found = runtime.Caller(y)
if !found {
continue
}

packageAndTestFunctionName := runtime.FuncForPC(callerId).Name()
if packageAndTestFunctionName == goTestHarness {
callerId, _, _, _ = runtime.Caller(y - 1)
name := runtime.FuncForPC(callerId).Name()
return name
if strings.HasSuffix(file, "_test.go") {
return
}
}
panic("Can't resolve test method name! Are you calling Convey() from a `*_test.go` file and a `Test*` method (because you should be)?")
return "", 0
}

const maxStackDepth = 100 // This had better be enough...
Expand Down
15 changes: 15 additions & 0 deletions convey/reporting_hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package convey

import (
"fmt"
"net/http"
"net/http/httptest"
"path"
"runtime"
"strconv"
Expand Down Expand Up @@ -175,6 +177,19 @@ func TestIterativeConveysReported(t *testing.T) {
expectEqual(t, "Begin|A|0|Success|Exit|Exit|A|1|Success|Exit|Exit|A|2|Success|Exit|Exit|End", myReporter.wholeStory())
}

func TestEmbeddedAssertionReported(t *testing.T) {
myReporter, test := setupFakeReporter()

Convey("A", test, func() {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
So(r.FormValue("msg"), ShouldEqual, "ping")
}))
http.DefaultClient.Get(ts.URL + "?msg=ping")
})

expectEqual(t, "Begin|A|Success|Exit|End", myReporter.wholeStory())
}

func expectEqual(t *testing.T, expected interface{}, actual interface{}) {
if expected != actual {
_, file, line, _ := runtime.Caller(1)
Expand Down

0 comments on commit 93e7460

Please sign in to comment.