WIP: Implementing App w/ tests

This commit is contained in:
Jae Kwon 2017-12-20 17:34:51 -08:00
parent 45c2e9c30b
commit 5c06e56c24
5 changed files with 167 additions and 112 deletions

View File

@ -36,7 +36,7 @@ type App struct {
handler sdk.Handler
// Cached validator changes from DeliverTx
valSetDiff []abci.Validator
valUpdates []abci.Validator
}
var _ abci.Application = &App{}
@ -57,14 +57,21 @@ func (app *App) SetHandler(handler Handler) {
}
func (app *App) LoadLatestVersion() error {
curVersion := app.store.NextVersion()
app.
store := app.store
store.LoadLastVersion()
return app.initFromStore()
}
func (app *App) LoadVersion(version int64) error {
store := app.store
store.LoadVersion(version)
return app.initFromStore()
}
// Initializes the remaining logic from app.store.
func (app *App) initFromStore() error {
store := app.store
lastCommitID := store.LastCommitID()
curVersion := store.NextVersion()
main := store.GetKVStore("main")
header := (*abci.Header)(nil)
storeCheck := store.CacheMultiStore()
@ -74,12 +81,7 @@ func (app *App) LoadVersion(version int64) error {
return errors.New("App expects MultiStore with 'main' KVStore")
}
// Basic sanity check.
if curVersion != lastCommitID.Version+1 {
return errors.New("NextVersion != LastCommitID.Version+1")
}
// If we've committed before, we expect store(main)/<mainKeyHeader>.
// If we've committed before, we expect main://<mainKeyHeader>.
if !lastCommitID.IsZero() {
headerBytes, ok := main.Get(mainKeyHeader)
if !ok {
@ -90,11 +92,18 @@ func (app *App) LoadVersion(version int64) error {
if err != nil {
return errors.Wrap(err, "Failed to parse Header")
}
if header.Height != curVersion-1 {
errStr := fmt.Sprintf("Expected header.Height %v but got %v", version, headerHeight)
if header.Height != lastCommitID.Version {
errStr := fmt.Sprintf("Expected main://%s.Height %v but got %v", mainKeyHeader, version, headerHeight)
return errors.New(errStr)
}
}
// Set App state.
app.header = header
app.storeCheck = app.store.CacheMultiStore()
app.valUpdates = nil
return nil
}
//----------------------------------------
@ -112,10 +121,8 @@ func (app *App) DeliverTx(txBytes []byte) abci.ResponseDeliverTx {
var result = app.handler(ctx, app.store, tx)
// After-handler hooks.
// TODO move to app.afterHandler(...).
if result.Code == abci.CodeType_OK {
// XXX No longer "diff", we need to replace old entries.
app.ValSetDiff = append(app.ValSetDiff, result.ValSetDiff)
app.valUpdates = append(app.valUpdates, result.ValUpdate)
} else {
// Even though the Code is not OK, there will be some side effects,
// like those caused by fee deductions or sequence incrementations.
@ -247,8 +254,8 @@ func (app *App) BeginBlock(req abci.RequestBeginBlock) {
// Returns a list of all validator changes made in this block
func (app *App) EndBlock(height uint64) (res abci.ResponseEndBlock) {
// XXX Update to res.Updates.
res.Diffs = app.valSetDiff
app.valSetDiff = nil
res.Diffs = app.valUpdates
app.valUpdates = nil
return
}

140
app/app_test.go Normal file
View File

@ -0,0 +1,140 @@
package app
import (
"bytes"
"encoding/json"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/go-crypto"
cmn "github.com/tendermint/tmlibs/common"
)
func TestBasic(t *testing.T) {
// A mock transaction to update a validator's voting power.
type testTx struct {
Addr []byte
NewPower int64
}
// Create app.
app := sdk.NewApp(t.Name())
app.SetStore(mockMultiStore())
app.SetHandler(func(ctx Context, store MultiStore, tx Tx) Result {
// This could be a decorator.
fromJSON(ctx.TxBytes(), &tx)
fmt.Println(">>", tx)
})
// Load latest state, which should be empty.
err := app.LoadLatestVersion()
assert.Nil(t, err)
assert.Equal(t, app.NextVersion(), 1)
// Create the validators
var numVals = 3
var valSet = make([]*abci.Validator, numVals)
for i := 0; i < numVals; i++ {
valSet[i] = makeVal(secret(i))
}
// Initialize the chain
app.InitChain(abci.RequestInitChain{
Validators: valset,
})
// Simulate the start of a block.
app.BeginBlock(abci.RequestBeginBlock{})
// Add 1 to each validator's voting power.
for i, val := range valSet {
tx := testTx{
Addr: makePubKey(secret(i)).Address(),
NewPower: val.Power + 1,
}
txBytes := toJSON(tx)
res := app.DeliverTx(txBytes)
require.True(res.IsOK(), "%#v", res)
}
// Simulate the end of a block.
// Get the summary of validator updates.
res := app.EndBlock(app.height)
valUpdates := res.ValidatorUpdates
// Assert that validator updates are correct.
for _, val := range valSet {
// Find matching update and splice it out.
for j := 0; j < len(valUpdates); {
assert.NotEqual(len(valUpdates.PubKey), 0)
// Matched.
if bytes.Equal(valUpdate.PubKey, val.PubKey) {
assert.Equal(valUpdate.NewPower, val.Power+1)
if j < len(valUpdates)-1 {
// Splice it out.
valUpdates = append(valUpdates[:j], valUpdates[j+1:]...)
}
break
}
// Not matched.
j += 1
}
}
assert.Equal(t, len(valUpdates), 0, "Some validator updates were unexpected")
}
//----------------------------------------
func randPower() int64 {
return cmn.RandInt64()
}
func makeVal(secret string) *abci.Validator {
return &abci.Validator{
PubKey: makePubKey(string).Bytes(),
Power: randPower(),
}
}
func makePubKey(secret string) crypto.PubKey {
return makePrivKey(secret).PubKey()
}
func makePrivKey(secret string) crypto.PrivKey {
return crypto.GenPrivKeyEd25519FromSecret([]byte(id))
}
func secret(index int) []byte {
return []byte(fmt.Sprintf("secret%d", index))
}
func copyVal(val *abci.Validator) *abci.Validator {
val2 := *val
return &val2
}
func toJSON(o interface{}) []byte {
bytes, err := json.Marshal(o)
if err != nil {
panic(err)
}
return bytes
}
func fromJSON(bytes []byte, ptr interface{}) {
err := json.Unmarshal(bytes, ptr)
if err != nil {
panic(err)
}
}

View File

@ -1,88 +0,0 @@
package app
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
wire "github.com/tendermint/go-wire"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
"github.com/cosmos/cosmos-sdk/modules/base"
)
//-----------------------------------
// Test cases start here
func randPower() uint64 {
return uint64(cmn.RandInt()%50 + 60)
}
func makeVal() *abci.Validator {
return &abci.Validator{
PubKey: cmn.RandBytes(10),
Power: randPower(),
}
}
// withNewPower returns a copy of the validator with a different power
func withNewPower(val *abci.Validator) *abci.Validator {
res := *val
res.Power = randPower()
return &res
}
func TestEndBlock(t *testing.T) {
assert, require := assert.New(t), require.New(t)
logger := log.NewNopLogger()
handler := base.ValSetHandler{}
store, err := MockStoreApp("vals", logger)
require.Nil(err, "%+v", err)
app := NewBaseApp(store, handler, nil)
val1 := makeVal()
val2 := makeVal()
val3 := makeVal()
val1a := withNewPower(val1)
val2a := withNewPower(val2)
cases := [...]struct {
changes [][]*abci.Validator
expected []*abci.Validator
}{
// Nothing in, nothing out, no crash
0: {},
// One in, one out, no problem
1: {
changes: [][]*abci.Validator{{val1}},
expected: []*abci.Validator{val1},
},
// Combine a few ones
2: {
changes: [][]*abci.Validator{{val1}, {val2, val3}},
expected: []*abci.Validator{val1, val2, val3},
},
// Make sure changes all to one validators are squished into one diff
3: {
changes: [][]*abci.Validator{{val1}, {val2, val1a}, {val2a, val3}},
expected: []*abci.Validator{val1a, val2a, val3},
},
}
for i, tc := range cases {
app.BeginBlock(abci.RequestBeginBlock{})
for _, c := range tc.changes {
tx := base.ValChangeTx{c}.Wrap()
txBytes := wire.BinaryBytes(tx)
res := app.DeliverTx(txBytes)
require.True(res.IsOK(), "%#v", res)
}
diff := app.EndBlock(app.height)
// TODO: don't care about order here...
assert.Equal(tc.expected, diff.Diffs, "%d", i)
}
}

View File

@ -6,4 +6,4 @@ import (
// Handler handles both ABCI DeliverTx and CheckTx requests.
// Iff ABCI.CheckTx, ctx.IsCheckTx() returns true.
type Handler func(ctx Context, ms store.MultiStore, tx Tx) Result
type Handler func(ctx Context, store store.MultiStore, tx Tx) Result

View File

@ -1,14 +1,10 @@
package types
import (
cmn "github.com/tendemrint/tmlibs/common"
abci "github.com/tendermint/abci/types"
)
type KVPair struct {
Key []byte
Value []byte
}
// Result is the union of ResponseDeliverTx and ResponseCheckTx.
type Result struct {
@ -35,5 +31,5 @@ type Result struct {
ValSetDiff []abci.Validator
// Tags are used for transaction indexing and pubsub.
Tags []KVPair
Tags []cmn.KVPair
}