Skip to content
Permalink
Browse files

Add StackMode to make stack traces configurable

  • Loading branch information
miketonks committed Nov 4, 2019
1 parent 505e419 commit 0950975264d1dbc1919736717833c160f44aeff3
@@ -79,6 +79,7 @@ type context struct {

focus bool
failureMode FailureMode
stackMode StackMode
}

// rootConvey is the main entry point to a test suite. This is called when
@@ -101,6 +102,7 @@ func rootConvey(items ...interface{}) {

focus: entry.Focus,
failureMode: defaultFailureMode.combine(entry.FailMode),
stackMode: defaultStackMode.combine(entry.StackMode),
}
ctxMgr.SetValues(gls.Values{nodeKey: ctx}, func() {
ctx.reporter.BeginStory(reporting.NewStoryReport(entry.Test))
@@ -154,6 +156,7 @@ func (ctx *context) Convey(items ...interface{}) {

focus: entry.Focus,
failureMode: ctx.failureMode.combine(entry.FailMode),
stackMode: ctx.stackMode.combine(entry.StackMode),
}
ctx.children[entry.Situation] = inner_ctx
}
@@ -173,7 +176,7 @@ func (ctx *context) So(actual interface{}, assert assertion, expected ...interfa
if result := assert(actual, expected...); result == assertionSuccess {
ctx.assertionReport(reporting.NewSuccessReport())
} else {
ctx.assertionReport(reporting.NewFailureReport(result))
ctx.assertionReport(reporting.NewFailureReport(result, ctx.shouldShowStack()))
}
}

@@ -206,6 +209,10 @@ func (c *context) shouldVisit() bool {
return !c.complete && *c.expectChildRun
}

func (c *context) shouldShowStack() bool {
return c.stackMode == StackFail
}

// conveyInner is the function which actually executes the user's anonymous test
// function body. At this point, Convey or RootConvey has decided that this
// function should actually run.
@@ -14,14 +14,16 @@ type suite struct {
Focus bool
Func func(C) // nil means skipped
FailMode FailureMode
StackMode StackMode
}

func newSuite(situation string, failureMode FailureMode, f func(C), test t, specifier actionSpecifier) *suite {
func newSuite(situation string, failureMode FailureMode, stackMode StackMode, f func(C), test t, specifier actionSpecifier) *suite {
ret := &suite{
Situation: situation,
Test: test,
Func: f,
FailMode: failureMode,
StackMode: stackMode,
}
switch specifier {
case skipConvey:
@@ -36,14 +38,15 @@ func discover(items []interface{}) *suite {
name, items := parseName(items)
test, items := parseGoTest(items)
failure, items := parseFailureMode(items)
stack, items := parseStackMode(items)
action, items := parseAction(items)
specifier, items := parseSpecifier(items)

if len(items) != 0 {
conveyPanic(parseError)
}

return newSuite(name, failure, action, test, specifier)
return newSuite(name, failure, stack, action, test, specifier)
}
func item(items []interface{}) interface{} {
if len(items) == 0 {
@@ -70,6 +73,12 @@ func parseFailureMode(items []interface{}) (FailureMode, []interface{}) {
}
return FailureInherits, items
}
func parseStackMode(items []interface{}) (StackMode, []interface{}) {
if mode, parsed := item(items).(StackMode); parsed {
return mode, items[1:]
}
return StackInherits, items
}
func parseAction(items []interface{}) (func(C), []interface{}) {
switch x := item(items).(type) {
case nil:
@@ -100,4 +109,4 @@ type t interface {
Fail()
}

const parseError = "You must provide a name (string), then a *testing.T (if in outermost scope), an optional FailureMode, and then an action (func())."
const parseError = "You must provide a name (string), then a *testing.T (if in outermost scope), an optional FailureMode and / or StackMode, and then an action (func())."
@@ -135,6 +135,10 @@ func SkipSo(stuff ...interface{}) {
// if their assertion fails. See constants further down for acceptable values
type FailureMode string

// StackMode is a type which determines whether the So() blocks should report
// stack traces their assertion fails. See constants further down for acceptable values
type StackMode string

const (

// FailureContinues is a failure mode which prevents failing
@@ -151,6 +155,19 @@ const (
// default to the failure-mode of the parent block. You should never
// need to specify this mode in your tests..
FailureInherits FailureMode = "inherits"

// StackError is a stack mode which tells Convey to print stack traces
// only for errors and not for test failures
StackError StackMode = "error"

// StackFail is a stack mode which tells Convey to print stack traces
// for both errors and test failures
StackFail StackMode = "fail"

// StackInherits is the default setting for stack-mode, it will
// default to the stack-mode of the parent block. You should never
// need to specify this mode in your tests..
StackInherits StackMode = "inherits"
)

func (f FailureMode) combine(other FailureMode) FailureMode {
@@ -164,7 +181,7 @@ var defaultFailureMode FailureMode = FailureHalts

// SetDefaultFailureMode allows you to specify the default failure mode
// for all Convey blocks. It is meant to be used in an init function to
// allow the default mode to be changdd across all tests for an entire packgae
// allow the default mode to be changed across all tests for an entire packgae
// but it can be used anywhere.
func SetDefaultFailureMode(mode FailureMode) {
if mode == FailureContinues || mode == FailureHalts {
@@ -174,6 +191,27 @@ func SetDefaultFailureMode(mode FailureMode) {
}
}

func (s StackMode) combine(other StackMode) StackMode {
if other == StackInherits {
return s
}
return other
}

var defaultStackMode StackMode = StackError

// SetDefaultStackMode allows you to specify the default stack mode
// for all Convey blocks. It is meant to be used in an init function to
// allow the default mode to be changed across all tests for an entire packgae
// but it can be used anywhere.
func SetDefaultStackMode(mode StackMode) {
if mode == StackError || mode == StackFail {
defaultStackMode = mode
} else {
panic("You may only use the constants named 'StackError' and 'StackFail' as default stack modes.")
}
}

//////////////////////////////////// Print functions ////////////////////////////////////

// Print is analogous to fmt.Print (and it even calls fmt.Print). It ensures that
@@ -12,7 +12,7 @@ func TestDotReporterAssertionPrinting(t *testing.T) {
reporter := NewDotReporter(printer)

reporter.Report(NewSuccessReport())
reporter.Report(NewFailureReport("failed"))
reporter.Report(NewFailureReport("failed", false))
reporter.Report(NewErrorReport(errors.New("error")))
reporter.Report(NewSkipReport())

@@ -17,7 +17,7 @@ func TestReporterReceivesFailureReport(t *testing.T) {
reporter := NewGoTestReporter()
test := new(fakeTest)
reporter.BeginStory(NewStoryReport(test))
reporter.Report(NewFailureReport("This is a failure."))
reporter.Report(NewFailureReport("This is a failure.", false))

if !test.failed {
t.Errorf("Test should have been marked as failed (but it wasn't).")
@@ -56,7 +56,8 @@ var (
dotError = "E"
dotSkip = "S"
errorTemplate = "* %s \nLine %d: - %v \n%s\n"
failureTemplate = "* %s \nLine %d:\n%s\n%s\n"
failureTemplate = "* %s \nLine %d:\n%s\n"
stackTemplate = "%s\n"
)

var (
@@ -53,7 +53,10 @@ func (self *problem) showFailures() {
self.out.Println("\nFailures:\n")
self.out.Indent()
}
self.out.Println(failureTemplate, f.File, f.Line, f.Failure, f.StackTrace)
self.out.Println(failureTemplate, f.File, f.Line, f.Failure)
if f.StackTrace != "" {
self.out.Println(stackTemplate, f.StackTrace)
}
}
}

@@ -19,7 +19,7 @@ func TestNoopProblemReporterActions(t *testing.T) {

func TestReporterPrintsFailuresAndErrorsAtTheEndOfTheStory(t *testing.T) {
file, reporter := setup()
reporter.Report(NewFailureReport("failed"))
reporter.Report(NewFailureReport("failed", false))
reporter.Report(NewErrorReport("error"))
reporter.Report(NewSuccessReport())
reporter.EndStory()
@@ -97,10 +97,12 @@ type AssertionResult struct {
Skipped bool
}

func NewFailureReport(failure string) *AssertionResult {
func NewFailureReport(failure string, showStack bool) *AssertionResult {
report := new(AssertionResult)
report.File, report.Line = caller()
report.StackTrace = stackTrace()
if showStack {
report.StackTrace = stackTrace()
}
parseFailure(failure, report)
return report
}
@@ -0,0 +1,137 @@
package convey

import (
"fmt"
"strings"
"testing"

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

func TestStackTrace(t *testing.T) {
file, test := setupFileReporter()

Convey("A", test, func() {
So(1, ShouldEqual, 2)
})

if !strings.Contains(file.String(), "Failures:\n") {
t.Errorf("Expected errors, found none.")
}
if strings.Contains(file.String(), "goroutine ") {
t.Errorf("Found stack trace, expected none.")
}

Convey("A", test, StackFail, func() {
So(1, ShouldEqual, 2)
})

if !strings.Contains(file.String(), "goroutine ") {
t.Errorf("Expected stack trace, found none.")
}
}

func TestSetDefaultStackMode(t *testing.T) {
file, test := setupFileReporter()
SetDefaultStackMode(StackFail) // the default is normally StackError
defer SetDefaultStackMode(StackError)

Convey("A", test, func() {
So(1, ShouldEqual, 2)
})

if !strings.Contains(file.String(), "goroutine ") {
t.Errorf("Expected stack trace, found none.")
}
}

func TestStackModeMultipleInvocationInheritance(t *testing.T) {
file, test := setupFileReporter()

// initial convey should default to StaskError, so no stack trace
Convey("A", test, FailureContinues, func() {
So(1, ShouldEqual, 2)

// nested convey has explicit StaskFail, so should emit stack trace
Convey("B", StackFail, func() {
So(1, ShouldEqual, 2)
})
})

stackCount := strings.Count(file.String(), "goroutine ")
if stackCount != 1 {
t.Errorf("Expected 1 stack trace, found %d.", stackCount)
fmt.Printf("RESULT: %s \n", file.String())
}
}

func TestStackModeMultipleInvocationInheritance2(t *testing.T) {
file, test := setupFileReporter()

// Explicit StackFail, expect stack trace
Convey("A", test, FailureContinues, StackFail, func() {
So(1, ShouldEqual, 2)

// Nested Convey inherits StackFail, expect stack trace
Convey("B", func() {
So(1, ShouldEqual, 2)
})
})

stackCount := strings.Count(file.String(), "goroutine ")
if stackCount != 2 {
t.Errorf("Expected 2 stack traces, found %d.", stackCount)
}
}

func TestStackModeMultipleInvocationInheritance3(t *testing.T) {
file, test := setupFileReporter()

// Explicit StackFail, expect stack trace
Convey("A", test, FailureContinues, StackFail, func() {
So(1, ShouldEqual, 2)

// Nested Convey explicitly sets StackError, so no stack trace
Convey("B", StackError, func() {
So(1, ShouldEqual, 2)
})
})

stackCount := strings.Count(file.String(), "goroutine ")
if stackCount != 1 {
t.Errorf("Expected 1 stack trace1, found %d.", stackCount)
}
}

func setupFileReporter() (*memoryFile, *fakeGoTest) {
//monochrome()
file := newMemoryFile()
printer := reporting.NewPrinter(file)
reporter := reporting.NewProblemReporter(printer)
testReporter = reporter

return file, new(fakeGoTest)
}

////////////////// memoryFile ////////////////////

type memoryFile struct {
buffer string
}

func (self *memoryFile) Write(p []byte) (n int, err error) {
self.buffer += string(p)
return len(p), nil
}

func (self *memoryFile) String() string {
return self.buffer
}

func newMemoryFile() *memoryFile {
return new(memoryFile)
}

// func monochrome() {
// greenColor, yellowColor, redColor, resetColor = "", "", "", ""
// }

0 comments on commit 0950975

Please sign in to comment.
You can’t perform that action at this time.