From 468f7ee65034cb0a4a9cddfb98f847c9d3abab28 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Sat, 29 Aug 2020 14:41:33 +0300 Subject: [PATCH] cli: add tests for NEP5 balance querying --- cli/executor_test.go | 148 ++++++++++++++++++++++++ cli/main.go | 13 ++- cli/main_test.go | 13 +++ cli/nep5_test.go | 46 ++++++++ cli/testdata/wallet1_solo.json | 72 ++++++++++++ config/protocol.unit_testnet.single.yml | 52 +++++++++ 6 files changed, 340 insertions(+), 4 deletions(-) create mode 100644 cli/executor_test.go create mode 100644 cli/main_test.go create mode 100644 cli/nep5_test.go create mode 100644 cli/testdata/wallet1_solo.json create mode 100644 config/protocol.unit_testnet.single.yml diff --git a/cli/executor_test.go b/cli/executor_test.go new file mode 100644 index 000000000..ce35b0143 --- /dev/null +++ b/cli/executor_test.go @@ -0,0 +1,148 @@ +package main + +import ( + "bytes" + "errors" + "io" + "testing" + + "github.com/nspcc-dev/neo-go/cli/input" + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/core" + "github.com/nspcc-dev/neo-go/pkg/core/storage" + "github.com/nspcc-dev/neo-go/pkg/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/network" + "github.com/nspcc-dev/neo-go/pkg/rpc/server" + "github.com/stretchr/testify/require" + "github.com/urfave/cli" + "go.uber.org/zap" + "go.uber.org/zap/zaptest" +) + +const ( + validatorAddr = "NVNvVRW5Q5naSx2k2iZm7xRgtRNGuZppAK" + + validatorWallet = "testdata/wallet1_solo.json" +) + +var validatorHash, _ = address.StringToUint160(validatorAddr) + +// executor represents context for a test instance. +// It can be safely used in multiple tests, but not in parallel. +type executor struct { + // CLI is a cli application to test. + CLI *cli.App + // Chain is a blockchain instance (can be empty). + Chain *core.Blockchain + // RPC is an RPC server to query (can be empty). + RPC *server.Server + // NetSrv is a network server (can be empty). + NetSrv *network.Server + // Out contains command output. + Out *bytes.Buffer + // Err contains command errors. + Err *bytes.Buffer +} + +func newTestChain(t *testing.T) (*core.Blockchain, *server.Server, *network.Server) { + configPath := "../config/protocol.unit_testnet.single.yml" + cfg, err := config.LoadFile(configPath) + require.NoError(t, err, "could not load config") + + memoryStore := storage.NewMemoryStore() + logger := zaptest.NewLogger(t) + chain, err := core.NewBlockchain(memoryStore, cfg.ProtocolConfiguration, logger) + require.NoError(t, err, "could not create chain") + + go chain.Run() + + serverConfig := network.NewServerConfig(cfg) + netSrv, err := network.NewServer(serverConfig, chain, zap.NewNop()) + require.NoError(t, err) + go netSrv.Start(make(chan error, 1)) + rpcServer := server.New(chain, cfg.ApplicationConfiguration.RPC, netSrv, logger) + errCh := make(chan error, 2) + rpcServer.Start(errCh) + + return chain, &rpcServer, netSrv +} + +func newExecutor(t *testing.T, needChain bool) *executor { + e := &executor{ + CLI: newApp(), + Out: bytes.NewBuffer(nil), + Err: bytes.NewBuffer(nil), + } + e.CLI.Writer = e.Out + e.CLI.ErrWriter = e.Err + if needChain { + e.Chain, e.RPC, e.NetSrv = newTestChain(t) + } + return e +} + +func (e *executor) Close(t *testing.T) { + input.Terminal = nil + if e.RPC != nil { + require.NoError(t, e.RPC.Shutdown()) + } + if e.NetSrv != nil { + e.NetSrv.Shutdown() + } + if e.Chain != nil { + e.Chain.Close() + } +} + +func (e *executor) checkNextLine(t *testing.T, expected string) { + line, err := e.Out.ReadString('\n') + require.NoError(t, err) + e.checkLine(t, line, expected) +} + +func (e *executor) checkLine(t *testing.T, line, expected string) { + require.Regexp(t, expected, line) +} + +func (e *executor) checkEOF(t *testing.T) { + _, err := e.Out.ReadString('\n') + require.True(t, errors.Is(err, io.EOF)) +} + +func setExitFunc() <-chan int { + ch := make(chan int, 1) + cli.OsExiter = func(code int) { + ch <- code + } + return ch +} + +func checkExit(t *testing.T, ch <-chan int, code int) { + select { + case c := <-ch: + require.Equal(t, code, c) + default: + if code != 0 { + require.Fail(t, "no exit was called") + } + } +} + +// RunWithError runs command and checks that is exits with error. +func (e *executor) RunWithError(t *testing.T, args ...string) { + ch := setExitFunc() + require.Error(t, e.run(args...)) + checkExit(t, ch, 1) +} + +// Run runs command and checks that there were no errors. +func (e *executor) Run(t *testing.T, args ...string) { + ch := setExitFunc() + require.NoError(t, e.run(args...)) + checkExit(t, ch, 0) +} +func (e *executor) run(args ...string) error { + e.Out.Reset() + e.Err.Reset() + return e.CLI.Run(args) +} diff --git a/cli/main.go b/cli/main.go index b3494b675..5d762a545 100644 --- a/cli/main.go +++ b/cli/main.go @@ -13,6 +13,14 @@ import ( ) func main() { + ctl := newApp() + + if err := ctl.Run(os.Args); err != nil { + panic(err) + } +} + +func newApp() *cli.App { ctl := cli.NewApp() ctl.Name = "neo-go" ctl.Version = config.Version @@ -23,8 +31,5 @@ func main() { ctl.Commands = append(ctl.Commands, wallet.NewCommands()...) ctl.Commands = append(ctl.Commands, vm.NewCommands()...) ctl.Commands = append(ctl.Commands, util.NewCommands()...) - - if err := ctl.Run(os.Args); err != nil { - panic(err) - } + return ctl } diff --git a/cli/main_test.go b/cli/main_test.go new file mode 100644 index 000000000..c5ac7a622 --- /dev/null +++ b/cli/main_test.go @@ -0,0 +1,13 @@ +package main + +import ( + "testing" +) + +func TestCLIVersion(t *testing.T) { + e := newExecutor(t, false) + defer e.Close(t) + e.Run(t, "neo-go", "--version") + e.checkNextLine(t, "^neo-go version") + e.checkEOF(t) +} diff --git a/cli/nep5_test.go b/cli/nep5_test.go new file mode 100644 index 000000000..81fcdd8de --- /dev/null +++ b/cli/nep5_test.go @@ -0,0 +1,46 @@ +package main + +import ( + "strconv" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/util" +) + +func TestNEP5Balance(t *testing.T) { + e := newExecutor(t, true) + defer e.Close(t) + cmd := []string{ + "neo-go", "wallet", "nep5", "balance", + "--rpc-endpoint", "http://" + e.RPC.Addr, + "--wallet", validatorWallet, + "--addr", validatorAddr, + } + t.Run("NEO", func(t *testing.T) { + b, index := e.Chain.GetGoverningTokenBalance(validatorHash) + checkResult := func(t *testing.T) { + e.checkNextLine(t, "^\\s*TokenHash:\\s*"+e.Chain.GoverningTokenHash().StringLE()) + e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+b.String()) + e.checkNextLine(t, "^\\s*Updated\\s*:\\s*"+strconv.FormatUint(uint64(index), 10)) + e.checkEOF(t) + } + t.Run("Alias", func(t *testing.T) { + e.Run(t, append(cmd, "--token", "neo")...) + checkResult(t) + }) + t.Run("Hash", func(t *testing.T) { + e.Run(t, append(cmd, "--token", e.Chain.GoverningTokenHash().StringLE())...) + checkResult(t) + }) + }) + t.Run("GAS", func(t *testing.T) { + e.Run(t, append(cmd, "--token", "gas")...) + e.checkNextLine(t, "^\\s*TokenHash:\\s*"+e.Chain.UtilityTokenHash().StringLE()) + b := e.Chain.GetUtilityTokenBalance(validatorHash) + e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+util.Fixed8(b.Int64()).String()) + }) + t.Run("Invalid", func(t *testing.T) { + e.RunWithError(t, append(cmd, "--token", "kek")...) + }) + return +} diff --git a/cli/testdata/wallet1_solo.json b/cli/testdata/wallet1_solo.json new file mode 100644 index 000000000..1612925d6 --- /dev/null +++ b/cli/testdata/wallet1_solo.json @@ -0,0 +1,72 @@ +{ + "version": "3.0", + "accounts": [ + { + "address": "NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc", + "key": "6PYN7LvaWqBNw7Xb7a52LSbPnP91kyuzYi3HncGvQwQoYAY2W8DncTgpux", + "label": "", + "contract": { + "script": "DCECs2Ir9AF73+MXxYrtX0x1PyBrfbiWBG+n13S7xL9/jcILQZVEDXg=", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isdefault": false + }, + { + "address": "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY", + "key": "6PYN7LvaWqBNw7Xb7a52LSbPnP91kyuzYi3HncGvQwQoYAY2W8DncTgpux", + "label": "", + "contract": { + "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw==", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + }, + { + "name": "parameter1", + "type": "Signature" + }, + { + "name": "parameter2", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isdefault": false + }, + { + "address": "NVNvVRW5Q5naSx2k2iZm7xRgtRNGuZppAK", + "key": "6PYN7LvaWqBNw7Xb7a52LSbPnP91kyuzYi3HncGvQwQoYAY2W8DncTgpux", + "label": "", + "contract": { + "script": "EQwhArNiK/QBe9/jF8WK7V9MdT8ga324lgRvp9d0u8S/f43CEQtBE43vrw==", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isdefault": false + } + ], + "scrypt": { + "n": 16384, + "r": 8, + "p": 8 + }, + "extra": { + "Tokens": null + } +} diff --git a/config/protocol.unit_testnet.single.yml b/config/protocol.unit_testnet.single.yml new file mode 100644 index 000000000..0d9646de6 --- /dev/null +++ b/config/protocol.unit_testnet.single.yml @@ -0,0 +1,52 @@ +ProtocolConfiguration: + Magic: 42 + SecondsPerBlock: 1 + MemPoolSize: 100 + StandbyCommittee: + - 02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2 + ValidatorsCount: 1 + VerifyBlocks: true + VerifyTransactions: true + +ApplicationConfiguration: + # LogPath could be set up in case you need stdout logs to some proper file. + # LogPath: "./log/neogo.log" + DBConfiguration: + Type: "inmemory" #other options: 'inmemory','redis','boltdb', 'badgerdb'. + # DB type options. Uncomment those you need in case you want to switch DB type. + # LevelDBOptions: + # DataDirectoryPath: "./chains/unit_testnet" + # RedisDBOptions: + # Addr: "localhost:6379" + # Password: "" + # DB: 0 + # BoltDBOptions: + # FilePath: "./chains/unit_testnet.bolt" + # BadgerDBOptions: + # BadgerDir: "./chains/unit_testnet.badger" + # Uncomment in order to set up custom address for node. + # Address: 127.0.0.1 + NodePort: 0 + Relay: true + DialTimeout: 3 + ProtoTickInterval: 2 + PingInterval: 30 + PingTimeout: 90 + MinPeers: 0 + MaxPeers: 10 + AttemptConnPeers: 5 + UnlockWallet: + Path: "testdata/wallet1_solo.json" + Password: "one" + RPC: + Address: 127.0.0.1 + MaxGasInvoke: 10 + Enabled: true + EnableCORSWorkaround: false + Port: 0 # let the system choose port dynamically + Prometheus: + Enabled: false #since it's not useful for unit tests. + Port: 2112 + Pprof: + Enabled: false #since it's not useful for unit tests. + Port: 2113