@@ -281,6 +281,10 @@ var (
281281 numFailed uint32 // number of test failures
282282)
283283
284+ // The maximum number of stack frames to go through when skipping helper functions for
285+ // the purpose of decorating log messages.
286+ const maxStackLen = 50
287+
284288// common holds the elements common between T and B and
285289// captures common methods such as Errorf.
286290type common struct {
@@ -301,6 +305,7 @@ type common struct {
301305
302306 parent * common
303307 level int // Nesting depth of test or benchmark.
308+ creator []uintptr // If level > 0, the stack trace at the point where the parent called t.Run.
304309 name string // Name of test or benchmark.
305310 start time.Time // Time test or benchmark started
306311 duration time.Duration
@@ -327,48 +332,75 @@ func Verbose() bool {
327332}
328333
329334// frameSkip searches, starting after skip frames, for the first caller frame
330- // in a function not marked as a helper and returns the frames to skip
331- // to reach that site. The search stops if it finds a tRunner function that
332- // was the entry point into the test.
335+ // in a function not marked as a helper and returns that frame.
336+ // The search stops if it finds a tRunner function that
337+ // was the entry point into the test and the test is not a subtest .
333338// This function must be called with c.mu held.
334- func (c * common ) frameSkip (skip int ) int {
335- if c .helpers == nil {
336- return skip
337- }
338- var pc [50 ]uintptr
339+ func (c * common ) frameSkip (skip int ) runtime.Frame {
340+ // If the search continues into the parent test, we'll have to hold
341+ // its mu temporarily. If we then return, we need to unlock it.
342+ shouldUnlock := false
343+ defer func () {
344+ if shouldUnlock {
345+ c .mu .Unlock ()
346+ }
347+ }()
348+ var pc [maxStackLen ]uintptr
339349 // Skip two extra frames to account for this function
340350 // and runtime.Callers itself.
341351 n := runtime .Callers (skip + 2 , pc [:])
342352 if n == 0 {
343353 panic ("testing: zero callers found" )
344354 }
345355 frames := runtime .CallersFrames (pc [:n ])
346- var frame runtime.Frame
347- more := true
348- for i := 0 ; more ; i ++ {
356+ var firstFrame , prevFrame , frame runtime.Frame
357+ for more := true ; more ; prevFrame = frame {
349358 frame , more = frames .Next ()
359+ if firstFrame .PC == 0 {
360+ firstFrame = frame
361+ }
350362 if frame .Function == c .runner {
351363 // We've gone up all the way to the tRunner calling
352364 // the test function (so the user must have
353365 // called tb.Helper from inside that test function).
354- // Only skip up to the test function itself.
355- return skip + i - 1
366+ // If this is a top-level test, only skip up to the test function itself.
367+ // If we're in a subtest, continue searching in the parent test,
368+ // starting from the point of the call to Run which created this subtest.
369+ if c .level > 1 {
370+ frames = runtime .CallersFrames (c .creator )
371+ parent := c .parent
372+ // We're no longer looking at the current c after this point,
373+ // so we should unlock its mu, unless it's the original receiver,
374+ // in which case our caller doesn't expect us to do that.
375+ if shouldUnlock {
376+ c .mu .Unlock ()
377+ }
378+ c = parent
379+ // Remember to unlock c.mu when we no longer need it, either
380+ // because we went up another nesting level, or because we
381+ // returned.
382+ shouldUnlock = true
383+ c .mu .Lock ()
384+ continue
385+ }
386+ return prevFrame
356387 }
357388 if _ , ok := c .helpers [frame .Function ]; ! ok {
358389 // Found a frame that wasn't inside a helper function.
359- return skip + i
390+ return frame
360391 }
361392 }
362- return skip
393+ return firstFrame
363394}
364395
365396// decorate prefixes the string with the file and line of the call site
366397// and inserts the final newline if needed and indentation tabs for formatting.
367398// This function must be called with c.mu held.
368399func (c * common ) decorate (s string ) string {
369- skip := c .frameSkip (3 ) // decorate + log + public function.
370- _ , file , line , ok := runtime .Caller (skip )
371- if ok {
400+ frame := c .frameSkip (3 ) // decorate + log + public function.
401+ file := frame .File
402+ line := frame .Line
403+ if file != "" {
372404 // Truncate file name at last file name separator.
373405 if index := strings .LastIndex (file , "/" ); index >= 0 {
374406 file = file [index + 1 :]
@@ -377,6 +409,8 @@ func (c *common) decorate(s string) string {
377409 }
378410 } else {
379411 file = "???"
412+ }
413+ if line == 0 {
380414 line = 1
381415 }
382416 buf := new (strings.Builder )
@@ -642,8 +676,6 @@ func (c *common) Skipped() bool {
642676// Helper marks the calling function as a test helper function.
643677// When printing file and line information, that function will be skipped.
644678// Helper may be called simultaneously from multiple goroutines.
645- // Helper has no effect if it is called directly from a TestXxx/BenchmarkXxx
646- // function or a subtest/sub-benchmark function.
647679func (c * common ) Helper () {
648680 c .mu .Lock ()
649681 defer c .mu .Unlock ()
@@ -810,13 +842,19 @@ func (t *T) Run(name string, f func(t *T)) bool {
810842 if ! ok || shouldFailFast () {
811843 return true
812844 }
845+ // Record the stack trace at the point of this call so that if the subtest
846+ // function - which runs in a separate stack - is marked as a helper, we can
847+ // continue walking the stack into the parent test.
848+ var pc [maxStackLen ]uintptr
849+ n := runtime .Callers (2 , pc [:])
813850 t = & T {
814851 common : common {
815852 barrier : make (chan bool ),
816853 signal : make (chan bool ),
817854 name : testName ,
818855 parent : & t .common ,
819856 level : t .level + 1 ,
857+ creator : pc [:n ],
820858 chatty : t .chatty ,
821859 },
822860 context : t .context ,
0 commit comments