diff --git a/gno.land/pkg/integration/doc.go b/gno.land/pkg/integration/doc.go index a8b40f9c321..fb0b0bdf7a0 100644 --- a/gno.land/pkg/integration/doc.go +++ b/gno.land/pkg/integration/doc.go @@ -17,6 +17,10 @@ // - `--remote`, `--insecure-password-stdin`, and `--home` flags are set automatically to // communicate with the gnoland node. // +// 3. `adduser`: +// - Creates a new user in the default keybase directory. +// - Must be run before `gnoland start`. +// // Logging: // // Gnoland logs aren't forwarded to stdout to avoid overwhelming the tests with too much diff --git a/gno.land/pkg/integration/testdata/adduser.txtar b/gno.land/pkg/integration/testdata/adduser.txtar new file mode 100644 index 00000000000..ebd0e4abb43 --- /dev/null +++ b/gno.land/pkg/integration/testdata/adduser.txtar @@ -0,0 +1,34 @@ +adduser test8 + +## 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/bar -pkgpath gno.land/r/foobar/bar -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test8 + +## execute Render +gnokey maketx run -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test8 $WORK/script/script.gno + +## compare render +stdout 'main: --- hello from foo ---' +stdout 'OK!' +stdout 'GAS WANTED: 200000' +stdout 'GAS USED: ' + +# should fail if user is added after node is started +! adduser test5 +stderr '"adduser" error: adduser must be used before starting node' + +-- bar/bar.gno -- +package bar + +func Render(path string) string { + return "hello from foo" +} + +-- script/script.gno -- +package main +import "gno.land/r/foobar/bar" +func main() { + println("main: ---", bar.Render(""), "---") +} diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 0d19237eab3..a56a0948c31 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -2,6 +2,7 @@ package integration import ( "context" + "errors" "fmt" "hash/crc32" "os" @@ -10,15 +11,20 @@ import ( "strings" "testing" + "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/tm2/pkg/bft/node" "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto/bip39" "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/gnolang/gno/tm2/pkg/std" "github.com/rogpeppe/go-internal/testscript" ) +const numTestAccounts int = 4 + type tSeqShim struct{ *testing.T } // noop Parallel method allow us to run test sequentially @@ -71,6 +77,10 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { // Testscripts run concurrently by default, so we need to be prepared for that. nodes := map[string]*testNode{} + // Track new user balances added via the `adduser` command. These are added to the genesis + // state when the node is started. + var newUserBalances []gnoland.Balance + updateScripts, _ := strconv.ParseBool(os.Getenv("UPDATE_SCRIPTS")) persistWorkDir, _ := strconv.ParseBool(os.Getenv("TESTWORK")) return testscript.Params{ @@ -107,12 +117,25 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { env.Values["_logger"] = logger } - // Setup "test1" default account + // test1 must be created outside of the loop below because it is already included in genesis so + // attempting to recreate results in it getting overwritten and breaking existing tests that + // rely on its address being static. kb.CreateAccount(DefaultAccount_Name, DefaultAccount_Seed, "", "", 0, 0) - env.Setenv("USER_SEED_"+DefaultAccount_Name, DefaultAccount_Seed) env.Setenv("USER_ADDR_"+DefaultAccount_Name, DefaultAccount_Address) + // Create test accounts starting from test2. + for i := 1; i < numTestAccounts; i++ { + accountName := "test" + strconv.Itoa(i+1) + + balance, err := createAccount(env, kb, accountName) + if err != nil { + return fmt.Errorf("error creating account %s: %w", accountName, err) + } + + newUserBalances = append(newUserBalances, balance) + } + env.Setenv("GNOROOT", gnoRootDir) env.Setenv("GNOHOME", gnoHomeDir) @@ -126,7 +149,7 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { } logger := ts.Value("_logger").(log.Logger) // grab logger - sid := ts.Getenv("SID") // grab session id + sid := getNodeSID(ts) // grab session id var cmd string cmd, args = args[0], args[1:] @@ -134,7 +157,7 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { var err error switch cmd { case "start": - if _, ok := nodes[sid]; ok { + if nodeIsRunning(nodes, sid) { err = fmt.Errorf("node already started") break } @@ -144,6 +167,16 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { // Generate config and node cfg, _ := TestingNodeConfig(t, gnoRootDir) + + // Add balances for users added via the `adduser` command. + genesis := cfg.Genesis + genesisState := gnoland.GnoGenesisState{ + Balances: genesis.AppState.(gnoland.GnoGenesisState).Balances, + Txs: genesis.AppState.(gnoland.GnoGenesisState).Txs, + } + genesisState.Balances = append(genesisState.Balances, newUserBalances...) + cfg.Genesis.AppState = genesisState + n, remoteAddr := TestingInMemoryNode(t, logger, cfg) // Register cleanup @@ -211,10 +244,42 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { tsValidateError(ts, "gnokey", neg, err) }, + // adduser commands must be executed before starting the node; it errors out otherwise. + "adduser": func(ts *testscript.TestScript, neg bool, args []string) { + if nodeIsRunning(nodes, getNodeSID(ts)) { + tsValidateError(ts, "adduser", neg, errors.New("adduser must be used before starting node")) + return + } + + if len(args) == 0 { + ts.Fatalf("new user name required") + } + + kb, err := keys.NewKeyBaseFromDir(gnoHomeDir) + if err != nil { + ts.Fatalf("unable to get keybase") + } + + balance, err := createAccount(ts, kb, args[0]) + if err != nil { + ts.Fatalf("error creating account %s: %s", args[0], err) + } + + newUserBalances = append(newUserBalances, balance) + }, }, } } +func getNodeSID(ts *testscript.TestScript) string { + return ts.Getenv("SID") +} + +func nodeIsRunning(nodes map[string]*testNode, sid string) bool { + _, ok := nodes[sid] + return ok +} + func getTestingLogger(env *testscript.Env, logname string) (log.Logger, error) { var path string @@ -273,3 +338,35 @@ func tsValidateError(ts *testscript.TestScript, cmd string, neg bool, err error) } } } + +type envSetter interface { + Setenv(key, value string) +} + +// createAccount creates a new account with the given name and adds it to the keybase. +func createAccount(env envSetter, kb keys.Keybase, accountName string) (gnoland.Balance, error) { + var balance gnoland.Balance + entropy, err := bip39.NewEntropy(256) + if err != nil { + return balance, fmt.Errorf("error creating entropy: %w", err) + } + + mnemonic, err := bip39.NewMnemonic(entropy) + if err != nil { + return balance, fmt.Errorf("error generating mnemonic: %w", err) + } + + var keyInfo keys.Info + if keyInfo, err = kb.CreateAccount(accountName, mnemonic, "", "", 0, 0); err != nil { + return balance, fmt.Errorf("unable to create account: %w", err) + } + + address := keyInfo.GetAddress() + env.Setenv("USER_SEED_"+accountName, mnemonic) + env.Setenv("USER_ADDR_"+accountName, address.String()) + + return gnoland.Balance{ + Address: address, + Amount: std.Coins{std.NewCoin("ugnot", 1000000000000000000)}, + }, nil +}