github.com/onsi/ginkgo@v1.16.6-0.20211118180735-4e1925ba4c95/types/code_location.go (about)

     1  package types
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"regexp"
     7  	"runtime"
     8  	"runtime/debug"
     9  	"strings"
    10  )
    11  
    12  type CodeLocation struct {
    13  	FileName       string `json:",omitempty"`
    14  	LineNumber     int    `json:",omitempty"`
    15  	FullStackTrace string `json:",omitempty"`
    16  	CustomMessage  string `json:",omitempty"`
    17  }
    18  
    19  func (codeLocation CodeLocation) String() string {
    20  	if codeLocation.CustomMessage != "" {
    21  		return codeLocation.CustomMessage
    22  	}
    23  	return fmt.Sprintf("%s:%d", codeLocation.FileName, codeLocation.LineNumber)
    24  }
    25  
    26  func (codeLocation CodeLocation) ContentsOfLine() string {
    27  	if codeLocation.CustomMessage != "" {
    28  		return ""
    29  	}
    30  	contents, err := os.ReadFile(codeLocation.FileName)
    31  	if err != nil {
    32  		return ""
    33  	}
    34  	lines := strings.Split(string(contents), "\n")
    35  	if len(lines) < codeLocation.LineNumber {
    36  		return ""
    37  	}
    38  	return lines[codeLocation.LineNumber-1]
    39  }
    40  
    41  func NewCustomCodeLocation(message string) CodeLocation {
    42  	return CodeLocation{
    43  		CustomMessage: message,
    44  	}
    45  }
    46  
    47  func NewCodeLocation(skip int) CodeLocation {
    48  	_, file, line, _ := runtime.Caller(skip + 1)
    49  	return CodeLocation{FileName: file, LineNumber: line}
    50  }
    51  
    52  func NewCodeLocationWithStackTrace(skip int) CodeLocation {
    53  	_, file, line, _ := runtime.Caller(skip + 1)
    54  	stackTrace := PruneStack(string(debug.Stack()), skip+1)
    55  	return CodeLocation{FileName: file, LineNumber: line, FullStackTrace: stackTrace}
    56  }
    57  
    58  // PruneStack removes references to functions that are internal to Ginkgo
    59  // and the Go runtime from a stack string and a certain number of stack entries
    60  // at the beginning of the stack. The stack string has the format
    61  // as returned by runtime/debug.Stack. The leading goroutine information is
    62  // optional and always removed if present. Beware that runtime/debug.Stack
    63  // adds itself as first entry, so typically skip must be >= 1 to remove that
    64  // entry.
    65  func PruneStack(fullStackTrace string, skip int) string {
    66  	stack := strings.Split(fullStackTrace, "\n")
    67  	// Ensure that the even entries are the method names and the
    68  	// the odd entries the source code information.
    69  	if len(stack) > 0 && strings.HasPrefix(stack[0], "goroutine ") {
    70  		// Ignore "goroutine 29 [running]:" line.
    71  		stack = stack[1:]
    72  	}
    73  	// The "+1" is for skipping over the initial entry, which is
    74  	// runtime/debug.Stack() itself.
    75  	if len(stack) > 2*(skip+1) {
    76  		stack = stack[2*(skip+1):]
    77  	}
    78  	prunedStack := []string{}
    79  	if os.Getenv("GINKGO_PRUNE_STACK") == "FALSE" {
    80  		prunedStack = stack
    81  	} else {
    82  		re := regexp.MustCompile(`\/ginkgo\/|\/pkg\/testing\/|\/pkg\/runtime\/`)
    83  		for i := 0; i < len(stack)/2; i++ {
    84  			// We filter out based on the source code file name.
    85  			if !re.Match([]byte(stack[i*2+1])) {
    86  				prunedStack = append(prunedStack, stack[i*2])
    87  				prunedStack = append(prunedStack, stack[i*2+1])
    88  			}
    89  		}
    90  	}
    91  	return strings.Join(prunedStack, "\n")
    92  }