...
1 package types
2
3 import (
4 "fmt"
5 "os"
6 "regexp"
7 "runtime"
8 "runtime/debug"
9 "strings"
10 "sync"
11 )
12
13 type CodeLocation struct {
14 FileName string `json:",omitempty"`
15 LineNumber int `json:",omitempty"`
16 FullStackTrace string `json:",omitempty"`
17 CustomMessage string `json:",omitempty"`
18 }
19
20 func (codeLocation CodeLocation) String() string {
21 if codeLocation.CustomMessage != "" {
22 return codeLocation.CustomMessage
23 }
24 return fmt.Sprintf("%s:%d", codeLocation.FileName, codeLocation.LineNumber)
25 }
26
27 func (codeLocation CodeLocation) ContentsOfLine() string {
28 if codeLocation.CustomMessage != "" {
29 return ""
30 }
31 contents, err := os.ReadFile(codeLocation.FileName)
32 if err != nil {
33 return ""
34 }
35 lines := strings.Split(string(contents), "\n")
36 if len(lines) < codeLocation.LineNumber {
37 return ""
38 }
39 return lines[codeLocation.LineNumber-1]
40 }
41
42 type codeLocationLocator struct {
43 pcs map[uintptr]bool
44 helpers map[string]bool
45 lock *sync.Mutex
46 }
47
48 func (c *codeLocationLocator) addHelper(pc uintptr) {
49 c.lock.Lock()
50 defer c.lock.Unlock()
51
52 if c.pcs[pc] {
53 return
54 }
55 c.lock.Unlock()
56 f := runtime.FuncForPC(pc)
57 c.lock.Lock()
58 if f == nil {
59 return
60 }
61 c.helpers[f.Name()] = true
62 c.pcs[pc] = true
63 }
64
65 func (c *codeLocationLocator) hasHelper(name string) bool {
66 c.lock.Lock()
67 defer c.lock.Unlock()
68 return c.helpers[name]
69 }
70
71 func (c *codeLocationLocator) getCodeLocation(skip int) CodeLocation {
72 pc := make([]uintptr, 40)
73 n := runtime.Callers(skip+2, pc)
74 if n == 0 {
75 return CodeLocation{}
76 }
77 pc = pc[:n]
78 frames := runtime.CallersFrames(pc)
79 for {
80 frame, more := frames.Next()
81 if !c.hasHelper(frame.Function) {
82 return CodeLocation{FileName: frame.File, LineNumber: frame.Line}
83 }
84 if !more {
85 break
86 }
87 }
88 return CodeLocation{}
89 }
90
91 var clLocator = &codeLocationLocator{
92 pcs: map[uintptr]bool{},
93 helpers: map[string]bool{},
94 lock: &sync.Mutex{},
95 }
96
97
98 func MarkAsHelper(optionalSkip ...int) {
99 skip := 1
100 if len(optionalSkip) > 0 {
101 skip += optionalSkip[0]
102 }
103 pc, _, _, ok := runtime.Caller(skip)
104 if ok {
105 clLocator.addHelper(pc)
106 }
107 }
108
109 func NewCustomCodeLocation(message string) CodeLocation {
110 return CodeLocation{
111 CustomMessage: message,
112 }
113 }
114
115 func NewCodeLocation(skip int) CodeLocation {
116 return clLocator.getCodeLocation(skip + 1)
117 }
118
119 func NewCodeLocationWithStackTrace(skip int) CodeLocation {
120 cl := clLocator.getCodeLocation(skip + 1)
121 cl.FullStackTrace = PruneStack(string(debug.Stack()), skip+1)
122 return cl
123 }
124
125
126
127
128
129
130
131
132 func PruneStack(fullStackTrace string, skip int) string {
133 stack := strings.Split(fullStackTrace, "\n")
134
135
136 if len(stack) > 0 && strings.HasPrefix(stack[0], "goroutine ") {
137
138 stack = stack[1:]
139 }
140
141
142 if len(stack) > 2*(skip+1) {
143 stack = stack[2*(skip+1):]
144 }
145 prunedStack := []string{}
146 if os.Getenv("GINKGO_PRUNE_STACK") == "FALSE" {
147 prunedStack = stack
148 } else {
149 re := regexp.MustCompile(`\/ginkgo\/|\/pkg\/testing\/|\/pkg\/runtime\/`)
150 for i := 0; i < len(stack)/2; i++ {
151
152 if !re.MatchString(stack[i*2+1]) {
153 prunedStack = append(prunedStack, stack[i*2])
154 prunedStack = append(prunedStack, stack[i*2+1])
155 }
156 }
157 }
158 return strings.Join(prunedStack, "\n")
159 }
160
View as plain text