diff --git a/x/mock/simulation/event_stats.go b/x/mock/simulation/event_stats.go new file mode 100644 index 000000000..5276c533b --- /dev/null +++ b/x/mock/simulation/event_stats.go @@ -0,0 +1,33 @@ +package simulation + +import ( + "fmt" + "sort" +) + +type eventStats map[string]uint + +func newEventStats() eventStats { + events := make(map[string]uint) + return events + event := func(what string) { + events[what]++ + } +} + +func (es *eventStats) tally(eventDesc string) { + es[eventDesc]++ +} + +// Pretty-print events as a table +func (es eventStats) Print() { + var keys []string + for key := range es { + keys = append(keys, key) + } + sort.Strings(keys) + fmt.Printf("Event statistics: \n") + for _, key := range keys { + fmt.Printf(" % 60s => %d\n", key, es[key]) + } +} diff --git a/x/mock/simulation/operation.go b/x/mock/simulation/operation.go index 27757df2d..7710cf5c9 100644 --- a/x/mock/simulation/operation.go +++ b/x/mock/simulation/operation.go @@ -18,17 +18,21 @@ import ( // // Operations can optionally provide a list of "FutureOperations" to run later // These will be ran at the beginning of the corresponding block. -type Operation func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, - accounts []Account, event func(string)) ( - action string, futureOperations []FutureOperation, err error) +type Operation func(r *rand.Rand, app *baseapp.BaseApp, + ctx sdk.Context, accounts []Account, event func(string)) ( + action string, futureOps []FutureOperation, err error) // queue of operations type OperationQueue map[int][]Operation +func newOperationQueue() OperationQueue { + operationQueue := make(OperationQueue) + return operationQueue +} + // adds all future operations into the operation queue. func queueOperations(queuedOps OperationQueue, - queuedTimeOps []FutureOperation, - futureOps []FutureOperation) { + queuedTimeOps []FutureOperation, futureOps []FutureOperation) { if futureOps == nil { return diff --git a/x/mock/simulation/simulate.go b/x/mock/simulation/simulate.go index dc7c12bfe..e6749f4e1 100644 --- a/x/mock/simulation/simulate.go +++ b/x/mock/simulation/simulate.go @@ -21,10 +21,12 @@ import ( // RandSetup performs the random setup the mock module needs. type RandSetup func(r *rand.Rand, accounts []Account) +// AppStateFn returns the app state json bytes +type AppStateFn func(r *rand.Rand, accs []Account) json.RawMessage + // Simulate tests application by sending random messages. func Simulate(t *testing.T, app *baseapp.BaseApp, - appStateFn func(r *rand.Rand, accs []Account) json.RawMessage, - ops WeightedOperations, setups []RandSetup, + appStateFn AppStateFn, ops WeightedOperations, setups []RandSetup, invariants Invariants, numBlocks int, blockSize int, commit bool) error { time := time.Now().UnixNano() @@ -33,9 +35,9 @@ func Simulate(t *testing.T, app *baseapp.BaseApp, } // initialize the chain for the simulation -func initChain(r *rand.Rand, params Params, - accounts []Account, setups []RandSetup, app *baseapp.BaseApp, - appStateFn func(r *rand.Rand, accounts []Account) json.RawMessage) mockValidators { +func initChain(r *rand.Rand, params Params, accounts []Account, + setups []RandSetup, app *baseapp.BaseApp, + appStateFn AppStateFn) mockValidators { req := abci.RequestInitChain{ AppStateBytes: appStateFn(r, accounts), @@ -51,9 +53,10 @@ func initChain(r *rand.Rand, params Params, // SimulateFromSeed tests an application by running the provided // operations, testing the provided invariants, but using the provided seed. +// TODO split this monster function up func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, - appStateFn func(r *rand.Rand, accs []Account) json.RawMessage, - seed int64, ops WeightedOperations, setups []RandSetup, invariants Invariants, + appStateFn AppStateFn, seed int64, ops WeightedOperations, + setups []RandSetup, invariants Invariants, numBlocks int, blockSize int, commit bool) (simError error) { // in case we have to end early, don't os.Exit so that we can run cleanup code. @@ -71,20 +74,13 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, timestamp.UTC().Format(time.UnixDate), timestamp.Unix()) timeDiff := maxTimePerBlock - minTimePerBlock - accs := RandomAccounts(r, params.NumKeys) - - // Setup event stats - events := make(map[string]uint) - event := func(what string) { - events[what]++ - } - + eventStats := newEventStats() validators := initChain(r, params, accs, setups, app, appStateFn) - // Second variable to keep pending validator set (delayed one block since TM 0.24) - // Initially this is the same as the initial validator set - nextValidators := validators + // Second variable to keep pending validator set (delayed one block since + // TM 0.24) Initially this is the same as the initial validator set + nextValidators := validators() header := abci.Header{ Height: 1, @@ -108,10 +104,10 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, var pastVoteInfos [][]abci.VoteInfo request := RandomRequestBeginBlock(r, params, - validators, pastTimes, pastVoteInfos, event, header) + validators, pastTimes, pastVoteInfos, eventStats.tally, header) // These are operations which have been queued by previous operations - operationQueue := make(map[int][]Operation) + operationQueue := newOperationQueue() timeOperationQueue := []FutureOperation{} var blockLogBuilders []*strings.Builder @@ -120,7 +116,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, } displayLogs := logPrinter(testingMode, blockLogBuilders) blockSimulator := createBlockSimulator( - testingMode, tb, t, params, event, invariants, + testingMode, tb, t, params, eventStats.tally, invariants, ops, operationQueue, timeOperationQueue, numBlocks, blockSize, displayLogs) @@ -143,6 +139,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, // TODO split up the contents of this for loop into new functions for i := 0; i < numBlocks && !stopEarly; i++ { + // Log the header time for future lookup pastTimes = append(pastTimes, header.Time) pastVoteInfos = append(pastVoteInfos, request.LastCommitInfo.Votes) @@ -155,7 +152,6 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, app.BeginBlock(request) if testingMode { - // Make sure invariants hold at beginning of block invariants.assertAll(t, app, "BeginBlock", displayLogs) } @@ -166,13 +162,14 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, numQueuedOpsRan := runQueuedOperations( operationQueue, int(header.Height), tb, r, app, ctx, accs, logWriter, - displayLogs, event) + displayLogs, eventStats.tally) + numQueuedTimeOpsRan := runQueuedTimeOperations( timeOperationQueue, header.Time, tb, r, app, ctx, accs, - logWriter, displayLogs, event) + logWriter, displayLogs, eventStats.tally) + if testingMode && onOperation { - // Make sure invariants hold at end of queued operations invariants.assertAll(t, app, "QueuedOperations", displayLogs) } @@ -180,7 +177,6 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, operations := blockSimulator(r, app, ctx, accs, header, logWriter) opCount += operations + numQueuedOpsRan + numQueuedTimeOpsRan if testingMode { - // Make sure invariants hold at the operation invariants.assertAll(t, app, "StandardOperations", displayLogs) } @@ -194,7 +190,6 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, logWriter("EndBlock") if testingMode { - // Make sure invariants hold at end of block invariants.assertAll(t, app, "EndBlock", displayLogs) } if commit { @@ -208,24 +203,27 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, break } - // Generate a random RequestBeginBlock with the current validator set for the next block + // Generate a random RequestBeginBlock with the current validator set + // for the next block request = RandomRequestBeginBlock(r, params, validators, - pastTimes, pastVoteInfos, event, header) + pastTimes, pastVoteInfos, eventStats.tally, header) - // Update the validator set, which will be reflected in the application on the next block + // Update the validator set, which will be reflected in the application + // on the next block validators = nextValidators nextValidators = updateValidators(tb, r, params, - validators, res.ValidatorUpdates, event) + validators, res.ValidatorUpdates, eventStats.tally) } + if stopEarly { - DisplayEvents(events) - return + eventStats.Print() + return simError } fmt.Printf("\nSimulation complete. Final height (blocks): %d, "+ "final time (seconds), : %v, operations ran %d\n", header.Height, header.Time, opCount) - DisplayEvents(events) + eventStats.Print() return nil } @@ -252,7 +250,8 @@ func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, params header.Height, totalNumBlocks, opCount, blocksize) lastBlocksizeState, blocksize = getBlockSize(r, params, lastBlocksizeState, avgBlockSize) - for j := 0; j < blocksize; j++ { + for i := 0; i < blocksize; i++ { + logUpdate, futureOps, err := selectOp(r)(r, app, ctx, accounts, event) logWriter(logUpdate) if err != nil { @@ -280,9 +279,9 @@ func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, params // nolint: errcheck func runQueuedOperations(queueOps map[int][]Operation, - height int, tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, - accounts []Account, logWriter func(string), - displayLogs func(), event func(string)) (numOpsRan int) { + height int, tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, + ctx sdk.Context, accounts []Account, logWriter func(string), + displayLogs func(), tallyEvent func(string)) (numOpsRan int) { queuedOp, ok := queueOps[height] if !ok { @@ -291,10 +290,11 @@ func runQueuedOperations(queueOps map[int][]Operation, numOpsRan = len(queuedOp) for i := 0; i < numOpsRan; i++ { + // For now, queued operations cannot queue more operations. // If a need arises for us to support queued messages to queue more messages, this can // be changed. - logUpdate, _, err := queuedOp[i](r, app, ctx, accounts, event) + logUpdate, _, err := queuedOp[i](r, app, ctx, accounts, tallyEvent) logWriter(logUpdate) if err != nil { displayLogs() @@ -308,19 +308,21 @@ func runQueuedOperations(queueOps map[int][]Operation, func runQueuedTimeOperations(queueOps []FutureOperation, currentTime time.Time, tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []Account, - logWriter func(string), displayLogs func(), event func(string)) (numOpsRan int) { + logWriter func(string), displayLogs func(), tallyEvent func(string)) (numOpsRan int) { numOpsRan = 0 for len(queueOps) > 0 && currentTime.After(queueOps[0].BlockTime) { + // For now, queued operations cannot queue more operations. // If a need arises for us to support queued messages to queue more messages, this can // be changed. - logUpdate, _, err := queueOps[0].Op(r, app, ctx, accounts, event) + logUpdate, _, err := queueOps[0].Op(r, app, ctx, accounts, tallyEvent) logWriter(logUpdate) if err != nil { displayLogs() tb.FailNow() } + queueOps = queueOps[1:] numOpsRan++ } diff --git a/x/mock/simulation/util.go b/x/mock/simulation/util.go index 6b2c01052..02ef56d2e 100644 --- a/x/mock/simulation/util.go +++ b/x/mock/simulation/util.go @@ -3,7 +3,6 @@ package simulation import ( "fmt" "os" - "sort" "strings" "testing" "time" @@ -20,19 +19,6 @@ func getTestingMode(tb testing.TB) (testingMode bool, t *testing.T, b *testing.B return } -// Pretty-print events as a table -func DisplayEvents(events map[string]uint) { - var keys []string - for key := range events { - keys = append(keys, key) - } - sort.Strings(keys) - fmt.Printf("Event statistics: \n") - for _, key := range keys { - fmt.Printf(" % 60s => %d\n", key, events[key]) - } -} - // Builds a function to add logs for this particular block func addLogMessage(testingmode bool, blockLogBuilders []*strings.Builder, height int) func(string) {