Add recovery middleware, test stack
This commit is contained in:
parent
7b5e41adf6
commit
4883476166
|
@ -22,6 +22,10 @@ const (
|
|||
msgTooManySignatures = "Too many signatures"
|
||||
)
|
||||
|
||||
func InternalError(msg string) TMError {
|
||||
return New(msg, abci.CodeType_InternalError)
|
||||
}
|
||||
|
||||
func DecodingError() TMError {
|
||||
return New(msgDecoding, abci.CodeType_EncodingError)
|
||||
}
|
||||
|
|
|
@ -7,75 +7,77 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
NameVoid = "void"
|
||||
NameOK = "ok"
|
||||
NameFail = "fail"
|
||||
NamePanic = "panic"
|
||||
)
|
||||
|
||||
// voidHandler just used to return okay to everything
|
||||
type voidHandler struct{}
|
||||
// OKHandler just used to return okay to everything
|
||||
type OKHandler struct {
|
||||
Log string
|
||||
}
|
||||
|
||||
var _ basecoin.Handler = voidHandler{}
|
||||
var _ basecoin.Handler = OKHandler{}
|
||||
|
||||
func (_ voidHandler) Name() string {
|
||||
return NameVoid
|
||||
func (_ OKHandler) Name() string {
|
||||
return NameOK
|
||||
}
|
||||
|
||||
// CheckTx always returns an empty success tx
|
||||
func (_ voidHandler) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
|
||||
return
|
||||
func (ok OKHandler) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
|
||||
return basecoin.Result{Log: ok.Log}, nil
|
||||
}
|
||||
|
||||
// DeliverTx always returns an empty success tx
|
||||
func (_ voidHandler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
|
||||
return
|
||||
func (ok OKHandler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
|
||||
return basecoin.Result{Log: ok.Log}, nil
|
||||
}
|
||||
|
||||
// failHandler always returns an error
|
||||
type failHandler struct {
|
||||
err error
|
||||
// FailHandler always returns an error
|
||||
type FailHandler struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
var _ basecoin.Handler = failHandler{}
|
||||
var _ basecoin.Handler = FailHandler{}
|
||||
|
||||
func (_ failHandler) Name() string {
|
||||
func (_ FailHandler) Name() string {
|
||||
return NameFail
|
||||
}
|
||||
|
||||
// CheckTx always returns the given error
|
||||
func (f failHandler) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
|
||||
return res, errors.WithStack(f.err)
|
||||
func (f FailHandler) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
|
||||
return res, errors.WithStack(f.Err)
|
||||
}
|
||||
|
||||
// DeliverTx always returns the given error
|
||||
func (f failHandler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
|
||||
return res, errors.WithStack(f.err)
|
||||
func (f FailHandler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
|
||||
return res, errors.WithStack(f.Err)
|
||||
}
|
||||
|
||||
// panicHandler always panics, using the given error (first choice) or msg (fallback)
|
||||
type panicHandler struct {
|
||||
msg string
|
||||
err error
|
||||
// PanicHandler always panics, using the given error (first choice) or msg (fallback)
|
||||
type PanicHandler struct {
|
||||
Msg string
|
||||
Err error
|
||||
}
|
||||
|
||||
var _ basecoin.Handler = panicHandler{}
|
||||
var _ basecoin.Handler = PanicHandler{}
|
||||
|
||||
func (_ panicHandler) Name() string {
|
||||
func (_ PanicHandler) Name() string {
|
||||
return NamePanic
|
||||
}
|
||||
|
||||
// CheckTx always panics
|
||||
func (p panicHandler) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
|
||||
if p.err != nil {
|
||||
panic(p.err)
|
||||
func (p PanicHandler) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
|
||||
if p.Err != nil {
|
||||
panic(p.Err)
|
||||
}
|
||||
panic(p.msg)
|
||||
panic(p.Msg)
|
||||
}
|
||||
|
||||
// DeliverTx always panics
|
||||
func (p panicHandler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
|
||||
if p.err != nil {
|
||||
panic(p.err)
|
||||
func (p PanicHandler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) {
|
||||
if p.Err != nil {
|
||||
panic(p.Err)
|
||||
}
|
||||
panic(p.msg)
|
||||
panic(p.Msg)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package stack
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tendermint/basecoin"
|
||||
"github.com/tendermint/basecoin/types"
|
||||
)
|
||||
|
||||
func TestOK(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
ctx := NewContext()
|
||||
store := types.NewMemKVStore()
|
||||
data := "this looks okay"
|
||||
tx := basecoin.Tx{}
|
||||
|
||||
ok := OKHandler{data}
|
||||
res, err := ok.CheckTx(ctx, store, tx)
|
||||
assert.Nil(err, "%+v", err)
|
||||
assert.Equal(data, res.Log)
|
||||
|
||||
res, err = ok.DeliverTx(ctx, store, tx)
|
||||
assert.Nil(err, "%+v", err)
|
||||
assert.Equal(data, res.Log)
|
||||
}
|
||||
|
||||
func TestFail(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
ctx := NewContext()
|
||||
store := types.NewMemKVStore()
|
||||
msg := "big problem"
|
||||
tx := basecoin.Tx{}
|
||||
|
||||
fail := FailHandler{errors.New(msg)}
|
||||
_, err := fail.CheckTx(ctx, store, tx)
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal(msg, err.Error())
|
||||
}
|
||||
|
||||
_, err = fail.DeliverTx(ctx, store, tx)
|
||||
if assert.NotNil(err) {
|
||||
assert.Equal(msg, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPanic(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
ctx := NewContext()
|
||||
store := types.NewMemKVStore()
|
||||
msg := "system crash!"
|
||||
tx := basecoin.Tx{}
|
||||
|
||||
fail := PanicHandler{Msg: msg}
|
||||
assert.Panics(func() { fail.CheckTx(ctx, store, tx) })
|
||||
assert.Panics(func() { fail.DeliverTx(ctx, store, tx) })
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package stack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tendermint/basecoin"
|
||||
"github.com/tendermint/basecoin/errors"
|
||||
"github.com/tendermint/basecoin/types"
|
||||
)
|
||||
|
||||
const (
|
||||
NameRecovery = "rcvr"
|
||||
)
|
||||
|
||||
// Recovery catches any panics and returns them as errors instead
|
||||
type Recovery struct{}
|
||||
|
||||
func (_ Recovery) Name() string {
|
||||
return NameRecovery
|
||||
}
|
||||
|
||||
var _ Middleware = Recovery{}
|
||||
|
||||
func (_ Recovery) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, next basecoin.Checker) (res basecoin.Result, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = normalizePanic(r)
|
||||
}
|
||||
}()
|
||||
return next.CheckTx(ctx, store, tx)
|
||||
}
|
||||
|
||||
func (_ Recovery) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx, next basecoin.Deliver) (res basecoin.Result, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = normalizePanic(r)
|
||||
}
|
||||
}()
|
||||
return next.DeliverTx(ctx, store, tx)
|
||||
}
|
||||
|
||||
// normalizePanic makes sure we can get a nice TMError (with stack) out of it
|
||||
func normalizePanic(p interface{}) error {
|
||||
if err, isErr := p.(error); isErr {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
msg := fmt.Sprintf("%v", p)
|
||||
return errors.InternalError(msg)
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package stack
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tendermint/basecoin"
|
||||
"github.com/tendermint/basecoin/types"
|
||||
)
|
||||
|
||||
func TestRecovery(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// generic args here...
|
||||
ctx := NewContext()
|
||||
store := types.NewMemKVStore()
|
||||
tx := basecoin.Tx{}
|
||||
|
||||
cases := []struct {
|
||||
msg string // what to send to panic
|
||||
err error // what to send to panic
|
||||
expected string // expected text in panic
|
||||
}{
|
||||
{"buzz", nil, "buzz"},
|
||||
{"", errors.New("owa!"), "owa!"},
|
||||
{"text", errors.New("error"), "error"},
|
||||
}
|
||||
|
||||
for idx, tc := range cases {
|
||||
i := strconv.Itoa(idx)
|
||||
fail := PanicHandler{Msg: tc.msg, Err: tc.err}
|
||||
rec := Recovery{}
|
||||
app := NewStack(rec).Use(fail)
|
||||
|
||||
// make sure check returns error, not a panic crash
|
||||
_, err := app.CheckTx(ctx, store, tx)
|
||||
if assert.NotNil(err, i) {
|
||||
assert.Equal(tc.expected, err.Error(), i)
|
||||
}
|
||||
|
||||
// make sure deliver returns error, not a panic crash
|
||||
_, err = app.DeliverTx(ctx, store, tx)
|
||||
if assert.NotNil(err, i) {
|
||||
assert.Equal(tc.expected, err.Error(), i)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue