diff --git a/stack.go b/stack.go index 6b1f289..2205298 100644 --- a/stack.go +++ b/stack.go @@ -13,30 +13,59 @@ type Frame uintptr // pc returns the program counter for this frame; // multiple frames may have the same PC value. -func (f Frame) pc() uintptr { return uintptr(f) - 1 } +func (f Frame) PC() uintptr { return uintptr(f) - 1 } -// file returns the full path to the file that contains the -// function for this Frame's pc. -func (f Frame) file() string { - fn := runtime.FuncForPC(f.pc()) +// FunctionFileLine returns the function name, full path, and line number +// for the Frame's pc. +func (f Frame) FunctionFileLine() (string, string, int) { + fn := runtime.FuncForPC(f.PC()) if fn == nil { - return "unknown" + return "unknown", "unknown", 0 } - file, _ := fn.FileLine(f.pc()) + file, line := fn.FileLine(f.PC()) + return fn.Name(), file, line +} + +// File returns the full path to the file that contains the +// function for this Frame's pc. +func (f Frame) File() string { + _, file, _ := f.FunctionFileLine() return file } -// line returns the line number of source code of the +// Line returns the line number of source code of the // function for this Frame's pc. -func (f Frame) line() int { - fn := runtime.FuncForPC(f.pc()) - if fn == nil { - return 0 - } - _, line := fn.FileLine(f.pc()) +func (f Frame) Line() int { + _, _, line := f.FunctionFileLine() return line } +// Package returns the package name of the function of +// for this Frame's pc. +func (f Frame) Package() string { + name, _, _ := f.FunctionFileLine() + if i := strings.LastIndex(name, "/"); i != -1 { + name = name[i+1:] + if i := strings.Index(name, "."); i != -1 { + return name[:i] + } + } + return name +} + +// Function returns the function name of the function of +// for this Frame's pc. +func (f Frame) Function() string { + name, _, _ := f.FunctionFileLine() + if i := strings.LastIndex(name, "/"); i != -1 { + name = name[i+1:] + if i := strings.Index(name, "."); i != -1 { + return name[i+1:] + } + } + return name +} + // Format formats the frame according to the fmt.Formatter interface. // // %s source file @@ -53,7 +82,7 @@ func (f Frame) Format(s fmt.State, verb rune) { case 's': switch { case s.Flag('+'): - pc := f.pc() + pc := f.PC() fn := runtime.FuncForPC(pc) if fn == nil { io.WriteString(s, "unknown") @@ -62,12 +91,12 @@ func (f Frame) Format(s fmt.State, verb rune) { fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) } default: - io.WriteString(s, path.Base(f.file())) + io.WriteString(s, path.Base(f.File())) } case 'd': - fmt.Fprintf(s, "%d", f.line()) + fmt.Fprintf(s, "%d", f.Line()) case 'n': - name := runtime.FuncForPC(f.pc()).Name() + name := runtime.FuncForPC(f.PC()).Name() io.WriteString(s, funcname(name)) case 'v': f.Format(s, 's') diff --git a/stack_test.go b/stack_test.go index 510c27a..07cb499 100644 --- a/stack_test.go +++ b/stack_test.go @@ -33,7 +33,73 @@ func TestFrameLine(t *testing.T) { }} for _, tt := range tests { - got := tt.Frame.line() + got := tt.Frame.Line() + want := tt.want + if want != got { + t.Errorf("Frame(%v): want: %v, got: %v", uintptr(tt.Frame), want, got) + } + } +} + +func TestFrameFunction(t *testing.T) { + var tests = []struct { + Frame + want string + }{{ + Frame(initpc), + "init", + }, { + func() Frame { + var pc, _, _, _ = runtime.Caller(0) + return Frame(pc) + }(), + "TestFrameFunction.func1", + }, { + func() Frame { + var pc, _, _, _ = runtime.Caller(1) + return Frame(pc) + }(), + "TestFrameFunction", + }, { + Frame(0), // invalid PC + "unknown", + }} + + for _, tt := range tests { + got := tt.Frame.Function() + want := tt.want + if want != got { + t.Errorf("Frame(%v): want: %v, got: %v", uintptr(tt.Frame), want, got) + } + } +} + +func TestFramePackage(t *testing.T) { + var tests = []struct { + Frame + want string + }{{ + Frame(initpc), + "errors", + }, { + func() Frame { + var pc, _, _, _ = runtime.Caller(0) + return Frame(pc) + }(), + "errors", + }, { + func() Frame { + var pc, _, _, _ = runtime.Caller(1) + return Frame(pc) + }(), + "errors", + }, { + Frame(0), // invalid PC + "unknown", + }} + + for _, tt := range tests { + got := tt.Frame.Package() want := tt.want if want != got { t.Errorf("Frame(%v): want: %v, got: %v", uintptr(tt.Frame), want, got) @@ -156,7 +222,7 @@ func TestTrimGOPATH(t *testing.T) { }} for i, tt := range tests { - pc := tt.Frame.pc() + pc := tt.Frame.PC() fn := runtime.FuncForPC(pc) file, _ := fn.FileLine(pc) got := trimGOPATH(fn.Name(), file)