diff --git a/cmd/gaia/simulation/sim_test.go b/cmd/gaia/simulation/sim_test.go index d00e56bab..3cbbd68c8 100644 --- a/cmd/gaia/simulation/sim_test.go +++ b/cmd/gaia/simulation/sim_test.go @@ -9,7 +9,13 @@ import ( "github.com/tendermint/tendermint/libs/log" gaia "github.com/cosmos/cosmos-sdk/cmd/gaia/app" - // stakesim "github.com/cosmos/cosmos-sdk/x/stake/simulation" + "github.com/cosmos/cosmos-sdk/x/mock" +) + +const ( + NumKeys = 10 + NumBlocks = 1000 + BlockSize = 1000 ) func TestFullGaiaSimulation(t *testing.T) { @@ -18,4 +24,16 @@ func TestFullGaiaSimulation(t *testing.T) { db := dbm.NewMemDB() app := gaia.NewGaiaApp(logger, db, nil) require.Equal(t, "GaiaApp", app.Name()) + + // Run randomized simulation + mock.RandomizedTesting( + t, app.BaseApp, + []mock.TestAndRunTx{}, + []mock.RandSetup{}, + []mock.Invariant{}, + NumKeys, + NumBlocks, + BlockSize, + ) + } diff --git a/x/mock/random_simulate_blocks.go b/x/mock/random_simulate_blocks.go index 452b8f6bf..5021d2785 100644 --- a/x/mock/random_simulate_blocks.go +++ b/x/mock/random_simulate_blocks.go @@ -2,36 +2,36 @@ package mock import ( "fmt" - "math/big" "math/rand" "testing" "time" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" ) // RandomizedTesting tests application by sending random messages. -func (app *App) RandomizedTesting( - t *testing.T, ops []TestAndRunTx, setups []RandSetup, +func RandomizedTesting( + t *testing.T, app *baseapp.BaseApp, ops []TestAndRunTx, setups []RandSetup, invariants []Invariant, numKeys int, numBlocks int, blockSize int, ) { time := time.Now().UnixNano() - app.RandomizedTestingFromSeed(t, time, ops, setups, invariants, numKeys, numBlocks, blockSize) + RandomizedTestingFromSeed(t, app, time, ops, setups, invariants, numKeys, numBlocks, blockSize) } // RandomizedTestingFromSeed tests an application by running the provided // operations, testing the provided invariants, but using the provided seed. -func (app *App) RandomizedTestingFromSeed( - t *testing.T, seed int64, ops []TestAndRunTx, setups []RandSetup, +func RandomizedTestingFromSeed( + t *testing.T, app *baseapp.BaseApp, seed int64, ops []TestAndRunTx, setups []RandSetup, invariants []Invariant, numKeys int, numBlocks int, blockSize int, ) { log := fmt.Sprintf("Starting SingleModuleTest with randomness created with seed %d", int(seed)) - keys, addrs := GeneratePrivKeyAddressPairs(numKeys) + keys, _ := GeneratePrivKeyAddressPairs(numKeys) r := rand.New(rand.NewSource(seed)) - RandomSetGenesis(r, app, addrs, []string{"foocoin"}) + // XXX TODO + // RandomSetGenesis(r, app, addrs, []string{"foocoin"}) app.InitChain(abci.RequestInitChain{}) for i := 0; i < len(setups); i++ { setups[i](r, keys) @@ -45,7 +45,7 @@ func (app *App) RandomizedTestingFromSeed( // Make sure invariants hold at beginning of block and when nothing was // done. - app.assertAllInvariants(t, invariants, log) + AssertAllInvariants(t, app, invariants, log) ctx := app.NewContext(false, header) @@ -56,7 +56,7 @@ func (app *App) RandomizedTestingFromSeed( log += "\n" + logUpdate require.Nil(t, err, log) - app.assertAllInvariants(t, invariants, log) + AssertAllInvariants(t, app, invariants, log) } app.EndBlock(abci.RequestEndBlock{}) @@ -64,100 +64,8 @@ func (app *App) RandomizedTestingFromSeed( } } -// SimpleRandomizedTestingFromSeed -func (app *App) SimpleRandomizedTestingFromSeed( - t *testing.T, seed int64, ops []TestAndRunMsg, setups []RandSetup, - invariants []Invariant, numKeys int, numBlocks int, blockSize int, -) { - log := fmt.Sprintf("Starting SimpleSingleModuleTest with randomness created with seed %d", int(seed)) - keys, addrs := GeneratePrivKeyAddressPairs(numKeys) - r := rand.New(rand.NewSource(seed)) - - RandomSetGenesis(r, app, addrs, []string{"foocoin"}) - app.InitChain(abci.RequestInitChain{}) - for i := 0; i < len(setups); i++ { - setups[i](r, keys) - } - app.Commit() - - header := abci.Header{Height: 0} - - for i := 0; i < numBlocks; i++ { - app.BeginBlock(abci.RequestBeginBlock{}) - - app.assertAllInvariants(t, invariants, log) - - ctx := app.NewContext(false, header) - - // TODO: Add modes to simulate "no load", "medium load", and - // "high load" blocks. - for j := 0; j < blockSize; j++ { - logUpdate, err := ops[r.Intn(len(ops))](t, r, ctx, keys, log) - log += "\n" + logUpdate - - require.Nil(t, err, log) - app.assertAllInvariants(t, invariants, log) - } - - app.EndBlock(abci.RequestEndBlock{}) - header.Height++ - } -} - -func (app *App) assertAllInvariants(t *testing.T, tests []Invariant, log string) { +func AssertAllInvariants(t *testing.T, app *baseapp.BaseApp, tests []Invariant, log string) { for i := 0; i < len(tests); i++ { tests[i](t, app, log) } } - -// BigInterval is a representation of the interval [lo, hi), where -// lo and hi are both of type sdk.Int -type BigInterval struct { - lo sdk.Int - hi sdk.Int -} - -// RandFromBigInterval chooses an interval uniformly from the provided list of -// BigIntervals, and then chooses an element from an interval uniformly at random. -func RandFromBigInterval(r *rand.Rand, intervals []BigInterval) sdk.Int { - if len(intervals) == 0 { - return sdk.ZeroInt() - } - - interval := intervals[r.Intn(len(intervals))] - - lo := interval.lo - hi := interval.hi - - diff := hi.Sub(lo) - result := sdk.NewIntFromBigInt(new(big.Int).Rand(r, diff.BigInt())) - result = result.Add(lo) - - return result -} - -// shamelessly copied from https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang#31832326 - -const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" -const ( - letterIdxBits = 6 // 6 bits to represent a letter index - letterIdxMask = 1<= 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) -} diff --git a/x/mock/types.go b/x/mock/types.go index 8a1c3d3e8..3a68e2422 100644 --- a/x/mock/types.go +++ b/x/mock/types.go @@ -4,6 +4,7 @@ import ( "math/rand" "testing" + "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/tendermint/tendermint/crypto" ) @@ -13,33 +14,24 @@ type ( // transition was as expected. It returns a descriptive message "action" // about what this fuzzed tx actually did, for ease of debugging. TestAndRunTx func( - t *testing.T, r *rand.Rand, app *App, ctx sdk.Context, + t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, privKeys []crypto.PrivKey, log string, ) (action string, err sdk.Error) - // TestAndRunMsg produces a fuzzed message, calls the appropriate handler - // (bypassing all ante handler checks), and ensures that the state - // transition was as expected. It returns a descriptive message "action" - // about what this fuzzed msg actually did for ease of debugging. - TestAndRunMsg func( - t *testing.T, r *rand.Rand, ctx sdk.Context, - privKey []crypto.PrivKey, log string, - ) (action string, err sdk.Error) - // RandSetup performs the random setup the mock module needs. RandSetup func(r *rand.Rand, privKeys []crypto.PrivKey) // An Invariant is a function which tests a particular invariant. // If the invariant has been broken, the function should halt the // test and output the log. - Invariant func(t *testing.T, app *App, log string) + Invariant func(t *testing.T, app *baseapp.BaseApp, log string) ) // PeriodicInvariant returns an Invariant function closure that asserts // a given invariant if the mock application's last block modulo the given // period is congruent to the given offset. func PeriodicInvariant(invariant Invariant, period int, offset int) Invariant { - return func(t *testing.T, app *App, log string) { + return func(t *testing.T, app *baseapp.BaseApp, log string) { if int(app.LastBlockHeight())%period == offset { invariant(t, app, log) } diff --git a/x/mock/util.go b/x/mock/util.go new file mode 100644 index 000000000..7b86cc69e --- /dev/null +++ b/x/mock/util.go @@ -0,0 +1,60 @@ +package mock + +import ( + "math/big" + "math/rand" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// BigInterval is a representation of the interval [lo, hi), where +// lo and hi are both of type sdk.Int +type BigInterval struct { + lo sdk.Int + hi sdk.Int +} + +// RandFromBigInterval chooses an interval uniformly from the provided list of +// BigIntervals, and then chooses an element from an interval uniformly at random. +func RandFromBigInterval(r *rand.Rand, intervals []BigInterval) sdk.Int { + if len(intervals) == 0 { + return sdk.ZeroInt() + } + + interval := intervals[r.Intn(len(intervals))] + + lo := interval.lo + hi := interval.hi + + diff := hi.Sub(lo) + result := sdk.NewIntFromBigInt(new(big.Int).Rand(r, diff.BigInt())) + result = result.Add(lo) + + return result +} + +// shamelessly copied from https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang#31832326 + +const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +const ( + letterIdxBits = 6 // 6 bits to represent a letter index + letterIdxMask = 1<= 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) +}