refactor!: extract simulation helpers out of simapp (#13402)

This commit is contained in:
Julien Robert
2022-09-27 21:19:44 +02:00
committed by GitHub
parent 9bae8a817f
commit 91d66f30e1
22 changed files with 179 additions and 195 deletions
+40
View File
@@ -1,6 +1,8 @@
package testutil
import (
"bytes"
"fmt"
"io"
"net/http"
)
@@ -36,3 +38,41 @@ func GetRequestWithHeaders(url string, headers map[string]string) ([]byte, error
return body, nil
}
// GetRequest defines a wrapper around an HTTP GET request with a provided URL.
// An error is returned if the request or reading the body fails.
func GetRequest(url string) ([]byte, error) {
res, err := http.Get(url) //nolint:gosec
if err != nil {
return nil, err
}
defer func() {
_ = res.Body.Close()
}()
body, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}
return body, nil
}
// PostRequest defines a wrapper around an HTTP POST request with a provided URL and data.
// An error is returned if the request or reading the body fails.
func PostRequest(url string, contentType string, data []byte) ([]byte, error) {
res, err := http.Post(url, contentType, bytes.NewBuffer(data)) //nolint:gosec
if err != nil {
return nil, fmt.Errorf("error while sending post request: %w", err)
}
defer func() {
_ = res.Body.Close()
}()
bz, err := io.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("error reading response body: %w", err)
}
return bz, nil
}
-48
View File
@@ -1,48 +0,0 @@
// Package rest provides HTTP types and primitives for REST
// requests validation and responses handling.
package rest
import (
"bytes"
"fmt"
"io"
"net/http"
)
// GetRequest defines a wrapper around an HTTP GET request with a provided URL.
// An error is returned if the request or reading the body fails.
func GetRequest(url string) ([]byte, error) {
res, err := http.Get(url) //nolint:gosec
if err != nil {
return nil, err
}
defer func() {
_ = res.Body.Close()
}()
body, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}
return body, nil
}
// PostRequest defines a wrapper around an HTTP POST request with a provided URL and data.
// An error is returned if the request or reading the body fails.
func PostRequest(url string, contentType string, data []byte) ([]byte, error) {
res, err := http.Post(url, contentType, bytes.NewBuffer(data)) //nolint:gosec
if err != nil {
return nil, fmt.Errorf("error while sending post request: %w", err)
}
defer func() {
_ = res.Body.Close()
}()
bz, err := io.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("error reading response body: %w", err)
}
return bz, nil
}
+1 -5
View File
@@ -29,11 +29,7 @@ import (
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)
// SimAppChainID hardcoded chainID for simulation
const (
DefaultGenTxGas = 10000000
SimAppChainID = "simulation-app"
)
const DefaultGenTxGas = 10000000
// DefaultConsensusParams defines the default Tendermint consensus params used in
// SimApp testing.
+124
View File
@@ -0,0 +1,124 @@
package sims
import (
"encoding/json"
"fmt"
"os"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/runtime"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/kv"
"github.com/cosmos/cosmos-sdk/types/module"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
"github.com/tendermint/tendermint/libs/log"
dbm "github.com/tendermint/tm-db"
)
// SetupSimulation creates the config, db (levelDB), temporary directory and logger for the simulation tests.
// If `skip` is false it skips the current test. `skip` should be set using the `FlagEnabledValue` flag.
// Returns error on an invalid db intantiation or temp dir creation.
func SetupSimulation(config simtypes.Config, dirPrefix, dbName string, verbose, skip bool) (dbm.DB, string, log.Logger, bool, error) {
if !skip {
return nil, "", nil, true, nil
}
var logger log.Logger
if verbose {
logger = log.TestingLogger()
} else {
logger = log.NewNopLogger()
}
dir, err := os.MkdirTemp("", dirPrefix)
if err != nil {
return nil, "", nil, false, err
}
db, err := dbm.NewDB(dbName, dbm.BackendType(config.DBBackend), dir)
if err != nil {
return nil, "", nil, false, err
}
return db, dir, logger, false, nil
}
// SimulationOperations retrieves the simulation params from the provided file path
// and returns all the modules weighted operations
func SimulationOperations(app runtime.AppI, cdc codec.JSONCodec, config simtypes.Config) []simtypes.WeightedOperation {
simState := module.SimulationState{
AppParams: make(simtypes.AppParams),
Cdc: cdc,
}
if config.ParamsFile != "" {
bz, err := os.ReadFile(config.ParamsFile)
if err != nil {
panic(err)
}
err = json.Unmarshal(bz, &simState.AppParams)
if err != nil {
panic(err)
}
}
simState.Contents = app.SimulationManager().GetProposalContents(simState)
return app.SimulationManager().WeightedOperations(simState)
}
// CheckExportSimulation exports the app state and simulation parameters to JSON
// if the export paths are defined.
func CheckExportSimulation(app runtime.AppI, config simtypes.Config, params simtypes.Params) error {
if config.ExportStatePath != "" {
fmt.Println("exporting app state...")
exported, err := app.ExportAppStateAndValidators(false, nil)
if err != nil {
return err
}
if err := os.WriteFile(config.ExportStatePath, []byte(exported.AppState), 0o600); err != nil {
return err
}
}
if config.ExportParamsPath != "" {
fmt.Println("exporting simulation params...")
paramsBz, err := json.MarshalIndent(params, "", " ")
if err != nil {
return err
}
if err := os.WriteFile(config.ExportParamsPath, paramsBz, 0o600); err != nil {
return err
}
}
return nil
}
// PrintStats prints the corresponding statistics from the app DB.
func PrintStats(db dbm.DB) {
fmt.Println("\nLevelDB Stats")
fmt.Println(db.Stats()["leveldb.stats"])
fmt.Println("LevelDB cached block size", db.Stats()["leveldb.cachedblock"])
}
// GetSimulationLog unmarshals the KVPair's Value to the corresponding type based on the
// each's module store key and the prefix bytes of the KVPair's key.
func GetSimulationLog(storeName string, sdr sdk.StoreDecoderRegistry, kvAs, kvBs []kv.Pair) (log string) {
for i := 0; i < len(kvAs); i++ {
if len(kvAs[i].Value) == 0 && len(kvBs[i].Value) == 0 {
// skip if the value doesn't have any bytes
continue
}
decoder, ok := sdr[storeName]
if ok {
log += decoder(kvAs[i], kvBs[i])
} else {
log += fmt.Sprintf("store A %X => %X\nstore B %X => %X\n", kvAs[i].Key, kvAs[i].Value, kvBs[i].Key, kvBs[i].Value)
}
}
return log
}
+48
View File
@@ -0,0 +1,48 @@
package sims
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/kv"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
)
func TestGetSimulationLog(t *testing.T) {
legacyAmino := codec.NewLegacyAmino()
decoders := make(sdk.StoreDecoderRegistry)
decoders[authtypes.StoreKey] = func(kvAs, kvBs kv.Pair) string { return "10" }
tests := []struct {
store string
kvPairs []kv.Pair
expectedLog string
}{
{
"Empty",
[]kv.Pair{{}},
"",
},
{
authtypes.StoreKey,
[]kv.Pair{{Key: authtypes.GlobalAccountNumberKey, Value: legacyAmino.MustMarshal(uint64(10))}},
"10",
},
{
"OtherStore",
[]kv.Pair{{Key: []byte("key"), Value: []byte("value")}},
fmt.Sprintf("store A %X => %X\nstore B %X => %X\n", []byte("key"), []byte("value"), []byte("key"), []byte("value")),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.store, func(t *testing.T) {
require.Equal(t, tt.expectedLog, GetSimulationLog(tt.store, decoders, tt.kvPairs, tt.kvPairs), tt.store)
})
}
}