github.com/markfisherdeloitte/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  }