Skip to content

Commit

Permalink
Support skip the first runtime panic stack automatically for RuntimeE…
Browse files Browse the repository at this point in the history
…rror
  • Loading branch information
timandy committed Oct 16, 2023
1 parent deeb2ce commit 2c28683
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 49 deletions.
11 changes: 6 additions & 5 deletions api_routine.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ func WrapTask(fun Runnable) FutureTask {
// catch
defer func() {
if cause := recover(); cause != nil {
err := NewRuntimeError(cause)
task.Fail(err)
fmt.Println(err.Error())
task.Fail(cause)
if err := task.(*futureTask).error; err != nil {
fmt.Println(err.Error())
}
}
}()
// restore
Expand Down Expand Up @@ -70,7 +71,7 @@ func WrapWaitTask(fun CancelRunnable) FutureTask {
// catch
defer func() {
if cause := recover(); cause != nil {
task.Fail(NewRuntimeError(cause))
task.Fail(cause)
}
}()
// restore
Expand Down Expand Up @@ -113,7 +114,7 @@ func WrapWaitResultTask(fun CancelCallable) FutureTask {
// catch
defer func() {
if cause := recover(); cause != nil {
task.Fail(NewRuntimeError(cause))
task.Fail(cause)
}
}()
// restore
Expand Down
15 changes: 10 additions & 5 deletions error.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package routine

import (
"bytes"
"fmt"
"reflect"
"runtime"
"strconv"
"strings"
"unicode"
)

Expand Down Expand Up @@ -89,13 +89,13 @@ func runtimeErrorNewWithMessageCause(message string, cause any) (goid int64, gop
}

func runtimeErrorError(re RuntimeError) string {
builder := &strings.Builder{}
builder := &bytes.Buffer{}
runtimeErrorPrintStackTrace(re, builder)
runtimeErrorPrintCreatedBy(re, builder)
return builder.String()
}

func runtimeErrorPrintStackTrace(re RuntimeError, builder *strings.Builder) {
func runtimeErrorPrintStackTrace(re RuntimeError, builder *bytes.Buffer) {
builder.WriteString(runtimeErrorTypeName(re))
message := re.Message()
if len(message) > 0 {
Expand All @@ -113,10 +113,12 @@ func runtimeErrorPrintStackTrace(re RuntimeError, builder *strings.Builder) {
}
stackTrace := re.StackTrace()
if stackTrace != nil {
savePoint := builder.Len()
skippedPanic := false
frames := runtime.CallersFrames(stackTrace)
for {
frame, more := frames.Next()
if len(frame.Function) > 0 && frame.Function != "runtime.goexit" {
if showFrame(frame.Function) {
builder.WriteString(newLine)
builder.WriteString(" ")
builder.WriteString(wordAt)
Expand All @@ -128,6 +130,9 @@ func runtimeErrorPrintStackTrace(re RuntimeError, builder *strings.Builder) {
builder.WriteString(frame.File)
builder.WriteString(":")
builder.WriteString(strconv.Itoa(frame.Line))
} else if skipFrame(frame.Function, skippedPanic) {
builder.Truncate(savePoint)
skippedPanic = true
}
if !more {
break
Expand All @@ -136,7 +141,7 @@ func runtimeErrorPrintStackTrace(re RuntimeError, builder *strings.Builder) {
}
}

func runtimeErrorPrintCreatedBy(re RuntimeError, builder *strings.Builder) {
func runtimeErrorPrintCreatedBy(re RuntimeError, builder *bytes.Buffer) {
goid := re.Goid()
if goid == 1 {
return
Expand Down
78 changes: 68 additions & 10 deletions error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,33 @@ func TestRuntimeError_StackTrace(t *testing.T) {
}
}

func TestRuntimeError_Panic_Panic(t *testing.T) {
defer func() {
cause := recover()
assert.NotNil(t, cause)
err := NewRuntimeError(cause)
lines := strings.Split(err.Error(), newLine)
assert.Equal(t, 6, len(lines))
//
line := lines[0]
assert.Equal(t, "RuntimeError: 1", line)
//
line = lines[1]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestRuntimeError_Panic_Panic."))
assert.True(t, strings.HasSuffix(line, "error_test.go:74"))
//
line = lines[2]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestRuntimeError_Panic_Panic()"))
assert.True(t, strings.HasSuffix(line, "error_test.go:77"))
}()
defer func() {
if cause := recover(); cause != nil {
panic(cause)
}
}()
panic(1)
}

func TestRuntimeError_Cause(t *testing.T) {
err := NewRuntimeError(nil)
assert.Nil(t, err.Cause())
Expand All @@ -74,7 +101,7 @@ func TestRuntimeError_Error_EmptyMessage_NilError(t *testing.T) {
//
line = lines[1]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestRuntimeError_Error_EmptyMessage_NilError() in "))
assert.True(t, strings.HasSuffix(line, "error_test.go:68"))
assert.True(t, strings.HasSuffix(line, "error_test.go:95"))
//
line = lines[2]
assert.True(t, strings.HasPrefix(line, " at testing.tRunner() in "))
Expand All @@ -100,7 +127,7 @@ func TestRuntimeError_Error_EmptyMessage_NormalError(t *testing.T) {
//
line = lines[2]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestRuntimeError_Error_EmptyMessage_NormalError() in "))
assert.True(t, strings.HasSuffix(line, "error_test.go:90"))
assert.True(t, strings.HasSuffix(line, "error_test.go:117"))
//
line = lines[3]
assert.True(t, strings.HasPrefix(line, " at testing.tRunner() in "))
Expand All @@ -110,7 +137,7 @@ func TestRuntimeError_Error_EmptyMessage_NormalError(t *testing.T) {
//
line = lines[5]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestRuntimeError_Error_EmptyMessage_NormalError() in "))
assert.True(t, strings.HasSuffix(line, "error_test.go:91"))
assert.True(t, strings.HasSuffix(line, "error_test.go:118"))
//
line = lines[6]
assert.True(t, strings.HasPrefix(line, " at testing.tRunner() in "))
Expand All @@ -132,7 +159,7 @@ func TestRuntimeError_Error_NormalMessage_NilError(t *testing.T) {
//
line = lines[1]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestRuntimeError_Error_NormalMessage_NilError() in "))
assert.True(t, strings.HasSuffix(line, "error_test.go:126"))
assert.True(t, strings.HasSuffix(line, "error_test.go:153"))
//
line = lines[2]
assert.True(t, strings.HasPrefix(line, " at testing.tRunner() in "))
Expand All @@ -158,7 +185,7 @@ func TestRuntimeError_Error_NormalMessage_NormalError(t *testing.T) {
//
line = lines[2]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestRuntimeError_Error_NormalMessage_NormalError() in "))
assert.True(t, strings.HasSuffix(line, "error_test.go:148"))
assert.True(t, strings.HasSuffix(line, "error_test.go:175"))
//
line = lines[3]
assert.True(t, strings.HasPrefix(line, " at testing.tRunner() in "))
Expand All @@ -168,7 +195,7 @@ func TestRuntimeError_Error_NormalMessage_NormalError(t *testing.T) {
//
line = lines[5]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestRuntimeError_Error_NormalMessage_NormalError() in "))
assert.True(t, strings.HasSuffix(line, "error_test.go:149"))
assert.True(t, strings.HasSuffix(line, "error_test.go:176"))
//
line = lines[6]
assert.True(t, strings.HasPrefix(line, " at testing.tRunner() in "))
Expand Down Expand Up @@ -215,7 +242,7 @@ func TestRuntimeError_Error_MainGoid(t *testing.T) {
//
line = lines[1]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestRuntimeError_Error_MainGoid() in "))
assert.True(t, strings.HasSuffix(line, "error_test.go:208"))
assert.True(t, strings.HasSuffix(line, "error_test.go:235"))
//
line = lines[2]
assert.True(t, strings.HasPrefix(line, " at testing.tRunner() in "))
Expand All @@ -232,7 +259,7 @@ func TestRuntimeError_Error_ZeroGopc(t *testing.T) {
//
line = lines[1]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestRuntimeError_Error_ZeroGopc() in "))
assert.True(t, strings.HasSuffix(line, "error_test.go:225"))
assert.True(t, strings.HasSuffix(line, "error_test.go:252"))
//
line = lines[2]
assert.True(t, strings.HasPrefix(line, " at testing.tRunner() in "))
Expand Down Expand Up @@ -283,6 +310,37 @@ func TestArgumentNilError_StackTrace(t *testing.T) {
}
}

func TestArgumentNilError_Panic_Panic(t *testing.T) {
defer func() {
cause := recover()
assert.NotNil(t, cause)
err := NewArgumentNilError("a", nil)
lines := strings.Split(err.Error(), newLine)
assert.Equal(t, 7, len(lines))
//
line := lines[0]
assert.Equal(t, "ArgumentNilError: Value cannot be null.", line)
//
line = lines[1]
assert.Equal(t, "Parameter name: a.", line)
//
line = lines[2]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestArgumentNilError_Panic_Panic."))
assert.True(t, strings.HasSuffix(line, "error_test.go:337"))
//
line = lines[3]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestArgumentNilError_Panic_Panic()"))
assert.True(t, strings.HasSuffix(line, "error_test.go:341"))
}()
defer func() {
if cause := recover(); cause != nil {
panic(cause)
}
}()
var a any
_ = a.(string)
}

func TestArgumentNilError_Cause(t *testing.T) {
err := NewArgumentNilError("", nil)
assert.Nil(t, err.Cause())
Expand Down Expand Up @@ -314,7 +372,7 @@ func TestArgumentNilError_Error(t *testing.T) {
//
line = lines[3]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestArgumentNilError_Error() in "))
assert.True(t, strings.HasSuffix(line, "error_test.go:301"))
assert.True(t, strings.HasSuffix(line, "error_test.go:359"))
//
line = lines[4]
assert.True(t, strings.HasPrefix(line, " at testing.tRunner() in "))
Expand All @@ -324,7 +382,7 @@ func TestArgumentNilError_Error(t *testing.T) {
//
line = lines[6]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestArgumentNilError_Error() in "))
assert.True(t, strings.HasSuffix(line, "error_test.go:302"))
assert.True(t, strings.HasSuffix(line, "error_test.go:360"))
//
line = lines[7]
assert.True(t, strings.HasPrefix(line, " at testing.tRunner() in "))
Expand Down
2 changes: 1 addition & 1 deletion future_task.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func (task *futureTask) Run() {
if atomic.CompareAndSwapInt32(&task.state, taskStateNew, taskStateRunning) {
defer func() {
if cause := recover(); cause != nil {
task.Fail(NewRuntimeError(cause))
task.Fail(cause)
}
}()
result := task.callable(task)
Expand Down
59 changes: 34 additions & 25 deletions future_task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,19 +154,8 @@ func TestFutureTask_Fail_Common(t *testing.T) {
assert.Equal(t, "RuntimeError: 1", line)
//
line = lines[1]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.(*futureTask).Fail() in "))
assert.True(t, strings.HasSuffix(line, "future_task.go:62"))
//
line = lines[2]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestFutureTask_Fail_Common."))
assert.True(t, strings.HasSuffix(line, "future_task_test.go:177"))
//
line = lines[3]
assert.True(t, strings.HasPrefix(line, " at runtime.gopanic() in "))
//
line = lines[4]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestFutureTask_Fail_Common."))
assert.True(t, strings.HasSuffix(line, "future_task_test.go:180"))
assert.True(t, strings.HasSuffix(line, "future_task_test.go:169"))
}
}()
//
Expand All @@ -190,20 +179,21 @@ func TestFutureTask_Fail_RuntimeError(t *testing.T) {
assert.NotNil(t, err)
assert.Equal(t, "1", err.Message())
lines := strings.Split(err.Error(), newLine)
assert.Equal(t, 4, len(lines))
//
line := lines[0]
assert.Equal(t, "RuntimeError: 1", line)
//
line = lines[1]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestFutureTask_Fail_RuntimeError."))
assert.True(t, strings.HasSuffix(line, "future_task_test.go:214"))
assert.True(t, strings.HasSuffix(line, "future_task_test.go:207"))
//
line = lines[2]
assert.True(t, strings.HasPrefix(line, " at runtime.gopanic() in "))
assert.Equal(t, " --- End of error stack trace ---", line)
//
line = lines[3]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestFutureTask_Fail_RuntimeError."))
assert.True(t, strings.HasSuffix(line, "future_task_test.go:217"))
assert.True(t, strings.HasPrefix(line, " created by github.com/timandy/routine.TestFutureTask_Fail_RuntimeError()"))
assert.True(t, strings.HasSuffix(line, "future_task_test.go:201"))
}
}()
//
Expand Down Expand Up @@ -420,14 +410,25 @@ func TestFutureTask_Run_Error(t *testing.T) {
err := cause.(RuntimeError)
assert.Equal(t, "1", err.Message())
lines := strings.Split(err.Error(), newLine)
assert.Equal(t, 7, len(lines))
assert.Equal(t, 5, len(lines))
//
line := lines[0]
assert.Equal(t, "RuntimeError: 1", line)
//
line = lines[1]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.(*futureTask).Run."))
assert.True(t, strings.HasSuffix(line, "future_task.go:105"))
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestFutureTask_Run_Error."))
assert.True(t, strings.HasSuffix(line, "future_task_test.go:397"))
//
line = lines[2]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.(*futureTask).Run()"))
assert.True(t, strings.HasSuffix(line, "future_task.go:108"))
//
line = lines[3]
assert.Equal(t, " --- End of error stack trace ---", line)
//
line = lines[4]
assert.True(t, strings.HasPrefix(line, " created by github.com/timandy/routine.TestFutureTask_Run_Error()"))
assert.True(t, strings.HasSuffix(line, "future_task_test.go:399"))
}()
task.Get()
assert.Fail(t, "should not be here")
Expand All @@ -454,19 +455,27 @@ func TestFutureTask_Run_RuntimeError(t *testing.T) {
assert.NotNil(t, cause)
assert.Implements(t, (*RuntimeError)(nil), cause)
err := cause.(RuntimeError)
assert.Equal(t, "", err.Message())
assert.Equal(t, "1", err.Message())
lines := strings.Split(err.Error(), newLine)
assert.Equal(t, 11, len(lines))
assert.Equal(t, 5, len(lines))
//
line := lines[0]
assert.Equal(t, "RuntimeError", line)
assert.Equal(t, "RuntimeError: 1", line)
//
line = lines[1]
assert.Equal(t, line, " ---> RuntimeError: 1")
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestFutureTask_Run_RuntimeError."))
assert.True(t, strings.HasSuffix(line, "future_task_test.go:443"))
//
line = lines[2]
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestFutureTask_Run_RuntimeError."))
assert.True(t, strings.HasSuffix(line, "future_task_test.go:442"))
assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.(*futureTask).Run()"))
assert.True(t, strings.HasSuffix(line, "future_task.go:108"))
//
line = lines[3]
assert.Equal(t, " --- End of error stack trace ---", line)
//
line = lines[4]
assert.True(t, strings.HasPrefix(line, " created by github.com/timandy/routine.TestFutureTask_Run_RuntimeError()"))
assert.True(t, strings.HasSuffix(line, "future_task_test.go:446"))
}()
task.Get()
assert.Fail(t, "should not be here")
Expand Down
Loading

0 comments on commit 2c28683

Please sign in to comment.