Skip to content

Commit

Permalink
new: add testcases for app
Browse files Browse the repository at this point in the history
  • Loading branch information
jdkanani committed Apr 9, 2020
1 parent 768e4f2 commit 6092d97
Show file tree
Hide file tree
Showing 6 changed files with 372 additions and 61 deletions.
66 changes: 5 additions & 61 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -556,60 +556,6 @@ func (app *HeimdallApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) ab
}
}

// initialize store from a genesis state
// func (app *HeimdallApp) initFromGenesisState(ctx sdk.Context, genesisState GenesisState) []abci.ValidatorUpdate {

// // Load the genesis accounts
// for _, genacc := range genesisState.Accounts {
// acc := app.accountKeeper.NewAccountWithAddress(ctx, types.BytesToHeimdallAddress(genacc.Address.Bytes()))
// acc.SetCoins(genacc.Coins)
// acc.SetSequence(genacc.Sequence)
// app.accountKeeper.SetAccount(ctx, acc)
// }

// //
// // InitGenesis
// //
// auth.InitGenesis(ctx, app.accountKeeper, genesisState.AuthData)
// bank.InitGenesis(ctx, app.bankKeeper, genesisState.BankData)
// supply.InitGenesis(ctx, app.supplyKeeper, app.accountKeeper, genesisState.SupplyData)
// bor.InitGenesis(ctx, app.borKeeper, genesisState.BorData)
// // staking should be initialized before checkpoint as checkpoint genesis initialization may depend on staking genesis. [eg.. rewardroot calculation]
// staking.InitGenesis(ctx, app.stakingKeeper, genesisState.StakingData)
// checkpoint.InitGenesis(ctx, app.checkpointKeeper, genesisState.CheckpointData)
// clerk.InitGenesis(ctx, app.clerkKeeper, genesisState.ClerkData)
// // validate genesis state
// if err := ValidateGenesisState(genesisState); err != nil {
// panic(err) // TODO find a way to do this w/o panics
// }

// // increment accumulator if starting from genesis
// if isGenesis {
// app.StakingKeeper.IncrementAccum(ctx, 1)
// }

// //
// // get val updates
// //

// var valUpdates []abci.ValidatorUpdate

// // check if validator is current validator
// // add to val updates else skip
// for _, validator := range genesisState.StakingData.Validators {
// if validator.IsCurrentValidator(genesisState.CheckpointData.AckCount) {
// // convert to Validator Update
// updateVal := abci.ValidatorUpdate{
// Power: int64(validator.VotingPower),
// PubKey: validator.PubKey.ABCIPubKey(),
// }
// // Add validator to validator updated to be processed below
// valUpdates = append(valUpdates, updateVal)
// }
// }
// return valUpdates
// }

// LoadHeight loads a particular height
func (app *HeimdallApp) LoadHeight(height int64) error {
return app.LoadVersion(height, app.keys[bam.MainStoreKey])
Expand Down Expand Up @@ -654,11 +600,9 @@ func (app *HeimdallApp) GetSubspace(moduleName string) subspace.Subspace {
return app.subspaces[moduleName]
}

// GetMaccPerms returns a copy of the module account permissions
func GetMaccPerms() map[string][]string {
dupMaccPerms := make(map[string][]string)
for k, v := range maccPerms {
dupMaccPerms[k] = v
}
return dupMaccPerms
// GetModuleManager returns module manager
//
// NOTE: This is solely to be used for testing purposes.
func (app *HeimdallApp) GetModuleManager() *module.Manager {
return app.mm
}
48 changes: 48 additions & 0 deletions app/app_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package app

import (
"os"
"testing"

"github.com/cosmos/cosmos-sdk/codec"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
db "github.com/tendermint/tm-db"
)

func TestHeimdalldExport(t *testing.T) {
db := db.NewMemDB()
happ := NewHeimdallApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db)
genesisState := NewDefaultGenesisState()
err := setGenesis(happ, genesisState)
require.NoError(t, err)

// Making a new app object with the db, so that initchain hasn't been called
newHapp := NewHeimdallApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db)
_, _, err = newHapp.ExportAppStateAndValidators()
require.NoError(t, err, "ExportAppStateAndValidators should not have an error")
}

func TestMakePulp(t *testing.T) {
pulp := MakePulp()
require.NotNil(t, pulp, "Pulp should be nil")
}

func setGenesis(app *HeimdallApp, genesisState GenesisState) error {
stateBytes, err := codec.MarshalJSONIndent(app.Codec(), genesisState)
if err != nil {
return err
}

// Initialize the chain
app.InitChain(
abci.RequestInitChain{
Validators: []abci.ValidatorUpdate{},
AppStateBytes: stateBytes,
},
)

app.Commit()
return nil
}
16 changes: 16 additions & 0 deletions auth/types/params_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package types

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestParamsEqual(t *testing.T) {
p1 := DefaultParams()
p2 := DefaultParams()
require.Equal(t, p1, p2)

p1.TxSigLimit += 10
require.NotEqual(t, p1, p2)
}
86 changes: 86 additions & 0 deletions types/simulation/account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package simulation

import (
"math/rand"

"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/secp256k1"

sdk "github.com/cosmos/cosmos-sdk/types"
hmTypes "github.com/maticnetwork/heimdall/types"
)

// Account contains a privkey, pubkey, address tuple
// eventually more useful data can be placed in here.
// (e.g. number of coins)
type Account struct {
PrivKey crypto.PrivKey
PubKey crypto.PubKey
Address hmTypes.HeimdallAddress
}

// Equals returns true if two accounts are equal
func (acc Account) Equals(acc2 Account) bool {
return acc.Address.Equals(acc2.Address)
}

// RandomAcc picks and returns a random account from an array and returs its
// position in the array.
func RandomAcc(r *rand.Rand, accs []Account) (Account, int) {
idx := r.Intn(len(accs))
return accs[idx], idx
}

// RandomAccounts generates n random accounts
func RandomAccounts(r *rand.Rand, n int) []Account {
accs := make([]Account, n)
for i := 0; i < n; i++ {
// don't need that much entropy for simulation
privkeySeed := make([]byte, 15)
r.Read(privkeySeed)

accs[i].PrivKey = secp256k1.GenPrivKeySecp256k1(privkeySeed)
accs[i].PubKey = accs[i].PrivKey.PubKey()
accs[i].Address = hmTypes.BytesToHeimdallAddress(accs[i].PubKey.Address().Bytes())
}

return accs
}

// FindAccount iterates over all the simulation accounts to find the one that matches
// the given address
func FindAccount(accs []Account, address hmTypes.HeimdallAddress) (Account, bool) {
for _, acc := range accs {
if acc.Address.Equals(address) {
return acc, true
}
}

return Account{}, false
}

// RandomFees returns a random fee by selecting a random coin denomination and
// amount from the account's available balance. If the user doesn't have enough
// funds for paying fees, it returns empty coins.
func RandomFees(r *rand.Rand, ctx sdk.Context, spendableCoins sdk.Coins) (sdk.Coins, error) {
if spendableCoins.Empty() {
return nil, nil
}

denomIndex := r.Intn(len(spendableCoins))
randCoin := spendableCoins[denomIndex]

if randCoin.Amount.IsZero() {
return nil, nil
}

amt, err := RandPositiveInt(r, randCoin.Amount)
if err != nil {
return nil, err
}

// Create a random fee and verify the fees are within the account's spendable
// balance.
fees := sdk.NewCoins(sdk.NewCoin(randCoin.Denom, amt))
return fees, nil
}
153 changes: 153 additions & 0 deletions types/simulation/rand_util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package simulation

import (
"errors"
"math/big"
"math/rand"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
)

const (
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)

// shamelessly copied from
// https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang#31832326

// RandStringOfLength generates a random string of a particular length
func RandStringOfLength(r *rand.Rand, n int) string {
b := make([]byte, n)
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
for i, cache, remain := n-1, r.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = r.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}

// RandPositiveInt get a rand positive sdk.Int
func RandPositiveInt(r *rand.Rand, max sdk.Int) (sdk.Int, error) {
if !max.GTE(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
}

// RandomAmount generates a random amount
// Note: The range of RandomAmount includes max, and is, in fact, biased to return max as well as 0.
func RandomAmount(r *rand.Rand, max sdk.Int) sdk.Int {
var randInt = big.NewInt(0)
switch r.Intn(10) {
case 0:
// randInt = big.NewInt(0)
case 1:
randInt = max.BigInt()
default: // NOTE: there are 10 total cases.
randInt = big.NewInt(0).Rand(r, max.BigInt()) // up to max - 1
}
return sdk.NewIntFromBigInt(randInt)
}

// RandomDecAmount generates a random decimal amount
// Note: The range of RandomDecAmount includes max, and is, in fact, biased to return max as well as 0.
func RandomDecAmount(r *rand.Rand, max sdk.Dec) sdk.Dec {
var randInt = big.NewInt(0)
switch r.Intn(10) {
case 0:
// randInt = big.NewInt(0)
case 1:
randInt = max.Int // the underlying big int with all precision bits.
default: // NOTE: there are 10 total cases.
randInt = big.NewInt(0).Rand(r, max.Int)
}
return sdk.NewDecFromBigIntWithPrec(randInt, sdk.Precision)
}

// RandTimestamp generates a random timestamp
func RandTimestamp(r *rand.Rand) time.Time {
// json.Marshal breaks for timestamps greater with year greater than 9999
unixTime := r.Int63n(253373529600)
return time.Unix(unixTime, 0)
}

// RandIntBetween returns a random int between two numbers inclusively.
func RandIntBetween(r *rand.Rand, min, max int) int {
return r.Intn(max-min) + min
}

// RandSubsetCoins returns random subset of the provided coins
// will return at least one coin unless coins argument is empty or malformed
// i.e. 0 amt in coins
func RandSubsetCoins(r *rand.Rand, coins sdk.Coins) sdk.Coins {
if len(coins) == 0 {
return sdk.Coins{}
}
// make sure at least one coin added
denomIdx := r.Intn(len(coins))
coin := coins[denomIdx]
amt, err := RandPositiveInt(r, coin.Amount)
// malformed coin. 0 amt in coins
if err != nil {
return sdk.Coins{}
}
subset := sdk.Coins{sdk.NewCoin(coin.Denom, amt)}
for i, c := range coins {
// skip denom that we already chose earlier
if i == denomIdx {
continue
}
// coin flip if multiple coins
// if there is single coin then return random amount of it
if r.Intn(2) == 0 && len(coins) != 1 {
continue
}

amt, err := RandPositiveInt(r, c.Amount)
// ignore errors and try another denom
if err != nil {
continue
}
subset = append(subset, sdk.NewCoin(c.Denom, amt))
}
return subset.Sort()
}

// DeriveRand derives a new Rand deterministically from another random source.
// Unlike rand.New(rand.NewSource(seed)), the result is "more random"
// depending on the source and state of r.
//
// NOTE: not crypto safe.
func DeriveRand(r *rand.Rand) *rand.Rand {
const num = 8 // TODO what's a good number? Too large is too slow.
ms := multiSource(make([]rand.Source, num))
for i := 0; i < num; i++ {
ms[i] = rand.NewSource(r.Int63())
}
return rand.New(ms)
}

type multiSource []rand.Source

func (ms multiSource) Int63() (r int64) {
for _, source := range ms {
r ^= source.Int63()
}
return r
}

func (ms multiSource) Seed(seed int64) {
panic("multiSource Seed should not be called")
}
Loading

0 comments on commit 6092d97

Please sign in to comment.