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 }