diff --git a/frame.go b/frame.go index 3481aaa..c9f41a9 100644 --- a/frame.go +++ b/frame.go @@ -6,6 +6,12 @@ import ( "github.com/pkg/errors" ) +type ErrorSet struct { + Error error + Frames Frames + Parent Frames +} + type Frame struct { File string Line int @@ -36,7 +42,7 @@ func ExtractFrames(st errors.StackTrace) Frames { } func (frames Frames) Exclude(excludes Frames) Frames { - newFrames := Frames{} + newFrames := make(Frames, 0, len(frames)) L1: for _, f := range frames { diff --git a/pperr.go b/pperr.go index 4952ad8..21ba22f 100644 --- a/pperr.go +++ b/pperr.go @@ -34,12 +34,18 @@ func Fprint(w io.Writer, err error) { } func FprintFunc(w io.Writer, err error, puts Printer) { - fprintFuncWithParent(w, err, puts, nil) + for _, e := range extractErrorSet(err, nil) { + puts(w, e.Error, e.Frames, e.Parent) + } +} + +func ExtractErrorSet(err error) []ErrorSet { + return extractErrorSet(err, nil) } -func fprintFuncWithParent(w io.Writer, err error, puts Printer, parent Frames) { +func extractErrorSet(err error, parent Frames) []ErrorSet { if err == nil { - return + return nil } realErr := err @@ -53,6 +59,8 @@ func fprintFuncWithParent(w io.Writer, err error, puts Printer, parent Frames) { } } + var errs []ErrorSet + if withCause, ok := realErr.(interface{ Unwrap() error }); ok { var causeParent Frames @@ -62,10 +70,14 @@ func fprintFuncWithParent(w io.Writer, err error, puts Printer, parent Frames) { causeParent = parent } - fprintFuncWithParent(w, withCause.Unwrap(), puts, causeParent) + if es := extractErrorSet(withCause.Unwrap(), causeParent); es != nil { + errs = es + } } - puts(w, err, frames, parent) + errs = append(errs, ErrorSet{Error: err, Frames: frames, Parent: parent}) + + return errs } func CauseType(err error) string { diff --git a/pperr_test.go b/pperr_test.go index 29453c4..76f6a31 100644 --- a/pperr_test.go +++ b/pperr_test.go @@ -42,6 +42,51 @@ func f3(native bool) error { } } +func TestExtractErrorSet(t *testing.T) { + assert := assert.New(t) + + err := f1(true) + + var buf strings.Builder + for _, e := range pperr.ExtractErrorSet(err) { + pperr.DefaultPrinter(&buf, e.Error, e.Frames, e.Parent) + } + actual := buf.String() + actual = regexp.MustCompile(`(?m)[^\s>]+/pperr_test.go:\d+$`).ReplaceAllString(actual, ".../pperr_test.go:NN") + actual = regexp.MustCompile(`(?m)[^\s>]+/go/.*:\d+$`).ReplaceAllString(actual, ".../go/...:NN") + actual = regexp.MustCompile(`(?m):\d+$`).ReplaceAllString(actual, ":NN") + + expected := strings.TrimPrefix(` +syscall.Errno: no such file or directory +*fs.PathError: open not_found: no such file or directory +*errors.withStack: from f3(): open not_found: no such file or directory + github.com/kanmu/pperr_test.f3 + .../pperr_test.go:NN + github.com/kanmu/pperr_test.f23 + .../pperr_test.go:NN + github.com/kanmu/pperr_test.f22 + .../pperr_test.go:NN + github.com/kanmu/pperr_test.f21 + .../pperr_test.go:NN +*xerrors.wrapError: from f23: from f3(): open not_found: no such file or directory +*fmt.wrapError: from f21(): from f23: from f3(): open not_found: no such file or directory +*errors.withStack: from f2(): from f21(): from f23: from f3(): open not_found: no such file or directory + github.com/kanmu/pperr_test.f2 + .../pperr_test.go:NN +*errors.withStack: from f1(): from f2(): from f21(): from f23: from f3(): open not_found: no such file or directory + github.com/kanmu/pperr_test.f1 + .../pperr_test.go:NN + github.com/kanmu/pperr_test.TestExtractErrorSet + .../pperr_test.go:NN + testing.tRunner + .../go/...:NN + runtime.goexit + .../go/...:NN +`, "\n") + + assert.Equal(expected, actual) +} + func TestFprint(t *testing.T) { assert := assert.New(t) @@ -50,8 +95,8 @@ func TestFprint(t *testing.T) { pperr.Fprint(&buf, err) actual := buf.String() - actual = regexp.MustCompile(`(?m)[^\s>]+/go/.*:\d+$`).ReplaceAllString(actual, ".../go/...:NN") actual = regexp.MustCompile(`(?m)[^\s>]+/pperr_test.go:\d+$`).ReplaceAllString(actual, ".../pperr_test.go:NN") + actual = regexp.MustCompile(`(?m)[^\s>]+/go/.*:\d+$`).ReplaceAllString(actual, ".../go/...:NN") actual = regexp.MustCompile(`(?m):\d+$`).ReplaceAllString(actual, ":NN") expected := strings.TrimPrefix(` @@ -113,8 +158,8 @@ func TestFprint_Indent(t *testing.T) { pperr.FprintFunc(&buf, err, pperr.NewPrinterWithIndent(">>")) actual := buf.String() - actual = regexp.MustCompile(`(?m)[^\s>]+/go/.*:\d+$`).ReplaceAllString(actual, ".../go/...:NN") actual = regexp.MustCompile(`(?m)[^\s>]+/pperr_test.go:\d+$`).ReplaceAllString(actual, ".../pperr_test.go:NN") + actual = regexp.MustCompile(`(?m)[^\s>]+/go/.*:\d+$`).ReplaceAllString(actual, ".../go/...:NN") actual = regexp.MustCompile(`(?m):\d+$`).ReplaceAllString(actual, ":NN") expected := strings.TrimPrefix(` @@ -154,8 +199,8 @@ func TestSprint(t *testing.T) { err := f1(true) actual := pperr.Sprint(err) - actual = regexp.MustCompile(`(?m)[^\s>]+/go/.*:\d+$`).ReplaceAllString(actual, ".../go/...:NN") actual = regexp.MustCompile(`(?m)[^\s>]+/pperr_test.go:\d+$`).ReplaceAllString(actual, ".../pperr_test.go:NN") + actual = regexp.MustCompile(`(?m)[^\s>]+/go/.*:\d+$`).ReplaceAllString(actual, ".../go/...:NN") actual = regexp.MustCompile(`(?m):\d+$`).ReplaceAllString(actual, ":NN") expected := strings.TrimPrefix(` @@ -195,8 +240,8 @@ func TestSprintFunc(t *testing.T) { err := f1(true) actual := pperr.SprintFunc(err, pperr.NewPrinterWithIndent(">>")) - actual = regexp.MustCompile(`(?m)[^\s>]+/go/.*:\d+$`).ReplaceAllString(actual, ".../go/...:NN") actual = regexp.MustCompile(`(?m)[^\s>]+/pperr_test.go:\d+$`).ReplaceAllString(actual, ".../pperr_test.go:NN") + actual = regexp.MustCompile(`(?m)[^\s>]+/go/.*:\d+$`).ReplaceAllString(actual, ".../go/...:NN") actual = regexp.MustCompile(`(?m):\d+$`).ReplaceAllString(actual, ":NN") expected := strings.TrimPrefix(` @@ -261,8 +306,8 @@ func TestFprint_WithoutMessage(t *testing.T) { pperr.FprintFunc(&buf, err, pperr.PrinterWithoutMessage) actual := buf.String() - actual = regexp.MustCompile(`(?m)[^\s>]+/go/.*:\d+$`).ReplaceAllString(actual, ".../go/...:NN") actual = regexp.MustCompile(`(?m)[^\s>]+/pperr_test.go:\d+$`).ReplaceAllString(actual, ".../pperr_test.go:NN") + actual = regexp.MustCompile(`(?m)[^\s>]+/go/.*:\d+$`).ReplaceAllString(actual, ".../go/...:NN") actual = regexp.MustCompile(`(?m):\d+$`).ReplaceAllString(actual, ":NN") expected := strings.TrimPrefix(` diff --git a/printer.go b/printer.go index 35f831b..edb0436 100644 --- a/printer.go +++ b/printer.go @@ -24,7 +24,7 @@ var DefaultPrinterWithIndent = func(w io.Writer, err error, frames, parent Frame } } -var PrinterWithoutMessage = func(w io.Writer, err error, frames, parent Frames) { +var PrinterWithoutMessage = func(w io.Writer, _ error, frames, parent Frames) { if frames != nil { if parent != nil { frames = frames.Exclude(parent)