Refactor bank tests

This commit is contained in:
Christopher Goes 2018-07-18 09:05:48 +02:00
parent ad410c1e2e
commit 8bd54f0701
6 changed files with 214 additions and 165 deletions

View File

@ -14,6 +14,7 @@ import (
"github.com/tendermint/tendermint/libs/log"
sdk "github.com/cosmos/cosmos-sdk/types"
banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation"
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
stake "github.com/cosmos/cosmos-sdk/x/stake"
stakesim "github.com/cosmos/cosmos-sdk/x/stake/simulation"
@ -85,6 +86,7 @@ func TestFullGaiaSimulation(t *testing.T) {
simulation.SimulateFromSeed(
t, app.BaseApp, appStateFn, seed,
[]simulation.TestAndRunTx{
banksim.TestAndRunSingleInputMsgSend(app.accountMapper),
stakesim.SimulateMsgCreateValidator(app.accountMapper, app.stakeKeeper),
stakesim.SimulateMsgEditValidator(app.stakeKeeper),
stakesim.SimulateMsgDelegate(app.accountMapper, app.stakeKeeper),
@ -95,6 +97,7 @@ func TestFullGaiaSimulation(t *testing.T) {
},
[]simulation.RandSetup{},
[]simulation.Invariant{
banksim.NonnegativeBalanceInvariant(app.accountMapper),
stakesim.AllInvariants(app.coinKeeper, app.stakeKeeper, app.accountMapper),
},
NumKeys,

View File

@ -83,21 +83,6 @@ func getMockApp(t *testing.T) *mock.App {
return mapp
}
func TestBankWithRandomMessages(t *testing.T) {
mapp := getMockApp(t)
setup := func(r *rand.Rand, keys []crypto.PrivKey) {
return
}
mapp.RandomizedTesting(
t,
[]mock.TestAndRunTx{TestAndRunSingleInputMsgSend},
[]mock.RandSetup{setup},
[]mock.Invariant{ModuleInvariants},
100, 30, 30,
)
}
func TestMsgSendWithAccounts(t *testing.T) {
mapp := getMockApp(t)

View File

@ -0,0 +1,50 @@
package simulation
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/mock"
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
abci "github.com/tendermint/tendermint/abci/types"
)
// NonnegativeBalanceInvariant checks that all accounts in the application have non-negative balances
func NonnegativeBalanceInvariant(mapper auth.AccountMapper) simulation.Invariant {
return func(t *testing.T, app *baseapp.BaseApp, log string) {
ctx := app.NewContext(false, abci.Header{})
accts := mock.GetAllAccounts(mapper, ctx)
for _, acc := range accts {
coins := acc.GetCoins()
require.True(t, coins.IsNotNegative(),
fmt.Sprintf("%s has a negative denomination of %s\n%s",
acc.GetAddress().String(),
coins.String(),
log),
)
}
}
}
// TotalCoinsInvariant checks that the sum of the coins across all accounts
// is what is expected
func TotalCoinsInvariant(mapper auth.AccountMapper, totalSupplyFn func() sdk.Coins) simulation.Invariant {
return func(t *testing.T, app *baseapp.BaseApp, log string) {
ctx := app.NewContext(false, abci.Header{})
totalCoins := sdk.Coins{}
chkAccount := func(acc auth.Account) bool {
coins := acc.GetCoins()
totalCoins = totalCoins.Plus(coins)
return false
}
mapper.IterateAccounts(ctx, chkAccount)
require.Equal(t, totalSupplyFn(), totalCoins, log)
}
}

117
x/bank/simulation/msgs.go Normal file
View File

@ -0,0 +1,117 @@
package simulation
import (
"errors"
"fmt"
"math/big"
"math/rand"
"testing"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/mock"
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
"github.com/tendermint/tendermint/crypto"
)
// TestAndRunSingleInputMsgSend tests and runs a single msg send, with one input and one output, where both
// accounts already exist.
func TestAndRunSingleInputMsgSend(mapper auth.AccountMapper) simulation.TestAndRunTx {
return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) {
fromKey := keys[r.Intn(len(keys))]
fromAddr := sdk.AccAddress(fromKey.PubKey().Address())
toKey := keys[r.Intn(len(keys))]
// Disallow sending money to yourself
for {
if !fromKey.Equals(toKey) {
break
}
toKey = keys[r.Intn(len(keys))]
}
toAddr := sdk.AccAddress(toKey.PubKey().Address())
initFromCoins := mapper.GetAccount(ctx, fromAddr).GetCoins()
denomIndex := r.Intn(len(initFromCoins))
amt, goErr := randPositiveInt(r, initFromCoins[denomIndex].Amount)
if goErr != nil {
return "skipping bank send due to account having no coins of denomination " + initFromCoins[denomIndex].Denom, nil
}
action = fmt.Sprintf("%s is sending %s %s to %s",
fromAddr.String(),
amt.String(),
initFromCoins[denomIndex].Denom,
toAddr.String(),
)
log = fmt.Sprintf("%s\n%s", log, action)
coins := sdk.Coins{{initFromCoins[denomIndex].Denom, amt}}
var msg = bank.MsgSend{
Inputs: []bank.Input{bank.NewInput(fromAddr, coins)},
Outputs: []bank.Output{bank.NewOutput(toAddr, coins)},
}
sendAndVerifyMsgSend(t, app, mapper, msg, ctx, log, []crypto.PrivKey{fromKey})
event("bank/sendAndVerifyMsgSend/ok")
return action, nil
}
}
// Sends and verifies the transition of a msg send. This fails if there are repeated inputs or outputs
func sendAndVerifyMsgSend(t *testing.T, app *baseapp.BaseApp, mapper auth.AccountMapper, msg bank.MsgSend, ctx sdk.Context, log string, privkeys []crypto.PrivKey) {
initialInputAddrCoins := make([]sdk.Coins, len(msg.Inputs))
initialOutputAddrCoins := make([]sdk.Coins, len(msg.Outputs))
AccountNumbers := make([]int64, len(msg.Inputs))
SequenceNumbers := make([]int64, len(msg.Inputs))
for i := 0; i < len(msg.Inputs); i++ {
acc := mapper.GetAccount(ctx, msg.Inputs[i].Address)
AccountNumbers[i] = acc.GetAccountNumber()
SequenceNumbers[i] = acc.GetSequence()
initialInputAddrCoins[i] = acc.GetCoins()
}
for i := 0; i < len(msg.Outputs); i++ {
acc := mapper.GetAccount(ctx, msg.Outputs[i].Address)
initialOutputAddrCoins[i] = acc.GetCoins()
}
tx := mock.GenTx([]sdk.Msg{msg},
AccountNumbers,
SequenceNumbers,
privkeys...)
res := app.Deliver(tx)
if !res.IsOK() {
// TODO: Do this in a more 'canonical' way
fmt.Println(res)
fmt.Println(log)
t.FailNow()
}
for i := 0; i < len(msg.Inputs); i++ {
terminalInputCoins := mapper.GetAccount(ctx, msg.Inputs[i].Address).GetCoins()
require.Equal(t,
initialInputAddrCoins[i].Minus(msg.Inputs[i].Coins),
terminalInputCoins,
fmt.Sprintf("Input #%d had an incorrect amount of coins\n%s", i, log),
)
}
for i := 0; i < len(msg.Outputs); i++ {
terminalOutputCoins := mapper.GetAccount(ctx, msg.Outputs[i].Address).GetCoins()
require.Equal(t,
initialOutputAddrCoins[i].Plus(msg.Outputs[i].Coins),
terminalOutputCoins,
fmt.Sprintf("Output #%d had an incorrect amount of coins\n%s", i, log),
)
}
}
func randPositiveInt(r *rand.Rand, max sdk.Int) (sdk.Int, error) {
if !max.GT(sdk.OneInt()) {
return sdk.Int{}, errors.New("max too small")
}
max = max.Sub(sdk.OneInt())
return sdk.NewIntFromBigInt(new(big.Int).Rand(r, max.BigInt())).Add(sdk.OneInt()), nil
}

View File

@ -0,0 +1,44 @@
package simulation
import (
"encoding/json"
"math/rand"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/mock"
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
)
func TestBankWithRandomMessages(t *testing.T) {
mapp := mock.NewApp()
bank.RegisterWire(mapp.Cdc)
mapper := mapp.AccountMapper
coinKeeper := bank.NewKeeper(mapper)
mapp.Router().AddRoute("bank", bank.NewHandler(coinKeeper))
err := mapp.CompleteSetup([]*sdk.KVStoreKey{})
if err != nil {
panic(err)
}
appStateFn := func(r *rand.Rand, accs []sdk.AccAddress) json.RawMessage {
mock.RandomSetGenesis(r, mapp, accs, []string{"stake"})
return json.RawMessage("{}")
}
simulation.Simulate(
t, mapp.BaseApp, appStateFn,
[]simulation.TestAndRunTx{
TestAndRunSingleInputMsgSend(mapper),
},
[]simulation.RandSetup{},
[]simulation.Invariant{
NonnegativeBalanceInvariant(mapper),
TotalCoinsInvariant(mapper, func() sdk.Coins { return mapp.TotalCoinsSupply }),
},
100, 30, 30,
)
}

View File

@ -1,150 +0,0 @@
package bank
import (
"errors"
"fmt"
"math/big"
"math/rand"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/mock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
)
// ModuleInvariants runs all invariants of the bank module.
// Currently runs non-negative balance invariant and TotalCoinsInvariant
func ModuleInvariants(t *testing.T, app *mock.App, log string) {
NonnegativeBalanceInvariant(t, app, log)
TotalCoinsInvariant(t, app, log)
}
// NonnegativeBalanceInvariant checks that all accounts in the application have non-negative balances
func NonnegativeBalanceInvariant(t *testing.T, app *mock.App, log string) {
ctx := app.NewContext(false, abci.Header{})
accts := mock.GetAllAccounts(app.AccountMapper, ctx)
for _, acc := range accts {
coins := acc.GetCoins()
assert.True(t, coins.IsNotNegative(),
fmt.Sprintf("%s has a negative denomination of %s\n%s",
acc.GetAddress().String(),
coins.String(),
log),
)
}
}
// TotalCoinsInvariant checks that the sum of the coins across all accounts
// is what is expected
func TotalCoinsInvariant(t *testing.T, app *mock.App, log string) {
ctx := app.BaseApp.NewContext(false, abci.Header{})
totalCoins := sdk.Coins{}
chkAccount := func(acc auth.Account) bool {
coins := acc.GetCoins()
totalCoins = totalCoins.Plus(coins)
return false
}
app.AccountMapper.IterateAccounts(ctx, chkAccount)
require.Equal(t, app.TotalCoinsSupply, totalCoins, log)
}
// TestAndRunSingleInputMsgSend tests and runs a single msg send, with one input and one output, where both
// accounts already exist.
func TestAndRunSingleInputMsgSend(t *testing.T, r *rand.Rand, app *mock.App, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) {
fromKey := keys[r.Intn(len(keys))]
fromAddr := sdk.AccAddress(fromKey.PubKey().Address())
toKey := keys[r.Intn(len(keys))]
// Disallow sending money to yourself
for {
if !fromKey.Equals(toKey) {
break
}
toKey = keys[r.Intn(len(keys))]
}
toAddr := sdk.AccAddress(toKey.PubKey().Address())
initFromCoins := app.AccountMapper.GetAccount(ctx, fromAddr).GetCoins()
denomIndex := r.Intn(len(initFromCoins))
amt, goErr := randPositiveInt(r, initFromCoins[denomIndex].Amount)
if goErr != nil {
return "skipping bank send due to account having no coins of denomination " + initFromCoins[denomIndex].Denom, nil
}
action = fmt.Sprintf("%s is sending %s %s to %s",
fromAddr.String(),
amt.String(),
initFromCoins[denomIndex].Denom,
toAddr.String(),
)
log = fmt.Sprintf("%s\n%s", log, action)
coins := sdk.Coins{{initFromCoins[denomIndex].Denom, amt}}
var msg = MsgSend{
Inputs: []Input{NewInput(fromAddr, coins)},
Outputs: []Output{NewOutput(toAddr, coins)},
}
sendAndVerifyMsgSend(t, app, msg, ctx, log, []crypto.PrivKey{fromKey})
return action, nil
}
// Sends and verifies the transition of a msg send. This fails if there are repeated inputs or outputs
func sendAndVerifyMsgSend(t *testing.T, app *mock.App, msg MsgSend, ctx sdk.Context, log string, privkeys []crypto.PrivKey) {
initialInputAddrCoins := make([]sdk.Coins, len(msg.Inputs))
initialOutputAddrCoins := make([]sdk.Coins, len(msg.Outputs))
AccountNumbers := make([]int64, len(msg.Inputs))
SequenceNumbers := make([]int64, len(msg.Inputs))
for i := 0; i < len(msg.Inputs); i++ {
acc := app.AccountMapper.GetAccount(ctx, msg.Inputs[i].Address)
AccountNumbers[i] = acc.GetAccountNumber()
SequenceNumbers[i] = acc.GetSequence()
initialInputAddrCoins[i] = acc.GetCoins()
}
for i := 0; i < len(msg.Outputs); i++ {
acc := app.AccountMapper.GetAccount(ctx, msg.Outputs[i].Address)
initialOutputAddrCoins[i] = acc.GetCoins()
}
tx := mock.GenTx([]sdk.Msg{msg},
AccountNumbers,
SequenceNumbers,
privkeys...)
res := app.Deliver(tx)
if !res.IsOK() {
// TODO: Do this in a more 'canonical' way
fmt.Println(res)
fmt.Println(log)
t.FailNow()
}
for i := 0; i < len(msg.Inputs); i++ {
terminalInputCoins := app.AccountMapper.GetAccount(ctx, msg.Inputs[i].Address).GetCoins()
require.Equal(t,
initialInputAddrCoins[i].Minus(msg.Inputs[i].Coins),
terminalInputCoins,
fmt.Sprintf("Input #%d had an incorrect amount of coins\n%s", i, log),
)
}
for i := 0; i < len(msg.Outputs); i++ {
terminalOutputCoins := app.AccountMapper.GetAccount(ctx, msg.Outputs[i].Address).GetCoins()
require.Equal(t,
initialOutputAddrCoins[i].Plus(msg.Outputs[i].Coins),
terminalOutputCoins,
fmt.Sprintf("Output #%d had an incorrect amount of coins\n%s", i, log),
)
}
}
func randPositiveInt(r *rand.Rand, max sdk.Int) (sdk.Int, error) {
if !max.GT(sdk.OneInt()) {
return sdk.Int{}, errors.New("max too small")
}
max = max.Sub(sdk.OneInt())
return sdk.NewIntFromBigInt(new(big.Int).Rand(r, max.BigInt())).Add(sdk.OneInt()), nil
}