github.com/joeky888/godog@v0.7.9/stacktrace.go (about) 1 package godog 2 3 import ( 4 "fmt" 5 "go/build" 6 "io" 7 "path" 8 "path/filepath" 9 "runtime" 10 "strings" 11 ) 12 13 // Frame represents a program counter inside a stack frame. 14 type stackFrame uintptr 15 16 // pc returns the program counter for this frame; 17 // multiple frames may have the same PC value. 18 func (f stackFrame) pc() uintptr { return uintptr(f) - 1 } 19 20 // file returns the full path to the file that contains the 21 // function for this Frame's pc. 22 func (f stackFrame) file() string { 23 fn := runtime.FuncForPC(f.pc()) 24 if fn == nil { 25 return "unknown" 26 } 27 file, _ := fn.FileLine(f.pc()) 28 return file 29 } 30 31 func trimGoPath(file string) string { 32 for _, p := range filepath.SplitList(build.Default.GOPATH) { 33 file = strings.Replace(file, filepath.Join(p, "src")+string(filepath.Separator), "", 1) 34 } 35 return file 36 } 37 38 // line returns the line number of source code of the 39 // function for this Frame's pc. 40 func (f stackFrame) line() int { 41 fn := runtime.FuncForPC(f.pc()) 42 if fn == nil { 43 return 0 44 } 45 _, line := fn.FileLine(f.pc()) 46 return line 47 } 48 49 // Format formats the frame according to the fmt.Formatter interface. 50 // 51 // %s source file 52 // %d source line 53 // %n function name 54 // %v equivalent to %s:%d 55 // 56 // Format accepts flags that alter the printing of some verbs, as follows: 57 // 58 // %+s path of source file relative to the compile time GOPATH 59 // %+v equivalent to %+s:%d 60 func (f stackFrame) Format(s fmt.State, verb rune) { 61 funcname := func(name string) string { 62 i := strings.LastIndex(name, "/") 63 name = name[i+1:] 64 i = strings.Index(name, ".") 65 return name[i+1:] 66 } 67 68 switch verb { 69 case 's': 70 switch { 71 case s.Flag('+'): 72 pc := f.pc() 73 fn := runtime.FuncForPC(pc) 74 if fn == nil { 75 io.WriteString(s, "unknown") 76 } else { 77 file, _ := fn.FileLine(pc) 78 fmt.Fprintf(s, "%s\n\t%s", fn.Name(), trimGoPath(file)) 79 } 80 default: 81 io.WriteString(s, path.Base(f.file())) 82 } 83 case 'd': 84 fmt.Fprintf(s, "%d", f.line()) 85 case 'n': 86 name := runtime.FuncForPC(f.pc()).Name() 87 io.WriteString(s, funcname(name)) 88 case 'v': 89 f.Format(s, 's') 90 io.WriteString(s, ":") 91 f.Format(s, 'd') 92 } 93 } 94 95 // stack represents a stack of program counters. 96 type stack []uintptr 97 98 func (s *stack) Format(st fmt.State, verb rune) { 99 switch verb { 100 case 'v': 101 switch { 102 case st.Flag('+'): 103 for _, pc := range *s { 104 f := stackFrame(pc) 105 fmt.Fprintf(st, "\n%+v", f) 106 } 107 } 108 } 109 } 110 111 func callStack() *stack { 112 const depth = 32 113 var pcs [depth]uintptr 114 n := runtime.Callers(3, pcs[:]) 115 var st stack = pcs[0:n] 116 return &st 117 } 118 119 // fundamental is an error that has a message and a stack, but no caller. 120 type traceError struct { 121 msg string 122 *stack 123 } 124 125 func (f *traceError) Error() string { return f.msg } 126 127 func (f *traceError) Format(s fmt.State, verb rune) { 128 switch verb { 129 case 'v': 130 if s.Flag('+') { 131 io.WriteString(s, f.msg) 132 f.stack.Format(s, verb) 133 return 134 } 135 fallthrough 136 case 's': 137 io.WriteString(s, f.msg) 138 case 'q': 139 fmt.Fprintf(s, "%q", f.msg) 140 } 141 }