Skip to content

Commit

Permalink
feat: new txtar command adduser (gnolang#1471)
Browse files Browse the repository at this point in the history
<!-- please provide a detailed description of the changes made in this
pull request. -->
As I've started to think about creating txtar tests for contracts with
logic depending on contract calls from multiple users and durations
since previous contract calls, the addition of the `adduser` command
seems to be necessary.
<details><summary>Contributors' checklist...</summary>

- [x] Added new tests, or not needed, or not feasible
- [x] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [ ] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
</details>

---------

Co-authored-by: Hariom Verma <[email protected]>
  • Loading branch information
2 people authored and gfanton committed Jan 18, 2024
1 parent e62d6fb commit d0d2f66
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 4 deletions.
4 changes: 4 additions & 0 deletions gno.land/pkg/integration/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 34 additions & 0 deletions gno.land/pkg/integration/testdata/adduser.txtar
Original file line number Diff line number Diff line change
@@ -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(""), "---")
}
105 changes: 101 additions & 4 deletions gno.land/pkg/integration/testing_integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package integration

import (
"context"
"errors"
"fmt"
"hash/crc32"
"os"
Expand All @@ -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
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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)

Expand All @@ -126,15 +149,15 @@ 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:]

var err error
switch cmd {
case "start":
if _, ok := nodes[sid]; ok {
if nodeIsRunning(nodes, sid) {
err = fmt.Errorf("node already started")
break
}
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
}

0 comments on commit d0d2f66

Please sign in to comment.