diff --git a/assert/assertions.go b/assert/assertions.go index 104a0c936..461182210 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -24,6 +24,8 @@ import ( "github.com/stretchr/testify/assert/yaml" ) +const stackFrameBufferSize = 10 + //go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_format.go.tmpl" // TestingT is an interface wrapper around *testing.T @@ -212,19 +214,26 @@ the problem actually occurred in calling code.*/ func CallerInfo() []string { var pc uintptr - var ok bool var file string var line int var name string callers := []string{} - for i := 0; ; i++ { - pc, file, line, ok = runtime.Caller(i) - if !ok { - // The breaks below failed to terminate the loop, and we ran off the - // end of the call stack. - break - } + pcs := make([]uintptr, stackFrameBufferSize) + offset := 1 + n := runtime.Callers(offset, pcs) + maybeMore := n == stackFrameBufferSize + + if n == 0 { + return []string{} + } + frames := runtime.CallersFrames(pcs[:n]) + + for { + frame, more := frames.Next() + pc = frame.PC + file = frame.File + line = frame.Line // This is a huge edge case, but it will panic if this is the case, see #180 if file == "" { @@ -263,6 +272,23 @@ func CallerInfo() []string { isTest(name, "Example") { break } + + if !more { + // We know we already have less than a buffer's worth of frames + if !maybeMore { + break + } + offset += stackFrameBufferSize + n = runtime.Callers(offset, pcs) + if n == 0 { + break + } + + maybeMore = n == stackFrameBufferSize + + frames = runtime.CallersFrames(pcs[:n]) + } + } return callers diff --git a/mock/mock.go b/mock/mock.go index d5eb1ef55..ba66ad5e6 100644 --- a/mock/mock.go +++ b/mock/mock.go @@ -923,6 +923,8 @@ func (args Arguments) Is(objects ...interface{}) bool { return true } +type outputRenderer interface{} + // Diff gets a string describing the differences between the arguments // and the specified objects. // @@ -930,7 +932,7 @@ func (args Arguments) Is(objects ...interface{}) bool { func (args Arguments) Diff(objects []interface{}) (string, int) { // TODO: could return string as error and nil for No difference - output := "\n" + var outputBuilder strings.Builder var differences int maxArgCount := len(args) @@ -938,24 +940,35 @@ func (args Arguments) Diff(objects []interface{}) (string, int) { maxArgCount = len(objects) } - for i := 0; i < maxArgCount; i++ { + outputRenderers := []outputRenderer{} + + for j := 0; j < maxArgCount; j++ { + i := j var actual, expected interface{} - var actualFmt, expectedFmt string + var actualFmt, expectedFmt func() string if len(objects) <= i { actual = "(Missing)" - actualFmt = "(Missing)" + actualFmt = func() string { + return "(Missing)" + } } else { actual = objects[i] - actualFmt = fmt.Sprintf("(%[1]T=%[1]v)", actual) + actualFmt = func() string { + return fmt.Sprintf("(%[1]T=%[1]v)", actual) + } } if len(args) <= i { expected = "(Missing)" - expectedFmt = "(Missing)" + expectedFmt = func() string { + return "(Missing)" + } } else { expected = args[i] - expectedFmt = fmt.Sprintf("(%[1]T=%[1]v)", expected) + expectedFmt = func() string { + return fmt.Sprintf("(%[1]T=%[1]v)", expected) + } } if matcher, ok := expected.(argumentMatcher); ok { @@ -963,16 +976,20 @@ func (args Arguments) Diff(objects []interface{}) (string, int) { func() { defer func() { if r := recover(); r != nil { - actualFmt = fmt.Sprintf("panic in argument matcher: %v", r) + actualFmt = func() string { + return fmt.Sprintf("panic in argument matcher: %v", r) + } } }() matches = matcher.Matches(actual) }() if matches { - output = fmt.Sprintf("%s\t%d: PASS: %s matched by %s\n", output, i, actualFmt, matcher) + outputRenderers = append(outputRenderers, func() string { + return fmt.Sprintf("\t%d: PASS: %s matched by %s\n", i, actualFmt(), matcher) + }) } else { differences++ - output = fmt.Sprintf("%s\t%d: FAIL: %s not matched by %s\n", output, i, actualFmt, matcher) + outputRenderers = append(outputRenderers, fmt.Sprintf("\t%d: FAIL: %s not matched by %s\n", i, actualFmt(), matcher)) } } else { switch expected := expected.(type) { @@ -981,13 +998,13 @@ func (args Arguments) Diff(objects []interface{}) (string, int) { if reflect.TypeOf(actual).Name() != string(expected) && reflect.TypeOf(actual).String() != string(expected) { // not match differences++ - output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, expected, reflect.TypeOf(actual).Name(), actualFmt) + outputRenderers = append(outputRenderers, fmt.Sprintf("\t%d: FAIL: type %s != type %s - %s\n", i, expected, reflect.TypeOf(actual).Name(), actualFmt())) } case *IsTypeArgument: actualT := reflect.TypeOf(actual) if actualT != expected.t { differences++ - output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, expected.t.Name(), actualT.Name(), actualFmt) + outputRenderers = append(outputRenderers, fmt.Sprintf("\t%d: FAIL: type %s != type %s - %s\n", i, expected.t.Name(), actualT.Name(), actualFmt())) } case *FunctionalOptionsArgument: t := expected.value @@ -1001,26 +1018,30 @@ func (args Arguments) Diff(objects []interface{}) (string, int) { tName := reflect.TypeOf(t).Name() if name != reflect.TypeOf(actual).String() && tValue.Len() != 0 { differences++ - output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, tName, reflect.TypeOf(actual).Name(), actualFmt) + outputRenderers = append(outputRenderers, fmt.Sprintf("\t%d: FAIL: type %s != type %s - %s\n", i, tName, reflect.TypeOf(actual).Name(), actualFmt())) } else { if ef, af := assertOpts(t, actual); ef == "" && af == "" { // match - output = fmt.Sprintf("%s\t%d: PASS: %s == %s\n", output, i, tName, tName) + outputRenderers = append(outputRenderers, func() string { + return fmt.Sprintf("\t%d: PASS: %s == %s\n", i, tName, tName) + }) } else { // not match differences++ - output = fmt.Sprintf("%s\t%d: FAIL: %s != %s\n", output, i, af, ef) + outputRenderers = append(outputRenderers, fmt.Sprintf("\t%d: FAIL: %s != %s\n", i, af, ef)) } } default: if assert.ObjectsAreEqual(expected, Anything) || assert.ObjectsAreEqual(actual, Anything) || assert.ObjectsAreEqual(actual, expected) { // match - output = fmt.Sprintf("%s\t%d: PASS: %s == %s\n", output, i, actualFmt, expectedFmt) + outputRenderers = append(outputRenderers, func() string { + return fmt.Sprintf("\t%d: PASS: %s == %s\n", i, actualFmt(), expectedFmt()) + }) } else { // not match differences++ - output = fmt.Sprintf("%s\t%d: FAIL: %s != %s\n", output, i, actualFmt, expectedFmt) + outputRenderers = append(outputRenderers, fmt.Sprintf("\t%d: FAIL: %s != %s\n", i, actualFmt(), expectedFmt())) } } } @@ -1031,7 +1052,19 @@ func (args Arguments) Diff(objects []interface{}) (string, int) { return "No differences.", differences } - return output, differences + outputBuilder.WriteString("\n") + for _, renderer := range outputRenderers { + switch r := renderer.(type) { + case string: + outputBuilder.WriteString(r) + case func() string: + outputBuilder.WriteString(r()) + default: + panic("Invalid Output Renderer") + } + } + + return outputBuilder.String(), differences } // Assert compares the arguments with the specified objects and fails if