Add recovery middleware, test stack

This commit is contained in:
Ethan Frey 2017-06-29 16:09:15 +02:00
parent 7b5e41adf6
commit 4883476166
5 changed files with 200 additions and 33 deletions

View File

@ -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)
}

View File

@ -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)
}

61
stack/helpers_test.go Normal file
View File

@ -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) })
}

49
stack/recovery.go Normal file
View File

@ -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)
}

51
stack/recovery_test.go Normal file
View File

@ -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)
}
}
}