From ad175a8b2d585ca88dc3d3e3f73fb3f58660febf Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Tue, 12 Sep 2023 23:08:59 +0200 Subject: [PATCH 01/45] feat: add txtar driver to gnoland ** as requested, this is a squash of the following commits: - feat: use commands.IO - chore: cleanup gnoland output using context logger - chore: cleanup integration test - chore: cleanup - feat: add test data example - chore: cleanup - fix: concurent run of gnoland - chore: cleanup - fix: cleanup - fix: docker integration test - chore: cleanup root dir and validate config - fix: correctly update command io argument - chore: cleanup Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/cmd/gnokey/main.go | 3 +- gno.land/cmd/gnoland/integration.go | 1 + gno.land/cmd/gnoland/integration_test.go | 365 ++++++++++++++++++ gno.land/cmd/gnoland/testdata/addpkg.txtar | 26 ++ .../cmd/gnoland/testdata/basic_commands.txtar | 22 ++ gno.land/pkg/gnoland/app.go | 103 ++++- gno.land/pkg/sdk/vm/keeper.go | 5 +- misc/docker-integration/integration_test.go | 1 + tm2/pkg/crypto/keys/client/add.go | 4 +- tm2/pkg/crypto/keys/client/addpkg.go | 4 +- tm2/pkg/crypto/keys/client/broadcast.go | 2 +- tm2/pkg/crypto/keys/client/call.go | 4 +- tm2/pkg/crypto/keys/client/delete.go | 4 +- tm2/pkg/crypto/keys/client/export.go | 4 +- tm2/pkg/crypto/keys/client/generate.go | 4 +- tm2/pkg/crypto/keys/client/generate_test.go | 2 +- tm2/pkg/crypto/keys/client/import.go | 4 +- tm2/pkg/crypto/keys/client/list.go | 4 +- tm2/pkg/crypto/keys/client/maketx.go | 8 +- tm2/pkg/crypto/keys/client/query.go | 14 +- tm2/pkg/crypto/keys/client/root.go | 24 +- tm2/pkg/crypto/keys/client/send.go | 4 +- tm2/pkg/crypto/keys/client/sign.go | 4 +- tm2/pkg/crypto/keys/client/verify.go | 4 +- 24 files changed, 556 insertions(+), 64 deletions(-) create mode 100644 gno.land/cmd/gnoland/integration.go create mode 100644 gno.land/cmd/gnoland/integration_test.go create mode 100644 gno.land/cmd/gnoland/testdata/addpkg.txtar create mode 100644 gno.land/cmd/gnoland/testdata/basic_commands.txtar diff --git a/gno.land/cmd/gnokey/main.go b/gno.land/cmd/gnokey/main.go index 1f3414dc893..28cb665eac1 100644 --- a/gno.land/cmd/gnokey/main.go +++ b/gno.land/cmd/gnokey/main.go @@ -5,11 +5,12 @@ import ( "fmt" "os" + "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" ) func main() { - cmd := client.NewRootCmd() + cmd := client.NewRootCmd(commands.NewDefaultIO()) if err := cmd.ParseAndRun(context.Background(), os.Args[1:]); err != nil { _, _ = fmt.Fprintf(os.Stderr, "%+v\n", err) diff --git a/gno.land/cmd/gnoland/integration.go b/gno.land/cmd/gnoland/integration.go new file mode 100644 index 00000000000..06ab7d0f9a3 --- /dev/null +++ b/gno.land/cmd/gnoland/integration.go @@ -0,0 +1 @@ +package main diff --git a/gno.land/cmd/gnoland/integration_test.go b/gno.land/cmd/gnoland/integration_test.go new file mode 100644 index 00000000000..bba3b8bdab1 --- /dev/null +++ b/gno.land/cmd/gnoland/integration_test.go @@ -0,0 +1,365 @@ +package main + +import ( + "context" + "flag" + "fmt" + "hash/crc32" + "os/exec" + "path/filepath" + "strconv" + "strings" + "sync" + "testing" + "time" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnomod" + abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" + "github.com/gnolang/gno/tm2/pkg/bft/config" + "github.com/gnolang/gno/tm2/pkg/bft/node" + "github.com/gnolang/gno/tm2/pkg/bft/privval" + bft "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/crypto/keys" + "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" + "github.com/gnolang/gno/tm2/pkg/db" + "github.com/gnolang/gno/tm2/pkg/log" + osm "github.com/gnolang/gno/tm2/pkg/os" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/jaekwon/testify/require" + "github.com/rogpeppe/go-internal/testscript" +) + +// XXX: should be centralize somewhere +const ( + test1Addr = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" + test1Seed = "source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast" +) + +func TestTestdata(t *testing.T) { + testscript.Run(t, setupGnolandTestScript2(t, "testdata")) +} + +func setupGnolandTestScript2(t *testing.T, txtarDir string) testscript.Params { + t.Helper() + + goModPath, err := exec.Command("go", "env", "GOMOD").CombinedOutput() + require.NoError(t, err) + + gnoRootDir := filepath.Dir(string(goModPath)) + gnoHomeDir := filepath.Join(t.TempDir(), "gno") + gnoDataDir := filepath.Join(t.TempDir(), "data") + + var muNodes sync.Mutex + nodes := map[string]*node.Node{} + t.Cleanup(func() { + for id, n := range nodes { + if err := n.Stop(); err != nil { + panic(fmt.Errorf("node %q was unable to stop: %w", id, err)) + } + } + }) + + return testscript.Params{ + Dir: txtarDir, + Setup: func(env *testscript.Env) error { + kb, err := keys.NewKeyBaseFromDir(gnoHomeDir) + if err != nil { + return err + } + + kb.CreateAccount("test1", test1Seed, "", "", 0, 0) + env.Setenv("USER_SEED_test1", test1Seed) + env.Setenv("USER_ADDR_test1", test1Addr) + + env.Setenv("GNOROOT", gnoRootDir) + env.Setenv("GNOHOME", gnoHomeDir) + + return nil + }, + Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){ + "gnoland": func(ts *testscript.TestScript, neg bool, args []string) { + muNodes.Lock() + defer muNodes.Unlock() + + if len(args) == 0 { + tsValidateError(ts, "gnoland", neg, fmt.Errorf("use gnoland [start|stop] command")) + return + } + + sid := getSessionID(ts) + + var cmd string + cmd, args = args[0], args[1:] + + var err error + switch cmd { + case "start": + if _, ok := nodes[sid]; ok { + err = fmt.Errorf("node %q already started", sid) + break + } + + dataDir := filepath.Join(gnoDataDir, sid) + var node *node.Node + if node, err = execTestingGnoland(t, dataDir, gnoRootDir, args); err == nil { + // XXX need mutex ? + nodes[sid] = node + + // get listen addr environement + // should have been updated with the right port on start + laddr := node.Config().RPC.ListenAddress + + // add default environement + ts.Setenv("RPC_ADDR", laddr) + ts.Setenv("GNODATA", gnoDataDir) + + // XXX: Use something similar to `require.Eventually` to check for node + // availability. For now, if this sleep duration is too short, the + // subsequent command might fail with an [internal error]. + time.Sleep(time.Millisecond * 300) + } + case "stop": + n, ok := nodes[sid] + if !ok { + err = fmt.Errorf("node %q not started cannot be stop", sid) + break + } + + if err = n.Stop(); err != nil { + delete(nodes, sid) + + // unset env dirs + ts.Setenv("RPC_ADDR", "") + ts.Setenv("GNODATA", "") + } + } + + tsValidateError(ts, "gnoland "+cmd, neg, err) + }, + "gnokey": func(ts *testscript.TestScript, neg bool, args []string) { + muNodes.Lock() + defer muNodes.Unlock() + + sid := getSessionID(ts) + + // Setup io command + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(ts.Stdout())) + io.SetErr(commands.WriteNopCloser(ts.Stderr())) + cmd := client.NewRootCmd(io) + + io.SetIn(strings.NewReader("\n")) // inject empty password to stdin + defaultArgs := []string{ + "-home", gnoHomeDir, + "-insecure-password-stdin=true", // there no use to not have this param by default + } + + if n, ok := nodes[sid]; ok { + if raddr := n.Config().RPC.ListenAddress; raddr != "" { + defaultArgs = append(defaultArgs, "-remote", raddr) + } + } + + // inject default argument, if duplicate + // arguments, it should be override by the ones + // user provided + args = append(defaultArgs, args...) + + err := cmd.ParseAndRun(context.Background(), args) + tsValidateError(ts, "gnokey", neg, err) + }, + }, + } +} + +func execTestingGnoland(t *testing.T, gnoDataDir, gnoRootDir string, args []string) (*node.Node, error) { + t.Helper() + + // Setup logger + logger := log.NewNopLogger() + + // Setup start config + scfg := &startCfg{} + { + fs := flag.NewFlagSet("start", flag.ExitOnError) + scfg.RegisterFlags(fs) + + // Override default value for flags + fs.VisitAll(func(f *flag.Flag) { + switch f.Name { + case "root-dir": + f.DefValue = gnoDataDir + case "chainid": + f.DefValue = "tendermint_test" + case "genesis-balances-file": + f.DefValue = filepath.Join(gnoRootDir, "gno.land", "genesis", "genesis_balances.txt") + case "genesis-txs-file": + f.DefValue = filepath.Join(gnoRootDir, "gno.land", "genesis", "genesis_txs.txt") + default: + return + } + + f.Value.Set(f.DefValue) + }) + + if err := fs.Parse(args); err != nil { + return nil, fmt.Errorf("unable to parse flags: %w", err) + } + } + + // Setup testing config + cfg := config.TestConfig().SetRootDir(gnoDataDir) + { + cfg.EnsureDirs() + cfg.Consensus.CreateEmptyBlocks = true + cfg.Consensus.CreateEmptyBlocksInterval = time.Duration(0) + cfg.RPC.ListenAddress = "tcp://127.0.0.1:0" + cfg.P2P.ListenAddress = "tcp://127.0.0.1:0" + } + + // Prepare genesis + if err := setupTestingGenesis(gnoDataDir, cfg, scfg, gnoRootDir); err != nil { + return nil, err + } + + // Create application and node + return createAppAndNode(cfg, logger, gnoRootDir, scfg) +} + +func getSessionID(ts *testscript.TestScript) string { + works := ts.Getenv("WORK") + sum := crc32.ChecksumIEEE([]byte(works)) + return strconv.FormatUint(uint64(sum), 16) +} + +func setupTestingGenesis(gnoDataDir string, cfg *config.Config, scfg *startCfg, gnoRootDir string) error { + newPrivValKey := cfg.PrivValidatorKeyFile() + newPrivValState := cfg.PrivValidatorStateFile() + priv := privval.LoadOrGenFilePV(newPrivValKey, newPrivValState) + + genesisFilePath := filepath.Join(gnoDataDir, cfg.Genesis) + osm.EnsureDir(filepath.Dir(genesisFilePath), 0o700) + if !osm.FileExists(genesisFilePath) { + genesisTxs := loadGenesisTxs(scfg.genesisTxsFile, scfg.chainID, scfg.genesisRemote) + pvPub := priv.GetPubKey() + + gen := &bft.GenesisDoc{ + GenesisTime: time.Now(), + ChainID: scfg.chainID, + ConsensusParams: abci.ConsensusParams{ + Block: &abci.BlockParams{ + // TODO: update limits. + MaxTxBytes: 1000000, // 1MB, + MaxDataBytes: 2000000, // 2MB, + MaxGas: 10000000, // 10M gas + TimeIotaMS: 100, // 100ms + }, + }, + Validators: []bft.GenesisValidator{ + { + Address: pvPub.Address(), + PubKey: pvPub, + Power: 10, + Name: "testvalidator", + }, + }, + } + + // Load distribution. + balances := loadGenesisBalances(scfg.genesisBalancesFile) + + // Load initial packages from examples. + // XXX: we should be able to config this + test1 := crypto.MustAddressFromString(test1Addr) + txs := []std.Tx{} + + // List initial packages to load from examples. + // println(filepath.Join(gnoRootDir, "examples")) + + pkgs, err := gnomod.ListPkgs(filepath.Join(gnoRootDir, "examples")) + if err != nil { + return fmt.Errorf("listing gno packages: %w", err) + } + + // Sort packages by dependencies. + sortedPkgs, err := pkgs.Sort() + if err != nil { + return fmt.Errorf("sorting packages: %w", err) + } + + // Filter out draft packages. + nonDraftPkgs := sortedPkgs.GetNonDraftPkgs() + + for _, pkg := range nonDraftPkgs { + // open files in directory as MemPackage. + memPkg := gno.ReadMemPackage(pkg.Dir, pkg.Name) + + var tx std.Tx + tx.Msgs = []std.Msg{ + vmm.MsgAddPackage{ + Creator: test1, + Package: memPkg, + Deposit: nil, + }, + } + + // XXX: add fee flag ? + // or maybe reduce fee to the minimum ? + tx.Fee = std.NewFee(50000, std.MustParseCoin("1000000ugnot")) + tx.Signatures = make([]std.Signature, len(tx.GetSigners())) + txs = append(txs, tx) + } + + // load genesis txs from file. + txs = append(txs, genesisTxs...) + + // construct genesis AppState. + gen.AppState = gnoland.GnoGenesisState{ + Balances: balances, + Txs: txs, + } + + writeGenesisFile(gen, genesisFilePath) + } + + return nil +} + +func createAppAndNode(cfg *config.Config, logger log.Logger, gnoRootDir string, scfg *startCfg) (*node.Node, error) { + gnoApp, err := gnoland.NewCustomApp(gnoland.CustomAppConfig{ + Logger: logger, + GnoRootDir: gnoRootDir, + SkipFailingGenesisTxs: scfg.skipFailingGenesisTxs, + MaxCycles: scfg.genesisMaxVMCycles, + DB: db.NewMemDB(), + }) + if err != nil { + return nil, fmt.Errorf("error in creating new app: %w", err) + } + + cfg.LocalApp = gnoApp + node, err := node.DefaultNewNode(cfg, logger) + if err != nil { + return nil, fmt.Errorf("error in creating node: %w", err) + } + + return node, node.Start() +} + +func tsValidateError(ts *testscript.TestScript, cmd string, neg bool, err error) { + if err != nil { + ts.Logf("%s error: %v\n", cmd, err) + if !neg { + ts.Fatalf("unexpected %s command failure", cmd) + } + } else { + if neg { + ts.Fatalf("unexpected %s command success", cmd) + } + } +} diff --git a/gno.land/cmd/gnoland/testdata/addpkg.txtar b/gno.land/cmd/gnoland/testdata/addpkg.txtar new file mode 100644 index 00000000000..5e871b058ac --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/addpkg.txtar @@ -0,0 +1,26 @@ +# test for add package + +## start a new node +gnoland start + +## add bar.gno package located in $WORK directory as gno.land/r/foobar/bar +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 + +## execute Render +gnokey maketx call -pkgpath gno.land/r/foobar/bar -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 + +## compare render +cmp stdout stdout.golden + +-- bar.gno -- +package bar + +func Render(path string) string { + return "hello from foo" +} + +-- stdout.golden -- +("hello from foo" string) +OK! +GAS WANTED: 2000000 +GAS USED: 69163 \ No newline at end of file diff --git a/gno.land/cmd/gnoland/testdata/basic_commands.txtar b/gno.land/cmd/gnoland/testdata/basic_commands.txtar new file mode 100644 index 00000000000..ee97d417d80 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/basic_commands.txtar @@ -0,0 +1,22 @@ +# test basic gnoland integrations commands + +## no arguments should fail +! gnoland + +## should be able to start +gnoland start + +## should not be able to start a node twice +! gnoland start + +## test1 account should be available on default +gnokey query auth/accounts/${USER_ADDR_test1} + +## invalid gnokey command should raise an error +! gnokey query foo/bar + +## should be able to stop default +gnoland stop + +## should not be able to stop a node twice +! gnoland stop diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index b10f251b115..dad87eaf44d 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -2,6 +2,8 @@ package gnoland import ( "fmt" + "os" + "os/exec" "path/filepath" "strings" @@ -20,12 +22,44 @@ import ( "github.com/gnolang/gno/tm2/pkg/store/iavl" ) +type CustomAppConfig struct { + DB dbm.DB + GnoRootDir string + SkipFailingGenesisTxs bool + Logger log.Logger + MaxCycles int64 +} + +func (c *CustomAppConfig) ApplyDefault() { + if c.Logger == nil { + c.Logger = log.NewNopLogger() + } + + if c.DB == nil { + c.DB = dbm.NewMemDB() + } + + if c.GnoRootDir == "" { + c.GnoRootDir = guessGnoRootDir() + } +} + +func (c *CustomAppConfig) validate() error { + if c.Logger == nil { + return fmt.Errorf("no logger provided") + } + + if c.DB == nil { + return fmt.Errorf("no db provided") + } + + return nil +} + // NewApp creates the GnoLand application. -func NewApp(rootDir string, skipFailingGenesisTxs bool, logger log.Logger, maxCycles int64) (abci.Application, error) { - // Get main DB. - db, err := dbm.NewDB("gnolang", dbm.GoLevelDBBackend, filepath.Join(rootDir, "data")) - if err != nil { - return nil, fmt.Errorf("error initializing database %q using path %q: %w", dbm.GoLevelDBBackend, rootDir, err) +func NewCustomApp(cfg CustomAppConfig) (abci.Application, error) { + if err := cfg.validate(); err != nil { + return nil, err } // Capabilities keys. @@ -33,21 +67,21 @@ func NewApp(rootDir string, skipFailingGenesisTxs bool, logger log.Logger, maxCy baseKey := store.NewStoreKey("base") // Create BaseApp. - baseApp := sdk.NewBaseApp("gnoland", logger, db, baseKey, mainKey) + baseApp := sdk.NewBaseApp("gnoland", cfg.Logger, cfg.DB, baseKey, mainKey) baseApp.SetAppVersion("dev") // Set mounts for BaseApp's MultiStore. - baseApp.MountStoreWithDB(mainKey, iavl.StoreConstructor, db) - baseApp.MountStoreWithDB(baseKey, dbadapter.StoreConstructor, db) + baseApp.MountStoreWithDB(mainKey, iavl.StoreConstructor, cfg.DB) + baseApp.MountStoreWithDB(baseKey, dbadapter.StoreConstructor, cfg.DB) // Construct keepers. acctKpr := auth.NewAccountKeeper(mainKey, ProtoGnoAccount) bankKpr := bank.NewBankKeeper(acctKpr) - stdlibsDir := filepath.Join("..", "gnovm", "stdlibs") - vmKpr := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, stdlibsDir, maxCycles) + stdlibsDir := filepath.Join(cfg.GnoRootDir, "gnovm", "stdlibs") + vmKpr := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, stdlibsDir, cfg.MaxCycles) // Set InitChainer - baseApp.SetInitChainer(InitChainer(baseApp, acctKpr, bankKpr, skipFailingGenesisTxs)) + baseApp.SetInitChainer(InitChainer(baseApp, acctKpr, bankKpr, cfg.SkipFailingGenesisTxs)) // Set AnteHandler authOptions := auth.AnteOptions{ @@ -88,6 +122,24 @@ func NewApp(rootDir string, skipFailingGenesisTxs bool, logger log.Logger, maxCy return baseApp, nil } +// NewApp creates the GnoLand application. +func NewApp(dataRootDir string, skipFailingGenesisTxs bool, logger log.Logger, maxCycles int64) (abci.Application, error) { + var err error + + var cfg CustomAppConfig + cfg.ApplyDefault() + + // Get main DB. + cfg.DB, err = dbm.NewDB("gnolang", dbm.GoLevelDBBackend, filepath.Join(dataRootDir, "data")) + if err != nil { + return nil, fmt.Errorf("error initializing database %q using path %q: %w", dbm.GoLevelDBBackend, dataRootDir, err) + } + + cfg.Logger = logger + + return NewCustomApp(cfg) +} + // InitChainer returns a function that can initialize the chain with genesis. func InitChainer(baseApp *sdk.BaseApp, acctKpr auth.AccountKeeperI, bankKpr bank.BankKeeperI, skipFailingGenesisTxs bool) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { @@ -107,14 +159,15 @@ func InitChainer(baseApp *sdk.BaseApp, acctKpr auth.AccountKeeperI, bankKpr bank for i, tx := range genState.Txs { res := baseApp.Deliver(tx) if res.IsErr() { - fmt.Println("ERROR LOG:", res.Log) - fmt.Println("#", i, string(amino.MustMarshalJSON(tx))) + ctx.Logger().Error("LOG", res.Log) + ctx.Logger().Error("#", i, string(amino.MustMarshalJSON(tx))) + // NOTE: comment out to ignore. if !skipFailingGenesisTxs { panic(res.Error) } } else { - fmt.Println("SUCCESS:", string(amino.MustMarshalJSON(tx))) + ctx.Logger().Info("SUCCESS:", string(amino.MustMarshalJSON(tx))) } } // Done! @@ -146,3 +199,25 @@ func EndBlocker(vmk vm.VMKeeperI) func(ctx sdk.Context, req abci.RequestEndBlock return abci.ResponseEndBlock{} } } + +func guessGnoRootDir() string { + var rootdir string + + // first try to get the root directory from the GNOROOT environment variable. + if rootdir = os.Getenv("GNOROOT"); rootdir != "" { + return filepath.Clean(rootdir) + } + + if gobin, err := exec.LookPath("go"); err == nil { + // if GNOROOT is not set, try to guess the root directory using the `go list` command. + cmd := exec.Command(gobin, "list", "-m", "-mod=mod", "-f", "{{.Dir}}", "github.com/gnolang/gno") + out, err := cmd.CombinedOutput() + if err != nil { + panic(fmt.Errorf("invalid gno directory %q: %w", rootdir, err)) + } + + return strings.TrimSpace(string(out)) + } + + panic("no go binary available, unable to determine gno root-dir path") +} diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index 28531f0a773..6f695e98558 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -188,7 +188,8 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) error { }) defer m2.Release() m2.RunMemPackage(memPkg, true) - fmt.Println("CPUCYCLES addpkg", m2.Cycles) + + ctx.Logger().Info("CPUCYCLES", "addpkg", m2.Cycles) return nil } @@ -270,7 +271,7 @@ func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) { m.Release() }() rtvs := m.Eval(xn) - fmt.Println("CPUCYCLES call", m.Cycles) + ctx.Logger().Info("CPUCYCLES call: ", m.Cycles) for i, rtv := range rtvs { res = res + rtv.String() if i < len(rtvs)-1 { diff --git a/misc/docker-integration/integration_test.go b/misc/docker-integration/integration_test.go index 6ae0ea1d36e..cf3f09a0401 100644 --- a/misc/docker-integration/integration_test.go +++ b/misc/docker-integration/integration_test.go @@ -149,6 +149,7 @@ func startGnoland(t *testing.T) { "docker", "run", "-d", "--name", gnolandContainerName, + "-e", "GNOROOT=/opt/gno/src", "-w", "/opt/gno/src/gno.land", "gno:integration", "gnoland", diff --git a/tm2/pkg/crypto/keys/client/add.go b/tm2/pkg/crypto/keys/client/add.go index 5f90a9f874e..30b612a9de2 100644 --- a/tm2/pkg/crypto/keys/client/add.go +++ b/tm2/pkg/crypto/keys/client/add.go @@ -29,7 +29,7 @@ type addCfg struct { index uint64 } -func newAddCmd(rootCfg *baseCfg) *commands.Command { +func newAddCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { cfg := &addCfg{ rootCfg: rootCfg, } @@ -42,7 +42,7 @@ func newAddCmd(rootCfg *baseCfg) *commands.Command { }, cfg, func(_ context.Context, args []string) error { - return execAdd(cfg, args, commands.NewDefaultIO()) + return execAdd(cfg, args, io) }, ) } diff --git a/tm2/pkg/crypto/keys/client/addpkg.go b/tm2/pkg/crypto/keys/client/addpkg.go index 885e1f123d7..3de9a6de546 100644 --- a/tm2/pkg/crypto/keys/client/addpkg.go +++ b/tm2/pkg/crypto/keys/client/addpkg.go @@ -24,7 +24,7 @@ type addPkgCfg struct { deposit string } -func newAddPkgCmd(rootCfg *makeTxCfg) *commands.Command { +func newAddPkgCmd(rootCfg *makeTxCfg, io *commands.IO) *commands.Command { cfg := &addPkgCfg{ rootCfg: rootCfg, } @@ -37,7 +37,7 @@ func newAddPkgCmd(rootCfg *makeTxCfg) *commands.Command { }, cfg, func(_ context.Context, args []string) error { - return execAddPkg(cfg, args, commands.NewDefaultIO()) + return execAddPkg(cfg, args, io) }, ) } diff --git a/tm2/pkg/crypto/keys/client/broadcast.go b/tm2/pkg/crypto/keys/client/broadcast.go index 039a9557c38..f1d448495a6 100644 --- a/tm2/pkg/crypto/keys/client/broadcast.go +++ b/tm2/pkg/crypto/keys/client/broadcast.go @@ -23,7 +23,7 @@ type broadcastCfg struct { tx *std.Tx } -func newBroadcastCmd(rootCfg *baseCfg) *commands.Command { +func newBroadcastCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { cfg := &broadcastCfg{ rootCfg: rootCfg, } diff --git a/tm2/pkg/crypto/keys/client/call.go b/tm2/pkg/crypto/keys/client/call.go index bcb7be3e550..29fe9739a36 100644 --- a/tm2/pkg/crypto/keys/client/call.go +++ b/tm2/pkg/crypto/keys/client/call.go @@ -22,7 +22,7 @@ type callCfg struct { args commands.StringArr } -func newCallCmd(rootCfg *makeTxCfg) *commands.Command { +func newCallCmd(rootCfg *makeTxCfg, io *commands.IO) *commands.Command { cfg := &callCfg{ rootCfg: rootCfg, } @@ -35,7 +35,7 @@ func newCallCmd(rootCfg *makeTxCfg) *commands.Command { }, cfg, func(_ context.Context, args []string) error { - return execCall(cfg, args, commands.NewDefaultIO()) + return execCall(cfg, args, io) }, ) } diff --git a/tm2/pkg/crypto/keys/client/delete.go b/tm2/pkg/crypto/keys/client/delete.go index 0f216d3467c..e22ac30988c 100644 --- a/tm2/pkg/crypto/keys/client/delete.go +++ b/tm2/pkg/crypto/keys/client/delete.go @@ -16,7 +16,7 @@ type deleteCfg struct { force bool } -func newDeleteCmd(rootCfg *baseCfg) *commands.Command { +func newDeleteCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { cfg := &deleteCfg{ rootCfg: rootCfg, } @@ -29,7 +29,7 @@ func newDeleteCmd(rootCfg *baseCfg) *commands.Command { }, cfg, func(_ context.Context, args []string) error { - return execDelete(cfg, args, commands.NewDefaultIO()) + return execDelete(cfg, args, io) }, ) } diff --git a/tm2/pkg/crypto/keys/client/export.go b/tm2/pkg/crypto/keys/client/export.go index eda04a5c92f..6eff8aa97b3 100644 --- a/tm2/pkg/crypto/keys/client/export.go +++ b/tm2/pkg/crypto/keys/client/export.go @@ -18,7 +18,7 @@ type exportCfg struct { unsafe bool } -func newExportCmd(rootCfg *baseCfg) *commands.Command { +func newExportCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { cfg := &exportCfg{ rootCfg: rootCfg, } @@ -31,7 +31,7 @@ func newExportCmd(rootCfg *baseCfg) *commands.Command { }, cfg, func(_ context.Context, args []string) error { - return execExport(cfg, commands.NewDefaultIO()) + return execExport(cfg, io) }, ) } diff --git a/tm2/pkg/crypto/keys/client/generate.go b/tm2/pkg/crypto/keys/client/generate.go index b721e6704ce..d209bd70bd3 100644 --- a/tm2/pkg/crypto/keys/client/generate.go +++ b/tm2/pkg/crypto/keys/client/generate.go @@ -16,7 +16,7 @@ type generateCfg struct { customEntropy bool } -func newGenerateCmd(rootCfg *baseCfg) *commands.Command { +func newGenerateCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { cfg := &generateCfg{ rootCfg: rootCfg, } @@ -29,7 +29,7 @@ func newGenerateCmd(rootCfg *baseCfg) *commands.Command { }, cfg, func(_ context.Context, args []string) error { - return execGenerate(cfg, args, commands.NewDefaultIO()) + return execGenerate(cfg, args, io) }, ) } diff --git a/tm2/pkg/crypto/keys/client/generate_test.go b/tm2/pkg/crypto/keys/client/generate_test.go index 516912046b6..fae85b664ac 100644 --- a/tm2/pkg/crypto/keys/client/generate_test.go +++ b/tm2/pkg/crypto/keys/client/generate_test.go @@ -46,7 +46,7 @@ func Test_execGenerateUser(t *testing.T) { err = execGenerate(cfg, []string{}, io) require.NoError(t, err) - // Now provide "good" entropy but no answer + // Now provide "io.good" entropy but no answer fakeEntropy = strings.Repeat(":)", 40) + "\n" // entropy + accept count io.SetIn(strings.NewReader(fakeEntropy)) err = execGenerate(cfg, []string{}, io) diff --git a/tm2/pkg/crypto/keys/client/import.go b/tm2/pkg/crypto/keys/client/import.go index 5e0eeecabb5..e1d8af55861 100644 --- a/tm2/pkg/crypto/keys/client/import.go +++ b/tm2/pkg/crypto/keys/client/import.go @@ -18,7 +18,7 @@ type importCfg struct { unsafe bool } -func newImportCmd(rootCfg *baseCfg) *commands.Command { +func newImportCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { cfg := &importCfg{ rootCfg: rootCfg, } @@ -31,7 +31,7 @@ func newImportCmd(rootCfg *baseCfg) *commands.Command { }, cfg, func(_ context.Context, _ []string) error { - return execImport(cfg, commands.NewDefaultIO()) + return execImport(cfg, io) }, ) } diff --git a/tm2/pkg/crypto/keys/client/list.go b/tm2/pkg/crypto/keys/client/list.go index 50be35cef43..cb86feb2395 100644 --- a/tm2/pkg/crypto/keys/client/list.go +++ b/tm2/pkg/crypto/keys/client/list.go @@ -8,7 +8,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto/keys" ) -func newListCmd(rootCfg *baseCfg) *commands.Command { +func newListCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { return commands.NewCommand( commands.Metadata{ Name: "list", @@ -17,7 +17,7 @@ func newListCmd(rootCfg *baseCfg) *commands.Command { }, nil, func(_ context.Context, args []string) error { - return execList(rootCfg, args, commands.NewDefaultIO()) + return execList(rootCfg, args, io) }, ) } diff --git a/tm2/pkg/crypto/keys/client/maketx.go b/tm2/pkg/crypto/keys/client/maketx.go index cbcc6def0de..36214a5a983 100644 --- a/tm2/pkg/crypto/keys/client/maketx.go +++ b/tm2/pkg/crypto/keys/client/maketx.go @@ -17,7 +17,7 @@ type makeTxCfg struct { chainID string } -func newMakeTxCmd(rootCfg *baseCfg) *commands.Command { +func newMakeTxCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { cfg := &makeTxCfg{ rootCfg: rootCfg, } @@ -33,9 +33,9 @@ func newMakeTxCmd(rootCfg *baseCfg) *commands.Command { ) cmd.AddSubCommands( - newAddPkgCmd(cfg), - newSendCmd(cfg), - newCallCmd(cfg), + newAddPkgCmd(cfg, io), + newSendCmd(cfg, io), + newCallCmd(cfg, io), ) return cmd diff --git a/tm2/pkg/crypto/keys/client/query.go b/tm2/pkg/crypto/keys/client/query.go index 58923f8787c..50c1f257213 100644 --- a/tm2/pkg/crypto/keys/client/query.go +++ b/tm2/pkg/crypto/keys/client/query.go @@ -3,7 +3,6 @@ package client import ( "context" "flag" - "fmt" "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" @@ -22,7 +21,7 @@ type queryCfg struct { path string } -func newQueryCmd(rootCfg *baseCfg) *commands.Command { +func newQueryCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { cfg := &queryCfg{ rootCfg: rootCfg, } @@ -35,7 +34,7 @@ func newQueryCmd(rootCfg *baseCfg) *commands.Command { }, cfg, func(_ context.Context, args []string) error { - return execQuery(cfg, args) + return execQuery(cfg, args, io) }, ) } @@ -63,7 +62,7 @@ func (c *queryCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execQuery(cfg *queryCfg, args []string) error { +func execQuery(cfg *queryCfg, args []string, io *commands.IO) error { if len(args) != 1 { return flag.ErrHelp } @@ -76,15 +75,16 @@ func execQuery(cfg *queryCfg, args []string) error { } if qres.Response.Error != nil { - fmt.Printf("Log: %s\n", - qres.Response.Log) + io.Printf("Log: %+v\n", + qres.Response) return qres.Response.Error } + resdata := qres.Response.Data // XXX in general, how do we know what to show? // proof := qres.Response.Proof height := qres.Response.Height - fmt.Printf("height: %d\ndata: %s\n", + io.Printf("height: %d\ndata: %s\n", height, string(resdata)) return nil diff --git a/tm2/pkg/crypto/keys/client/root.go b/tm2/pkg/crypto/keys/client/root.go index ad8983f2bb9..550dd408b77 100644 --- a/tm2/pkg/crypto/keys/client/root.go +++ b/tm2/pkg/crypto/keys/client/root.go @@ -18,7 +18,7 @@ type baseCfg struct { BaseOptions } -func NewRootCmd() *commands.Command { +func NewRootCmd(io *commands.IO) *commands.Command { cfg := &baseCfg{} cmd := commands.NewCommand( @@ -35,17 +35,17 @@ func NewRootCmd() *commands.Command { ) cmd.AddSubCommands( - newAddCmd(cfg), - newDeleteCmd(cfg), - newGenerateCmd(cfg), - newExportCmd(cfg), - newImportCmd(cfg), - newListCmd(cfg), - newSignCmd(cfg), - newVerifyCmd(cfg), - newQueryCmd(cfg), - newBroadcastCmd(cfg), - newMakeTxCmd(cfg), + newAddCmd(cfg, io), + newDeleteCmd(cfg, io), + newGenerateCmd(cfg, io), + newExportCmd(cfg, io), + newImportCmd(cfg, io), + newListCmd(cfg, io), + newSignCmd(cfg, io), + newVerifyCmd(cfg, io), + newQueryCmd(cfg, io), + newBroadcastCmd(cfg, io), + newMakeTxCmd(cfg, io), ) return cmd diff --git a/tm2/pkg/crypto/keys/client/send.go b/tm2/pkg/crypto/keys/client/send.go index 8f82778b1e3..6d19ffcb393 100644 --- a/tm2/pkg/crypto/keys/client/send.go +++ b/tm2/pkg/crypto/keys/client/send.go @@ -21,7 +21,7 @@ type sendCfg struct { to string } -func newSendCmd(rootCfg *makeTxCfg) *commands.Command { +func newSendCmd(rootCfg *makeTxCfg, io *commands.IO) *commands.Command { cfg := &sendCfg{ rootCfg: rootCfg, } @@ -34,7 +34,7 @@ func newSendCmd(rootCfg *makeTxCfg) *commands.Command { }, cfg, func(_ context.Context, args []string) error { - return execSend(cfg, args, commands.NewDefaultIO()) + return execSend(cfg, args, io) }, ) } diff --git a/tm2/pkg/crypto/keys/client/sign.go b/tm2/pkg/crypto/keys/client/sign.go index bfc39647141..761e0d7a563 100644 --- a/tm2/pkg/crypto/keys/client/sign.go +++ b/tm2/pkg/crypto/keys/client/sign.go @@ -28,7 +28,7 @@ type signCfg struct { pass string } -func newSignCmd(rootCfg *baseCfg) *commands.Command { +func newSignCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { cfg := &signCfg{ rootCfg: rootCfg, } @@ -41,7 +41,7 @@ func newSignCmd(rootCfg *baseCfg) *commands.Command { }, cfg, func(_ context.Context, args []string) error { - return execSign(cfg, args, commands.NewDefaultIO()) + return execSign(cfg, args, io) }, ) } diff --git a/tm2/pkg/crypto/keys/client/verify.go b/tm2/pkg/crypto/keys/client/verify.go index 3dcc5f35dee..bb486c1a8fa 100644 --- a/tm2/pkg/crypto/keys/client/verify.go +++ b/tm2/pkg/crypto/keys/client/verify.go @@ -16,7 +16,7 @@ type verifyCfg struct { docPath string } -func newVerifyCmd(rootCfg *baseCfg) *commands.Command { +func newVerifyCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command { cfg := &verifyCfg{ rootCfg: rootCfg, } @@ -29,7 +29,7 @@ func newVerifyCmd(rootCfg *baseCfg) *commands.Command { }, cfg, func(_ context.Context, args []string) error { - return execVerify(cfg, args, commands.NewDefaultIO()) + return execVerify(cfg, args, io) }, ) } From 9f4be75dd063f45412a25b1487d698f268322ce8 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 20 Sep 2023 15:50:51 +0200 Subject: [PATCH 02/45] chore: reload coverage Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> From 686ae5f66662deb5d16a849376220e34a307e4e2 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Tue, 26 Sep 2023 16:05:51 +0200 Subject: [PATCH 03/45] fix: increase sleep before gnoland is ready Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/cmd/gnoland/integration_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gno.land/cmd/gnoland/integration_test.go b/gno.land/cmd/gnoland/integration_test.go index bba3b8bdab1..85527a88e91 100644 --- a/gno.land/cmd/gnoland/integration_test.go +++ b/gno.land/cmd/gnoland/integration_test.go @@ -41,10 +41,10 @@ const ( ) func TestTestdata(t *testing.T) { - testscript.Run(t, setupGnolandTestScript2(t, "testdata")) + testscript.Run(t, setupGnolandTestScript(t, "testdata")) } -func setupGnolandTestScript2(t *testing.T, txtarDir string) testscript.Params { +func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { t.Helper() goModPath, err := exec.Command("go", "env", "GOMOD").CombinedOutput() @@ -121,7 +121,7 @@ func setupGnolandTestScript2(t *testing.T, txtarDir string) testscript.Params { // XXX: Use something similar to `require.Eventually` to check for node // availability. For now, if this sleep duration is too short, the // subsequent command might fail with an [internal error]. - time.Sleep(time.Millisecond * 300) + time.Sleep(time.Second) } case "stop": n, ok := nodes[sid] From 89420bc20c59fde4630252cadd62ced43471545d Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Mon, 25 Sep 2023 20:32:28 +0200 Subject: [PATCH 04/45] feat: export gnoland integration function Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/cmd/gnoland/integration_test.go | 357 +----------- .../pkg/integration/testing_integration.go | 534 ++++++++++++++++++ 2 files changed, 536 insertions(+), 355 deletions(-) create mode 100644 gno.land/pkg/integration/testing_integration.go diff --git a/gno.land/cmd/gnoland/integration_test.go b/gno.land/cmd/gnoland/integration_test.go index 85527a88e91..b629dff2b92 100644 --- a/gno.land/cmd/gnoland/integration_test.go +++ b/gno.land/cmd/gnoland/integration_test.go @@ -1,365 +1,12 @@ package main import ( - "context" - "flag" - "fmt" - "hash/crc32" - "os/exec" - "path/filepath" - "strconv" - "strings" - "sync" "testing" - "time" - "github.com/gnolang/gno/gno.land/pkg/gnoland" - vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/gnomod" - abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" - "github.com/gnolang/gno/tm2/pkg/bft/config" - "github.com/gnolang/gno/tm2/pkg/bft/node" - "github.com/gnolang/gno/tm2/pkg/bft/privval" - bft "github.com/gnolang/gno/tm2/pkg/bft/types" - "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/crypto/keys" - "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" - "github.com/gnolang/gno/tm2/pkg/db" - "github.com/gnolang/gno/tm2/pkg/log" - osm "github.com/gnolang/gno/tm2/pkg/os" - "github.com/gnolang/gno/tm2/pkg/std" - "github.com/jaekwon/testify/require" + integration "github.com/gnolang/gno/gno.land/pkg/integration" "github.com/rogpeppe/go-internal/testscript" ) -// XXX: should be centralize somewhere -const ( - test1Addr = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" - test1Seed = "source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast" -) - func TestTestdata(t *testing.T) { - testscript.Run(t, setupGnolandTestScript(t, "testdata")) -} - -func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { - t.Helper() - - goModPath, err := exec.Command("go", "env", "GOMOD").CombinedOutput() - require.NoError(t, err) - - gnoRootDir := filepath.Dir(string(goModPath)) - gnoHomeDir := filepath.Join(t.TempDir(), "gno") - gnoDataDir := filepath.Join(t.TempDir(), "data") - - var muNodes sync.Mutex - nodes := map[string]*node.Node{} - t.Cleanup(func() { - for id, n := range nodes { - if err := n.Stop(); err != nil { - panic(fmt.Errorf("node %q was unable to stop: %w", id, err)) - } - } - }) - - return testscript.Params{ - Dir: txtarDir, - Setup: func(env *testscript.Env) error { - kb, err := keys.NewKeyBaseFromDir(gnoHomeDir) - if err != nil { - return err - } - - kb.CreateAccount("test1", test1Seed, "", "", 0, 0) - env.Setenv("USER_SEED_test1", test1Seed) - env.Setenv("USER_ADDR_test1", test1Addr) - - env.Setenv("GNOROOT", gnoRootDir) - env.Setenv("GNOHOME", gnoHomeDir) - - return nil - }, - Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){ - "gnoland": func(ts *testscript.TestScript, neg bool, args []string) { - muNodes.Lock() - defer muNodes.Unlock() - - if len(args) == 0 { - tsValidateError(ts, "gnoland", neg, fmt.Errorf("use gnoland [start|stop] command")) - return - } - - sid := getSessionID(ts) - - var cmd string - cmd, args = args[0], args[1:] - - var err error - switch cmd { - case "start": - if _, ok := nodes[sid]; ok { - err = fmt.Errorf("node %q already started", sid) - break - } - - dataDir := filepath.Join(gnoDataDir, sid) - var node *node.Node - if node, err = execTestingGnoland(t, dataDir, gnoRootDir, args); err == nil { - // XXX need mutex ? - nodes[sid] = node - - // get listen addr environement - // should have been updated with the right port on start - laddr := node.Config().RPC.ListenAddress - - // add default environement - ts.Setenv("RPC_ADDR", laddr) - ts.Setenv("GNODATA", gnoDataDir) - - // XXX: Use something similar to `require.Eventually` to check for node - // availability. For now, if this sleep duration is too short, the - // subsequent command might fail with an [internal error]. - time.Sleep(time.Second) - } - case "stop": - n, ok := nodes[sid] - if !ok { - err = fmt.Errorf("node %q not started cannot be stop", sid) - break - } - - if err = n.Stop(); err != nil { - delete(nodes, sid) - - // unset env dirs - ts.Setenv("RPC_ADDR", "") - ts.Setenv("GNODATA", "") - } - } - - tsValidateError(ts, "gnoland "+cmd, neg, err) - }, - "gnokey": func(ts *testscript.TestScript, neg bool, args []string) { - muNodes.Lock() - defer muNodes.Unlock() - - sid := getSessionID(ts) - - // Setup io command - io := commands.NewTestIO() - io.SetOut(commands.WriteNopCloser(ts.Stdout())) - io.SetErr(commands.WriteNopCloser(ts.Stderr())) - cmd := client.NewRootCmd(io) - - io.SetIn(strings.NewReader("\n")) // inject empty password to stdin - defaultArgs := []string{ - "-home", gnoHomeDir, - "-insecure-password-stdin=true", // there no use to not have this param by default - } - - if n, ok := nodes[sid]; ok { - if raddr := n.Config().RPC.ListenAddress; raddr != "" { - defaultArgs = append(defaultArgs, "-remote", raddr) - } - } - - // inject default argument, if duplicate - // arguments, it should be override by the ones - // user provided - args = append(defaultArgs, args...) - - err := cmd.ParseAndRun(context.Background(), args) - tsValidateError(ts, "gnokey", neg, err) - }, - }, - } -} - -func execTestingGnoland(t *testing.T, gnoDataDir, gnoRootDir string, args []string) (*node.Node, error) { - t.Helper() - - // Setup logger - logger := log.NewNopLogger() - - // Setup start config - scfg := &startCfg{} - { - fs := flag.NewFlagSet("start", flag.ExitOnError) - scfg.RegisterFlags(fs) - - // Override default value for flags - fs.VisitAll(func(f *flag.Flag) { - switch f.Name { - case "root-dir": - f.DefValue = gnoDataDir - case "chainid": - f.DefValue = "tendermint_test" - case "genesis-balances-file": - f.DefValue = filepath.Join(gnoRootDir, "gno.land", "genesis", "genesis_balances.txt") - case "genesis-txs-file": - f.DefValue = filepath.Join(gnoRootDir, "gno.land", "genesis", "genesis_txs.txt") - default: - return - } - - f.Value.Set(f.DefValue) - }) - - if err := fs.Parse(args); err != nil { - return nil, fmt.Errorf("unable to parse flags: %w", err) - } - } - - // Setup testing config - cfg := config.TestConfig().SetRootDir(gnoDataDir) - { - cfg.EnsureDirs() - cfg.Consensus.CreateEmptyBlocks = true - cfg.Consensus.CreateEmptyBlocksInterval = time.Duration(0) - cfg.RPC.ListenAddress = "tcp://127.0.0.1:0" - cfg.P2P.ListenAddress = "tcp://127.0.0.1:0" - } - - // Prepare genesis - if err := setupTestingGenesis(gnoDataDir, cfg, scfg, gnoRootDir); err != nil { - return nil, err - } - - // Create application and node - return createAppAndNode(cfg, logger, gnoRootDir, scfg) -} - -func getSessionID(ts *testscript.TestScript) string { - works := ts.Getenv("WORK") - sum := crc32.ChecksumIEEE([]byte(works)) - return strconv.FormatUint(uint64(sum), 16) -} - -func setupTestingGenesis(gnoDataDir string, cfg *config.Config, scfg *startCfg, gnoRootDir string) error { - newPrivValKey := cfg.PrivValidatorKeyFile() - newPrivValState := cfg.PrivValidatorStateFile() - priv := privval.LoadOrGenFilePV(newPrivValKey, newPrivValState) - - genesisFilePath := filepath.Join(gnoDataDir, cfg.Genesis) - osm.EnsureDir(filepath.Dir(genesisFilePath), 0o700) - if !osm.FileExists(genesisFilePath) { - genesisTxs := loadGenesisTxs(scfg.genesisTxsFile, scfg.chainID, scfg.genesisRemote) - pvPub := priv.GetPubKey() - - gen := &bft.GenesisDoc{ - GenesisTime: time.Now(), - ChainID: scfg.chainID, - ConsensusParams: abci.ConsensusParams{ - Block: &abci.BlockParams{ - // TODO: update limits. - MaxTxBytes: 1000000, // 1MB, - MaxDataBytes: 2000000, // 2MB, - MaxGas: 10000000, // 10M gas - TimeIotaMS: 100, // 100ms - }, - }, - Validators: []bft.GenesisValidator{ - { - Address: pvPub.Address(), - PubKey: pvPub, - Power: 10, - Name: "testvalidator", - }, - }, - } - - // Load distribution. - balances := loadGenesisBalances(scfg.genesisBalancesFile) - - // Load initial packages from examples. - // XXX: we should be able to config this - test1 := crypto.MustAddressFromString(test1Addr) - txs := []std.Tx{} - - // List initial packages to load from examples. - // println(filepath.Join(gnoRootDir, "examples")) - - pkgs, err := gnomod.ListPkgs(filepath.Join(gnoRootDir, "examples")) - if err != nil { - return fmt.Errorf("listing gno packages: %w", err) - } - - // Sort packages by dependencies. - sortedPkgs, err := pkgs.Sort() - if err != nil { - return fmt.Errorf("sorting packages: %w", err) - } - - // Filter out draft packages. - nonDraftPkgs := sortedPkgs.GetNonDraftPkgs() - - for _, pkg := range nonDraftPkgs { - // open files in directory as MemPackage. - memPkg := gno.ReadMemPackage(pkg.Dir, pkg.Name) - - var tx std.Tx - tx.Msgs = []std.Msg{ - vmm.MsgAddPackage{ - Creator: test1, - Package: memPkg, - Deposit: nil, - }, - } - - // XXX: add fee flag ? - // or maybe reduce fee to the minimum ? - tx.Fee = std.NewFee(50000, std.MustParseCoin("1000000ugnot")) - tx.Signatures = make([]std.Signature, len(tx.GetSigners())) - txs = append(txs, tx) - } - - // load genesis txs from file. - txs = append(txs, genesisTxs...) - - // construct genesis AppState. - gen.AppState = gnoland.GnoGenesisState{ - Balances: balances, - Txs: txs, - } - - writeGenesisFile(gen, genesisFilePath) - } - - return nil -} - -func createAppAndNode(cfg *config.Config, logger log.Logger, gnoRootDir string, scfg *startCfg) (*node.Node, error) { - gnoApp, err := gnoland.NewCustomApp(gnoland.CustomAppConfig{ - Logger: logger, - GnoRootDir: gnoRootDir, - SkipFailingGenesisTxs: scfg.skipFailingGenesisTxs, - MaxCycles: scfg.genesisMaxVMCycles, - DB: db.NewMemDB(), - }) - if err != nil { - return nil, fmt.Errorf("error in creating new app: %w", err) - } - - cfg.LocalApp = gnoApp - node, err := node.DefaultNewNode(cfg, logger) - if err != nil { - return nil, fmt.Errorf("error in creating node: %w", err) - } - - return node, node.Start() -} - -func tsValidateError(ts *testscript.TestScript, cmd string, neg bool, err error) { - if err != nil { - ts.Logf("%s error: %v\n", cmd, err) - if !neg { - ts.Fatalf("unexpected %s command failure", cmd) - } - } else { - if neg { - ts.Fatalf("unexpected %s command success", cmd) - } - } + testscript.Run(t, integration.SetupGnolandTestScript(t, "testdata")) } diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go new file mode 100644 index 00000000000..d5fd4ae0853 --- /dev/null +++ b/gno.land/pkg/integration/testing_integration.go @@ -0,0 +1,534 @@ +// ------------------------------------------------------------------------------------------------ +// WARNING: TEMPORARY CODE +// +// This file is a rapid prototype to meet immediate project needs and is not intended for long-term +// use in its current form. It requires review, possible refactoring, and thorough testing. +// Use at your own risk. The author(s) accept no liability for any issues arising from its use. +// ------------------------------------------------------------------------------------------------ + +package integration + +import ( + "context" + "flag" + "fmt" + "hash/crc32" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "sync" + "testing" + "time" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gno/tm2/pkg/amino" + abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" + "github.com/gnolang/gno/tm2/pkg/bft/config" + "github.com/gnolang/gno/tm2/pkg/bft/node" + "github.com/gnolang/gno/tm2/pkg/bft/privval" + bft "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/crypto/keys" + "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" + "github.com/gnolang/gno/tm2/pkg/db" + "github.com/gnolang/gno/tm2/pkg/log" + osm "github.com/gnolang/gno/tm2/pkg/os" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/jaekwon/testify/require" + "github.com/rogpeppe/go-internal/testscript" +) + +// XXX: should be centralize somewhere +const ( + test1Addr = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" + test1Seed = "source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast" +) + +func TestTestdata(t *testing.T) { + testscript.Run(t, SetupGnolandTestScript(t, "testdata")) +} + +type IntegrationConfig struct { + skipFailingGenesisTxs bool + skipStart bool + genesisBalancesFile string + genesisTxsFile string + chainID string + genesisRemote string + rootDir string + genesisMaxVMCycles int64 + config string +} + +func (c *IntegrationConfig) RegisterFlags(fs *flag.FlagSet) { + fs.BoolVar( + &c.skipFailingGenesisTxs, + "skip-failing-genesis-txs", + false, + "don't panic when replaying invalid genesis txs", + ) + + fs.BoolVar( + &c.skipStart, + "skip-start", + false, + "quit after initialization, don't start the node", + ) + + fs.StringVar( + &c.genesisBalancesFile, + "genesis-balances-file", + "./genesis/genesis_balances.txt", + "initial distribution file", + ) + + fs.StringVar( + &c.genesisTxsFile, + "genesis-txs-file", + "./genesis/genesis_txs.txt", + "initial txs to replay", + ) + + fs.StringVar( + &c.chainID, + "chainid", + "dev", + "the ID of the chain", + ) + + fs.StringVar( + &c.rootDir, + "root-dir", + "testdir", + "directory for config and data", + ) + + fs.StringVar( + &c.genesisRemote, + "genesis-remote", + "localhost:26657", + "replacement for '%%REMOTE%%' in genesis", + ) + + fs.Int64Var( + &c.genesisMaxVMCycles, + "genesis-max-vm-cycles", + 10_000_000, + "set maximum allowed vm cycles per operation. Zero means no limit.", + ) + + fs.StringVar( + &c.config, + "config", + "", + "config file (optional)", + ) +} + +func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { + t.Helper() + + cmd := exec.Command("go", "list", "-m", "-mod=mod", "-f", "{{.Dir}}", "github.com/gnolang/gno") + out, err := cmd.CombinedOutput() + require.NoError(t, err) + + gnoRootDir := strings.TrimSpace(string(out)) + gnoHomeDir := filepath.Join(t.TempDir(), "gno") + gnoDataDir := filepath.Join(t.TempDir(), "data") + + var muNodes sync.Mutex + nodes := map[string]*node.Node{} + t.Cleanup(func() { + for id, n := range nodes { + if err := n.Stop(); err != nil { + panic(fmt.Errorf("node %q was unable to stop: %w", id, err)) + } + } + }) + + return testscript.Params{ + Dir: txtarDir, + Setup: func(env *testscript.Env) error { + kb, err := keys.NewKeyBaseFromDir(gnoHomeDir) + if err != nil { + return err + } + + kb.CreateAccount("test1", test1Seed, "", "", 0, 0) + env.Setenv("USER_SEED_test1", test1Seed) + env.Setenv("USER_ADDR_test1", test1Addr) + + env.Setenv("GNOROOT", gnoRootDir) + env.Setenv("GNOHOME", gnoHomeDir) + + return nil + }, + Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){ + "gnoland": func(ts *testscript.TestScript, neg bool, args []string) { + muNodes.Lock() + defer muNodes.Unlock() + + if len(args) == 0 { + tsValidateError(ts, "gnoland", neg, fmt.Errorf("use gnoland [start|stop] command")) + return + } + + sid := getSessionID(ts) + + var cmd string + cmd, args = args[0], args[1:] + + var err error + switch cmd { + case "start": + if _, ok := nodes[sid]; ok { + err = fmt.Errorf("node %q already started", sid) + break + } + + dataDir := filepath.Join(gnoDataDir, sid) + var node *node.Node + if node, err = execTestingGnoland(t, dataDir, gnoRootDir, args); err == nil { + // XXX need mutex ? + nodes[sid] = node + + // get listen addr environement + // should have been updated with the right port on start + laddr := node.Config().RPC.ListenAddress + + // add default environement + ts.Setenv("RPC_ADDR", laddr) + ts.Setenv("GNODATA", gnoDataDir) + + // XXX: Use something similar to `require.Eventually` to check for node + // availability. For now, if this sleep duration is too short, the + // subsequent command might fail with an [internal error]. + time.Sleep(time.Millisecond * 300) + } + case "stop": + n, ok := nodes[sid] + if !ok { + err = fmt.Errorf("node %q not started cannot be stop", sid) + break + } + + if err = n.Stop(); err != nil { + delete(nodes, sid) + + // unset env dirs + ts.Setenv("RPC_ADDR", "") + ts.Setenv("GNODATA", "") + } + } + + tsValidateError(ts, "gnoland "+cmd, neg, err) + }, + "gnokey": func(ts *testscript.TestScript, neg bool, args []string) { + muNodes.Lock() + defer muNodes.Unlock() + + sid := getSessionID(ts) + + // Setup io command + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(ts.Stdout())) + io.SetErr(commands.WriteNopCloser(ts.Stderr())) + cmd := client.NewRootCmd(io) + + io.SetIn(strings.NewReader("\n")) // inject empty password to stdin + defaultArgs := []string{ + "-home", gnoHomeDir, + "-insecure-password-stdin=true", // there no use to not have this param by default + } + + if n, ok := nodes[sid]; ok { + if raddr := n.Config().RPC.ListenAddress; raddr != "" { + defaultArgs = append(defaultArgs, "-remote", raddr) + } + } + + // inject default argument, if duplicate + // arguments, it should be override by the ones + // user provided + args = append(defaultArgs, args...) + + err := cmd.ParseAndRun(context.Background(), args) + tsValidateError(ts, "gnokey", neg, err) + }, + }, + } +} + +func execTestingGnoland(t *testing.T, gnoDataDir, gnoRootDir string, args []string) (*node.Node, error) { + t.Helper() + + // Setup logger + logger := log.NewNopLogger() + + // Setup start config + icfg := &IntegrationConfig{} + { + fs := flag.NewFlagSet("start", flag.ExitOnError) + icfg.RegisterFlags(fs) + + // Override default value for flags + fs.VisitAll(func(f *flag.Flag) { + switch f.Name { + case "root-dir": + f.DefValue = gnoDataDir + case "chainid": + f.DefValue = "tendermint_test" + case "genesis-balances-file": + f.DefValue = filepath.Join(gnoRootDir, "gno.land", "genesis", "genesis_balances.txt") + case "genesis-txs-file": + f.DefValue = filepath.Join(gnoRootDir, "gno.land", "genesis", "genesis_txs.txt") + default: + return + } + + f.Value.Set(f.DefValue) + }) + + if err := fs.Parse(args); err != nil { + return nil, fmt.Errorf("unable to parse flags: %w", err) + } + } + + // Setup testing config + cfg := config.TestConfig().SetRootDir(gnoDataDir) + { + cfg.EnsureDirs() + cfg.Consensus.CreateEmptyBlocks = true + cfg.Consensus.CreateEmptyBlocksInterval = time.Duration(0) + cfg.RPC.ListenAddress = "tcp://127.0.0.1:0" + cfg.P2P.ListenAddress = "tcp://127.0.0.1:0" + } + + // Prepare genesis + if err := setupTestingGenesis(gnoDataDir, cfg, icfg, gnoRootDir); err != nil { + return nil, err + } + + // Create application and node + return createAppAndNode(cfg, logger, gnoRootDir, icfg) +} + +func getSessionID(ts *testscript.TestScript) string { + works := ts.Getenv("WORK") + sum := crc32.ChecksumIEEE([]byte(works)) + return strconv.FormatUint(uint64(sum), 16) +} + +func setupTestingGenesis(gnoDataDir string, cfg *config.Config, icfg *IntegrationConfig, gnoRootDir string) error { + newPrivValKey := cfg.PrivValidatorKeyFile() + newPrivValState := cfg.PrivValidatorStateFile() + priv := privval.LoadOrGenFilePV(newPrivValKey, newPrivValState) + + genesisFilePath := filepath.Join(gnoDataDir, cfg.Genesis) + osm.EnsureDir(filepath.Dir(genesisFilePath), 0o700) + if !osm.FileExists(genesisFilePath) { + genesisTxs := loadGenesisTxs(icfg.genesisTxsFile, icfg.chainID, icfg.genesisRemote) + pvPub := priv.GetPubKey() + + gen := &bft.GenesisDoc{ + GenesisTime: time.Now(), + ChainID: icfg.chainID, + ConsensusParams: abci.ConsensusParams{ + Block: &abci.BlockParams{ + // TODO: update limits. + MaxTxBytes: 1000000, // 1MB, + MaxDataBytes: 2000000, // 2MB, + MaxGas: 10000000, // 10M gas + TimeIotaMS: 100, // 100ms + }, + }, + Validators: []bft.GenesisValidator{ + { + Address: pvPub.Address(), + PubKey: pvPub, + Power: 10, + Name: "testvalidator", + }, + }, + } + + // Load distribution. + balances := loadGenesisBalances(icfg.genesisBalancesFile) + + // Load initial packages from examples. + // XXX: we should be able to config this + test1 := crypto.MustAddressFromString(test1Addr) + txs := []std.Tx{} + + // List initial packages to load from examples. + // println(filepath.Join(gnoRootDir, "examples")) + + pkgs, err := gnomod.ListPkgs(filepath.Join(gnoRootDir, "examples")) + if err != nil { + return fmt.Errorf("listing gno packages: %w", err) + } + + // Sort packages by dependencies. + sortedPkgs, err := pkgs.Sort() + if err != nil { + return fmt.Errorf("sorting packages: %w", err) + } + + // Filter out draft packages. + nonDraftPkgs := sortedPkgs.GetNonDraftPkgs() + + for _, pkg := range nonDraftPkgs { + // open files in directory as MemPackage. + memPkg := gno.ReadMemPackage(pkg.Dir, pkg.Name) + + var tx std.Tx + tx.Msgs = []std.Msg{ + vmm.MsgAddPackage{ + Creator: test1, + Package: memPkg, + Deposit: nil, + }, + } + + // XXX: add fee flag ? + // or maybe reduce fee to the minimum ? + tx.Fee = std.NewFee(50000, std.MustParseCoin("1000000ugnot")) + tx.Signatures = make([]std.Signature, len(tx.GetSigners())) + txs = append(txs, tx) + } + + // load genesis txs from file. + txs = append(txs, genesisTxs...) + + // construct genesis AppState. + gen.AppState = gnoland.GnoGenesisState{ + Balances: balances, + Txs: txs, + } + + writeGenesisFile(gen, genesisFilePath) + } + + return nil +} + +func createAppAndNode(cfg *config.Config, logger log.Logger, gnoRootDir string, icfg *IntegrationConfig) (*node.Node, error) { + gnoApp, err := gnoland.NewCustomApp(gnoland.CustomAppConfig{ + Logger: logger, + GnoRootDir: gnoRootDir, + SkipFailingGenesisTxs: icfg.skipFailingGenesisTxs, + MaxCycles: icfg.genesisMaxVMCycles, + DB: db.NewMemDB(), + }) + if err != nil { + return nil, fmt.Errorf("error in creating new app: %w", err) + } + + cfg.LocalApp = gnoApp + node, err := node.DefaultNewNode(cfg, logger) + if err != nil { + return nil, fmt.Errorf("error in creating node: %w", err) + } + + return node, node.Start() +} + +func tsValidateError(ts *testscript.TestScript, cmd string, neg bool, err error) { + if err != nil { + ts.Logf("%s error: %v\n", cmd, err) + if !neg { + ts.Fatalf("unexpected %s command failure", cmd) + } + } else { + if neg { + ts.Fatalf("unexpected %s command success", cmd) + } + } +} + +func loadGenesisTxs( + path string, + chainID string, + genesisRemote string, +) []std.Tx { + txs := []std.Tx{} + txsBz := osm.MustReadFile(path) + txsLines := strings.Split(string(txsBz), "\n") + for _, txLine := range txsLines { + if txLine == "" { + continue // skip empty line + } + + // patch the TX + txLine = strings.ReplaceAll(txLine, "%%CHAINID%%", chainID) + txLine = strings.ReplaceAll(txLine, "%%REMOTE%%", genesisRemote) + + var tx std.Tx + amino.MustUnmarshalJSON([]byte(txLine), &tx) + txs = append(txs, tx) + } + + return txs +} + +func loadGenesisBalances(path string) []string { + // each balance is in the form: g1xxxxxxxxxxxxxxxx=100000ugnot + balances := []string{} + content := osm.MustReadFile(path) + lines := strings.Split(string(content), "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + + // remove comments. + line = strings.Split(line, "#")[0] + line = strings.TrimSpace(line) + + // skip empty lines. + if line == "" { + continue + } + + parts := strings.Split(line, "=") + if len(parts) != 2 { + panic("invalid genesis_balance line: " + line) + } + + balances = append(balances, line) + } + return balances +} + +func writeGenesisFile(gen *bft.GenesisDoc, filePath string) { + err := gen.SaveAs(filePath) + if err != nil { + panic(err) + } +} + +func guessGnoRootDir() string { + var rootdir string + + // first try to get the root directory from the GNOROOT environment variable. + if rootdir = os.Getenv("GNOROOT"); rootdir != "" { + return filepath.Clean(rootdir) + } + + if gobin, err := exec.LookPath("go"); err == nil { + // if GNOROOT is not set, try to guess the root directory using the `go list` command. + cmd := exec.Command(gobin, "list", "-m", "-mod=mod", "-f", "{{.Dir}}", "github.com/gnolang/gno") + out, err := cmd.CombinedOutput() + if err != nil { + panic(fmt.Errorf("invalid gno directory %q: %w", rootdir, err)) + } + + return strings.TrimSpace(string(out)) + } + + panic("no go binary available, unable to determine gno root-dir path") +} From f88761376fc192ea4ecb9a10a36cf85f418bc41d Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Thu, 28 Sep 2023 15:15:00 +0200 Subject: [PATCH 05/45] feat: txtar log improvements Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/cmd/gnoland/testdata/addpkg.txtar | 2 +- .../pkg/integration/testing_integration.go | 102 +++++++++++++++--- 2 files changed, 86 insertions(+), 18 deletions(-) diff --git a/gno.land/cmd/gnoland/testdata/addpkg.txtar b/gno.land/cmd/gnoland/testdata/addpkg.txtar index 5e871b058ac..fd07f6b26c4 100644 --- a/gno.land/cmd/gnoland/testdata/addpkg.txtar +++ b/gno.land/cmd/gnoland/testdata/addpkg.txtar @@ -7,7 +7,7 @@ gnoland start gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 ## execute Render -gnokey maketx call -pkgpath gno.land/r/foobar/bar -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/foobar/bar -func RenderThanNotExist -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 ## compare render cmp stdout stdout.golden diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index d5fd4ae0853..6a8b6d09297 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -131,6 +131,12 @@ func (c *IntegrationConfig) RegisterFlags(fs *flag.FlagSet) { ) } +type testNode struct { + *node.Node + logger log.Logger + gnokeyexec uint // counter for execution of gnokey +} + func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { t.Helper() @@ -143,17 +149,12 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { gnoDataDir := filepath.Join(t.TempDir(), "data") var muNodes sync.Mutex - nodes := map[string]*node.Node{} - t.Cleanup(func() { - for id, n := range nodes { - if err := n.Stop(); err != nil { - panic(fmt.Errorf("node %q was unable to stop: %w", id, err)) - } - } - }) + nodes := map[string]*testNode{} + bTestWork, _ := strconv.ParseBool(os.Getenv("TESTWORK")) return testscript.Params{ - Dir: txtarDir, + TestWork: bTestWork, + Dir: txtarDir, Setup: func(env *testscript.Env) error { kb, err := keys.NewKeyBaseFromDir(gnoHomeDir) if err != nil { @@ -192,11 +193,26 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { break } + logger := log.NewNopLogger() + if testing.Verbose() && (os.Getenv("LOG_DIR") != "" || bTestWork) { + logname := fmt.Sprintf("gnoland-%s.log", sid) + logger = getTestingLogger(ts, logname) + } + dataDir := filepath.Join(gnoDataDir, sid) var node *node.Node - if node, err = execTestingGnoland(t, dataDir, gnoRootDir, args); err == nil { - // XXX need mutex ? - nodes[sid] = node + if node, err = execTestingGnoland(t, logger, dataDir, gnoRootDir, args); err == nil { + nodes[sid] = &testNode{ + Node: node, + logger: logger, + } + ts.Defer(func() { + if n := nodes[sid]; n != nil { + if err := n.Stop(); err != nil { + panic(fmt.Errorf("node %q was unable to stop: %w", sid, err)) + } + } + }) // get listen addr environement // should have been updated with the right port on start @@ -209,7 +225,7 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { // XXX: Use something similar to `require.Eventually` to check for node // availability. For now, if this sleep duration is too short, the // subsequent command might fail with an [internal error]. - time.Sleep(time.Millisecond * 300) + time.Sleep(time.Second) } case "stop": n, ok := nodes[sid] @@ -248,9 +264,16 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { } if n, ok := nodes[sid]; ok { + if raddr := n.Config().RPC.ListenAddress; raddr != "" { defaultArgs = append(defaultArgs, "-remote", raddr) } + + n.gnokeyexec++ + headerlog := fmt.Sprintf("%.02d!EXEC_GNOKEY", n.gnokeyexec) + // we error to log this even on lower level + n.logger.Info(headerlog, strings.Join(args, " ")) + defer n.logger.Info(headerlog, "END") } // inject default argument, if duplicate @@ -259,18 +282,16 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { args = append(defaultArgs, args...) err := cmd.ParseAndRun(context.Background(), args) + tsValidateError(ts, "gnokey", neg, err) }, }, } } -func execTestingGnoland(t *testing.T, gnoDataDir, gnoRootDir string, args []string) (*node.Node, error) { +func execTestingGnoland(t *testing.T, logger log.Logger, gnoDataDir, gnoRootDir string, args []string) (*node.Node, error) { t.Helper() - // Setup logger - logger := log.NewNopLogger() - // Setup start config icfg := &IntegrationConfig{} { @@ -325,6 +346,53 @@ func getSessionID(ts *testscript.TestScript) string { return strconv.FormatUint(uint64(sum), 16) } +func getTestingLogger(ts *testscript.TestScript, logname string) log.Logger { + var path string + if logdir := os.Getenv("LOG_DIR"); logdir != "" { + if err := os.MkdirAll(logdir, 0o755); err != nil { + ts.Fatalf("unable to make log directory %q", logdir) + } + + var err error + if path, err = filepath.Abs(filepath.Join(logdir, logname)); err != nil { + ts.Fatalf("uanble to get absolute path of logdir %q", logdir) + } + + } else if workdir := ts.Getenv("WORK"); workdir != "" { + path = filepath.Join(workdir, logname) + } else { + return log.NewNopLogger() + } + + // create the file + f, err := os.Create(path) + if err != nil { + ts.Fatalf("unable to create log file %q: %s", path, err.Error()) + } + + ts.Defer(func() { + if err := f.Close(); err != nil { + panic(fmt.Errorf("unable to close log file %q: %s", path, err)) + } + }) + + logger := log.NewTMLogger(f) + switch level := os.Getenv("LOG_LEVEL"); strings.ToLower(level) { + case "error": + logger.SetLevel(log.LevelError) + case "debug": + logger.SetLevel(log.LevelDebug) + case "info": + logger.SetLevel(log.LevelInfo) + case "": + default: + panic("invalid log level: " + level) + } + + ts.Logf("starting logger: %q", path) + return logger +} + func setupTestingGenesis(gnoDataDir string, cfg *config.Config, icfg *IntegrationConfig, gnoRootDir string) error { newPrivValKey := cfg.PrivValidatorKeyFile() newPrivValState := cfg.PrivValidatorStateFile() From 30ba1de059349f50c16ea20d90e2401e89c3bc9b Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Thu, 28 Sep 2023 15:49:37 +0200 Subject: [PATCH 06/45] fix: fixup render Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/cmd/gnoland/testdata/addpkg.txtar | 2 +- gno.land/pkg/integration/gnoland.go | 300 +++++++++++++++++ .../pkg/integration/testing_integration.go | 310 +----------------- 3 files changed, 310 insertions(+), 302 deletions(-) create mode 100644 gno.land/pkg/integration/gnoland.go diff --git a/gno.land/cmd/gnoland/testdata/addpkg.txtar b/gno.land/cmd/gnoland/testdata/addpkg.txtar index fd07f6b26c4..5e871b058ac 100644 --- a/gno.land/cmd/gnoland/testdata/addpkg.txtar +++ b/gno.land/cmd/gnoland/testdata/addpkg.txtar @@ -7,7 +7,7 @@ gnoland start gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 ## execute Render -gnokey maketx call -pkgpath gno.land/r/foobar/bar -func RenderThanNotExist -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/foobar/bar -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 ## compare render cmp stdout stdout.golden diff --git a/gno.land/pkg/integration/gnoland.go b/gno.land/pkg/integration/gnoland.go new file mode 100644 index 00000000000..88a5c289537 --- /dev/null +++ b/gno.land/pkg/integration/gnoland.go @@ -0,0 +1,300 @@ +package integration + +import ( + "flag" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gno/tm2/pkg/amino" + abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" + "github.com/gnolang/gno/tm2/pkg/bft/config" + "github.com/gnolang/gno/tm2/pkg/bft/node" + "github.com/gnolang/gno/tm2/pkg/bft/privval" + bft "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/db" + "github.com/gnolang/gno/tm2/pkg/log" + osm "github.com/gnolang/gno/tm2/pkg/os" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/rogpeppe/go-internal/testscript" +) + +type IntegrationConfig struct { + SkipFailingGenesisTxs bool + SkipStart bool + GenesisBalancesFile string + GenesisTxsFile string + ChainID string + GenesisRemote string + RootDir string + GenesisMaxVMCycles int64 + Config string +} + +func execTestingGnoland(t *testing.T, logger log.Logger, gnoDataDir, gnoRootDir string, args []string) (*node.Node, error) { + t.Helper() + + // Setup start config + icfg := &IntegrationConfig{} + { + fs := flag.NewFlagSet("start", flag.ExitOnError) + icfg.RegisterFlags(fs) + + // Override default value for flags + fs.VisitAll(func(f *flag.Flag) { + switch f.Name { + case "root-dir": + f.DefValue = gnoDataDir + case "chainid": + f.DefValue = "tendermint_test" + case "genesis-balances-file": + f.DefValue = filepath.Join(gnoRootDir, "gno.land", "genesis", "genesis_balances.txt") + case "genesis-txs-file": + f.DefValue = filepath.Join(gnoRootDir, "gno.land", "genesis", "genesis_txs.txt") + default: + return + } + + f.Value.Set(f.DefValue) + }) + + if err := fs.Parse(args); err != nil { + return nil, fmt.Errorf("unable to parse flags: %w", err) + } + } + + // Setup testing config + cfg := config.TestConfig().SetRootDir(gnoDataDir) + { + cfg.EnsureDirs() + cfg.Consensus.CreateEmptyBlocks = true + cfg.Consensus.CreateEmptyBlocksInterval = time.Duration(0) + cfg.RPC.ListenAddress = "tcp://127.0.0.1:0" + cfg.P2P.ListenAddress = "tcp://127.0.0.1:0" + } + + // Prepare genesis + if err := setupTestingGenesis(gnoDataDir, cfg, icfg, gnoRootDir); err != nil { + return nil, err + } + + // Create application and node + return createAppAndNode(cfg, logger, gnoRootDir, icfg) +} + +func setupTestingGenesis(gnoDataDir string, cfg *config.Config, icfg *IntegrationConfig, gnoRootDir string) error { + newPrivValKey := cfg.PrivValidatorKeyFile() + newPrivValState := cfg.PrivValidatorStateFile() + priv := privval.LoadOrGenFilePV(newPrivValKey, newPrivValState) + + genesisFilePath := filepath.Join(gnoDataDir, cfg.Genesis) + osm.EnsureDir(filepath.Dir(genesisFilePath), 0o700) + if !osm.FileExists(genesisFilePath) { + genesisTxs := loadGenesisTxs(icfg.GenesisTxsFile, icfg.ChainID, icfg.GenesisRemote) + pvPub := priv.GetPubKey() + + gen := &bft.GenesisDoc{ + GenesisTime: time.Now(), + ChainID: icfg.ChainID, + ConsensusParams: abci.ConsensusParams{ + Block: &abci.BlockParams{ + // TODO: update limits. + MaxTxBytes: 1000000, // 1MB, + MaxDataBytes: 2000000, // 2MB, + MaxGas: 10000000, // 10M gas + TimeIotaMS: 100, // 100ms + }, + }, + Validators: []bft.GenesisValidator{ + { + Address: pvPub.Address(), + PubKey: pvPub, + Power: 10, + Name: "testvalidator", + }, + }, + } + + // Load distribution. + balances := loadGenesisBalances(icfg.GenesisBalancesFile) + + // Load initial packages from examples. + // XXX: we should be able to config this + test1 := crypto.MustAddressFromString(test1Addr) + txs := []std.Tx{} + + // List initial packages to load from examples. + // println(filepath.Join(gnoRootDir, "examples")) + + pkgs, err := gnomod.ListPkgs(filepath.Join(gnoRootDir, "examples")) + if err != nil { + return fmt.Errorf("listing gno packages: %w", err) + } + + // Sort packages by dependencies. + sortedPkgs, err := pkgs.Sort() + if err != nil { + return fmt.Errorf("sorting packages: %w", err) + } + + // Filter out draft packages. + nonDraftPkgs := sortedPkgs.GetNonDraftPkgs() + + for _, pkg := range nonDraftPkgs { + // open files in directory as MemPackage. + memPkg := gno.ReadMemPackage(pkg.Dir, pkg.Name) + + var tx std.Tx + tx.Msgs = []std.Msg{ + vmm.MsgAddPackage{ + Creator: test1, + Package: memPkg, + Deposit: nil, + }, + } + + // XXX: add fee flag ? + // or maybe reduce fee to the minimum ? + tx.Fee = std.NewFee(50000, std.MustParseCoin("1000000ugnot")) + tx.Signatures = make([]std.Signature, len(tx.GetSigners())) + txs = append(txs, tx) + } + + // load genesis txs from file. + txs = append(txs, genesisTxs...) + + // construct genesis AppState. + gen.AppState = gnoland.GnoGenesisState{ + Balances: balances, + Txs: txs, + } + + writeGenesisFile(gen, genesisFilePath) + } + + return nil +} + +func createAppAndNode(cfg *config.Config, logger log.Logger, gnoRootDir string, icfg *IntegrationConfig) (*node.Node, error) { + gnoApp, err := gnoland.NewCustomApp(gnoland.CustomAppConfig{ + Logger: logger, + GnoRootDir: gnoRootDir, + SkipFailingGenesisTxs: icfg.SkipFailingGenesisTxs, + MaxCycles: icfg.GenesisMaxVMCycles, + DB: db.NewMemDB(), + }) + if err != nil { + return nil, fmt.Errorf("error in creating new app: %w", err) + } + + cfg.LocalApp = gnoApp + node, err := node.DefaultNewNode(cfg, logger) + if err != nil { + return nil, fmt.Errorf("error in creating node: %w", err) + } + + return node, node.Start() +} + +func tsValidateError(ts *testscript.TestScript, cmd string, neg bool, err error) { + if err != nil { + ts.Logf("%s error: %v\n", cmd, err) + if !neg { + ts.Fatalf("unexpected %s command failure", cmd) + } + } else { + if neg { + ts.Fatalf("unexpected %s command success", cmd) + } + } +} + +func loadGenesisTxs( + path string, + chainID string, + genesisRemote string, +) []std.Tx { + txs := []std.Tx{} + txsBz := osm.MustReadFile(path) + txsLines := strings.Split(string(txsBz), "\n") + for _, txLine := range txsLines { + if txLine == "" { + continue // skip empty line + } + + // patch the TX + txLine = strings.ReplaceAll(txLine, "%%CHAINID%%", chainID) + txLine = strings.ReplaceAll(txLine, "%%REMOTE%%", genesisRemote) + + var tx std.Tx + amino.MustUnmarshalJSON([]byte(txLine), &tx) + txs = append(txs, tx) + } + + return txs +} + +func loadGenesisBalances(path string) []string { + // each balance is in the form: g1xxxxxxxxxxxxxxxx=100000ugnot + balances := []string{} + content := osm.MustReadFile(path) + lines := strings.Split(string(content), "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + + // remove comments. + line = strings.Split(line, "#")[0] + line = strings.TrimSpace(line) + + // skip empty lines. + if line == "" { + continue + } + + parts := strings.Split(line, "=") + if len(parts) != 2 { + panic("invalid genesis_balance line: " + line) + } + + balances = append(balances, line) + } + return balances +} + +func writeGenesisFile(gen *bft.GenesisDoc, filePath string) { + err := gen.SaveAs(filePath) + if err != nil { + panic(err) + } +} + +func guessGnoRootDir() string { + var rootdir string + + // first try to get the root directory from the GNOROOT environment variable. + if rootdir = os.Getenv("GNOROOT"); rootdir != "" { + return filepath.Clean(rootdir) + } + + if gobin, err := exec.LookPath("go"); err == nil { + // if GNOROOT is not set, try to guess the root directory using the `go list` command. + cmd := exec.Command(gobin, "list", "-m", "-mod=mod", "-f", "{{.Dir}}", "github.com/gnolang/gno") + out, err := cmd.CombinedOutput() + if err != nil { + panic(fmt.Errorf("invalid gno directory %q: %w", rootdir, err)) + } + + return strings.TrimSpace(string(out)) + } + + panic("no go binary available, unable to determine gno root-dir path") +} diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 6a8b6d09297..699362e0441 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -1,11 +1,3 @@ -// ------------------------------------------------------------------------------------------------ -// WARNING: TEMPORARY CODE -// -// This file is a rapid prototype to meet immediate project needs and is not intended for long-term -// use in its current form. It requires review, possible refactoring, and thorough testing. -// Use at your own risk. The author(s) accept no liability for any issues arising from its use. -// ------------------------------------------------------------------------------------------------ - package integration import ( @@ -22,24 +14,11 @@ import ( "testing" "time" - "github.com/gnolang/gno/gno.land/pkg/gnoland" - vmm "github.com/gnolang/gno/gno.land/pkg/sdk/vm" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/gnomod" - "github.com/gnolang/gno/tm2/pkg/amino" - abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" - "github.com/gnolang/gno/tm2/pkg/bft/config" "github.com/gnolang/gno/tm2/pkg/bft/node" - "github.com/gnolang/gno/tm2/pkg/bft/privval" - bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" - "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/crypto/keys" "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" - "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/log" - osm "github.com/gnolang/gno/tm2/pkg/os" - "github.com/gnolang/gno/tm2/pkg/std" "github.com/jaekwon/testify/require" "github.com/rogpeppe/go-internal/testscript" ) @@ -54,77 +33,65 @@ func TestTestdata(t *testing.T) { testscript.Run(t, SetupGnolandTestScript(t, "testdata")) } -type IntegrationConfig struct { - skipFailingGenesisTxs bool - skipStart bool - genesisBalancesFile string - genesisTxsFile string - chainID string - genesisRemote string - rootDir string - genesisMaxVMCycles int64 - config string -} - func (c *IntegrationConfig) RegisterFlags(fs *flag.FlagSet) { fs.BoolVar( - &c.skipFailingGenesisTxs, + &c.SkipFailingGenesisTxs, "skip-failing-genesis-txs", false, "don't panic when replaying invalid genesis txs", ) fs.BoolVar( - &c.skipStart, + &c.SkipStart, "skip-start", false, "quit after initialization, don't start the node", ) fs.StringVar( - &c.genesisBalancesFile, + &c.GenesisBalancesFile, "genesis-balances-file", "./genesis/genesis_balances.txt", "initial distribution file", ) fs.StringVar( - &c.genesisTxsFile, + &c.GenesisTxsFile, "genesis-txs-file", "./genesis/genesis_txs.txt", "initial txs to replay", ) fs.StringVar( - &c.chainID, + &c.ChainID, "chainid", "dev", "the ID of the chain", ) fs.StringVar( - &c.rootDir, + &c.RootDir, "root-dir", "testdir", "directory for config and data", ) fs.StringVar( - &c.genesisRemote, + &c.GenesisRemote, "genesis-remote", "localhost:26657", "replacement for '%%REMOTE%%' in genesis", ) fs.Int64Var( - &c.genesisMaxVMCycles, + &c.GenesisMaxVMCycles, "genesis-max-vm-cycles", 10_000_000, "set maximum allowed vm cycles per operation. Zero means no limit.", ) fs.StringVar( - &c.config, + &c.Config, "config", "", "config file (optional)", @@ -289,57 +256,6 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { } } -func execTestingGnoland(t *testing.T, logger log.Logger, gnoDataDir, gnoRootDir string, args []string) (*node.Node, error) { - t.Helper() - - // Setup start config - icfg := &IntegrationConfig{} - { - fs := flag.NewFlagSet("start", flag.ExitOnError) - icfg.RegisterFlags(fs) - - // Override default value for flags - fs.VisitAll(func(f *flag.Flag) { - switch f.Name { - case "root-dir": - f.DefValue = gnoDataDir - case "chainid": - f.DefValue = "tendermint_test" - case "genesis-balances-file": - f.DefValue = filepath.Join(gnoRootDir, "gno.land", "genesis", "genesis_balances.txt") - case "genesis-txs-file": - f.DefValue = filepath.Join(gnoRootDir, "gno.land", "genesis", "genesis_txs.txt") - default: - return - } - - f.Value.Set(f.DefValue) - }) - - if err := fs.Parse(args); err != nil { - return nil, fmt.Errorf("unable to parse flags: %w", err) - } - } - - // Setup testing config - cfg := config.TestConfig().SetRootDir(gnoDataDir) - { - cfg.EnsureDirs() - cfg.Consensus.CreateEmptyBlocks = true - cfg.Consensus.CreateEmptyBlocksInterval = time.Duration(0) - cfg.RPC.ListenAddress = "tcp://127.0.0.1:0" - cfg.P2P.ListenAddress = "tcp://127.0.0.1:0" - } - - // Prepare genesis - if err := setupTestingGenesis(gnoDataDir, cfg, icfg, gnoRootDir); err != nil { - return nil, err - } - - // Create application and node - return createAppAndNode(cfg, logger, gnoRootDir, icfg) -} - func getSessionID(ts *testscript.TestScript) string { works := ts.Getenv("WORK") sum := crc32.ChecksumIEEE([]byte(works)) @@ -392,211 +308,3 @@ func getTestingLogger(ts *testscript.TestScript, logname string) log.Logger { ts.Logf("starting logger: %q", path) return logger } - -func setupTestingGenesis(gnoDataDir string, cfg *config.Config, icfg *IntegrationConfig, gnoRootDir string) error { - newPrivValKey := cfg.PrivValidatorKeyFile() - newPrivValState := cfg.PrivValidatorStateFile() - priv := privval.LoadOrGenFilePV(newPrivValKey, newPrivValState) - - genesisFilePath := filepath.Join(gnoDataDir, cfg.Genesis) - osm.EnsureDir(filepath.Dir(genesisFilePath), 0o700) - if !osm.FileExists(genesisFilePath) { - genesisTxs := loadGenesisTxs(icfg.genesisTxsFile, icfg.chainID, icfg.genesisRemote) - pvPub := priv.GetPubKey() - - gen := &bft.GenesisDoc{ - GenesisTime: time.Now(), - ChainID: icfg.chainID, - ConsensusParams: abci.ConsensusParams{ - Block: &abci.BlockParams{ - // TODO: update limits. - MaxTxBytes: 1000000, // 1MB, - MaxDataBytes: 2000000, // 2MB, - MaxGas: 10000000, // 10M gas - TimeIotaMS: 100, // 100ms - }, - }, - Validators: []bft.GenesisValidator{ - { - Address: pvPub.Address(), - PubKey: pvPub, - Power: 10, - Name: "testvalidator", - }, - }, - } - - // Load distribution. - balances := loadGenesisBalances(icfg.genesisBalancesFile) - - // Load initial packages from examples. - // XXX: we should be able to config this - test1 := crypto.MustAddressFromString(test1Addr) - txs := []std.Tx{} - - // List initial packages to load from examples. - // println(filepath.Join(gnoRootDir, "examples")) - - pkgs, err := gnomod.ListPkgs(filepath.Join(gnoRootDir, "examples")) - if err != nil { - return fmt.Errorf("listing gno packages: %w", err) - } - - // Sort packages by dependencies. - sortedPkgs, err := pkgs.Sort() - if err != nil { - return fmt.Errorf("sorting packages: %w", err) - } - - // Filter out draft packages. - nonDraftPkgs := sortedPkgs.GetNonDraftPkgs() - - for _, pkg := range nonDraftPkgs { - // open files in directory as MemPackage. - memPkg := gno.ReadMemPackage(pkg.Dir, pkg.Name) - - var tx std.Tx - tx.Msgs = []std.Msg{ - vmm.MsgAddPackage{ - Creator: test1, - Package: memPkg, - Deposit: nil, - }, - } - - // XXX: add fee flag ? - // or maybe reduce fee to the minimum ? - tx.Fee = std.NewFee(50000, std.MustParseCoin("1000000ugnot")) - tx.Signatures = make([]std.Signature, len(tx.GetSigners())) - txs = append(txs, tx) - } - - // load genesis txs from file. - txs = append(txs, genesisTxs...) - - // construct genesis AppState. - gen.AppState = gnoland.GnoGenesisState{ - Balances: balances, - Txs: txs, - } - - writeGenesisFile(gen, genesisFilePath) - } - - return nil -} - -func createAppAndNode(cfg *config.Config, logger log.Logger, gnoRootDir string, icfg *IntegrationConfig) (*node.Node, error) { - gnoApp, err := gnoland.NewCustomApp(gnoland.CustomAppConfig{ - Logger: logger, - GnoRootDir: gnoRootDir, - SkipFailingGenesisTxs: icfg.skipFailingGenesisTxs, - MaxCycles: icfg.genesisMaxVMCycles, - DB: db.NewMemDB(), - }) - if err != nil { - return nil, fmt.Errorf("error in creating new app: %w", err) - } - - cfg.LocalApp = gnoApp - node, err := node.DefaultNewNode(cfg, logger) - if err != nil { - return nil, fmt.Errorf("error in creating node: %w", err) - } - - return node, node.Start() -} - -func tsValidateError(ts *testscript.TestScript, cmd string, neg bool, err error) { - if err != nil { - ts.Logf("%s error: %v\n", cmd, err) - if !neg { - ts.Fatalf("unexpected %s command failure", cmd) - } - } else { - if neg { - ts.Fatalf("unexpected %s command success", cmd) - } - } -} - -func loadGenesisTxs( - path string, - chainID string, - genesisRemote string, -) []std.Tx { - txs := []std.Tx{} - txsBz := osm.MustReadFile(path) - txsLines := strings.Split(string(txsBz), "\n") - for _, txLine := range txsLines { - if txLine == "" { - continue // skip empty line - } - - // patch the TX - txLine = strings.ReplaceAll(txLine, "%%CHAINID%%", chainID) - txLine = strings.ReplaceAll(txLine, "%%REMOTE%%", genesisRemote) - - var tx std.Tx - amino.MustUnmarshalJSON([]byte(txLine), &tx) - txs = append(txs, tx) - } - - return txs -} - -func loadGenesisBalances(path string) []string { - // each balance is in the form: g1xxxxxxxxxxxxxxxx=100000ugnot - balances := []string{} - content := osm.MustReadFile(path) - lines := strings.Split(string(content), "\n") - for _, line := range lines { - line = strings.TrimSpace(line) - - // remove comments. - line = strings.Split(line, "#")[0] - line = strings.TrimSpace(line) - - // skip empty lines. - if line == "" { - continue - } - - parts := strings.Split(line, "=") - if len(parts) != 2 { - panic("invalid genesis_balance line: " + line) - } - - balances = append(balances, line) - } - return balances -} - -func writeGenesisFile(gen *bft.GenesisDoc, filePath string) { - err := gen.SaveAs(filePath) - if err != nil { - panic(err) - } -} - -func guessGnoRootDir() string { - var rootdir string - - // first try to get the root directory from the GNOROOT environment variable. - if rootdir = os.Getenv("GNOROOT"); rootdir != "" { - return filepath.Clean(rootdir) - } - - if gobin, err := exec.LookPath("go"); err == nil { - // if GNOROOT is not set, try to guess the root directory using the `go list` command. - cmd := exec.Command(gobin, "list", "-m", "-mod=mod", "-f", "{{.Dir}}", "github.com/gnolang/gno") - out, err := cmd.CombinedOutput() - if err != nil { - panic(fmt.Errorf("invalid gno directory %q: %w", rootdir, err)) - } - - return strings.TrimSpace(string(out)) - } - - panic("no go binary available, unable to determine gno root-dir path") -} From cc1de0b8c7db18177dc0d670045b721b8ae84945 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Fri, 29 Sep 2023 15:46:16 +0200 Subject: [PATCH 07/45] feat: add logger Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/integration/gnoland.go | 66 +++++++++++++ .../pkg/integration/testing_integration.go | 98 ++++--------------- tm2/pkg/crypto/keys/client/generate_test.go | 2 +- 3 files changed, 86 insertions(+), 80 deletions(-) diff --git a/gno.land/pkg/integration/gnoland.go b/gno.land/pkg/integration/gnoland.go index 88a5c289537..db78fa040b4 100644 --- a/gno.land/pkg/integration/gnoland.go +++ b/gno.land/pkg/integration/gnoland.go @@ -40,6 +40,72 @@ type IntegrationConfig struct { Config string } +// NOTE: this is a copy of gnoland actual flags +// XXX: a lot this make no sense for integration +func (c *IntegrationConfig) RegisterFlags(fs *flag.FlagSet) { + fs.BoolVar( + &c.SkipFailingGenesisTxs, + "skip-failing-genesis-txs", + false, + "don't panic when replaying invalid genesis txs", + ) + fs.BoolVar( + &c.SkipStart, + "skip-start", + false, + "quit after initialization, don't start the node", + ) + + fs.StringVar( + &c.GenesisBalancesFile, + "genesis-balances-file", + "./genesis/genesis_balances.txt", + "initial distribution file", + ) + + fs.StringVar( + &c.GenesisTxsFile, + "genesis-txs-file", + "./genesis/genesis_txs.txt", + "initial txs to replay", + ) + + fs.StringVar( + &c.ChainID, + "chainid", + "dev", + "the ID of the chain", + ) + + fs.StringVar( + &c.RootDir, + "root-dir", + "testdir", + "directory for config and data", + ) + + fs.StringVar( + &c.GenesisRemote, + "genesis-remote", + "localhost:26657", + "replacement for '%%REMOTE%%' in genesis", + ) + + fs.Int64Var( + &c.GenesisMaxVMCycles, + "genesis-max-vm-cycles", + 10_000_000, + "set maximum allowed vm cycles per operation. Zero means no limit.", + ) + + fs.StringVar( + &c.Config, + "config", + "", + "config file (optional)", + ) +} + func execTestingGnoland(t *testing.T, logger log.Logger, gnoDataDir, gnoRootDir string, args []string) (*node.Node, error) { t.Helper() diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 699362e0441..918d690d35a 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -2,7 +2,6 @@ package integration import ( "context" - "flag" "fmt" "hash/crc32" "os" @@ -29,79 +28,10 @@ const ( test1Seed = "source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast" ) -func TestTestdata(t *testing.T) { - testscript.Run(t, SetupGnolandTestScript(t, "testdata")) -} - -func (c *IntegrationConfig) RegisterFlags(fs *flag.FlagSet) { - fs.BoolVar( - &c.SkipFailingGenesisTxs, - "skip-failing-genesis-txs", - false, - "don't panic when replaying invalid genesis txs", - ) - - fs.BoolVar( - &c.SkipStart, - "skip-start", - false, - "quit after initialization, don't start the node", - ) - - fs.StringVar( - &c.GenesisBalancesFile, - "genesis-balances-file", - "./genesis/genesis_balances.txt", - "initial distribution file", - ) - - fs.StringVar( - &c.GenesisTxsFile, - "genesis-txs-file", - "./genesis/genesis_txs.txt", - "initial txs to replay", - ) - - fs.StringVar( - &c.ChainID, - "chainid", - "dev", - "the ID of the chain", - ) - - fs.StringVar( - &c.RootDir, - "root-dir", - "testdir", - "directory for config and data", - ) - - fs.StringVar( - &c.GenesisRemote, - "genesis-remote", - "localhost:26657", - "replacement for '%%REMOTE%%' in genesis", - ) - - fs.Int64Var( - &c.GenesisMaxVMCycles, - "genesis-max-vm-cycles", - 10_000_000, - "set maximum allowed vm cycles per operation. Zero means no limit.", - ) - - fs.StringVar( - &c.Config, - "config", - "", - "config file (optional)", - ) -} - type testNode struct { *node.Node - logger log.Logger - gnokeyexec uint // counter for execution of gnokey + logger log.Logger + nGnoKeyExec uint // counter for execution of gnokey } func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { @@ -118,9 +48,9 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { var muNodes sync.Mutex nodes := map[string]*testNode{} - bTestWork, _ := strconv.ParseBool(os.Getenv("TESTWORK")) + persistWorkDir, _ := strconv.ParseBool(os.Getenv("TESTWORK")) return testscript.Params{ - TestWork: bTestWork, + TestWork: persistWorkDir, Dir: txtarDir, Setup: func(env *testscript.Env) error { kb, err := keys.NewKeyBaseFromDir(gnoHomeDir) @@ -138,6 +68,17 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { return nil }, Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){ + "sleep": func(ts *testscript.TestScript, neg bool, args []string) { + d := time.Second + if len(args) > 0 { + var err error + if d, err = time.ParseDuration(args[0]); err != nil { + ts.Fatalf("uanble to parse duration %q: %s", args[1], err) + } + } + + time.Sleep(d) + }, "gnoland": func(ts *testscript.TestScript, neg bool, args []string) { muNodes.Lock() defer muNodes.Unlock() @@ -161,7 +102,7 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { } logger := log.NewNopLogger() - if testing.Verbose() && (os.Getenv("LOG_DIR") != "" || bTestWork) { + if persistWorkDir || os.Getenv("LOG_DIR") != "" { logname := fmt.Sprintf("gnoland-%s.log", sid) logger = getTestingLogger(ts, logname) } @@ -231,14 +172,13 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { } if n, ok := nodes[sid]; ok { - if raddr := n.Config().RPC.ListenAddress; raddr != "" { defaultArgs = append(defaultArgs, "-remote", raddr) } - n.gnokeyexec++ - headerlog := fmt.Sprintf("%.02d!EXEC_GNOKEY", n.gnokeyexec) - // we error to log this even on lower level + n.nGnoKeyExec++ + headerlog := fmt.Sprintf("%.02d!EXEC_GNOKEY", n.nGnoKeyExec) + // log the command inside gnoland logger, so we can better scope errors n.logger.Info(headerlog, strings.Join(args, " ")) defer n.logger.Info(headerlog, "END") } diff --git a/tm2/pkg/crypto/keys/client/generate_test.go b/tm2/pkg/crypto/keys/client/generate_test.go index fae85b664ac..516912046b6 100644 --- a/tm2/pkg/crypto/keys/client/generate_test.go +++ b/tm2/pkg/crypto/keys/client/generate_test.go @@ -46,7 +46,7 @@ func Test_execGenerateUser(t *testing.T) { err = execGenerate(cfg, []string{}, io) require.NoError(t, err) - // Now provide "io.good" entropy but no answer + // Now provide "good" entropy but no answer fakeEntropy = strings.Repeat(":)", 40) + "\n" // entropy + accept count io.SetIn(strings.NewReader(fakeEntropy)) err = execGenerate(cfg, []string{}, io) From b9d9156b41bd37f857b9ecd2b9e0f76a6a5dd9c4 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Fri, 29 Sep 2023 15:55:44 +0200 Subject: [PATCH 08/45] chore: gno directory name definition Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/integration/testing_integration.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 918d690d35a..a1e5e6b6fa1 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -41,8 +41,15 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { out, err := cmd.CombinedOutput() require.NoError(t, err) + // `gnoRootDir` should point to the local location of the gno repository. + // It serves as the gno equivalent of GOROOT. gnoRootDir := strings.TrimSpace(string(out)) + + // `gnoHomeDir` should be the local directory where gnokey stores keys. gnoHomeDir := filepath.Join(t.TempDir(), "gno") + + // `gnoDataDir` should refer to the local location where the gnoland node + // stores its configuration and data. gnoDataDir := filepath.Join(t.TempDir(), "data") var muNodes sync.Mutex @@ -64,6 +71,7 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { env.Setenv("GNOROOT", gnoRootDir) env.Setenv("GNOHOME", gnoHomeDir) + env.Setenv("GNODATA", gnoDataDir) return nil }, From fc811ff09772bd26f903a17dbcf188dccafed5f6 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Fri, 29 Sep 2023 17:22:29 +0200 Subject: [PATCH 09/45] feat: upload logs as artifact Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- .github/workflows/gnoland.yml | 8 ++++++++ gno.land/pkg/integration/testing_integration.go | 3 +-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gnoland.yml b/.github/workflows/gnoland.yml index 9fdfa3011cc..53f3aae3a8d 100644 --- a/.github/workflows/gnoland.yml +++ b/.github/workflows/gnoland.yml @@ -73,7 +73,14 @@ jobs: run: | export GOPATH=$HOME/go export GOTEST_FLAGS="-v -p 1 -timeout=30m -coverprofile=coverage.out -covermode=atomic" + export LOG_DIR="${{ runner.temp }}/logs/test-${{ matrix.goversion }}-gnoland" make ${{ matrix.args }} + - name: Upload Test Log + if: always() + uses: actions/upload-artifact@v3 + with: + name: logs-test-gnoland-go${{ matrix.goversion }} + path: ${{ runner.temp }}/logs/**/*.log - if: ${{ runner.os == 'Linux' && matrix.goversion == '1.21.x' }} uses: codecov/codecov-action@v3 with: @@ -83,6 +90,7 @@ jobs: files: ./gno.land/coverage.out fail_ci_if_error: ${{ github.repository == 'gnolang/gno' }} + docker-integration: strategy: fail-fast: false diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index a1e5e6b6fa1..4713dd35613 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -221,7 +221,6 @@ func getTestingLogger(ts *testscript.TestScript, logname string) log.Logger { if path, err = filepath.Abs(filepath.Join(logdir, logname)); err != nil { ts.Fatalf("uanble to get absolute path of logdir %q", logdir) } - } else if workdir := ts.Getenv("WORK"); workdir != "" { path = filepath.Join(workdir, logname) } else { @@ -236,7 +235,7 @@ func getTestingLogger(ts *testscript.TestScript, logname string) log.Logger { ts.Defer(func() { if err := f.Close(); err != nil { - panic(fmt.Errorf("unable to close log file %q: %s", path, err)) + panic(fmt.Errorf("unable to close log file %q: %w", path, err)) } }) From c0ed4b1b4d6ed95cbc8a80338a56830ab1385287 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Mon, 2 Oct 2023 11:19:37 +0200 Subject: [PATCH 10/45] chore: lint Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/cmd/gnoland/integration.go | 1 - gno.land/pkg/integration/gnoland.go | 1 + gno.land/pkg/integration/testing_integration.go | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 gno.land/cmd/gnoland/integration.go diff --git a/gno.land/cmd/gnoland/integration.go b/gno.land/cmd/gnoland/integration.go deleted file mode 100644 index 06ab7d0f9a3..00000000000 --- a/gno.land/cmd/gnoland/integration.go +++ /dev/null @@ -1 +0,0 @@ -package main diff --git a/gno.land/pkg/integration/gnoland.go b/gno.land/pkg/integration/gnoland.go index db78fa040b4..6016831a842 100644 --- a/gno.land/pkg/integration/gnoland.go +++ b/gno.land/pkg/integration/gnoland.go @@ -343,6 +343,7 @@ func writeGenesisFile(gen *bft.GenesisDoc, filePath string) { } } +// XXX: This helper will need to be moved elsewhere later func guessGnoRootDir() string { var rootdir string diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 4713dd35613..32051a107a7 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -81,7 +81,7 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { if len(args) > 0 { var err error if d, err = time.ParseDuration(args[0]); err != nil { - ts.Fatalf("uanble to parse duration %q: %s", args[1], err) + ts.Fatalf("unable to parse duration %q: %s", args[1], err) } } @@ -249,7 +249,7 @@ func getTestingLogger(ts *testscript.TestScript, logname string) log.Logger { logger.SetLevel(log.LevelInfo) case "": default: - panic("invalid log level: " + level) + ts.Fatalf("invalid log level %q", level) } ts.Logf("starting logger: %q", path) From e808b91618d686e3905f664c741ab7c96f0fc926 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Tue, 3 Oct 2023 11:17:30 +0200 Subject: [PATCH 11/45] fix: add sleep after addpkg command Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/cmd/gnoland/testdata/addpkg.txtar | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gno.land/cmd/gnoland/testdata/addpkg.txtar b/gno.land/cmd/gnoland/testdata/addpkg.txtar index 5e871b058ac..90984e5d377 100644 --- a/gno.land/cmd/gnoland/testdata/addpkg.txtar +++ b/gno.land/cmd/gnoland/testdata/addpkg.txtar @@ -9,6 +9,12 @@ gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 10000 ## execute Render gnokey maketx call -pkgpath gno.land/r/foobar/bar -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 +## NOTE: In some rare cases, the previous command hasn't finished. Wait a bit to +## ensure that the command has been correctly executed. +## XXX: Find a more convenient and reliable way to ensure that the previous +## command has been executed. +sleep 500ms + ## compare render cmp stdout stdout.golden From 5a7f78468ab28bdb2cbb2d7c555f6c4c847eac71 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Tue, 3 Oct 2023 11:20:13 +0200 Subject: [PATCH 12/45] chore: add comment on app gnorootdir Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/gnoland/app.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index dad87eaf44d..1442f7fc5d0 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -23,7 +23,9 @@ import ( ) type CustomAppConfig struct { - DB dbm.DB + DB dbm.DB + // `gnoRootDir` should point to the local location of the gno repository. + // It serves as the gno equivalent of GOROOT. GnoRootDir string SkipFailingGenesisTxs bool Logger log.Logger From 1b7d71ce984762a79cc304ccf064dcedfd1fb519 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Tue, 3 Oct 2023 11:22:02 +0200 Subject: [PATCH 13/45] chore: fix and update comments Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/gnoland/app.go | 1 + gno.land/pkg/integration/gnoland.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index 1442f7fc5d0..dde83fd61bf 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -202,6 +202,7 @@ func EndBlocker(vmk vm.VMKeeperI) func(ctx sdk.Context, req abci.RequestEndBlock } } +// XXX: This helper will need to be relocated in the future. func guessGnoRootDir() string { var rootdir string diff --git a/gno.land/pkg/integration/gnoland.go b/gno.land/pkg/integration/gnoland.go index 6016831a842..c3f85bd7e29 100644 --- a/gno.land/pkg/integration/gnoland.go +++ b/gno.land/pkg/integration/gnoland.go @@ -343,7 +343,7 @@ func writeGenesisFile(gen *bft.GenesisDoc, filePath string) { } } -// XXX: This helper will need to be moved elsewhere later +// XXX: This helper will need to be relocated in the future. func guessGnoRootDir() string { var rootdir string From 8e2516afa01b7ed1a2225e26ea2fa7ee9dd88a7b Mon Sep 17 00:00:00 2001 From: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> Date: Tue, 3 Oct 2023 19:51:51 +0200 Subject: [PATCH 14/45] Update gno.land/cmd/gnoland/integration_test.go Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com> --- gno.land/cmd/gnoland/integration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gno.land/cmd/gnoland/integration_test.go b/gno.land/cmd/gnoland/integration_test.go index b629dff2b92..78c8e94fa09 100644 --- a/gno.land/cmd/gnoland/integration_test.go +++ b/gno.land/cmd/gnoland/integration_test.go @@ -3,7 +3,7 @@ package main import ( "testing" - integration "github.com/gnolang/gno/gno.land/pkg/integration" + "github.com/gnolang/gno/gno.land/pkg/integration" "github.com/rogpeppe/go-internal/testscript" ) From c7762130cb631f9f0322eff117f585d2b548c4f0 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Tue, 3 Oct 2023 21:08:21 +0200 Subject: [PATCH 15/45] chore: remove useless (not used) config argument for gnoland integration Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/integration/gnoland.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/gno.land/pkg/integration/gnoland.go b/gno.land/pkg/integration/gnoland.go index c3f85bd7e29..bdba2a656b1 100644 --- a/gno.land/pkg/integration/gnoland.go +++ b/gno.land/pkg/integration/gnoland.go @@ -97,13 +97,6 @@ func (c *IntegrationConfig) RegisterFlags(fs *flag.FlagSet) { 10_000_000, "set maximum allowed vm cycles per operation. Zero means no limit.", ) - - fs.StringVar( - &c.Config, - "config", - "", - "config file (optional)", - ) } func execTestingGnoland(t *testing.T, logger log.Logger, gnoDataDir, gnoRootDir string, args []string) (*node.Node, error) { From 4b57cd5c478283a68c377c4f23e37d6c7d454a2f Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Tue, 3 Oct 2023 21:44:56 +0200 Subject: [PATCH 16/45] chore: comments, capitalize, punctuation Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/gnoland/app.go | 4 +- gno.land/pkg/integration/gnoland.go | 37 +++++++++---------- .../pkg/integration/testing_integration.go | 26 ++++++------- 3 files changed, 33 insertions(+), 34 deletions(-) diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index dde83fd61bf..e6e11687a48 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -206,13 +206,13 @@ func EndBlocker(vmk vm.VMKeeperI) func(ctx sdk.Context, req abci.RequestEndBlock func guessGnoRootDir() string { var rootdir string - // first try to get the root directory from the GNOROOT environment variable. + // First try to get the root directory from the GNOROOT environment variable. if rootdir = os.Getenv("GNOROOT"); rootdir != "" { return filepath.Clean(rootdir) } if gobin, err := exec.LookPath("go"); err == nil { - // if GNOROOT is not set, try to guess the root directory using the `go list` command. + // If GNOROOT is not set, try to guess the root directory using the `go list` command. cmd := exec.Command(gobin, "list", "-m", "-mod=mod", "-f", "{{.Dir}}", "github.com/gnolang/gno") out, err := cmd.CombinedOutput() if err != nil { diff --git a/gno.land/pkg/integration/gnoland.go b/gno.land/pkg/integration/gnoland.go index bdba2a656b1..9c8e13713dd 100644 --- a/gno.land/pkg/integration/gnoland.go +++ b/gno.land/pkg/integration/gnoland.go @@ -40,8 +40,8 @@ type IntegrationConfig struct { Config string } -// NOTE: this is a copy of gnoland actual flags -// XXX: a lot this make no sense for integration +// NOTE: This is a copy of gnoland actual flags. +// XXX: A lot this make no sense for integration. func (c *IntegrationConfig) RegisterFlags(fs *flag.FlagSet) { fs.BoolVar( &c.SkipFailingGenesisTxs, @@ -102,13 +102,13 @@ func (c *IntegrationConfig) RegisterFlags(fs *flag.FlagSet) { func execTestingGnoland(t *testing.T, logger log.Logger, gnoDataDir, gnoRootDir string, args []string) (*node.Node, error) { t.Helper() - // Setup start config + // Setup start config. icfg := &IntegrationConfig{} { fs := flag.NewFlagSet("start", flag.ExitOnError) icfg.RegisterFlags(fs) - // Override default value for flags + // Override default value for flags. fs.VisitAll(func(f *flag.Flag) { switch f.Name { case "root-dir": @@ -131,7 +131,7 @@ func execTestingGnoland(t *testing.T, logger log.Logger, gnoDataDir, gnoRootDir } } - // Setup testing config + // Setup testing config. cfg := config.TestConfig().SetRootDir(gnoDataDir) { cfg.EnsureDirs() @@ -141,12 +141,12 @@ func execTestingGnoland(t *testing.T, logger log.Logger, gnoDataDir, gnoRootDir cfg.P2P.ListenAddress = "tcp://127.0.0.1:0" } - // Prepare genesis + // Prepare genesis. if err := setupTestingGenesis(gnoDataDir, cfg, icfg, gnoRootDir); err != nil { return nil, err } - // Create application and node + // Create application and node. return createAppAndNode(cfg, logger, gnoRootDir, icfg) } @@ -187,13 +187,12 @@ func setupTestingGenesis(gnoDataDir string, cfg *config.Config, icfg *Integratio balances := loadGenesisBalances(icfg.GenesisBalancesFile) // Load initial packages from examples. - // XXX: we should be able to config this + // XXX: We should be able to config this. test1 := crypto.MustAddressFromString(test1Addr) txs := []std.Tx{} // List initial packages to load from examples. // println(filepath.Join(gnoRootDir, "examples")) - pkgs, err := gnomod.ListPkgs(filepath.Join(gnoRootDir, "examples")) if err != nil { return fmt.Errorf("listing gno packages: %w", err) @@ -209,7 +208,7 @@ func setupTestingGenesis(gnoDataDir string, cfg *config.Config, icfg *Integratio nonDraftPkgs := sortedPkgs.GetNonDraftPkgs() for _, pkg := range nonDraftPkgs { - // open files in directory as MemPackage. + // Open files in directory as MemPackage. memPkg := gno.ReadMemPackage(pkg.Dir, pkg.Name) var tx std.Tx @@ -221,17 +220,17 @@ func setupTestingGenesis(gnoDataDir string, cfg *config.Config, icfg *Integratio }, } - // XXX: add fee flag ? - // or maybe reduce fee to the minimum ? + // XXX: Add fee flag ? + // Or maybe reduce fee to the minimum ? tx.Fee = std.NewFee(50000, std.MustParseCoin("1000000ugnot")) tx.Signatures = make([]std.Signature, len(tx.GetSigners())) txs = append(txs, tx) } - // load genesis txs from file. + // Load genesis txs from file. txs = append(txs, genesisTxs...) - // construct genesis AppState. + // Construct genesis AppState. gen.AppState = gnoland.GnoGenesisState{ Balances: balances, Txs: txs, @@ -287,10 +286,10 @@ func loadGenesisTxs( txsLines := strings.Split(string(txsBz), "\n") for _, txLine := range txsLines { if txLine == "" { - continue // skip empty line + continue // Skip empty line. } - // patch the TX + // Patch the TX. txLine = strings.ReplaceAll(txLine, "%%CHAINID%%", chainID) txLine = strings.ReplaceAll(txLine, "%%REMOTE%%", genesisRemote) @@ -303,18 +302,18 @@ func loadGenesisTxs( } func loadGenesisBalances(path string) []string { - // each balance is in the form: g1xxxxxxxxxxxxxxxx=100000ugnot + // Each balance is in the form: g1xxxxxxxxxxxxxxxx=100000ugnot. balances := []string{} content := osm.MustReadFile(path) lines := strings.Split(string(content), "\n") for _, line := range lines { line = strings.TrimSpace(line) - // remove comments. + // Remove comments. line = strings.Split(line, "#")[0] line = strings.TrimSpace(line) - // skip empty lines. + // Skip empty lines. if line == "" { continue } diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 32051a107a7..c3480472034 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -22,7 +22,7 @@ import ( "github.com/rogpeppe/go-internal/testscript" ) -// XXX: should be centralize somewhere +// XXX: This should be centralize somewhere. const ( test1Addr = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" test1Seed = "source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast" @@ -31,7 +31,7 @@ const ( type testNode struct { *node.Node logger log.Logger - nGnoKeyExec uint // counter for execution of gnokey + nGnoKeyExec uint // Counter for execution of gnokey. } func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { @@ -65,6 +65,7 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { return err } + // XXX: Add a command to add custom account. kb.CreateAccount("test1", test1Seed, "", "", 0, 0) env.Setenv("USER_SEED_test1", test1Seed) env.Setenv("USER_ADDR_test1", test1Addr) @@ -130,11 +131,11 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { } }) - // get listen addr environement - // should have been updated with the right port on start + // Get listen address environment. + // It should have been updated with the right port on start. laddr := node.Config().RPC.ListenAddress - // add default environement + // Add default environements. ts.Setenv("RPC_ADDR", laddr) ts.Setenv("GNODATA", gnoDataDir) @@ -153,7 +154,7 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { if err = n.Stop(); err != nil { delete(nodes, sid) - // unset env dirs + // Unset gnoland environements. ts.Setenv("RPC_ADDR", "") ts.Setenv("GNODATA", "") } @@ -167,16 +168,16 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { sid := getSessionID(ts) - // Setup io command + // Setup IO command. io := commands.NewTestIO() io.SetOut(commands.WriteNopCloser(ts.Stdout())) io.SetErr(commands.WriteNopCloser(ts.Stderr())) cmd := client.NewRootCmd(io) - io.SetIn(strings.NewReader("\n")) // inject empty password to stdin + io.SetIn(strings.NewReader("\n")) // Inject empty password to stdin. defaultArgs := []string{ "-home", gnoHomeDir, - "-insecure-password-stdin=true", // there no use to not have this param by default + "-insecure-password-stdin=true", // There no use to not have this param by default. } if n, ok := nodes[sid]; ok { @@ -186,14 +187,14 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { n.nGnoKeyExec++ headerlog := fmt.Sprintf("%.02d!EXEC_GNOKEY", n.nGnoKeyExec) - // log the command inside gnoland logger, so we can better scope errors + // Log the command inside gnoland logger, so we can better scope errors. n.logger.Info(headerlog, strings.Join(args, " ")) defer n.logger.Info(headerlog, "END") } - // inject default argument, if duplicate + // Inject default argument, if duplicate // arguments, it should be override by the ones - // user provided + // user provided. args = append(defaultArgs, args...) err := cmd.ParseAndRun(context.Background(), args) @@ -227,7 +228,6 @@ func getTestingLogger(ts *testscript.TestScript, logname string) log.Logger { return log.NewNopLogger() } - // create the file f, err := os.Create(path) if err != nil { ts.Fatalf("unable to create log file %q: %s", path, err.Error()) From 911320becad352c6a6c44a685c632e89f794381a Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 12:06:55 +0200 Subject: [PATCH 17/45] chore: correctly move sleep at the right place Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/cmd/gnoland/testdata/addpkg.txtar | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gno.land/cmd/gnoland/testdata/addpkg.txtar b/gno.land/cmd/gnoland/testdata/addpkg.txtar index 90984e5d377..80cd962fb61 100644 --- a/gno.land/cmd/gnoland/testdata/addpkg.txtar +++ b/gno.land/cmd/gnoland/testdata/addpkg.txtar @@ -6,15 +6,15 @@ gnoland start ## add bar.gno package located in $WORK directory as gno.land/r/foobar/bar gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 -## execute Render -gnokey maketx call -pkgpath gno.land/r/foobar/bar -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 - ## NOTE: In some rare cases, the previous command hasn't finished. Wait a bit to ## ensure that the command has been correctly executed. ## XXX: Find a more convenient and reliable way to ensure that the previous ## command has been executed. sleep 500ms +## execute Render +gnokey maketx call -pkgpath gno.land/r/foobar/bar -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 + ## compare render cmp stdout stdout.golden From bc081764afb7eefee6419f6c61c7fe162bcaa3bd Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 15:57:37 +0200 Subject: [PATCH 18/45] fix(docker): set GNOROOT in dockerfile instead of the docker integration Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- Dockerfile | 3 ++- misc/docker-integration/integration_test.go | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 70e2d01bf04..9e7fc48dcb0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,8 @@ RUN rm -rf /opt/gno/src/.git # runtime-base + runtime-tls FROM debian:stable-slim AS runtime-base -ENV PATH="${PATH}:/opt/gno/bin" +ENV PATH="${PATH}:/opt/gno/bin" \ + GNOROOT="/opt/gno/src" WORKDIR /opt/gno/src FROM runtime-base AS runtime-tls RUN apt-get update && apt-get install -y expect ca-certificates && update-ca-certificates diff --git a/misc/docker-integration/integration_test.go b/misc/docker-integration/integration_test.go index cf3f09a0401..6ae0ea1d36e 100644 --- a/misc/docker-integration/integration_test.go +++ b/misc/docker-integration/integration_test.go @@ -149,7 +149,6 @@ func startGnoland(t *testing.T) { "docker", "run", "-d", "--name", gnolandContainerName, - "-e", "GNOROOT=/opt/gno/src", "-w", "/opt/gno/src/gno.land", "gno:integration", "gnoland", From 18046ead2d1501bb00f3b876a6080f2c6857a5ed Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 15:58:16 +0200 Subject: [PATCH 19/45] fix(test): use mutex in defer Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/integration/testing_integration.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index c3480472034..6f04d2f195f 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -124,6 +124,9 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { logger: logger, } ts.Defer(func() { + muNodes.Lock() + defer muNodes.Unlock() + if n := nodes[sid]; n != nil { if err := n.Stop(); err != nil { panic(fmt.Errorf("node %q was unable to stop: %w", sid, err)) From 0cb9586cb080e1ad003a7e044c3ca2e6cd64871c Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 15:59:26 +0200 Subject: [PATCH 20/45] chore(log): set back %s instead of %+v Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- tm2/pkg/crypto/keys/client/query.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm2/pkg/crypto/keys/client/query.go b/tm2/pkg/crypto/keys/client/query.go index 50c1f257213..e2bef3758cb 100644 --- a/tm2/pkg/crypto/keys/client/query.go +++ b/tm2/pkg/crypto/keys/client/query.go @@ -75,7 +75,7 @@ func execQuery(cfg *queryCfg, args []string, io *commands.IO) error { } if qres.Response.Error != nil { - io.Printf("Log: %+v\n", + io.Printf("Log: %s\n", qres.Response) return qres.Response.Error } From 8d0f2d9c970357204f46bfdc545ec3205f703490 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 16:03:07 +0200 Subject: [PATCH 21/45] fix(tempdir): use only one tempdir call Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/integration/testing_integration.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 6f04d2f195f..58ba3618ee8 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -41,16 +41,18 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { out, err := cmd.CombinedOutput() require.NoError(t, err) + tmpdir := t.TempDir() + // `gnoRootDir` should point to the local location of the gno repository. // It serves as the gno equivalent of GOROOT. gnoRootDir := strings.TrimSpace(string(out)) // `gnoHomeDir` should be the local directory where gnokey stores keys. - gnoHomeDir := filepath.Join(t.TempDir(), "gno") + gnoHomeDir := filepath.Join(tmpdir, "gno") // `gnoDataDir` should refer to the local location where the gnoland node // stores its configuration and data. - gnoDataDir := filepath.Join(t.TempDir(), "data") + gnoDataDir := filepath.Join(tmpdir, "data") var muNodes sync.Mutex nodes := map[string]*testNode{} From 221e216abd29f2b0243dcd1d13987bcc61cc3e9d Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 17:08:54 +0200 Subject: [PATCH 22/45] feat: add `update_scripts` environment variable Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/integration/testing_integration.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 58ba3618ee8..d9f233ccbc1 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -57,10 +57,12 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { var muNodes sync.Mutex nodes := map[string]*testNode{} + updatesCripts, _ := strconv.ParseBool(os.Getenv("UPDATE_SCRIPTS")) persistWorkDir, _ := strconv.ParseBool(os.Getenv("TESTWORK")) return testscript.Params{ - TestWork: persistWorkDir, - Dir: txtarDir, + UpdateScripts: updatesCripts, + TestWork: persistWorkDir, + Dir: txtarDir, Setup: func(env *testscript.Env) error { kb, err := keys.NewKeyBaseFromDir(gnoHomeDir) if err != nil { From 3544390a2415d06d4fb0360031c97d2b14743426 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 17:09:28 +0200 Subject: [PATCH 23/45] feat: add partial `doc.go` Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/integration/doc.go | 75 +++++++++++++++++++ .../pkg/integration/testing_integration.go | 9 +++ 2 files changed, 84 insertions(+) create mode 100644 gno.land/pkg/integration/doc.go diff --git a/gno.land/pkg/integration/doc.go b/gno.land/pkg/integration/doc.go new file mode 100644 index 00000000000..f8442c74f74 --- /dev/null +++ b/gno.land/pkg/integration/doc.go @@ -0,0 +1,75 @@ +// Package integration offers utilities to run txtar-based tests against the gnoland system +// by extending the functionalities provided by the standard testscript package. +// +// The primary utility, SetupGnolandTestScript, sets up the environment for running txtar +// tests, introducing additional commands like "gnoland" and "gnokey" into the test script ecosystem. +// +// Additional Command Overview: +// +// 1. `gnoland [start|stop]`: +// - The gnoland node doesn't start automatically. This enables the user to do some +// pre-configuration or pass custom arguments to the start command. +// +// 2. `gnokey`: +// - Supports most of the common commands. +// - `--remote`, `--insecure-password-stdin`, and `--home` flags are set automatically to +// communicate with the gnoland node. +// +// Logging: +// +// The gnoland logs aren't forwarded to stdout to avoid overwhelming the tests with too much +// information. Instead, a log directory can be specified with `LOG_DIR`, or you +// can set `TESTWORK=true` +// to persist logs in the txtar working directory. In any case, the log file should be printed +// on start if one of these environment variables is set. +// +// Environment Variables: +// +// Input: +// +// - LOG_LEVEL: +// The logging level to be used, which can be one of "error", "debug", "info", or an empty string. +// If empty, the log level defaults to "debug". +// +// - LOG_DIR: +// If set, logs will be directed to the specified directory. +// +// - TESTWORK: +// A boolean that, when enabled, retains working directories after tests for +// inspection. If enabled, gnoland logs will be persisted inside this +// folder. +// +// - UPDATE_SCRIPTS: +// A boolean that, when enabled, updates the test scripts if a `cmp` command +// fails and its second argument refers to a file inside the testscript +// file. The content will be quoted with txtar.Quote if needed, requiring +// manual edits if it's not unquoted in the script. +// +// Output (available inside testscripts files): +// +// - WORK: +// The path to the temporary work directory tree created for each script. +// +// - GNOROOT: +// Points to the local location of the gno repository, serving as the GOROOT equivalent for gno. +// +// - GNOHOME: +// Refers to the local directory where gnokey stores its keys. +// +// - GNODATA: +// The path where the gnoland node stores its configuration and data. It's +// set only if the node has started. +// +// - USER_SEED_test1: +// Contains the seed for the test1 account. +// +// - USER_ADDR_test1: +// Contains the address for the test1 account. +// +// - RPC_ADDR: +// Points to the gnoland node's remote address. It's set only if the node has started. +// +// For a more comprehensive guide on original behaviors, additional commands and environment +// variables, refer to the original documentation of testscripts available here: +// https://github.com/rogpeppe/go-internal/blob/master/testscript/doc.go +package integration diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index d9f233ccbc1..2f51d28d39d 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -34,6 +34,15 @@ type testNode struct { nGnoKeyExec uint // Counter for execution of gnokey. } +// SetupGnolandTestScript prepares the test environment to execute txtar tests +// using a partial InMemory gnoland node. It initializes key storage, sets up the gnoland node, +// and provides custom commands like "gnoland" and "gnokey" for txtar script execution. +// +// The function returns testscript.Params which contain the test setup and command +// executions to be used with the testscript package. +// +// For a detailed explanation of the commands and their behaviors, as well as +// example txtar scripts, refer to the package documentation in doc.go. func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { t.Helper() From 5f6f041f9b454cf83c075b3308024c39545f1417 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 17:10:18 +0200 Subject: [PATCH 24/45] fix: remove sleep command inside testscripts Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/cmd/gnoland/testdata/addpkg.txtar | 6 ------ gno.land/pkg/integration/testing_integration.go | 14 +------------- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/gno.land/cmd/gnoland/testdata/addpkg.txtar b/gno.land/cmd/gnoland/testdata/addpkg.txtar index 80cd962fb61..5e871b058ac 100644 --- a/gno.land/cmd/gnoland/testdata/addpkg.txtar +++ b/gno.land/cmd/gnoland/testdata/addpkg.txtar @@ -6,12 +6,6 @@ gnoland start ## add bar.gno package located in $WORK directory as gno.land/r/foobar/bar gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 -## NOTE: In some rare cases, the previous command hasn't finished. Wait a bit to -## ensure that the command has been correctly executed. -## XXX: Find a more convenient and reliable way to ensure that the previous -## command has been executed. -sleep 500ms - ## execute Render gnokey maketx call -pkgpath gno.land/r/foobar/bar -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1 diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 2f51d28d39d..906a8c66cf7 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -85,22 +85,10 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { env.Setenv("GNOROOT", gnoRootDir) env.Setenv("GNOHOME", gnoHomeDir) - env.Setenv("GNODATA", gnoDataDir) return nil }, Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){ - "sleep": func(ts *testscript.TestScript, neg bool, args []string) { - d := time.Second - if len(args) > 0 { - var err error - if d, err = time.ParseDuration(args[0]); err != nil { - ts.Fatalf("unable to parse duration %q: %s", args[1], err) - } - } - - time.Sleep(d) - }, "gnoland": func(ts *testscript.TestScript, neg bool, args []string) { muNodes.Lock() defer muNodes.Unlock() @@ -158,7 +146,7 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { // XXX: Use something similar to `require.Eventually` to check for node // availability. For now, if this sleep duration is too short, the // subsequent command might fail with an [internal error]. - time.Sleep(time.Second) + time.Sleep(time.Second * 2) } case "stop": n, ok := nodes[sid] From 2f171141d325c5b8ce963f946ceb12568b1f2078 Mon Sep 17 00:00:00 2001 From: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 17:50:44 +0200 Subject: [PATCH 25/45] Update gno.land/pkg/integration/testing_integration.go Co-authored-by: Morgan --- gno.land/pkg/integration/testing_integration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 906a8c66cf7..588f8d7ff46 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -94,7 +94,7 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { defer muNodes.Unlock() if len(args) == 0 { - tsValidateError(ts, "gnoland", neg, fmt.Errorf("use gnoland [start|stop] command")) + tsValidateError(ts, "gnoland", neg, fmt.Errorf("syntax: gnoland [start|stop]")) return } From 0c589e21a980a1880e71b9feb816b74cf7a6af83 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 17:37:49 +0200 Subject: [PATCH 26/45] fix: query reponse log Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- tm2/pkg/crypto/keys/client/query.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm2/pkg/crypto/keys/client/query.go b/tm2/pkg/crypto/keys/client/query.go index e2bef3758cb..8cc5757aba7 100644 --- a/tm2/pkg/crypto/keys/client/query.go +++ b/tm2/pkg/crypto/keys/client/query.go @@ -76,7 +76,7 @@ func execQuery(cfg *queryCfg, args []string, io *commands.IO) error { if qres.Response.Error != nil { io.Printf("Log: %s\n", - qres.Response) + qres.Response.Log) return qres.Response.Error } From a265376f294a3cd74808fad22d8c0bf1fb6f0d09 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 18:01:17 +0200 Subject: [PATCH 27/45] chore: centralize GuessGnoRootDir in gnoland Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/gnoland/app.go | 5 ++-- gno.land/pkg/integration/gnoland.go | 23 ------------------- .../pkg/integration/testing_integration.go | 9 ++------ 3 files changed, 4 insertions(+), 33 deletions(-) diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index e6e11687a48..cc2e999c275 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -42,7 +42,7 @@ func (c *CustomAppConfig) ApplyDefault() { } if c.GnoRootDir == "" { - c.GnoRootDir = guessGnoRootDir() + c.GnoRootDir = GuessGnoRootDir() } } @@ -202,8 +202,7 @@ func EndBlocker(vmk vm.VMKeeperI) func(ctx sdk.Context, req abci.RequestEndBlock } } -// XXX: This helper will need to be relocated in the future. -func guessGnoRootDir() string { +func GuessGnoRootDir() string { var rootdir string // First try to get the root directory from the GNOROOT environment variable. diff --git a/gno.land/pkg/integration/gnoland.go b/gno.land/pkg/integration/gnoland.go index 9c8e13713dd..c7b6028b88c 100644 --- a/gno.land/pkg/integration/gnoland.go +++ b/gno.land/pkg/integration/gnoland.go @@ -334,26 +334,3 @@ func writeGenesisFile(gen *bft.GenesisDoc, filePath string) { panic(err) } } - -// XXX: This helper will need to be relocated in the future. -func guessGnoRootDir() string { - var rootdir string - - // first try to get the root directory from the GNOROOT environment variable. - if rootdir = os.Getenv("GNOROOT"); rootdir != "" { - return filepath.Clean(rootdir) - } - - if gobin, err := exec.LookPath("go"); err == nil { - // if GNOROOT is not set, try to guess the root directory using the `go list` command. - cmd := exec.Command(gobin, "list", "-m", "-mod=mod", "-f", "{{.Dir}}", "github.com/gnolang/gno") - out, err := cmd.CombinedOutput() - if err != nil { - panic(fmt.Errorf("invalid gno directory %q: %w", rootdir, err)) - } - - return strings.TrimSpace(string(out)) - } - - panic("no go binary available, unable to determine gno root-dir path") -} diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 588f8d7ff46..ae0b5c69bbb 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -5,7 +5,6 @@ import ( "fmt" "hash/crc32" "os" - "os/exec" "path/filepath" "strconv" "strings" @@ -13,12 +12,12 @@ import ( "testing" "time" + "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/tm2/pkg/bft/node" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys" "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" "github.com/gnolang/gno/tm2/pkg/log" - "github.com/jaekwon/testify/require" "github.com/rogpeppe/go-internal/testscript" ) @@ -46,15 +45,11 @@ type testNode struct { func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { t.Helper() - cmd := exec.Command("go", "list", "-m", "-mod=mod", "-f", "{{.Dir}}", "github.com/gnolang/gno") - out, err := cmd.CombinedOutput() - require.NoError(t, err) - tmpdir := t.TempDir() // `gnoRootDir` should point to the local location of the gno repository. // It serves as the gno equivalent of GOROOT. - gnoRootDir := strings.TrimSpace(string(out)) + gnoRootDir := gnoland.GuessGnoRootDir() // `gnoHomeDir` should be the local directory where gnokey stores keys. gnoHomeDir := filepath.Join(tmpdir, "gno") From 97c80481f8b02d3842f8698467ca9f44d26bdf77 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 18:09:24 +0200 Subject: [PATCH 28/45] fix: correctly handle unknown gnoland subcommand Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/integration/testing_integration.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index ae0b5c69bbb..75ed37e4da5 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -157,6 +157,8 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { ts.Setenv("RPC_ADDR", "") ts.Setenv("GNODATA", "") } + default: + err = fmt.Errorf("invalid gnoland subcommand: %q", cmd) } tsValidateError(ts, "gnoland "+cmd, neg, err) From 348a4100c0662a4b301d2e002872c5d3e42faca0 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 18:16:41 +0200 Subject: [PATCH 29/45] fix: unused imports Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/integration/gnoland.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/gno.land/pkg/integration/gnoland.go b/gno.land/pkg/integration/gnoland.go index c7b6028b88c..c963a12aba4 100644 --- a/gno.land/pkg/integration/gnoland.go +++ b/gno.land/pkg/integration/gnoland.go @@ -3,8 +3,6 @@ package integration import ( "flag" "fmt" - "os" - "os/exec" "path/filepath" "strings" "testing" From 9ea042217b08befd50091fecc83382ef8b93004c Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 19:00:45 +0200 Subject: [PATCH 30/45] fix: node stop not correctly handled Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/integration/testing_integration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 75ed37e4da5..e23da110bcb 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -150,7 +150,7 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { break } - if err = n.Stop(); err != nil { + if err = n.Stop(); err == nil { delete(nodes, sid) // Unset gnoland environements. From ebb9fc2e33e2d4c3e59175d7fca3746ca7124141 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 19:01:34 +0200 Subject: [PATCH 31/45] feat: add UPDATE_SCRIPTS to test and sync gnoland integration Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/Makefile | 11 +++-- .../cmd/gnoland/testdata/basic_commands.txtar | 22 ---------- gno.land/pkg/integration/gnoland.go | 4 +- gno.land/pkg/integration/integration_test.go | 11 +++++ .../pkg/integration/testdata/gnokey.txtar | 32 ++++++++++++++ .../pkg/integration/testdata/gnoland.txtar | 43 +++++++++++++++++++ .../pkg/integration/testing_integration.go | 10 +++-- 7 files changed, 101 insertions(+), 32 deletions(-) delete mode 100644 gno.land/cmd/gnoland/testdata/basic_commands.txtar create mode 100644 gno.land/pkg/integration/integration_test.go create mode 100644 gno.land/pkg/integration/testdata/gnokey.txtar create mode 100644 gno.land/pkg/integration/testdata/gnoland.txtar diff --git a/gno.land/Makefile b/gno.land/Makefile index 117de18fc46..e794bb58174 100644 --- a/gno.land/Makefile +++ b/gno.land/Makefile @@ -47,7 +47,10 @@ test: _test.gnoland _test.gnoweb _test.gnokey _test.pkgs GOTEST_FLAGS ?= -v -p 1 -timeout=30m -_test.gnoland:; go test $(GOTEST_FLAGS) ./cmd/gnoland -_test.gnoweb:; go test $(GOTEST_FLAGS) ./cmd/gnoweb -_test.gnokey:; go test $(GOTEST_FLAGS) ./cmd/gnokey -_test.pkgs:; go test $(GOTEST_FLAGS) ./pkg/... +_test.gnoland:; go test $(GOTEST_FLAGS) ./cmd/gnoland +_test.gnoweb:; go test $(GOTEST_FLAGS) ./cmd/gnoweb +_test.gnokey:; go test $(GOTEST_FLAGS) ./cmd/gnokey +_test.pkgs:; go test $(GOTEST_FLAGS) ./pkg/... +_test.pkgs.sync:; UPDATE_SCRIPTS=true go test $(GOTEST_FLAGS) ./pkg/... + + diff --git a/gno.land/cmd/gnoland/testdata/basic_commands.txtar b/gno.land/cmd/gnoland/testdata/basic_commands.txtar deleted file mode 100644 index ee97d417d80..00000000000 --- a/gno.land/cmd/gnoland/testdata/basic_commands.txtar +++ /dev/null @@ -1,22 +0,0 @@ -# test basic gnoland integrations commands - -## no arguments should fail -! gnoland - -## should be able to start -gnoland start - -## should not be able to start a node twice -! gnoland start - -## test1 account should be available on default -gnokey query auth/accounts/${USER_ADDR_test1} - -## invalid gnokey command should raise an error -! gnokey query foo/bar - -## should be able to stop default -gnoland stop - -## should not be able to stop a node twice -! gnoland stop diff --git a/gno.land/pkg/integration/gnoland.go b/gno.land/pkg/integration/gnoland.go index c963a12aba4..42db9a2b508 100644 --- a/gno.land/pkg/integration/gnoland.go +++ b/gno.land/pkg/integration/gnoland.go @@ -263,9 +263,9 @@ func createAppAndNode(cfg *config.Config, logger log.Logger, gnoRootDir string, func tsValidateError(ts *testscript.TestScript, cmd string, neg bool, err error) { if err != nil { - ts.Logf("%s error: %v\n", cmd, err) + fmt.Fprintf(ts.Stderr(), "%q error: %v\n", cmd, err) if !neg { - ts.Fatalf("unexpected %s command failure", cmd) + ts.Fatalf("unexpected %q command failure: %s", cmd, err) } } else { if neg { diff --git a/gno.land/pkg/integration/integration_test.go b/gno.land/pkg/integration/integration_test.go new file mode 100644 index 00000000000..3c22a190d64 --- /dev/null +++ b/gno.land/pkg/integration/integration_test.go @@ -0,0 +1,11 @@ +package integration + +import ( + "testing" + + "github.com/rogpeppe/go-internal/testscript" +) + +func TestTestdata(t *testing.T) { + testscript.Run(t, SetupGnolandTestScript(t, "testdata")) +} diff --git a/gno.land/pkg/integration/testdata/gnokey.txtar b/gno.land/pkg/integration/testdata/gnokey.txtar new file mode 100644 index 00000000000..e4d2c93c0c9 --- /dev/null +++ b/gno.land/pkg/integration/testdata/gnokey.txtar @@ -0,0 +1,32 @@ +# test basic gnokey integrations commands +# golden files have been generated using UPDATE_SCRIPTS=true + +# start gnoland +gnoland start + +## test1 account should be available on default +gnokey query auth/accounts/${USER_ADDR_test1} +cmp stdout gnokey-query-valid.stdout.golden +cmp stderr gnokey-query-valid.stderr.golden + +## invalid gnokey command should raise an error +! gnokey query foo/bar +cmp stdout gnokey-query-invalid.stdout.golden +cmp stderr gnokey-query-invalid.stderr.golden + +-- gnokey-query-valid.stdout.golden -- +height: 0 +data: { + "BaseAccount": { + "address": "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", + "coins": "9999892000000ugnot", + "public_key": null, + "account_number": "0", + "sequence": "0" + } +} +-- gnokey-query-valid.stderr.golden -- +-- gnokey-query-invalid.stdout.golden -- +Log: +-- gnokey-query-invalid.stderr.golden -- +"gnokey" error: unknown request error diff --git a/gno.land/pkg/integration/testdata/gnoland.txtar b/gno.land/pkg/integration/testdata/gnoland.txtar new file mode 100644 index 00000000000..d7363920e38 --- /dev/null +++ b/gno.land/pkg/integration/testdata/gnoland.txtar @@ -0,0 +1,43 @@ +# test basic gnoland integrations commands +# golden files have been generated using UPDATE_SCRIPTS=true + +## no arguments should fail +! gnoland +cmp stdout gnoland-no-arguments.stdout.golden +cmp stderr gnoland-no-arguments.stderr.golden + +## should be able to start +gnoland start +cmp stdout gnoland-start.stdout.golden +cmp stderr gnoland-start.stderr.golden + +## should not be able to start a node twice +! gnoland start +cmp stdout gnoland-already-start.stdout.golden +cmp stderr gnoland-already-start.stderr.golden + +## should be able to stop default +gnoland stop +cmp stdout gnoland-stop.stdout.golden +cmp stderr gnoland-stop.stderr.golden + +## should not be able to stop a node twice +! gnoland stop +cmp stdout gnoland-already-stop.stdout.golden +cmp stderr gnoland-already-stop.stderr.golden + +-- gnoland-no-arguments.stdout.golden -- +-- gnoland-no-arguments.stderr.golden -- +"gnoland" error: syntax: gnoland [start|stop] +-- gnoland-start.stdout.golden -- +node started successfully +-- gnoland-start.stderr.golden -- +-- gnoland-already-start.stdout.golden -- +-- gnoland-already-start.stderr.golden -- +"gnoland start" error: node already started +-- gnoland-stop.stdout.golden -- +node stoped successfully +-- gnoland-stop.stderr.golden -- +-- gnoland-already-stop.stdout.golden -- +-- gnoland-already-stop.stderr.golden -- +"gnoland stop" error: node not started cannot be stop diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index e23da110bcb..43512bc7025 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -61,10 +61,10 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { var muNodes sync.Mutex nodes := map[string]*testNode{} - updatesCripts, _ := strconv.ParseBool(os.Getenv("UPDATE_SCRIPTS")) + updateScripts, _ := strconv.ParseBool(os.Getenv("UPDATE_SCRIPTS")) persistWorkDir, _ := strconv.ParseBool(os.Getenv("TESTWORK")) return testscript.Params{ - UpdateScripts: updatesCripts, + UpdateScripts: updateScripts, TestWork: persistWorkDir, Dir: txtarDir, Setup: func(env *testscript.Env) error { @@ -102,7 +102,7 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { switch cmd { case "start": if _, ok := nodes[sid]; ok { - err = fmt.Errorf("node %q already started", sid) + err = fmt.Errorf("node already started") break } @@ -142,11 +142,12 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { // availability. For now, if this sleep duration is too short, the // subsequent command might fail with an [internal error]. time.Sleep(time.Second * 2) + fmt.Fprintln(ts.Stdout(), "node started successfully") } case "stop": n, ok := nodes[sid] if !ok { - err = fmt.Errorf("node %q not started cannot be stop", sid) + err = fmt.Errorf("node not started cannot be stop") break } @@ -156,6 +157,7 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { // Unset gnoland environements. ts.Setenv("RPC_ADDR", "") ts.Setenv("GNODATA", "") + fmt.Fprintln(ts.Stdout(), "node stoped successfully") } default: err = fmt.Errorf("invalid gnoland subcommand: %q", cmd) From 358fbe2802cf7a5857378f0ddce4454c7c850d7f Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 19:05:28 +0200 Subject: [PATCH 32/45] chore: rename `CustomAppConfig` to `AppOptions` and `NewCustomApp` to `NewAppWithOptions` Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/gnoland/app.go | 12 ++++++------ gno.land/pkg/integration/gnoland.go | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index cc2e999c275..d89eb3da1d3 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -22,7 +22,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/store/iavl" ) -type CustomAppConfig struct { +type AppOptions struct { DB dbm.DB // `gnoRootDir` should point to the local location of the gno repository. // It serves as the gno equivalent of GOROOT. @@ -32,7 +32,7 @@ type CustomAppConfig struct { MaxCycles int64 } -func (c *CustomAppConfig) ApplyDefault() { +func (c *AppOptions) ApplyDefault() { if c.Logger == nil { c.Logger = log.NewNopLogger() } @@ -46,7 +46,7 @@ func (c *CustomAppConfig) ApplyDefault() { } } -func (c *CustomAppConfig) validate() error { +func (c *AppOptions) validate() error { if c.Logger == nil { return fmt.Errorf("no logger provided") } @@ -59,7 +59,7 @@ func (c *CustomAppConfig) validate() error { } // NewApp creates the GnoLand application. -func NewCustomApp(cfg CustomAppConfig) (abci.Application, error) { +func NewAppWithOptions(cfg AppOptions) (abci.Application, error) { if err := cfg.validate(); err != nil { return nil, err } @@ -128,7 +128,7 @@ func NewCustomApp(cfg CustomAppConfig) (abci.Application, error) { func NewApp(dataRootDir string, skipFailingGenesisTxs bool, logger log.Logger, maxCycles int64) (abci.Application, error) { var err error - var cfg CustomAppConfig + var cfg AppOptions cfg.ApplyDefault() // Get main DB. @@ -139,7 +139,7 @@ func NewApp(dataRootDir string, skipFailingGenesisTxs bool, logger log.Logger, m cfg.Logger = logger - return NewCustomApp(cfg) + return NewAppWithOptions(cfg) } // InitChainer returns a function that can initialize the chain with genesis. diff --git a/gno.land/pkg/integration/gnoland.go b/gno.land/pkg/integration/gnoland.go index 42db9a2b508..ac7edf6fe53 100644 --- a/gno.land/pkg/integration/gnoland.go +++ b/gno.land/pkg/integration/gnoland.go @@ -241,7 +241,7 @@ func setupTestingGenesis(gnoDataDir string, cfg *config.Config, icfg *Integratio } func createAppAndNode(cfg *config.Config, logger log.Logger, gnoRootDir string, icfg *IntegrationConfig) (*node.Node, error) { - gnoApp, err := gnoland.NewCustomApp(gnoland.CustomAppConfig{ + gnoApp, err := gnoland.NewAppWithOptions(gnoland.AppOptions{ Logger: logger, GnoRootDir: gnoRootDir, SkipFailingGenesisTxs: icfg.SkipFailingGenesisTxs, From aa253b3de937b8a0870b0abe9f3ced0452e702c1 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 19:39:01 +0200 Subject: [PATCH 33/45] chore: improve package doc.go Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/integration/doc.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/gno.land/pkg/integration/doc.go b/gno.land/pkg/integration/doc.go index f8442c74f74..43d855ea037 100644 --- a/gno.land/pkg/integration/doc.go +++ b/gno.land/pkg/integration/doc.go @@ -3,6 +3,7 @@ // // The primary utility, SetupGnolandTestScript, sets up the environment for running txtar // tests, introducing additional commands like "gnoland" and "gnokey" into the test script ecosystem. +// This will let you launch an in-memory gnoland node and interact with it with the `gnokey` command // // Additional Command Overview: // @@ -23,6 +24,16 @@ // to persist logs in the txtar working directory. In any case, the log file should be printed // on start if one of these environment variables is set. // +// Accounts: +// +// Initially, only the test1 user will be created in the default keybase directory, +// with no password set. The default gnoland genesis balance file and the genesis +// transaction file are also registered by default. +// +// Examples: +// +// Examples can be found in the testdata directory of this package. +// // Environment Variables: // // Input: From fbdf88840a41c2c1dd4d0a4f0491f53331189836 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 20:34:00 +0200 Subject: [PATCH 34/45] fix: check for first block, instead of sleepin Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/integration/testing_integration.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 43512bc7025..9ed6cf178da 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -138,10 +138,16 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { ts.Setenv("RPC_ADDR", laddr) ts.Setenv("GNODATA", gnoDataDir) - // XXX: Use something similar to `require.Eventually` to check for node - // availability. For now, if this sleep duration is too short, the - // subsequent command might fail with an [internal error]. - time.Sleep(time.Second * 2) + // Wait for first block by pulling height. + timeout := time.After(time.Second * 5) + for node.BlockStore().Height() == 0 { + select { + case <-time.After(time.Millisecond * 50): // poll interval + case <-timeout: + ts.Fatalf("unable to start node: timeout") + } + } + fmt.Fprintln(ts.Stdout(), "node started successfully") } case "stop": From 9f3f52f07d676b2c7922698c5053a9e95372e6da Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 20:35:10 +0200 Subject: [PATCH 35/45] REMOVE_ME: ci test with count 50 Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- .github/workflows/gnoland.yml | 3 ++- gno.land/Makefile | 1 + gno.land/pkg/integration/doc.go | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gnoland.yml b/.github/workflows/gnoland.yml index 2b38f254a13..3042ca00a5b 100644 --- a/.github/workflows/gnoland.yml +++ b/.github/workflows/gnoland.yml @@ -60,6 +60,7 @@ jobs: - _test.gnoland - _test.gnokey - _test.pkgs + - _test.wip.integration #- _test.gnoweb # this test should be rewritten to run an inmemory localnode runs-on: ubuntu-latest timeout-minutes: 15 @@ -82,7 +83,7 @@ jobs: name: logs-test-gnoland-go${{ matrix.goversion }} path: ${{ runner.temp }}/logs/**/*.log - uses: actions/upload-artifact@v3 - if: ${{ runner.os == 'Linux' && matrix.goversion == '1.21.x' }} + if: ${{ runner.os == 'Linux' && matrix.goversion == '1.21.x' && matrix.args != '_test.wip.integration' }} with: name: ${{runner.os}}-coverage-gnoland-${{ matrix.args}}-${{matrix.goversion}} path: ./gno.land/coverage.out diff --git a/gno.land/Makefile b/gno.land/Makefile index e794bb58174..b90372a9d4a 100644 --- a/gno.land/Makefile +++ b/gno.land/Makefile @@ -53,4 +53,5 @@ _test.gnokey:; go test $(GOTEST_FLAGS) ./cmd/gnokey _test.pkgs:; go test $(GOTEST_FLAGS) ./pkg/... _test.pkgs.sync:; UPDATE_SCRIPTS=true go test $(GOTEST_FLAGS) ./pkg/... +_test.wip.integration:; go test -count=50 $(GOTEST_FLAGS) ./pkg/integration/... diff --git a/gno.land/pkg/integration/doc.go b/gno.land/pkg/integration/doc.go index 43d855ea037..ec5c41c8815 100644 --- a/gno.land/pkg/integration/doc.go +++ b/gno.land/pkg/integration/doc.go @@ -26,7 +26,7 @@ // // Accounts: // -// Initially, only the test1 user will be created in the default keybase directory, +// Only the test1 user will be created in the default keybase directory, // with no password set. The default gnoland genesis balance file and the genesis // transaction file are also registered by default. // From d3624dd075c0cb052486df4df8ba21cb5f581098 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 21:00:56 +0200 Subject: [PATCH 36/45] fix: use event system for beter node start handling Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- .../pkg/integration/testing_integration.go | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 9ed6cf178da..cd581487412 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -14,9 +14,11 @@ import ( "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/tm2/pkg/bft/node" + "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys" "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" + "github.com/gnolang/gno/tm2/pkg/events" "github.com/gnolang/gno/tm2/pkg/log" "github.com/rogpeppe/go-internal/testscript" ) @@ -138,16 +140,29 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { ts.Setenv("RPC_ADDR", laddr) ts.Setenv("GNODATA", gnoDataDir) - // Wait for first block by pulling height. - timeout := time.After(time.Second * 5) - for node.BlockStore().Height() == 0 { + const listenerID = "testing_listener" + + // Wait for first block by waiting for `EventNewBlock` event. + nb := make(chan struct{}, 1) + node.EventSwitch().AddListener(listenerID, func(ev events.Event) { + if _, ok := ev.(types.EventNewBlock); ok { + select { + case nb <- struct{}{}: + default: + } + } + }) + + if node.BlockStore().Height() == 0 { select { - case <-time.After(time.Millisecond * 50): // poll interval - case <-timeout: - ts.Fatalf("unable to start node: timeout") + case <-nb: // ok + case <-time.After(time.Second * 6): + ts.Fatalf("timeout while wait for the node to start") } } + node.EventSwitch().RemoveListener(listenerID) + fmt.Fprintln(ts.Stdout(), "node started successfully") } case "stop": From 24369120444e34cb59dc0fcee2798c58ac606757 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 21:13:49 +0200 Subject: [PATCH 37/45] Revert "REMOVE_ME: ci test with count 50" This reverts commit 9f3f52f07d676b2c7922698c5053a9e95372e6da. Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- .github/workflows/gnoland.yml | 3 +-- gno.land/Makefile | 1 - gno.land/pkg/integration/doc.go | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gnoland.yml b/.github/workflows/gnoland.yml index 3042ca00a5b..2b38f254a13 100644 --- a/.github/workflows/gnoland.yml +++ b/.github/workflows/gnoland.yml @@ -60,7 +60,6 @@ jobs: - _test.gnoland - _test.gnokey - _test.pkgs - - _test.wip.integration #- _test.gnoweb # this test should be rewritten to run an inmemory localnode runs-on: ubuntu-latest timeout-minutes: 15 @@ -83,7 +82,7 @@ jobs: name: logs-test-gnoland-go${{ matrix.goversion }} path: ${{ runner.temp }}/logs/**/*.log - uses: actions/upload-artifact@v3 - if: ${{ runner.os == 'Linux' && matrix.goversion == '1.21.x' && matrix.args != '_test.wip.integration' }} + if: ${{ runner.os == 'Linux' && matrix.goversion == '1.21.x' }} with: name: ${{runner.os}}-coverage-gnoland-${{ matrix.args}}-${{matrix.goversion}} path: ./gno.land/coverage.out diff --git a/gno.land/Makefile b/gno.land/Makefile index b90372a9d4a..e794bb58174 100644 --- a/gno.land/Makefile +++ b/gno.land/Makefile @@ -53,5 +53,4 @@ _test.gnokey:; go test $(GOTEST_FLAGS) ./cmd/gnokey _test.pkgs:; go test $(GOTEST_FLAGS) ./pkg/... _test.pkgs.sync:; UPDATE_SCRIPTS=true go test $(GOTEST_FLAGS) ./pkg/... -_test.wip.integration:; go test -count=50 $(GOTEST_FLAGS) ./pkg/integration/... diff --git a/gno.land/pkg/integration/doc.go b/gno.land/pkg/integration/doc.go index ec5c41c8815..43d855ea037 100644 --- a/gno.land/pkg/integration/doc.go +++ b/gno.land/pkg/integration/doc.go @@ -26,7 +26,7 @@ // // Accounts: // -// Only the test1 user will be created in the default keybase directory, +// Initially, only the test1 user will be created in the default keybase directory, // with no password set. The default gnoland genesis balance file and the genesis // transaction file are also registered by default. // From d66a4f6ccd5b7e013dd18a51fddc034df7dff3e7 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 21:14:38 +0200 Subject: [PATCH 38/45] chore: lint doc Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/integration/doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gno.land/pkg/integration/doc.go b/gno.land/pkg/integration/doc.go index 43d855ea037..ec5c41c8815 100644 --- a/gno.land/pkg/integration/doc.go +++ b/gno.land/pkg/integration/doc.go @@ -26,7 +26,7 @@ // // Accounts: // -// Initially, only the test1 user will be created in the default keybase directory, +// Only the test1 user will be created in the default keybase directory, // with no password set. The default gnoland genesis balance file and the genesis // transaction file are also registered by default. // From c3ddec58021ca6889addc563677f1b9c00a926f2 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 21:45:30 +0200 Subject: [PATCH 39/45] chore: add comment on `why a mutex will you say?` Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/integration/testing_integration.go | 1 + 1 file changed, 1 insertion(+) diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index cd581487412..3448cc4f6d7 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -60,6 +60,7 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { // stores its configuration and data. gnoDataDir := filepath.Join(tmpdir, "data") + // Testscripts run concurrently by default, so we need to be prepared for that. var muNodes sync.Mutex nodes := map[string]*testNode{} From f077906f97bf777c40f221c2d30d9cf3fe7abaf7 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 22:10:11 +0200 Subject: [PATCH 40/45] chore: update doc.go Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/integration/doc.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/gno.land/pkg/integration/doc.go b/gno.land/pkg/integration/doc.go index ec5c41c8815..a8b40f9c321 100644 --- a/gno.land/pkg/integration/doc.go +++ b/gno.land/pkg/integration/doc.go @@ -1,9 +1,10 @@ // Package integration offers utilities to run txtar-based tests against the gnoland system -// by extending the functionalities provided by the standard testscript package. +// by extending the functionalities provided by the standard testscript package. This package is +// currently in an experimental phase and may undergo significant changes in the future. // -// The primary utility, SetupGnolandTestScript, sets up the environment for running txtar -// tests, introducing additional commands like "gnoland" and "gnokey" into the test script ecosystem. -// This will let you launch an in-memory gnoland node and interact with it with the `gnokey` command +// SetupGnolandTestScript, sets up the environment for running txtar tests, introducing additional +// commands like "gnoland" and "gnokey" into the test script ecosystem. Specifically, it allows the +// user to initiate an in-memory gnoland node and interact with it via the `gnokey` command. // // Additional Command Overview: // @@ -18,7 +19,7 @@ // // Logging: // -// The gnoland logs aren't forwarded to stdout to avoid overwhelming the tests with too much +// Gnoland logs aren't forwarded to stdout to avoid overwhelming the tests with too much // information. Instead, a log directory can be specified with `LOG_DIR`, or you // can set `TESTWORK=true` // to persist logs in the txtar working directory. In any case, the log file should be printed @@ -26,13 +27,13 @@ // // Accounts: // -// Only the test1 user will be created in the default keybase directory, +// By default, only the test1 user will be created in the default keybase directory, // with no password set. The default gnoland genesis balance file and the genesis // transaction file are also registered by default. // // Examples: // -// Examples can be found in the testdata directory of this package. +// Examples can be found in the `testdata` directory of this package. // // Environment Variables: // From b8bd6b57223bee0ef4ae8c5b1efd8d00488b82f5 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 23:07:02 +0200 Subject: [PATCH 41/45] chore: add testing guide Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- docs/testing_guide.md | 86 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 docs/testing_guide.md diff --git a/docs/testing_guide.md b/docs/testing_guide.md new file mode 100644 index 00000000000..7b60c1c86bc --- /dev/null +++ b/docs/testing_guide.md @@ -0,0 +1,86 @@ +# Gnoland Testing Guide + +This guide provides an overview of our testing practices and conventions. While most of our testing aligns with typical Go practices, there are exceptions and specifics you should be aware of. + +## Standard Package Testing + +For most packages, tests are written and executed in the standard Go manner: + +- Tests are located alongside the code they test. +- The `go test` command can be used to execute tests. + +However, as mentioned earlier, there are some exceptions. In the following sections, we will explore our specialized tests and how to work with them. + +## Gno Filetests + +**Location:** `gnovm/test/files` + +These are our custom file-based tests tailored specifically for this project. + +**Execution:** + +There are two main commands to run Gno filetests: + +1. To test native files, use: +``` +make _test.gnolang.native +``` + +2. To test standard libraries, use: +``` +make _test.gnolang.stdlibs +``` + +**Golden Files Update:** + +Golden files are references for expected outputs. Sometimes, after certain updates, these need to be synchronized. To do so: + +1. For native tests: +``` +make _test.gnolang.native.sync +``` + +2. For standard library tests: +``` +make _test.gnolang.stdlibs.sync +``` + +## Integration Tests + +**Location:** `gno.land/**/testdata` + +Integration tests are designed to ensure different parts of the project work cohesively. Specifically: + +1. **Real Node Integration Testing:** + Found in `gno.land/cmd/gnoland/testdata`, these are dedicated to running integration tests against a genuine `gnoland` node. + +2. **System Features Testing:** + Located in `gno.land/pkg/integration/testdata`, these tests target integrations specific commands. + +These integration tests utilize the `testscript` package and follow the `txtar` file specifications. + +**Documentation:** + +- For general `testscript` package documentation, refer to: [testscript documentation](https://github.com/rogpeppe/go-internal/blob/v1.11.0/testscript/doc.go) + +- For more specific details about our integration tests, consult our extended documentation: [gnoland integration documentation](https://github.com/gnolang/gno/blob/master/gno.land/pkg/integration/doc.go) + +**Execution:** + +To run the integration tests (alongside other packages): + +``` +make _test.pkgs +``` + +**Golden Files Update within txtar:** + +For tests utilizing the `cmp` command inside `txtar` files, golden files can be synchronized using: + +``` +make _test.pkgs.sync +``` + +--- + +As the project evolves, this guide might be updated. From 4b80e80d9347f11eaaa49364724b79014c065031 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 4 Oct 2023 23:17:07 +0200 Subject: [PATCH 42/45] chore: guide fix Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- docs/testing_guide.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/testing_guide.md b/docs/testing_guide.md index 7b60c1c86bc..6019f238f01 100644 --- a/docs/testing_guide.md +++ b/docs/testing_guide.md @@ -19,7 +19,7 @@ These are our custom file-based tests tailored specifically for this project. **Execution:** -There are two main commands to run Gno filetests: +From the gnovm directory, There are two main commands to run Gno filetests: 1. To test native files, use: ``` @@ -49,12 +49,12 @@ make _test.gnolang.stdlibs.sync **Location:** `gno.land/**/testdata` -Integration tests are designed to ensure different parts of the project work cohesively. Specifically: +From the gno.land directory, Integration tests are designed to ensure different parts of the project work cohesively. Specifically: -1. **Real Node Integration Testing:** +1. **InMemory Node Integration Testing:** Found in `gno.land/cmd/gnoland/testdata`, these are dedicated to running integration tests against a genuine `gnoland` node. -2. **System Features Testing:** +2. **Integration Features Testing:** Located in `gno.land/pkg/integration/testdata`, these tests target integrations specific commands. These integration tests utilize the `testscript` package and follow the `txtar` file specifications. From 977fe83fa5c60bb9f8d4b363d0964ab91689ea70 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Thu, 5 Oct 2023 01:44:00 +0200 Subject: [PATCH 43/45] chore: minor fixes Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/integration/gnoland.go | 140 +++++++++--------- .../pkg/integration/testing_integration.go | 6 +- 2 files changed, 74 insertions(+), 72 deletions(-) diff --git a/gno.land/pkg/integration/gnoland.go b/gno.land/pkg/integration/gnoland.go index ac7edf6fe53..c4fee341bfc 100644 --- a/gno.land/pkg/integration/gnoland.go +++ b/gno.land/pkg/integration/gnoland.go @@ -154,94 +154,96 @@ func setupTestingGenesis(gnoDataDir string, cfg *config.Config, icfg *Integratio priv := privval.LoadOrGenFilePV(newPrivValKey, newPrivValState) genesisFilePath := filepath.Join(gnoDataDir, cfg.Genesis) - osm.EnsureDir(filepath.Dir(genesisFilePath), 0o700) - if !osm.FileExists(genesisFilePath) { - genesisTxs := loadGenesisTxs(icfg.GenesisTxsFile, icfg.ChainID, icfg.GenesisRemote) - pvPub := priv.GetPubKey() - - gen := &bft.GenesisDoc{ - GenesisTime: time.Now(), - ChainID: icfg.ChainID, - ConsensusParams: abci.ConsensusParams{ - Block: &abci.BlockParams{ - // TODO: update limits. - MaxTxBytes: 1000000, // 1MB, - MaxDataBytes: 2000000, // 2MB, - MaxGas: 10000000, // 10M gas - TimeIotaMS: 100, // 100ms - }, + genesisDirPath := filepath.Dir(genesisFilePath) + if err := osm.EnsureDir(genesisDirPath, 0o700); err != nil { + return fmt.Errorf("unable to ensure directory %q: %w", genesisDirPath, err) + } + + genesisTxs := loadGenesisTxs(icfg.GenesisTxsFile, icfg.ChainID, icfg.GenesisRemote) + pvPub := priv.GetPubKey() + + gen := &bft.GenesisDoc{ + GenesisTime: time.Now(), + ChainID: icfg.ChainID, + ConsensusParams: abci.ConsensusParams{ + Block: &abci.BlockParams{ + // TODO: update limits. + MaxTxBytes: 1000000, // 1MB, + MaxDataBytes: 2000000, // 2MB, + MaxGas: 10000000, // 10M gas + TimeIotaMS: 100, // 100ms }, - Validators: []bft.GenesisValidator{ - { - Address: pvPub.Address(), - PubKey: pvPub, - Power: 10, - Name: "testvalidator", - }, + }, + Validators: []bft.GenesisValidator{ + { + Address: pvPub.Address(), + PubKey: pvPub, + Power: 10, + Name: "testvalidator", }, - } - - // Load distribution. - balances := loadGenesisBalances(icfg.GenesisBalancesFile) + }, + } - // Load initial packages from examples. - // XXX: We should be able to config this. - test1 := crypto.MustAddressFromString(test1Addr) - txs := []std.Tx{} + // Load distribution. + balances := loadGenesisBalances(icfg.GenesisBalancesFile) - // List initial packages to load from examples. - // println(filepath.Join(gnoRootDir, "examples")) - pkgs, err := gnomod.ListPkgs(filepath.Join(gnoRootDir, "examples")) - if err != nil { - return fmt.Errorf("listing gno packages: %w", err) - } + // Load initial packages from examples. + // XXX: We should be able to config this. + test1 := crypto.MustAddressFromString(test1Addr) + txs := []std.Tx{} - // Sort packages by dependencies. - sortedPkgs, err := pkgs.Sort() - if err != nil { - return fmt.Errorf("sorting packages: %w", err) - } + // List initial packages to load from examples. + // println(filepath.Join(gnoRootDir, "examples")) + pkgs, err := gnomod.ListPkgs(filepath.Join(gnoRootDir, "examples")) + if err != nil { + return fmt.Errorf("listing gno packages: %w", err) + } - // Filter out draft packages. - nonDraftPkgs := sortedPkgs.GetNonDraftPkgs() + // Sort packages by dependencies. + sortedPkgs, err := pkgs.Sort() + if err != nil { + return fmt.Errorf("sorting packages: %w", err) + } - for _, pkg := range nonDraftPkgs { - // Open files in directory as MemPackage. - memPkg := gno.ReadMemPackage(pkg.Dir, pkg.Name) + // Filter out draft packages. + nonDraftPkgs := sortedPkgs.GetNonDraftPkgs() - var tx std.Tx - tx.Msgs = []std.Msg{ - vmm.MsgAddPackage{ - Creator: test1, - Package: memPkg, - Deposit: nil, - }, - } + for _, pkg := range nonDraftPkgs { + // Open files in directory as MemPackage. + memPkg := gno.ReadMemPackage(pkg.Dir, pkg.Name) - // XXX: Add fee flag ? - // Or maybe reduce fee to the minimum ? - tx.Fee = std.NewFee(50000, std.MustParseCoin("1000000ugnot")) - tx.Signatures = make([]std.Signature, len(tx.GetSigners())) - txs = append(txs, tx) + var tx std.Tx + tx.Msgs = []std.Msg{ + vmm.MsgAddPackage{ + Creator: test1, + Package: memPkg, + Deposit: nil, + }, } - // Load genesis txs from file. - txs = append(txs, genesisTxs...) + // XXX: Add fee flag ? + // Or maybe reduce fee to the minimum ? + tx.Fee = std.NewFee(50000, std.MustParseCoin("1000000ugnot")) + tx.Signatures = make([]std.Signature, len(tx.GetSigners())) + txs = append(txs, tx) + } - // Construct genesis AppState. - gen.AppState = gnoland.GnoGenesisState{ - Balances: balances, - Txs: txs, - } + // Load genesis txs from file. + txs = append(txs, genesisTxs...) - writeGenesisFile(gen, genesisFilePath) + // Construct genesis AppState. + gen.AppState = gnoland.GnoGenesisState{ + Balances: balances, + Txs: txs, } + writeGenesisFile(gen, genesisFilePath) + return nil } func createAppAndNode(cfg *config.Config, logger log.Logger, gnoRootDir string, icfg *IntegrationConfig) (*node.Node, error) { - gnoApp, err := gnoland.NewAppWithOptions(gnoland.AppOptions{ + gnoApp, err := gnoland.NewAppWithOptions(&gnoland.AppOptions{ Logger: logger, GnoRootDir: gnoRootDir, SkipFailingGenesisTxs: icfg.SkipFailingGenesisTxs, diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 3448cc4f6d7..f0a696ddd85 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -158,7 +158,7 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { select { case <-nb: // ok case <-time.After(time.Second * 6): - ts.Fatalf("timeout while wait for the node to start") + ts.Fatalf("timeout while waiting for the node to start") } } @@ -169,7 +169,7 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { case "stop": n, ok := nodes[sid] if !ok { - err = fmt.Errorf("node not started cannot be stop") + err = fmt.Errorf("node not started cannot be stopped") break } @@ -179,7 +179,7 @@ func SetupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { // Unset gnoland environements. ts.Setenv("RPC_ADDR", "") ts.Setenv("GNODATA", "") - fmt.Fprintln(ts.Stdout(), "node stoped successfully") + fmt.Fprintln(ts.Stdout(), "node stopped successfully") } default: err = fmt.Errorf("invalid gnoland subcommand: %q", cmd) From c87a7a65e9aab45e07e33c5ba2ede857a99900a8 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Thu, 5 Oct 2023 01:44:11 +0200 Subject: [PATCH 44/45] chore: use NewOptions instead of AppyDefault Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/gnoland/app.go | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index d89eb3da1d3..3585f99d7de 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -32,17 +32,11 @@ type AppOptions struct { MaxCycles int64 } -func (c *AppOptions) ApplyDefault() { - if c.Logger == nil { - c.Logger = log.NewNopLogger() - } - - if c.DB == nil { - c.DB = dbm.NewMemDB() - } - - if c.GnoRootDir == "" { - c.GnoRootDir = GuessGnoRootDir() +func NewAppOptions() *AppOptions { + return &AppOptions{ + Logger: log.NewNopLogger(), + DB: dbm.NewMemDB(), + GnoRootDir: GuessGnoRootDir(), } } @@ -59,7 +53,7 @@ func (c *AppOptions) validate() error { } // NewApp creates the GnoLand application. -func NewAppWithOptions(cfg AppOptions) (abci.Application, error) { +func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { if err := cfg.validate(); err != nil { return nil, err } @@ -128,8 +122,7 @@ func NewAppWithOptions(cfg AppOptions) (abci.Application, error) { func NewApp(dataRootDir string, skipFailingGenesisTxs bool, logger log.Logger, maxCycles int64) (abci.Application, error) { var err error - var cfg AppOptions - cfg.ApplyDefault() + cfg := NewAppOptions() // Get main DB. cfg.DB, err = dbm.NewDB("gnolang", dbm.GoLevelDBBackend, filepath.Join(dataRootDir, "data")) From 57497cf4fafda2d261069581e228dd0d2edc70e1 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Thu, 5 Oct 2023 01:49:57 +0200 Subject: [PATCH 45/45] chore: sync golden files Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/pkg/integration/testdata/gnoland.txtar | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gno.land/pkg/integration/testdata/gnoland.txtar b/gno.land/pkg/integration/testdata/gnoland.txtar index d7363920e38..c675e7578b6 100644 --- a/gno.land/pkg/integration/testdata/gnoland.txtar +++ b/gno.land/pkg/integration/testdata/gnoland.txtar @@ -36,8 +36,8 @@ node started successfully -- gnoland-already-start.stderr.golden -- "gnoland start" error: node already started -- gnoland-stop.stdout.golden -- -node stoped successfully +node stopped successfully -- gnoland-stop.stderr.golden -- -- gnoland-already-stop.stdout.golden -- -- gnoland-already-stop.stderr.golden -- -"gnoland stop" error: node not started cannot be stop +"gnoland stop" error: node not started cannot be stopped