Skip to content

Commit

Permalink
Stacktrace refinements
Browse files Browse the repository at this point in the history
  • Loading branch information
gavv committed Jun 2, 2023
1 parent 7c797ad commit ca5a5dd
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 114 deletions.
4 changes: 2 additions & 2 deletions assertion.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,8 @@ type AssertionFailure struct {
// Allowed delta between actual and expected
Delta *AssertionValue

// Stacktrace of a fail
Stacktrace []CallerInfo
// Stacktrace of the failure
Stacktrace []StacktraceEntry
}

// AssertionValue holds expected or actual value
Expand Down
41 changes: 1 addition & 40 deletions chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package httpexpect

import (
"fmt"
"runtime"
"sync"
"testing"

Expand Down Expand Up @@ -451,7 +450,7 @@ func (c *chain) fail(failure AssertionFailure) {
failure.IsFatal = true
}

failure.Stacktrace = GetCallerInfo()
failure.Stacktrace = stacktrace()

c.failure = &failure
}
Expand Down Expand Up @@ -520,41 +519,3 @@ func isTestingTB(in AssertionHandler) bool {
}
return false
}

type CallerInfo struct {
FuncName string
File string
Line int
}

func GetCallerInfo() []CallerInfo {
callers := []CallerInfo{}
for i := 0; ; i++ {
pc, file, line, ok := runtime.Caller(i)
if !ok {
break
}

if file == "<autogenerated>" {
break
}

f := runtime.FuncForPC(pc)
if f == nil {
break
}
funcName := f.Name()

if funcName == "testing.tRunner" {
break
}

callers = append(callers, CallerInfo{
FuncName: funcName,
File: file,
Line: line,
})
}

return callers
}
5 changes: 4 additions & 1 deletion chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,10 @@ func TestChain_Stacktrace(t *testing.T) {

assert.True(t, opChain.failed())
assert.NotNil(t, handler.failure)
assert.NotEmpty(t, handler.failure.Stacktrace)

assert.GreaterOrEqual(t, len(handler.failure.Stacktrace), 2)
assert.Contains(t, handler.failure.Stacktrace[0].FuncName, "(*chain).fail")
assert.Contains(t, handler.failure.Stacktrace[1].FuncName, "TestChain_Stacktrace")
}

func TestChain_Reporting(t *testing.T) {
Expand Down
75 changes: 48 additions & 27 deletions formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"math"
"net/http/httputil"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
Expand Down Expand Up @@ -59,9 +60,6 @@ type DefaultFormatter struct {
// Exclude HTTP response from failure report.
DisableResponses bool

// Enables printing of stacktrace on failure
StacktraceMode StacktraceMode

// Thousand separator.
// Default is DigitSeparatorUnderscore.
DigitSeparator DigitSeparator
Expand All @@ -70,6 +68,10 @@ type DefaultFormatter struct {
// Default is FloatFormatAuto.
FloatFormat FloatFormat

// Defines whether to print stacktrace on failure and in what format.
// Default is StacktraceModeDisabled.
StacktraceMode StacktraceMode

// Colorization mode.
// Default is ColorModeAuto.
ColorMode ColorMode
Expand Down Expand Up @@ -116,16 +118,6 @@ func (f *DefaultFormatter) FormatFailure(
}
}

type StacktraceMode int

const (
// Unconditionally disable stacktrace.
StacktraceModeDisabled StacktraceMode = iota

// Format caller info as `at [function name]([file]:[line])`
StacktraceModeDefault
)

// DigitSeparator defines the separator used to format integers and floats.
type DigitSeparator int

Expand Down Expand Up @@ -164,6 +156,20 @@ const (
FloatFormatScientific
)

// StacktraceMode defines the format of stacktrace.
type StacktraceMode int

const (
// Don't print stacktrace.
StacktraceModeDisabled StacktraceMode = iota

// Standard, verbose format.
StacktraceModeStandard

// Compact format.
StacktraceModeCompact
)

// ColorMode defines how the text color is enabled.
type ColorMode int

Expand Down Expand Up @@ -323,12 +329,6 @@ func (f *DefaultFormatter) fillGeneral(
}
}

if f.LineWidth != 0 {
data.LineWidth = f.LineWidth
} else {
data.LineWidth = defaultLineWidth
}

switch f.ColorMode {
case ColorModeAuto:
switch colorMode() {
Expand All @@ -344,6 +344,12 @@ func (f *DefaultFormatter) fillGeneral(
case ColorModeNever:
data.EnableColors = false
}

if f.LineWidth != 0 {
data.LineWidth = f.LineWidth
} else {
data.LineWidth = defaultLineWidth
}
}

func (f *DefaultFormatter) fillErrors(
Expand Down Expand Up @@ -569,15 +575,30 @@ func (f *DefaultFormatter) fillStacktrace(
) {
data.Stacktrace = []string{}

if f.StacktraceMode == StacktraceModeDisabled {
return
}
if f.StacktraceMode == StacktraceModeDefault {
for _, call := range failure.Stacktrace {
formatted := fmt.Sprintf("at %s(%s:%d)", call.FuncName, call.File, call.Line)
data.Stacktrace = append(data.Stacktrace, formatted)
switch f.StacktraceMode {
case StacktraceModeDisabled:
break

case StacktraceModeStandard:
for _, entry := range failure.Stacktrace {
data.HaveStacktrace = true
data.Stacktrace = append(data.Stacktrace,
fmt.Sprintf("%s()\n\t%s:%d +0x%x",
entry.Func.Name(), entry.File, entry.Line, entry.FuncOffset))

}

case StacktraceModeCompact:
for _, entry := range failure.Stacktrace {
if entry.IsEntrypoint {
break
}
data.HaveStacktrace = true
data.Stacktrace = append(data.Stacktrace,
fmt.Sprintf("%s() at %s:%d (%s)",
entry.FuncName, filepath.Base(entry.File), entry.Line, entry.FuncPackage))

}
data.HaveStacktrace = len(failure.Stacktrace) != 0
}
}

Expand Down
88 changes: 44 additions & 44 deletions formatter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1028,76 +1028,76 @@ func TestFormatter_FormatDiff(t *testing.T) {
})
}

func TestFormatter_ColorMode(t *testing.T) {
func TestFormatter_StacktraceMode(t *testing.T) {
cases := []struct {
name string
mode ColorMode
mode StacktraceMode
want bool
}{
{
name: "always",
mode: ColorModeAlways,
name: "disabled",
mode: StacktraceModeDisabled,
want: false,
},
{
name: "standard",
mode: StacktraceModeStandard,
want: true,
},
{
name: "never",
mode: ColorModeNever,
want: false,
name: "compact",
mode: StacktraceModeCompact,
want: true,
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
f := DefaultFormatter{
ColorMode: tc.mode,
f := &DefaultFormatter{
StacktraceMode: tc.mode,
}
fd := f.buildFormatData(&AssertionContext{}, &AssertionFailure{
Stacktrace: stacktrace(),
})

if tc.want {
require.GreaterOrEqual(t, len(fd.Stacktrace), 1)
assert.Contains(t, fd.Stacktrace[0], "TestFormatter_StacktraceMode.func")
assert.Contains(t, fd.Stacktrace[0], "formatter_test.go")
assert.Contains(t, fd.Stacktrace[0], "github.com/gavv/httpexpect")
} else {
assert.NotNil(t, fd.Stacktrace)
assert.Equal(t, 0, len(fd.Stacktrace))
}
fd := f.buildFormatData(&AssertionContext{}, &AssertionFailure{})
assert.Equal(t, tc.want, fd.EnableColors)
})
}
}

func TestFormatter_Stacktrace(t *testing.T) {
df := &DefaultFormatter{
StacktraceMode: StacktraceModeDefault,
}
ctx := &AssertionContext{}

func TestFormatter_ColorMode(t *testing.T) {
cases := []struct {
callerInfo CallerInfo
want string
name string
mode ColorMode
want bool
}{
{
CallerInfo{
FuncName: "Foo()",
File: "formatter_test.go",
Line: 228,
},
"at Foo()(formatter_test.go:228)",
},
{
CallerInfo{
FuncName: "Bar()",
File: "formatter.go",
Line: 123,
},
"at Bar()(formatter.go:123)",
name: "always",
mode: ColorModeAlways,
want: true,
},
{
CallerInfo{
FuncName: "Buzz()",
File: "file.go",
Line: 5,
},
"at Buzz()(file.go:5)",
name: "never",
mode: ColorModeNever,
want: false,
},
}

for _, tc := range cases {
fl := &AssertionFailure{
Stacktrace: []CallerInfo{tc.callerInfo},
}
fd := df.buildFormatData(ctx, fl)
assert.Equal(t, []string{tc.want}, fd.Stacktrace)
t.Run(tc.name, func(t *testing.T) {
f := DefaultFormatter{
ColorMode: tc.mode,
}
fd := f.buildFormatData(&AssertionContext{}, &AssertionFailure{})
assert.Equal(t, tc.want, fd.EnableColors)
})
}
}
Loading

0 comments on commit ca5a5dd

Please sign in to comment.