Skip to content

Commit 3b8bd9b

Browse files
Merge pull request #1614 from DevotedHealth/mauclair-call-stack-perf
Call stack perf change for CallerInfo
2 parents 1e7fb58 + cfee234 commit 3b8bd9b

1 file changed

Lines changed: 56 additions & 38 deletions

File tree

assert/assertions.go

Lines changed: 56 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424
"github.com/stretchr/testify/assert/yaml"
2525
)
2626

27+
const stackFrameBufferSize = 10
28+
2729
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_format.go.tmpl"
2830

2931
// TestingT is an interface wrapper around *testing.T
@@ -212,57 +214,73 @@ the problem actually occurred in calling code.*/
212214
func CallerInfo() []string {
213215

214216
var pc uintptr
215-
var ok bool
216217
var file string
217218
var line int
218219
var name string
219220

220221
callers := []string{}
221-
for i := 0; ; i++ {
222-
pc, file, line, ok = runtime.Caller(i)
223-
if !ok {
224-
// The breaks below failed to terminate the loop, and we ran off the
225-
// end of the call stack.
226-
break
227-
}
222+
pcs := make([]uintptr, stackFrameBufferSize)
223+
offset := 1
228224

229-
// This is a huge edge case, but it will panic if this is the case, see #180
230-
if file == "<autogenerated>" {
231-
break
232-
}
225+
for {
226+
n := runtime.Callers(offset, pcs)
233227

234-
f := runtime.FuncForPC(pc)
235-
if f == nil {
236-
break
237-
}
238-
name = f.Name()
239-
240-
// testing.tRunner is the standard library function that calls
241-
// tests. Subtests are called directly by tRunner, without going through
242-
// the Test/Benchmark/Example function that contains the t.Run calls, so
243-
// with subtests we should break when we hit tRunner, without adding it
244-
// to the list of callers.
245-
if name == "testing.tRunner" {
228+
if n == 0 {
246229
break
247230
}
248231

249-
parts := strings.Split(file, "/")
250-
if len(parts) > 1 {
251-
filename := parts[len(parts)-1]
252-
dir := parts[len(parts)-2]
253-
if (dir != "assert" && dir != "mock" && dir != "require") || filename == "mock_test.go" {
254-
callers = append(callers, fmt.Sprintf("%s:%d", file, line))
232+
frames := runtime.CallersFrames(pcs[:n])
233+
234+
for {
235+
frame, more := frames.Next()
236+
pc = frame.PC
237+
file = frame.File
238+
line = frame.Line
239+
240+
// This is a huge edge case, but it will panic if this is the case, see #180
241+
if file == "<autogenerated>" {
242+
break
255243
}
256-
}
257244

258-
// Drop the package
259-
segments := strings.Split(name, ".")
260-
name = segments[len(segments)-1]
261-
if isTest(name, "Test") ||
262-
isTest(name, "Benchmark") ||
263-
isTest(name, "Example") {
264-
break
245+
f := runtime.FuncForPC(pc)
246+
if f == nil {
247+
break
248+
}
249+
name = f.Name()
250+
251+
// testing.tRunner is the standard library function that calls
252+
// tests. Subtests are called directly by tRunner, without going through
253+
// the Test/Benchmark/Example function that contains the t.Run calls, so
254+
// with subtests we should break when we hit tRunner, without adding it
255+
// to the list of callers.
256+
if name == "testing.tRunner" {
257+
break
258+
}
259+
260+
parts := strings.Split(file, "/")
261+
if len(parts) > 1 {
262+
filename := parts[len(parts)-1]
263+
dir := parts[len(parts)-2]
264+
if (dir != "assert" && dir != "mock" && dir != "require") || filename == "mock_test.go" {
265+
callers = append(callers, fmt.Sprintf("%s:%d", file, line))
266+
}
267+
}
268+
269+
// Drop the package
270+
segments := strings.Split(name, ".")
271+
name = segments[len(segments)-1]
272+
if isTest(name, "Test") ||
273+
isTest(name, "Benchmark") ||
274+
isTest(name, "Example") {
275+
break
276+
}
277+
if !more {
278+
break
279+
}
265280
}
281+
// We know we already have less than a buffer's worth of frames
282+
offset += stackFrameBufferSize
283+
266284
}
267285

268286
return callers

0 commit comments

Comments
 (0)