From 468ca57f0a466af0dc74b7462ade921a76da4abe Mon Sep 17 00:00:00 2001 From: deelawn Date: Fri, 15 Dec 2023 10:29:24 +0100 Subject: [PATCH 01/12] added sleep option --- gno.land/pkg/integration/testing_integration.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 0d19237eab3..61f2fbb01d0 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -9,6 +9,7 @@ import ( "strconv" "strings" "testing" + "time" "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/tm2/pkg/bft/node" @@ -211,6 +212,17 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { tsValidateError(ts, "gnokey", neg, err) }, + "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) + }, }, } } From 0743157771d6ee514fe2d2180d9777e3a563072d Mon Sep 17 00:00:00 2001 From: deelawn Date: Sun, 17 Dec 2023 10:11:40 +0100 Subject: [PATCH 02/12] adduser wip --- .../pkg/integration/testing_integration.go | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 61f2fbb01d0..7d201ccd8d3 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -14,6 +14,7 @@ import ( "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" @@ -223,6 +224,35 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { time.Sleep(d) }, + "adduser": func(ts *testscript.TestScript, _ bool, args []string) { + if len(args) == 0 { + ts.Fatalf("new user name required") + } + + entropy, err := bip39.NewEntropy(256) + if err != nil { + ts.Fatalf("error creating entropy: %v", err) + } + + mnemonic, err := bip39.NewMnemonic(entropy) + if err != nil { + ts.Fatalf("error generating mnemonic: %v", err) + } + + accountName := args[0] + kb, err := keys.NewKeyBaseFromDir(gnoHomeDir) + if err != nil { + ts.Fatalf("unable to fetch keybase: %v", err) + } + + var keyInfo keys.Info + if keyInfo, err = kb.CreateAccount(accountName, mnemonic, "", "", 0, 0); err != nil { + ts.Fatalf("unable to create account: %v", err) + } + + ts.Setenv("USER_SEED_"+accountName, mnemonic) + ts.Setenv("USER_ADDR_"+accountName, keyInfo.GetAddress().String()) + }, }, } } From a3793fe65231946871129c86d56694b33481dcae Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 19 Dec 2023 15:12:10 -0800 Subject: [PATCH 03/12] finished adduser implementation --- .../pkg/integration/testing_integration.go | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 7d201ccd8d3..e76b0304be3 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "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" @@ -18,6 +19,7 @@ import ( "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" ) @@ -73,6 +75,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{ @@ -128,7 +134,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:] @@ -136,7 +142,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 } @@ -146,6 +152,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 @@ -224,7 +240,12 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { time.Sleep(d) }, + // adduser commands must be executed before starting the node; it errors out otherwise. "adduser": func(ts *testscript.TestScript, _ bool, args []string) { + if nodeIsRunning(nodes, getNodeSID(ts)) { + ts.Fatalf("adduser must be used before starting node") + } + if len(args) == 0 { ts.Fatalf("new user name required") } @@ -250,13 +271,30 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { ts.Fatalf("unable to create account: %v", err) } + address := keyInfo.GetAddress() + newUserBalances = append( + newUserBalances, + gnoland.Balance{ + Address: address, + Amount: std.Coins{std.NewCoin("ugnot", 1000000000000000000)}, + }, + ) ts.Setenv("USER_SEED_"+accountName, mnemonic) - ts.Setenv("USER_ADDR_"+accountName, keyInfo.GetAddress().String()) + ts.Setenv("USER_ADDR_"+accountName, address.String()) }, }, } } +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 From 398a5752cd07fb529cf164ddebe25502eb9d1cba Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 19 Dec 2023 16:28:30 -0800 Subject: [PATCH 04/12] added tests --- .../cmd/gnoland/testdata/adduser-err.txtar | 5 ++++ gno.land/cmd/gnoland/testdata/adduser.txtar | 30 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 gno.land/cmd/gnoland/testdata/adduser-err.txtar create mode 100644 gno.land/cmd/gnoland/testdata/adduser.txtar diff --git a/gno.land/cmd/gnoland/testdata/adduser-err.txtar b/gno.land/cmd/gnoland/testdata/adduser-err.txtar new file mode 100644 index 00000000000..9a7f43f8df2 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/adduser-err.txtar @@ -0,0 +1,5 @@ +gnoland start + +# should fail if user is added after node is started +! adduser test5 +stderr '"adduser" error: adduser must be used before starting node' \ No newline at end of file diff --git a/gno.land/cmd/gnoland/testdata/adduser.txtar b/gno.land/cmd/gnoland/testdata/adduser.txtar new file mode 100644 index 00000000000..9ea44788199 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/adduser.txtar @@ -0,0 +1,30 @@ +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: ' + +-- 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(""), "---") +} From 038456e6a5574a07badae0f981af8d5ae76e8c0b Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 19 Dec 2023 16:32:52 -0800 Subject: [PATCH 05/12] updated txtar docs --- gno.land/pkg/integration/doc.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gno.land/pkg/integration/doc.go b/gno.land/pkg/integration/doc.go index a8b40f9c321..e365cfa8fab 100644 --- a/gno.land/pkg/integration/doc.go +++ b/gno.land/pkg/integration/doc.go @@ -17,6 +17,14 @@ // - `--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`. +// +// 4. `sleep`: +// - Sleeps for a specified duration string, parsed by `time.ParseDuration`. +// - Defaults to 1 second if no duration is specified. +// // Logging: // // Gnoland logs aren't forwarded to stdout to avoid overwhelming the tests with too much From 07c5de25a03bd1848a0943761dd985544293d448 Mon Sep 17 00:00:00 2001 From: deelawn Date: Tue, 19 Dec 2023 16:38:23 -0800 Subject: [PATCH 06/12] modify how adduser returns error --- 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 e76b0304be3..10be41cef1f 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" @@ -241,9 +242,10 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { time.Sleep(d) }, // adduser commands must be executed before starting the node; it errors out otherwise. - "adduser": func(ts *testscript.TestScript, _ bool, args []string) { + "adduser": func(ts *testscript.TestScript, neg bool, args []string) { if nodeIsRunning(nodes, getNodeSID(ts)) { - ts.Fatalf("adduser must be used before starting node") + tsValidateError(ts, "adduser", neg, errors.New("adduser must be used before starting node")) + return } if len(args) == 0 { From b2cd8a4280dd690d28fa247d77673349b279d3e9 Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 20 Dec 2023 15:33:49 -0800 Subject: [PATCH 07/12] remove sleep command --- gno.land/pkg/integration/doc.go | 4 ---- gno.land/pkg/integration/testing_integration.go | 12 ------------ 2 files changed, 16 deletions(-) diff --git a/gno.land/pkg/integration/doc.go b/gno.land/pkg/integration/doc.go index e365cfa8fab..e635b3e8c79 100644 --- a/gno.land/pkg/integration/doc.go +++ b/gno.land/pkg/integration/doc.go @@ -21,10 +21,6 @@ // - Creates a new user in the default keybase directory // - Must be run before `gnoland start`. // -// 4. `sleep`: -// - Sleeps for a specified duration string, parsed by `time.ParseDuration`. -// - Defaults to 1 second if no duration is specified. -// // Logging: // // Gnoland logs aren't forwarded to stdout to avoid overwhelming the tests with too much diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index 10be41cef1f..f452f7093b9 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -10,7 +10,6 @@ import ( "strconv" "strings" "testing" - "time" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gnovm/pkg/gnoenv" @@ -230,17 +229,6 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { tsValidateError(ts, "gnokey", neg, err) }, - "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) - }, // 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)) { From a707f797e13e612fa1a1e78354b459b25a7d21a8 Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 20 Dec 2023 16:02:54 -0800 Subject: [PATCH 08/12] create n testing accounts by default --- .../pkg/integration/testing_integration.go | 77 ++++++++++++------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index f452f7093b9..d8ab534ad9a 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -23,6 +23,8 @@ import ( "github.com/rogpeppe/go-internal/testscript" ) +const numTestAccounts int = 4 + type tSeqShim struct{ *testing.T } // noop Parallel method allow us to run test sequentially @@ -115,11 +117,17 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { env.Values["_logger"] = logger } - // Setup "test1" default account - kb.CreateAccount(DefaultAccount_Name, DefaultAccount_Seed, "", "", 0, 0) + // Create test accounts. + for i := 0; i < numTestAccounts; i++ { + accountName := "test" + strconv.Itoa(i+1) - env.Setenv("USER_SEED_"+DefaultAccount_Name, DefaultAccount_Seed) - env.Setenv("USER_ADDR_"+DefaultAccount_Name, DefaultAccount_Address) + 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) @@ -240,37 +248,17 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { ts.Fatalf("new user name required") } - entropy, err := bip39.NewEntropy(256) - if err != nil { - ts.Fatalf("error creating entropy: %v", err) - } - - mnemonic, err := bip39.NewMnemonic(entropy) - if err != nil { - ts.Fatalf("error generating mnemonic: %v", err) - } - - accountName := args[0] kb, err := keys.NewKeyBaseFromDir(gnoHomeDir) if err != nil { - ts.Fatalf("unable to fetch keybase: %v", err) + ts.Fatalf("unable to get keybase") } - var keyInfo keys.Info - if keyInfo, err = kb.CreateAccount(accountName, mnemonic, "", "", 0, 0); err != nil { - ts.Fatalf("unable to create account: %v", err) + balance, err := createAccount(ts, kb, args[0]) + if err != nil { + ts.Fatalf("error creating account %s: %s", args[0], err) } - address := keyInfo.GetAddress() - newUserBalances = append( - newUserBalances, - gnoland.Balance{ - Address: address, - Amount: std.Coins{std.NewCoin("ugnot", 1000000000000000000)}, - }, - ) - ts.Setenv("USER_SEED_"+accountName, mnemonic) - ts.Setenv("USER_ADDR_"+accountName, address.String()) + newUserBalances = append(newUserBalances, balance) }, }, } @@ -343,3 +331,34 @@ 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) (balance gnoland.Balance, err error) { + 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 +} From 90e9511a0839bf3233547f75b2af5e6e1dffd1b9 Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 20 Dec 2023 16:15:23 -0800 Subject: [PATCH 09/12] exclude test1 from generic account creation --- gno.land/pkg/integration/testing_integration.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index d8ab534ad9a..a7f653ad482 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -117,8 +117,15 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params { env.Values["_logger"] = logger } - // Create test accounts. - for i := 0; i < numTestAccounts; i++ { + // 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) From 6820f2809fcc0a017639c5d2d15a1e8934f647a0 Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 21 Dec 2023 17:12:44 +0000 Subject: [PATCH 10/12] Update gno.land/pkg/integration/doc.go Co-authored-by: Hariom Verma --- 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 e635b3e8c79..fb0b0bdf7a0 100644 --- a/gno.land/pkg/integration/doc.go +++ b/gno.land/pkg/integration/doc.go @@ -18,7 +18,7 @@ // communicate with the gnoland node. // // 3. `adduser`: -// - Creates a new user in the default keybase directory +// - Creates a new user in the default keybase directory. // - Must be run before `gnoland start`. // // Logging: From 05ceef4cdf9ecf535ac2d661488cb5e7680dde8f Mon Sep 17 00:00:00 2001 From: deelawn Date: Thu, 21 Dec 2023 11:52:25 -0800 Subject: [PATCH 11/12] remove named return --- gno.land/pkg/integration/testing_integration.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gno.land/pkg/integration/testing_integration.go b/gno.land/pkg/integration/testing_integration.go index a7f653ad482..a56a0948c31 100644 --- a/gno.land/pkg/integration/testing_integration.go +++ b/gno.land/pkg/integration/testing_integration.go @@ -344,7 +344,8 @@ type envSetter interface { } // createAccount creates a new account with the given name and adds it to the keybase. -func createAccount(env envSetter, kb keys.Keybase, accountName string) (balance gnoland.Balance, err error) { +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) From 211c82c4177453e2d12861444ab56b36a0720d5d Mon Sep 17 00:00:00 2001 From: deelawn Date: Wed, 10 Jan 2024 13:01:09 -0800 Subject: [PATCH 12/12] consolidated and moved adduser tests --- gno.land/cmd/gnoland/testdata/adduser-err.txtar | 5 ----- .../{cmd/gnoland => pkg/integration}/testdata/adduser.txtar | 4 ++++ 2 files changed, 4 insertions(+), 5 deletions(-) delete mode 100644 gno.land/cmd/gnoland/testdata/adduser-err.txtar rename gno.land/{cmd/gnoland => pkg/integration}/testdata/adduser.txtar (84%) diff --git a/gno.land/cmd/gnoland/testdata/adduser-err.txtar b/gno.land/cmd/gnoland/testdata/adduser-err.txtar deleted file mode 100644 index 9a7f43f8df2..00000000000 --- a/gno.land/cmd/gnoland/testdata/adduser-err.txtar +++ /dev/null @@ -1,5 +0,0 @@ -gnoland start - -# should fail if user is added after node is started -! adduser test5 -stderr '"adduser" error: adduser must be used before starting node' \ No newline at end of file diff --git a/gno.land/cmd/gnoland/testdata/adduser.txtar b/gno.land/pkg/integration/testdata/adduser.txtar similarity index 84% rename from gno.land/cmd/gnoland/testdata/adduser.txtar rename to gno.land/pkg/integration/testdata/adduser.txtar index 9ea44788199..ebd0e4abb43 100644 --- a/gno.land/cmd/gnoland/testdata/adduser.txtar +++ b/gno.land/pkg/integration/testdata/adduser.txtar @@ -15,6 +15,10 @@ 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