From 723e2c7fa833d7ad0253ca41d3673c5212f520a3 Mon Sep 17 00:00:00 2001 From: Blake <104744707+r3v4s@users.noreply.github.com> Date: Wed, 5 Feb 2025 10:06:54 +0900 Subject: [PATCH] test: txtar based testing (#492) * test: txtar deploy testing * doc (#490) * GSW-2080 docs: update comments (#488) * docs: comments, docs, ... * chore: unify block interval to ms * chore: milliToSec() to convert milisecond to second * refactor: gnsmath (#489) * refactor: gnsmath Co-authored-by: Lee ByeongJun Co-authored-by: Dongwon <74406335+dongwon8247@users.noreply.github.com> --- go.mod | 63 ++ go.sum | 234 ++++++ tests/integration/doc.go | 123 ++++ tests/integration/node_testing.go | 188 +++++ tests/integration/pkgloader.go | 173 +++++ tests/integration/process.go | 451 ++++++++++++ tests/integration/process/main.go | 20 + tests/integration/process_test.go | 144 ++++ tests/integration/testdata/deploy.txtar | 100 +++ tests/integration/testdata/public_tx.txtar | 142 ++++ tests/integration/testdata_test.go | 67 ++ tests/integration/testscript_gnoland.go | 782 +++++++++++++++++++++ tests/integration/testscript_testing.go | 41 ++ tests/integration/utils.go | 73 ++ tests/integration/utils_test.go | 51 ++ 15 files changed, 2652 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 tests/integration/doc.go create mode 100644 tests/integration/node_testing.go create mode 100644 tests/integration/pkgloader.go create mode 100644 tests/integration/process.go create mode 100644 tests/integration/process/main.go create mode 100644 tests/integration/process_test.go create mode 100644 tests/integration/testdata/deploy.txtar create mode 100644 tests/integration/testdata/public_tx.txtar create mode 100644 tests/integration/testdata_test.go create mode 100644 tests/integration/testscript_gnoland.go create mode 100644 tests/integration/testscript_testing.go create mode 100644 tests/integration/utils.go create mode 100644 tests/integration/utils_test.go diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..23520efc4 --- /dev/null +++ b/go.mod @@ -0,0 +1,63 @@ +module github.com/gnoswap-labs/gnoswap + +go 1.23.5 + +require ( + github.com/gnolang/gno v0.0.0-20250204100358-df14762147e9 + github.com/rogpeppe/go-internal v1.13.1 + github.com/stretchr/testify v1.10.0 +) + +require ( + dario.cat/mergo v1.0.1 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect + github.com/btcsuite/btcd/btcutil v1.1.6 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cockroachdb/apd/v3 v3.2.1 // indirect + github.com/cosmos/ledger-cosmos-go v0.14.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect + github.com/libp2p/go-buffer-pool v0.1.0 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/peterbourgon/ff/v3 v3.4.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rs/cors v1.11.1 // indirect + github.com/rs/xid v1.6.0 // indirect + github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/zondax/hid v0.9.2 // indirect + github.com/zondax/ledger-go v0.14.3 // indirect + go.etcd.io/bbolt v1.3.11 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/tools v0.29.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..cd2b8969f --- /dev/null +++ b/go.sum @@ -0,0 +1,234 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= +github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= +github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= +github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= +github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= +github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gnolang/gno v0.0.0-20250204100358-df14762147e9 h1:Fzp10rZW9sIPdH8kUfzgSVjCBQGUfJ/UUo7EIWvNno0= +github.com/gnolang/gno v0.0.0-20250204100358-df14762147e9/go.mod h1:9UN8hxUrbTQzq5oqXdx39seAtRBhUyONGw0voV4lMBs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= +github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= +github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= +github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= +github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= +github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= +go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0 h1:ajl4QczuJVA2TU9W9AGw++86Xga/RKt//16z/yxPgdk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0 h1:opwv08VbCZ8iecIWs+McMdHRcAXzjAeda3uG2kI/hcA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0/go.mod h1:oOP3ABpW7vFHulLpE8aYtNBodrHhMTrvfxUXGvqm7Ac= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= +google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tests/integration/doc.go b/tests/integration/doc.go new file mode 100644 index 000000000..d93d4607a --- /dev/null +++ b/tests/integration/doc.go @@ -0,0 +1,123 @@ +// Package integration offers utilities to run txtar-based tests against the gnoland system +// 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. +// +// 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: +// +// 1. `gnoland [start|stop|restart]`: +// - The gnoland node doesn't start automatically. This enables the user to do some +// pre-configuration or pass custom arguments to the start command. +// - `gnoland restart` will simulate restarting a node, as in stopping and +// starting it again, recovering state from the persisted database data. +// - `gnoland start -non-validator` can be used to start a node as a non-validator node. +// +// 2. `gnokey`: +// - Supports most of the common commands. +// - `--remote`, `--insecure-password-stdin`, and `--home` flags are set automatically to +// communicate with the gnoland node. +// - In order to handle escape sequences like `\n` within arguments, you can enclose the argument +// in `"` +// +// 3. `adduser`: +// - Must be run before `gnoland start`. +// - Creates a new user in the default keybase directory. +// +// 4. `adduserfrom`: +// - Must be run before `gnoland start`. +// - Creates a new user in the default keybase directory from a given seed. ( Optionally, account and index can be provided ) +// +// 5. `loadpkg`: +// - Must be run before `gnoland start`. +// - Loads a specific package from the 'examples' directory or from the working ($WORK) directory. +// - Can be used to load a single package or all packages within a directory. +// - If the target package has a `gno.mod`, all its dependencies (and their respective +// dependencies) will also be loaded. +// - The command takes either one or two arguments. The first argument is the name of the package(s), +// and the second (optional) argument is the path to the package(s). +// Examples: +// -- # Load a package from the 'examples' directory: +// -- loadpkg gno.land/p/demo/ufmt +// -- # Load a package `./bar` from the testscript's working directory with the name `gno.land/r/foobar/bar`: +// -- loadpkg gno.land/r/foobar/bar $WORK/bar +// - If the path is not prefixed with the working directory, it is assumed to be relative to the +// examples directory. +// - It's important to note that the load order is significant when using multiple `loadpkg` +// command; packages should be loaded in the order they are dependent upon. +// +// 6. `patchpkg`: +// - Patches any loaded files by package by replacing all occurrences of the first argument with the second. +// - This is mostly used to replace hardcoded addresses from loaded packages. +// - NOTE: this command may only be temporary, as it's not best approach to +// solve the above problem +// +// Logging: +// +// 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. +// +// Accounts: +// +// 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. +// +// Environment Variables: +// +// Input: +// +// - 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. +// +// - xxx_user_seed: +// Where `xxx` is the account name; Contains the seed for the test1 account. +// +// - xxx_user_addr: +// Where `xxx` is the account name; Contains the address for the test1 account. +// +// - xxx_account_num: +// Where `xxx` is the account name; Contains the account number for the test1 account. +// +// - xxx_account_seq: +// Where `xxx` is the account name; 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/tests/integration/node_testing.go b/tests/integration/node_testing.go new file mode 100644 index 000000000..c613048eb --- /dev/null +++ b/tests/integration/node_testing.go @@ -0,0 +1,188 @@ +package integration + +import ( + "log/slog" + "path/filepath" + "slices" + "time" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" + abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" + tmcfg "github.com/gnolang/gno/tm2/pkg/bft/config" + "github.com/gnolang/gno/tm2/pkg/bft/node" + bft "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/db/memdb" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/stretchr/testify/require" +) + +const ( + DefaultAccount_Name = "test1" + DefaultAccount_Address = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" + DefaultAccount_Seed = "source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast" +) + +// TestingInMemoryNode initializes and starts an in-memory node for testing. +// It returns the node instance and its RPC remote address. +func TestingInMemoryNode(t TestingTS, logger *slog.Logger, config *gnoland.InMemoryNodeConfig) (*node.Node, string) { + node, err := gnoland.NewInMemoryNode(logger, config) + require.NoError(t, err) + + err = node.Start() + require.NoError(t, err) + + ourAddress := config.PrivValidator.GetPubKey().Address() + isValidator := slices.ContainsFunc(config.Genesis.Validators, func(val bft.GenesisValidator) bool { + return val.Address == ourAddress + }) + + // Wait for first block if we are a validator. + // If we are not a validator, we don't produce blocks, so node.Ready() hangs. + if isValidator { + select { + case <-node.Ready(): + case <-time.After(time.Second * 10): + require.FailNow(t, "timeout while waiting for the node to start") + } + } + + return node, node.Config().RPC.ListenAddress +} + +// TestingNodeConfig constructs an in-memory node configuration +// with default packages and genesis transactions already loaded. +// It will return the default creator address of the loaded packages. +func TestingNodeConfig(t TestingTS, gnoroot string, additionalTxs ...gnoland.TxWithMetadata) (*gnoland.InMemoryNodeConfig, bft.Address) { + cfg := TestingMinimalNodeConfig(gnoroot) + cfg.SkipGenesisVerification = true + + creator := crypto.MustAddressFromString(DefaultAccount_Address) // test1 + + params := LoadDefaultGenesisParamFile(t, gnoroot) + balances := LoadDefaultGenesisBalanceFile(t, gnoroot) + txs := make([]gnoland.TxWithMetadata, 0) + txs = append(txs, LoadDefaultPackages(t, creator, gnoroot)...) + txs = append(txs, additionalTxs...) + + cfg.Genesis.AppState = gnoland.GnoGenesisState{ + Balances: balances, + Txs: txs, + Params: params, + } + + return cfg, creator +} + +// TestingMinimalNodeConfig constructs the default minimal in-memory node configuration for testing. +func TestingMinimalNodeConfig(gnoroot string) *gnoland.InMemoryNodeConfig { + tmconfig := DefaultTestingTMConfig(gnoroot) + + // Create Mocked Identity + pv := gnoland.NewMockedPrivValidator() + + // Generate genesis config + genesis := DefaultTestingGenesisConfig(gnoroot, pv.GetPubKey(), tmconfig) + + return &gnoland.InMemoryNodeConfig{ + PrivValidator: pv, + Genesis: genesis, + TMConfig: tmconfig, + DB: memdb.NewMemDB(), + InitChainerConfig: gnoland.InitChainerConfig{ + GenesisTxResultHandler: gnoland.PanicOnFailingTxResultHandler, + CacheStdlibLoad: true, + }, + } +} + +func DefaultTestingGenesisConfig(gnoroot string, self crypto.PubKey, tmconfig *tmcfg.Config) *bft.GenesisDoc { + return &bft.GenesisDoc{ + GenesisTime: time.Now(), + ChainID: tmconfig.ChainID(), + ConsensusParams: abci.ConsensusParams{ + Block: &abci.BlockParams{ + MaxTxBytes: 1_000_000, // 1MB, + MaxDataBytes: 2_000_000, // 2MB, + MaxGas: 3_000_000_000, // 3B gas + TimeIotaMS: 100, // 100ms + }, + }, + Validators: []bft.GenesisValidator{ + { + Address: self.Address(), + PubKey: self, + Power: 10, + Name: "self", + }, + }, + AppState: gnoland.GnoGenesisState{ + Balances: []gnoland.Balance{ + { + Address: crypto.MustAddressFromString(DefaultAccount_Address), + Amount: std.MustParseCoins(ugnot.ValueString(10_000_000_000_000)), + }, + }, + Txs: []gnoland.TxWithMetadata{}, + Params: []gnoland.Param{}, + }, + } +} + +// LoadDefaultPackages loads the default packages for testing using a given creator address and gnoroot directory. +func LoadDefaultPackages(t TestingTS, creator bft.Address, gnoroot string) []gnoland.TxWithMetadata { + examplesDir := filepath.Join(gnoroot, "examples") + + defaultFee := std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) + txs, err := gnoland.LoadPackagesFromDir(examplesDir, creator, defaultFee) + require.NoError(t, err) + + return txs +} + +// LoadDefaultGenesisBalanceFile loads the default genesis balance file for testing. +func LoadDefaultGenesisBalanceFile(t TestingTS, gnoroot string) []gnoland.Balance { + balanceFile := filepath.Join(gnoroot, "gno.land", "genesis", "genesis_balances.txt") + + genesisBalances, err := gnoland.LoadGenesisBalancesFile(balanceFile) + require.NoError(t, err) + + return genesisBalances.List() +} + +// LoadDefaultGenesisParamFile loads the default genesis balance file for testing. +func LoadDefaultGenesisParamFile(t TestingTS, gnoroot string) []gnoland.Param { + paramFile := filepath.Join(gnoroot, "gno.land", "genesis", "genesis_params.toml") + + genesisParams, err := gnoland.LoadGenesisParamsFile(paramFile) + require.NoError(t, err) + + return genesisParams +} + +// LoadDefaultGenesisTXsFile loads the default genesis transactions file for testing. +func LoadDefaultGenesisTXsFile(t TestingTS, chainid string, gnoroot string) []gnoland.TxWithMetadata { + txsFile := filepath.Join(gnoroot, "gno.land", "genesis", "genesis_txs.jsonl") + + // NOTE: We dont care about giving a correct address here, as it's only for display + // XXX: Do we care loading this TXs for testing ? + genesisTXs, err := gnoland.LoadGenesisTxsFile(txsFile, chainid, "https://127.0.0.1:26657") + require.NoError(t, err) + + return genesisTXs +} + +// DefaultTestingTMConfig constructs the default Tendermint configuration for testing. +func DefaultTestingTMConfig(gnoroot string) *tmcfg.Config { + const defaultListner = "tcp://127.0.0.1:0" + + tmconfig := tmcfg.TestConfig().SetRootDir(gnoroot) + tmconfig.Consensus.WALDisabled = true + tmconfig.Consensus.SkipTimeoutCommit = true + tmconfig.Consensus.CreateEmptyBlocks = true + tmconfig.Consensus.CreateEmptyBlocksInterval = time.Millisecond * 100 + tmconfig.RPC.ListenAddress = defaultListner + tmconfig.P2P.ListenAddress = defaultListner + return tmconfig +} diff --git a/tests/integration/pkgloader.go b/tests/integration/pkgloader.go new file mode 100644 index 000000000..5968ce2b4 --- /dev/null +++ b/tests/integration/pkgloader.go @@ -0,0 +1,173 @@ +package integration + +import ( + "fmt" + "path/filepath" + "strings" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gno/gnovm/pkg/packages" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/std" +) + +type PkgsLoader struct { + pkgs []gnomod.Pkg + visited map[string]struct{} + + // list of occurrences to patchs with the given value + // XXX: find a better way + patchs map[string]string +} + +func NewPkgsLoader() *PkgsLoader { + return &PkgsLoader{ + pkgs: make([]gnomod.Pkg, 0), + visited: make(map[string]struct{}), + patchs: make(map[string]string), + } +} + +func (pl *PkgsLoader) List() gnomod.PkgList { + return pl.pkgs +} + +func (pl *PkgsLoader) SetPatch(replace, with string) { + pl.patchs[replace] = with +} + +func (pl *PkgsLoader) LoadPackages(creatorKey crypto.PrivKey, fee std.Fee, deposit std.Coins) ([]gnoland.TxWithMetadata, error) { + pkgslist, err := pl.List().Sort() // sorts packages by their dependencies. + if err != nil { + return nil, fmt.Errorf("unable to sort packages: %w", err) + } + + txs := make([]gnoland.TxWithMetadata, len(pkgslist)) + for i, pkg := range pkgslist { + tx, err := gnoland.LoadPackage(pkg, creatorKey.PubKey().Address(), fee, deposit) + if err != nil { + return nil, fmt.Errorf("unable to load pkg %q: %w", pkg.Name, err) + } + + // If any replace value is specified, apply them + if len(pl.patchs) > 0 { + for _, msg := range tx.Msgs { + addpkg, ok := msg.(vm.MsgAddPackage) + if !ok { + continue + } + + if addpkg.Package == nil { + continue + } + + for _, file := range addpkg.Package.Files { + for replace, with := range pl.patchs { + file.Body = strings.ReplaceAll(file.Body, replace, with) + } + } + } + } + + txs[i] = gnoland.TxWithMetadata{ + Tx: tx, + } + } + + if err = gnoland.SignGenesisTxs(txs, creatorKey, "tendermint_test"); err != nil { + return nil, fmt.Errorf("unable to sign txs: %w", err) + } + + return txs, nil +} + +func (pl *PkgsLoader) LoadAllPackagesFromDir(path string) error { + // list all packages from target path + pkgslist, err := gnomod.ListPkgs(path) + if err != nil { + return fmt.Errorf("listing gno packages: %w", err) + } + + for _, pkg := range pkgslist { + if !pl.exist(pkg) { + pl.add(pkg) + } + } + + return nil +} + +func (pl *PkgsLoader) LoadPackage(modroot string, path, name string) error { + // Initialize a queue with the root package + queue := []gnomod.Pkg{{Dir: path, Name: name}} + + for len(queue) > 0 { + // Dequeue the first package + currentPkg := queue[0] + queue = queue[1:] + + if currentPkg.Dir == "" { + return fmt.Errorf("no path specified for package") + } + + if currentPkg.Name == "" { + // Load `gno.mod` information + gnoModPath := filepath.Join(currentPkg.Dir, "gno.mod") + gm, err := gnomod.ParseGnoMod(gnoModPath) + if err != nil { + return fmt.Errorf("unable to load %q: %w", gnoModPath, err) + } + gm.Sanitize() + + // Override package info with mod infos + currentPkg.Name = gm.Module.Mod.Path + currentPkg.Draft = gm.Draft + + pkg, err := gnolang.ReadMemPackage(currentPkg.Dir, currentPkg.Name) + if err != nil { + return fmt.Errorf("unable to read package at %q: %w", currentPkg.Dir, err) + } + importsMap, err := packages.Imports(pkg, nil) + if err != nil { + return fmt.Errorf("unable to load package imports in %q: %w", currentPkg.Dir, err) + } + imports := importsMap.Merge(packages.FileKindPackageSource, packages.FileKindTest) + for _, imp := range imports { + if imp.PkgPath == currentPkg.Name || gnolang.IsStdlib(imp.PkgPath) { + continue + } + currentPkg.Imports = append(currentPkg.Imports, imp.PkgPath) + } + } + + if currentPkg.Draft { + continue // Skip draft package + } + + if pl.exist(currentPkg) { + continue + } + pl.add(currentPkg) + + // Add requirements to the queue + for _, pkgPath := range currentPkg.Imports { + fullPath := filepath.Join(modroot, pkgPath) + queue = append(queue, gnomod.Pkg{Dir: fullPath}) + } + } + + return nil +} + +func (pl *PkgsLoader) add(pkg gnomod.Pkg) { + pl.visited[pkg.Name] = struct{}{} + pl.pkgs = append(pl.pkgs, pkg) +} + +func (pl *PkgsLoader) exist(pkg gnomod.Pkg) (ok bool) { + _, ok = pl.visited[pkg.Name] + return +} diff --git a/tests/integration/process.go b/tests/integration/process.go new file mode 100644 index 000000000..839004ca1 --- /dev/null +++ b/tests/integration/process.go @@ -0,0 +1,451 @@ +package integration + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "log/slog" + "os" + "os/exec" + "os/signal" + "slices" + "sync" + "testing" + "time" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/tm2/pkg/amino" + tmcfg "github.com/gnolang/gno/tm2/pkg/bft/config" + bft "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" + "github.com/gnolang/gno/tm2/pkg/db" + "github.com/gnolang/gno/tm2/pkg/db/goleveldb" + "github.com/gnolang/gno/tm2/pkg/db/memdb" + "github.com/stretchr/testify/require" +) + +const gracefulShutdown = time.Second * 5 + +type ProcessNodeConfig struct { + ValidatorKey ed25519.PrivKeyEd25519 `json:"priv"` + Verbose bool `json:"verbose"` + DBDir string `json:"dbdir"` + RootDir string `json:"rootdir"` + Genesis *MarshalableGenesisDoc `json:"genesis"` + TMConfig *tmcfg.Config `json:"tm"` +} + +type ProcessConfig struct { + Node *ProcessNodeConfig + + // These parameters are not meant to be passed to the process + CoverDir string + Stderr, Stdout io.Writer +} + +func (i ProcessConfig) validate() error { + if i.Node.TMConfig == nil { + return errors.New("no tm config set") + } + + if i.Node.Genesis == nil { + return errors.New("no genesis is set") + } + + return nil +} + +// RunNode initializes and runs a gnoaland node with the provided configuration. +func RunNode(ctx context.Context, pcfg *ProcessNodeConfig, stdout, stderr io.Writer) error { + // Setup logger based on verbosity + var handler slog.Handler + if pcfg.Verbose { + handler = slog.NewTextHandler(stdout, &slog.HandlerOptions{Level: slog.LevelDebug}) + } else { + handler = slog.NewTextHandler(stdout, &slog.HandlerOptions{Level: slog.LevelError}) + } + logger := slog.New(handler) + + // Initialize database + db, err := initDatabase(pcfg.DBDir) + if err != nil { + return err + } + defer db.Close() // ensure db is close + + nodecfg := TestingMinimalNodeConfig(pcfg.RootDir) + + // Configure validator if provided + if len(pcfg.ValidatorKey) > 0 && !isAllZero(pcfg.ValidatorKey) { + nodecfg.PrivValidator = bft.NewMockPVWithParams(pcfg.ValidatorKey, false, false) + } + pv := nodecfg.PrivValidator.GetPubKey() + + // Setup node configuration + nodecfg.DB = db + nodecfg.TMConfig.DBPath = pcfg.DBDir + nodecfg.TMConfig = pcfg.TMConfig + nodecfg.Genesis = pcfg.Genesis.ToGenesisDoc() + nodecfg.Genesis.Validators = []bft.GenesisValidator{ + { + Address: pv.Address(), + PubKey: pv, + Power: 10, + Name: "self", + }, + } + + // Create and start the node + node, err := gnoland.NewInMemoryNode(logger, nodecfg) + if err != nil { + return fmt.Errorf("failed to create new in-memory node: %w", err) + } + + if err := node.Start(); err != nil { + return fmt.Errorf("failed to start node: %w", err) + } + defer node.Stop() + + // Determine if the node is a validator + ourAddress := nodecfg.PrivValidator.GetPubKey().Address() + isValidator := slices.ContainsFunc(nodecfg.Genesis.Validators, func(val bft.GenesisValidator) bool { + return val.Address == ourAddress + }) + + lisnAddress := node.Config().RPC.ListenAddress + if isValidator { + select { + case <-ctx.Done(): + return fmt.Errorf("waiting for the node to start: %w", ctx.Err()) + case <-node.Ready(): + } + } + + // Write READY signal to stdout + signalWriteReady(stdout, lisnAddress) + + <-ctx.Done() + return node.Stop() +} + +type NodeProcess interface { + Stop() error + Address() string +} + +type nodeProcess struct { + cmd *exec.Cmd + address string + + stopOnce sync.Once + stopErr error +} + +func (n *nodeProcess) Address() string { + return n.address +} + +func (n *nodeProcess) Stop() error { + n.stopOnce.Do(func() { + // Send SIGTERM to the process + if err := n.cmd.Process.Signal(os.Interrupt); err != nil { + n.stopErr = fmt.Errorf("error sending `SIGINT` to the node: %w", err) + return + } + + // Optionally wait for the process to exit + if _, err := n.cmd.Process.Wait(); err != nil { + n.stopErr = fmt.Errorf("process exited with error: %w", err) + return + } + }) + + return n.stopErr +} + +// RunNodeProcess runs the binary at the given path with the provided configuration. +func RunNodeProcess(ctx context.Context, cfg ProcessConfig, name string, args ...string) (NodeProcess, error) { + if cfg.Stdout == nil { + cfg.Stdout = os.Stdout + } + + if cfg.Stderr == nil { + cfg.Stderr = os.Stderr + } + + if err := cfg.validate(); err != nil { + return nil, err + } + + // Marshal the configuration to JSON + nodeConfigData, err := json.Marshal(cfg.Node) + if err != nil { + return nil, fmt.Errorf("failed to marshal config to JSON: %w", err) + } + + // Create and configure the command to execute the binary + cmd := exec.Command(name, args...) + cmd.Env = os.Environ() + cmd.Stdin = bytes.NewReader(nodeConfigData) + + if cfg.CoverDir != "" { + cmd.Env = append(cmd.Env, "GOCOVERDIR="+cfg.CoverDir) + } + + // Redirect all errors into a buffer + cmd.Stderr = os.Stderr + if cfg.Stderr != nil { + cmd.Stderr = cfg.Stderr + } + + // Create pipes for stdout + stdoutPipe, err := cmd.StdoutPipe() + if err != nil { + return nil, fmt.Errorf("failed to create stdout pipe: %w", err) + } + + // Start the command + if err := cmd.Start(); err != nil { + return nil, fmt.Errorf("failed to start command: %w", err) + } + + address, err := waitForProcessReady(ctx, stdoutPipe, cfg.Stdout) + if err != nil { + return nil, fmt.Errorf("waiting for readiness: %w", err) + } + + return &nodeProcess{ + cmd: cmd, + address: address, + }, nil +} + +type nodeInMemoryProcess struct { + address string + + stopOnce sync.Once + stopErr error + stop context.CancelFunc + ccNodeError chan error +} + +func (n *nodeInMemoryProcess) Address() string { + return n.address +} + +func (n *nodeInMemoryProcess) Stop() error { + n.stopOnce.Do(func() { + n.stop() + var err error + select { + case err = <-n.ccNodeError: + case <-time.After(time.Second * 5): + err = fmt.Errorf("timeout while waiting for node to stop") + } + + if err != nil { + n.stopErr = fmt.Errorf("unable to node gracefully: %w", err) + } + }) + + return n.stopErr +} + +func RunInMemoryProcess(ctx context.Context, cfg ProcessConfig) (NodeProcess, error) { + ctx, cancel := context.WithCancel(ctx) + + out, in := io.Pipe() + ccStopErr := make(chan error, 1) + go func() { + defer close(ccStopErr) + defer cancel() + + err := RunNode(ctx, cfg.Node, in, cfg.Stderr) + if err != nil { + fmt.Fprintf(cfg.Stderr, "run node failed: %v", err) + } + + ccStopErr <- err + }() + + address, err := waitForProcessReady(ctx, out, cfg.Stdout) + if err == nil { // ok + return &nodeInMemoryProcess{ + address: address, + stop: cancel, + ccNodeError: ccStopErr, + }, nil + } + + cancel() + + select { + case err = <-ccStopErr: // return node error in priority + default: + } + + return nil, err +} + +func RunMain(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer) error { + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) + defer stop() + + // Read the configuration from standard input + configData, err := io.ReadAll(stdin) + if err != nil { + // log.Fatalf("error reading stdin: %v", err) + return fmt.Errorf("error reading stdin: %w", err) + } + + // Unmarshal the JSON configuration + var cfg ProcessNodeConfig + if err := json.Unmarshal(configData, &cfg); err != nil { + return fmt.Errorf("error unmarshaling JSON: %w", err) + // log.Fatalf("error unmarshaling JSON: %v", err) + } + + // Run the node + ccErr := make(chan error, 1) + go func() { + ccErr <- RunNode(ctx, &cfg, stdout, stderr) + close(ccErr) + }() + + // Wait for the node to gracefully terminate + <-ctx.Done() + + // Attempt graceful shutdown + select { + case <-time.After(gracefulShutdown): + return fmt.Errorf("unable to gracefully stop the node, exiting now") + case err = <-ccErr: // done + } + + return err +} + +func runTestingNodeProcess(t TestingTS, ctx context.Context, pcfg ProcessConfig) NodeProcess { + bin, err := os.Executable() + require.NoError(t, err) + args := []string{ + "-test.run=^$", + "-run-node-process", + } + + if pcfg.CoverDir != "" && testing.CoverMode() != "" { + args = append(args, "-test.gocoverdir="+pcfg.CoverDir) + } + + node, err := RunNodeProcess(ctx, pcfg, bin, args...) + require.NoError(t, err) + + return node +} + +// initDatabase initializes the database based on the provided directory configuration. +func initDatabase(dbDir string) (db.DB, error) { + if dbDir == "" { + return memdb.NewMemDB(), nil + } + + data, err := goleveldb.NewGoLevelDB("testdb", dbDir) + if err != nil { + return nil, fmt.Errorf("unable to init database in %q: %w", dbDir, err) + } + + return data, nil +} + +func signalWriteReady(w io.Writer, address string) error { + _, err := fmt.Fprintf(w, "READY:%s\n", address) + return err +} + +func signalReadReady(line string) (string, bool) { + var address string + if _, err := fmt.Sscanf(line, "READY:%s", &address); err == nil { + return address, true + } + return "", false +} + +// waitForProcessReady waits for the process to signal readiness and returns the address. +func waitForProcessReady(ctx context.Context, stdoutPipe io.Reader, out io.Writer) (string, error) { + var address string + + cReady := make(chan error, 2) + go func() { + defer close(cReady) + + scanner := bufio.NewScanner(stdoutPipe) + ready := false + for scanner.Scan() { + line := scanner.Text() + + if !ready { + if addr, ok := signalReadReady(line); ok { + address = addr + ready = true + cReady <- nil + } + } + + fmt.Fprintln(out, line) + } + + if err := scanner.Err(); err != nil { + cReady <- fmt.Errorf("error reading stdout: %w", err) + } else { + cReady <- fmt.Errorf("process exited without 'READY'") + } + }() + + select { + case err := <-cReady: + return address, err + case <-ctx.Done(): + return "", ctx.Err() + } +} + +// isAllZero checks if a 64-byte key consists entirely of zeros. +func isAllZero(key [64]byte) bool { + for _, v := range key { + if v != 0 { + return false + } + } + return true +} + +type MarshalableGenesisDoc bft.GenesisDoc + +func NewMarshalableGenesisDoc(doc *bft.GenesisDoc) *MarshalableGenesisDoc { + m := MarshalableGenesisDoc(*doc) + return &m +} + +func (m *MarshalableGenesisDoc) MarshalJSON() ([]byte, error) { + doc := (*bft.GenesisDoc)(m) + return amino.MarshalJSON(doc) +} + +func (m *MarshalableGenesisDoc) UnmarshalJSON(data []byte) (err error) { + doc, err := bft.GenesisDocFromJSON(data) + if err != nil { + return err + } + + *m = MarshalableGenesisDoc(*doc) + return +} + +// Cast back to the original bft.GenesisDoc. +func (m *MarshalableGenesisDoc) ToGenesisDoc() *bft.GenesisDoc { + return (*bft.GenesisDoc)(m) +} diff --git a/tests/integration/process/main.go b/tests/integration/process/main.go new file mode 100644 index 000000000..bcd52e6fd --- /dev/null +++ b/tests/integration/process/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/gnolang/gno/gno.land/pkg/integration" +) + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + defer cancel() + + if err := integration.RunMain(ctx, os.Stdin, os.Stdout, os.Stderr); err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } +} diff --git a/tests/integration/process_test.go b/tests/integration/process_test.go new file mode 100644 index 000000000..b8768ad0e --- /dev/null +++ b/tests/integration/process_test.go @@ -0,0 +1,144 @@ +package integration + +import ( + "bytes" + "context" + "flag" + "fmt" + "os" + "path/filepath" + "testing" + "time" + + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" + "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Define a flag to indicate whether to run the embedded command +var runCommand = flag.Bool("run-node-process", false, "execute the embedded command") + +func TestMain(m *testing.M) { + flag.Parse() + + // Check if the embedded command should be executed + if !*runCommand { + fmt.Println("Running tests...") + os.Exit(m.Run()) + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + defer cancel() + + if err := RunMain(ctx, os.Stdin, os.Stdout, os.Stderr); err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } +} + +// TestGnolandIntegration tests the forking of a Gnoland node. +func TestNodeProcess(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) + defer cancel() + + gnoRootDir := gnoenv.RootDir() + + // Define paths for the build directory and the gnoland binary + gnolandDBDir := filepath.Join(t.TempDir(), "db") + + // Prepare a minimal node configuration for testing + cfg := TestingMinimalNodeConfig(gnoRootDir) + + var stdio bytes.Buffer + defer func() { + t.Log("node output:") + t.Log(stdio.String()) + }() + + start := time.Now() + node := runTestingNodeProcess(t, ctx, ProcessConfig{ + Stderr: &stdio, Stdout: &stdio, + Node: &ProcessNodeConfig{ + Verbose: true, + ValidatorKey: ed25519.GenPrivKey(), + DBDir: gnolandDBDir, + RootDir: gnoRootDir, + TMConfig: cfg.TMConfig, + Genesis: NewMarshalableGenesisDoc(cfg.Genesis), + }, + }) + t.Logf("time to start the node: %v", time.Since(start).String()) + + // Create a new HTTP client to interact with the integration node + cli, err := client.NewHTTPClient(node.Address()) + require.NoError(t, err) + + // Retrieve node info + info, err := cli.ABCIInfo() + require.NoError(t, err) + assert.NotEmpty(t, info.Response.Data) + + // Attempt to stop the node + err = node.Stop() + require.NoError(t, err) + + // Attempt to stop the node a second time, should not fail + err = node.Stop() + require.NoError(t, err) +} + +// TestGnolandIntegration tests the forking of a Gnoland node. +func TestInMemoryNodeProcess(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) + defer cancel() + + gnoRootDir := gnoenv.RootDir() + + // Define paths for the build directory and the gnoland binary + gnolandDBDir := filepath.Join(t.TempDir(), "db") + + // Prepare a minimal node configuration for testing + cfg := TestingMinimalNodeConfig(gnoRootDir) + + var stdio bytes.Buffer + defer func() { + t.Log("node output:") + t.Log(stdio.String()) + }() + + start := time.Now() + node, err := RunInMemoryProcess(ctx, ProcessConfig{ + Stderr: &stdio, Stdout: &stdio, + Node: &ProcessNodeConfig{ + Verbose: true, + ValidatorKey: ed25519.GenPrivKey(), + DBDir: gnolandDBDir, + RootDir: gnoRootDir, + TMConfig: cfg.TMConfig, + Genesis: NewMarshalableGenesisDoc(cfg.Genesis), + }, + }) + require.NoError(t, err) + t.Logf("time to start the node: %v", time.Since(start).String()) + + // Create a new HTTP client to interact with the integration node + cli, err := client.NewHTTPClient(node.Address()) + require.NoError(t, err) + + // Retrieve node info + info, err := cli.ABCIInfo() + require.NoError(t, err) + assert.NotEmpty(t, info.Response.Data) + + // Attempt to stop the node + err = node.Stop() + require.NoError(t, err) + + // Attempt to stop the node a second time, should not fail + err = node.Stop() + require.NoError(t, err) +} diff --git a/tests/integration/testdata/deploy.txtar b/tests/integration/testdata/deploy.txtar new file mode 100644 index 000000000..67f01e093 --- /dev/null +++ b/tests/integration/testdata/deploy.txtar @@ -0,0 +1,100 @@ +loadpkg gno.land/p/demo/avl +loadpkg gno.land/p/demo/ufmt +loadpkg gno.land/p/demo/json +loadpkg gno.land/p/demo/ownable +loadpkg gno.land/p/demo/grc/grc20 +loadpkg gno.land/p/demo/grc/grc721 + +loadpkg gno.land/r/demo/users +loadpkg gno.land/r/demo/wugnot +loadpkg gno.land/r/demo/grc20reg + + +## start a new node +gnoland start + + +## deploy test-tokens + +### deploy usdc +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/test_token/usdc -pkgpath gno.land/r/gnoswap/v1/test_token/usdc -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +### deploy foo +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/test_token/foo -pkgpath gno.land/r/gnoswap/v1/test_token/foo -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +### deploy bar +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/test_token/bar -pkgpath gno.land/r/gnoswap/v1/test_token/bar -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +### deploy baz +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/test_token/baz -pkgpath gno.land/r/gnoswap/v1/test_token/baz -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +### deploy qux +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/test_token/qux -pkgpath gno.land/r/gnoswap/v1/test_token/qux -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +### deploy obl +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/test_token/obl -pkgpath gno.land/r/gnoswap/v1/test_token/obl -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + + + +## deploy packages + +### deploy uint256 +gnokey maketx addpkg -pkgdir ../../contract/p/gnoswap/uint256 -pkgpath gno.land/p/gnoswap/uint256 -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +## deploy int256 +gnokey maketx addpkg -pkgdir ../../contract/p/gnoswap/int256 -pkgpath gno.land/p/gnoswap/int256 -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +## deploy consts +gnokey maketx addpkg -pkgdir ../../contract/p/gnoswap/consts -pkgpath gno.land/p/gnoswap/consts -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +## deploy gnsmath +gnokey maketx addpkg -pkgdir ../../contract/p/gnoswap/gnsmath -pkgpath gno.land/p/gnoswap/gnsmath -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + + + +## deploy realm + +### deploy common +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/common -pkgpath gno.land/r/gnoswap/v1/common -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +### deploy gns +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/gns -pkgpath gno.land/r/gnoswap/v1/gns -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +### deploy gnft +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/gnft -pkgpath gno.land/r/gnoswap/v1/gnft -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +### deploy gov/xgns +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/gov/xgns -pkgpath gno.land/r/gnoswap/v1/gov/xgns -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +### deploy emission +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/emission -pkgpath gno.land/r/gnoswap/v1/emission -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +### deploy protocol_fee +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/protocol_fee -pkgpath gno.land/r/gnoswap/v1/protocol_fee -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +### deploy pool +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/pool -pkgpath gno.land/r/gnoswap/v1/pool -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +### deploy position +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/position -pkgpath gno.land/r/gnoswap/v1/position -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +### deploy router +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/router -pkgpath gno.land/r/gnoswap/v1/router -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +### deploy staker +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/staker -pkgpath gno.land/r/gnoswap/v1/staker -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +### deploy community_pool +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/community_pool -pkgpath gno.land/r/gnoswap/v1/community_pool -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +### deploy gov/staker +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/gov/staker -pkgpath gno.land/r/gnoswap/v1/gov/staker -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +### deploy gov/governance +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/gov/governance -pkgpath gno.land/r/gnoswap/v1/gov/governance -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +### deploy launchpad +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/launchpad -pkgpath gno.land/r/gnoswap/v1/launchpad -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 + +### deploy referral +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/referral -pkgpath gno.land/r/gnoswap/v1/referral -gas-fee 1ugnot -gas-wanted 100000000 -broadcast -chainid=tendermint_test test1 diff --git a/tests/integration/testdata/public_tx.txtar b/tests/integration/testdata/public_tx.txtar new file mode 100644 index 000000000..36b82d6c8 --- /dev/null +++ b/tests/integration/testdata/public_tx.txtar @@ -0,0 +1,142 @@ +loadpkg gno.land/p/demo/avl +loadpkg gno.land/p/demo/ufmt +loadpkg gno.land/p/demo/json +loadpkg gno.land/p/demo/ownable +loadpkg gno.land/p/demo/grc/grc20 +loadpkg gno.land/p/demo/grc/grc721 + +loadpkg gno.land/r/demo/users +loadpkg gno.land/r/demo/wugnot +loadpkg gno.land/r/demo/grc20reg + +adduser user2 +adduser user3 + + +## start a new node +gnoland start + + +## deploy test-tokens + +### deploy usdc +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/test_token/usdc -pkgpath gno.land/r/gnoswap/v1/test_token/usdc -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +### deploy foo +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/test_token/foo -pkgpath gno.land/r/gnoswap/v1/test_token/foo -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +### deploy bar +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/test_token/bar -pkgpath gno.land/r/gnoswap/v1/test_token/bar -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +### deploy baz +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/test_token/baz -pkgpath gno.land/r/gnoswap/v1/test_token/baz -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +### deploy qux +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/test_token/qux -pkgpath gno.land/r/gnoswap/v1/test_token/qux -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +### deploy obl +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/test_token/obl -pkgpath gno.land/r/gnoswap/v1/test_token/obl -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + + + +## deploy packages + +### deploy uint256 +gnokey maketx addpkg -pkgdir ../../contract/p/gnoswap/uint256 -pkgpath gno.land/p/gnoswap/uint256 -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +## deploy int256 +gnokey maketx addpkg -pkgdir ../../contract/p/gnoswap/int256 -pkgpath gno.land/p/gnoswap/int256 -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +## deploy consts +gnokey maketx addpkg -pkgdir ../../contract/p/gnoswap/consts -pkgpath gno.land/p/gnoswap/consts -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +## deploy gnsmath +gnokey maketx addpkg -pkgdir ../../contract/p/gnoswap/gnsmath -pkgpath gno.land/p/gnoswap/gnsmath -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + + + +## deploy realm + +### deploy common +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/common -pkgpath gno.land/r/gnoswap/v1/common -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +### deploy gns +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/gns -pkgpath gno.land/r/gnoswap/v1/gns -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +### deploy gnft +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/gnft -pkgpath gno.land/r/gnoswap/v1/gnft -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +### deploy gov/xgns +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/gov/xgns -pkgpath gno.land/r/gnoswap/v1/gov/xgns -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +### deploy emission +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/emission -pkgpath gno.land/r/gnoswap/v1/emission -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +### deploy protocol_fee +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/protocol_fee -pkgpath gno.land/r/gnoswap/v1/protocol_fee -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +### deploy pool +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/pool -pkgpath gno.land/r/gnoswap/v1/pool -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +### deploy position +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/position -pkgpath gno.land/r/gnoswap/v1/position -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +### deploy router +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/router -pkgpath gno.land/r/gnoswap/v1/router -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +### deploy staker +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/staker -pkgpath gno.land/r/gnoswap/v1/staker -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +### deploy community_pool +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/community_pool -pkgpath gno.land/r/gnoswap/v1/community_pool -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +### deploy gov/staker +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/gov/staker -pkgpath gno.land/r/gnoswap/v1/gov/staker -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +### deploy gov/governance +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/gov/governance -pkgpath gno.land/r/gnoswap/v1/gov/governance -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +### deploy launchpad +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/launchpad -pkgpath gno.land/r/gnoswap/v1/launchpad -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +### deploy referral +gnokey maketx addpkg -pkgdir ../../contract/r/gnoswap/referral -pkgpath gno.land/r/gnoswap/v1/referral -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +# create pool +gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args g148tjamj80yyrm309z7rk690an22thd2l3z8ank -args 18446744073709551615 -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/pool -func CreatePool -args "gno.land/r/demo/wugnot" -args "gno.land/r/gnoswap/v1/gns" -args 3000 -args 79228162514264337593543950337 -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 +stdout '{"key":"poolPath","value":"gno.land/r/demo/wugnot:gno.land/r/gnoswap/v1/gns:3000"}' + + +# mint new position +gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/position -func ApiGetPositions -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args g148tjamj80yyrm309z7rk690an22thd2l3z8ank -args 18446744073709551615 -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args g148tjamj80yyrm309z7rk690an22thd2l3z8ank -args 18446744073709551615 -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args g1q646ctzhvn60v492x8ucvyqnrj2w30cwh6efk5 -args 18446744073709551615 -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/position -func Mint -send "20000000ugnot" -args "gno.land/r/gnoswap/v1/gns" -args "gnot" -args 3000 -args "-49980" -args "49980" -args 20000000 -args 20000000 -args 1 -args 1 -args 9999999999 -args g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -args g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +# increase liquidity +gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/position -func IncreaseLiquidity -send "20000000ugnot" -args 1 -args 20000000 -args 20000000 -args 1 -args 1 -args 9999999999 -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +# decrease liquditiy +gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/position -func DecreaseLiquidity -args 1 -args 12345678 -args 0 -args 0 -args 9999999999 -args "false" -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +# swap exact in +gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args g148tjamj80yyrm309z7rk690an22thd2l3z8ank -args 18446744073709551615 -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args g1lm2l7tf49h3mykesct7rhfml30yx8dw5xrval7 -args 18446744073709551615 -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/router -func ExactInSwapRoute -args "gno.land/r/gnoswap/v1/gns" -args "gno.land/r/demo/wugnot" -args 50000 -args "gno.land/r/gnoswap/v1/gns:gno.land/r/demo/wugnot:3000" -args "100" -args "0" -args 9999999999 -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +# swap exact out +gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gns -func Approve -args g148tjamj80yyrm309z7rk690an22thd2l3z8ank -args 18446744073709551615 -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args g1lm2l7tf49h3mykesct7rhfml30yx8dw5xrval7 -args 18446744073709551615 -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/router -func ExactOutSwapRoute -args "gno.land/r/gnoswap/v1/gns" -args "gno.land/r/demo/wugnot" -args 50000 -args "gno.land/r/gnoswap/v1/gns:gno.land/r/demo/wugnot:3000" -args "100" -args "60000" -args 9999999999 -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +# stake position +gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/gnft -func Approve -args g1cceshmzzlmrh7rr3z30j2t5mrvsq9yccysw9nu -args 1 -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/staker -func StakeToken -args 1 -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +# collect staking reward +gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/staker -func CollectReward -args 1 -args false -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 + +# unstake token +gnokey maketx call -pkgpath gno.land/r/gnoswap/v1/staker -func UnStakeToken -args 1 -args true -gas-fee 1ugnot -gas-wanted 3000000000 -broadcast -chainid=tendermint_test test1 diff --git a/tests/integration/testdata_test.go b/tests/integration/testdata_test.go new file mode 100644 index 000000000..ba4d5176d --- /dev/null +++ b/tests/integration/testdata_test.go @@ -0,0 +1,67 @@ +package integration + +import ( + "os" + "strconv" + "testing" + + gno_integration "github.com/gnolang/gno/gnovm/pkg/integration" + "github.com/rogpeppe/go-internal/testscript" + "github.com/stretchr/testify/require" +) + +func TestTestdata(t *testing.T) { + t.Parallel() + + flagInMemoryTS, _ := strconv.ParseBool(os.Getenv("INMEMORY_TS")) + flagNoSeqTS, _ := strconv.ParseBool(os.Getenv("NO_SEQ_TS")) + + p := gno_integration.NewTestingParams(t, "testdata") + + if coverdir, ok := gno_integration.ResolveCoverageDir(); ok { + err := gno_integration.SetupTestscriptsCoverage(&p, coverdir) + require.NoError(t, err) + } + + // Set up gnoland for testscript + err := SetupGnolandTestscript(t, &p) + require.NoError(t, err) + + mode := commandKindTesting + if flagInMemoryTS { + mode = commandKindInMemory + } + + origSetup := p.Setup + p.Setup = func(env *testscript.Env) error { + env.Values[envKeyExecCommand] = mode + if origSetup != nil { + if err := origSetup(env); err != nil { + return err + } + } + + return nil + } + + if flagInMemoryTS && !flagNoSeqTS { + testscript.RunT(tSeqShim{t}, p) + } else { + testscript.Run(t, p) + } +} + +type tSeqShim struct{ *testing.T } + +// noop Parallel method allow us to run test sequentially +func (tSeqShim) Parallel() {} + +func (t tSeqShim) Run(name string, f func(testscript.T)) { + t.T.Run(name, func(t *testing.T) { + f(tSeqShim{t}) + }) +} + +func (t tSeqShim) Verbose() bool { + return testing.Verbose() +} diff --git a/tests/integration/testscript_gnoland.go b/tests/integration/testscript_gnoland.go new file mode 100644 index 000000000..1531b83df --- /dev/null +++ b/tests/integration/testscript_gnoland.go @@ -0,0 +1,782 @@ +package integration + +import ( + "bytes" + "context" + "errors" + "flag" + "fmt" + "hash/crc32" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "sync" + "testing" + "time" + + "github.com/gnolang/gno/gno.land/pkg/gnoland" + "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" + "github.com/gnolang/gno/gno.land/pkg/keyscli" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/tm2/pkg/amino" + rpcclient "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" + ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" + 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/bip39" + "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" + "github.com/gnolang/gno/tm2/pkg/crypto/hd" + "github.com/gnolang/gno/tm2/pkg/crypto/keys" + "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" + "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/rogpeppe/go-internal/testscript" + "github.com/stretchr/testify/require" +) + +const nodeMaxLifespan = time.Second * 30 + +type envKey int + +const ( + envKeyGenesis envKey = iota + envKeyLogger + envKeyPkgsLoader + envKeyPrivValKey + envKeyExecCommand + envKeyExecBin + envKeyBase +) + +type commandkind int + +const ( + // commandKindBin builds and uses an integration binary to run the testscript + // in a separate process. This should be used for any external package that + // wants to use test scripts. + commandKindBin commandkind = iota + // commandKindTesting uses the current testing binary to run the testscript + // in a separate process. This command cannot be used outside this package. + commandKindTesting + // commandKindInMemory runs testscripts in memory. + commandKindInMemory +) + +type tNodeProcess struct { + NodeProcess + cfg *gnoland.InMemoryNodeConfig + nGnoKeyExec uint // Counter for execution of gnokey. +} + +// NodesManager manages access to the nodes map with synchronization. +type NodesManager struct { + nodes map[string]*tNodeProcess + mu sync.RWMutex +} + +// NewNodesManager creates a new instance of NodesManager. +func NewNodesManager() *NodesManager { + return &NodesManager{ + nodes: make(map[string]*tNodeProcess), + } +} + +func (nm *NodesManager) IsNodeRunning(sid string) bool { + nm.mu.RLock() + defer nm.mu.RUnlock() + + _, ok := nm.nodes[sid] + return ok +} + +// Get retrieves a node by its SID. +func (nm *NodesManager) Get(sid string) (*tNodeProcess, bool) { + nm.mu.RLock() + defer nm.mu.RUnlock() + node, exists := nm.nodes[sid] + return node, exists +} + +// Set adds or updates a node in the map. +func (nm *NodesManager) Set(sid string, node *tNodeProcess) { + nm.mu.Lock() + defer nm.mu.Unlock() + nm.nodes[sid] = node +} + +// Delete removes a node from the map. +func (nm *NodesManager) Delete(sid string) { + nm.mu.Lock() + defer nm.mu.Unlock() + delete(nm.nodes, sid) +} + +func SetupGnolandTestscript(t *testing.T, p *testscript.Params) error { + t.Helper() + + gnoRootDir := gnoenv.RootDir() + + nodesManager := NewNodesManager() + + defaultPK, err := GeneratePrivKeyFromMnemonic(DefaultAccount_Seed, "", 0, 0) + require.NoError(t, err) + + var buildOnce sync.Once + var gnolandBin string + + // Store the original setup scripts for potential wrapping + origSetup := p.Setup + p.Setup = func(env *testscript.Env) error { + // If there's an original setup, execute it + if origSetup != nil { + if err := origSetup(env); err != nil { + return err + } + } + + cmd, isSet := env.Values[envKeyExecCommand].(commandkind) + switch { + case !isSet: + cmd = commandKindBin // fallback on commandKindBin + fallthrough + case cmd == commandKindBin: + buildOnce.Do(func() { + t.Logf("building the gnoland integration node") + start := time.Now() + gnolandBin = buildGnoland(t, gnoRootDir) + t.Logf("time to build the node: %v", time.Since(start).String()) + }) + + env.Values[envKeyExecBin] = gnolandBin + } + + tmpdir, dbdir := t.TempDir(), t.TempDir() + gnoHomeDir := filepath.Join(tmpdir, "gno") + + kb, err := keys.NewKeyBaseFromDir(gnoHomeDir) + if err != nil { + return err + } + + kb.ImportPrivKey(DefaultAccount_Name, defaultPK, "") + env.Setenv(DefaultAccount_Name+"_user_seed", DefaultAccount_Seed) + env.Setenv(DefaultAccount_Name+"_user_addr", DefaultAccount_Address) + + // New private key + env.Values[envKeyPrivValKey] = ed25519.GenPrivKey() + + // Set gno dbdir + env.Setenv("GNO_DBDIR", dbdir) + + // Setup account store + env.Values[envKeyBase] = kb + + // Generate node short id + var sid string + { + works := env.Getenv("WORK") + sum := crc32.ChecksumIEEE([]byte(works)) + sid = strconv.FormatUint(uint64(sum), 16) + env.Setenv("SID", sid) + } + + balanceFile := LoadDefaultGenesisBalanceFile(t, gnoRootDir) + genesisParamFile := LoadDefaultGenesisParamFile(t, gnoRootDir) + + // Track new user balances added via the `adduser` + // command and packages added with the `loadpkg` command. + // This genesis will be use when node is started. + genesis := &gnoland.GnoGenesisState{ + Balances: balanceFile, + Params: genesisParamFile, + Txs: []gnoland.TxWithMetadata{}, + } + + env.Values[envKeyGenesis] = genesis + env.Values[envKeyPkgsLoader] = NewPkgsLoader() + + env.Setenv("GNOROOT", gnoRootDir) + env.Setenv("GNOHOME", gnoHomeDir) + + env.Defer(func() { + // Gracefully stop the node, if any + n, exist := nodesManager.Get(sid) + if !exist { + return + } + + if err := n.Stop(); err != nil { + err = fmt.Errorf("unable to stop the node gracefully: %w", err) + env.T().Fatal(err.Error()) + } + }) + + return nil + } + + cmds := map[string]func(ts *testscript.TestScript, neg bool, args []string){ + "gnoland": gnolandCmd(t, nodesManager, gnoRootDir), + "gnokey": gnokeyCmd(nodesManager), + "adduser": adduserCmd(nodesManager), + "adduserfrom": adduserfromCmd(nodesManager), + "patchpkg": patchpkgCmd(), + "loadpkg": loadpkgCmd(gnoRootDir), + "scanf": loadpkgCmd(gnoRootDir), + } + + // Initialize cmds map if needed + if p.Cmds == nil { + p.Cmds = make(map[string]func(ts *testscript.TestScript, neg bool, args []string)) + } + + // Register gnoland command + for cmd, call := range cmds { + if _, exist := p.Cmds[cmd]; exist { + panic(fmt.Errorf("unable register %q: command already exist", cmd)) + } + + p.Cmds[cmd] = call + } + + return nil +} + +func gnolandCmd(t *testing.T, nodesManager *NodesManager, gnoRootDir string) func(ts *testscript.TestScript, neg bool, args []string) { + t.Helper() + + defaultPK, err := GeneratePrivKeyFromMnemonic(DefaultAccount_Seed, "", 0, 0) + require.NoError(t, err) + + return func(ts *testscript.TestScript, neg bool, args []string) { + sid := getNodeSID(ts) + + cmd, cmdargs := "", []string{} + if len(args) > 0 { + cmd, cmdargs = args[0], args[1:] + } + + var err error + switch cmd { + case "": + err = errors.New("no command provided") + case "start": + if nodesManager.IsNodeRunning(sid) { + err = fmt.Errorf("node already started") + break + } + + // XXX: this is a bit hacky, we should consider moving + // gnoland into his own package to be able to use it + // directly or use the config command for this. + fs := flag.NewFlagSet("start", flag.ContinueOnError) + nonVal := fs.Bool("non-validator", false, "set up node as a non-validator") + if err := fs.Parse(cmdargs); err != nil { + ts.Fatalf("unable to parse `gnoland start` flags: %s", err) + } + + pkgs := ts.Value(envKeyPkgsLoader).(*PkgsLoader) + defaultFee := std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1000000))) + pkgsTxs, err := pkgs.LoadPackages(defaultPK, defaultFee, nil) + if err != nil { + ts.Fatalf("unable to load packages txs: %s", err) + } + + cfg := TestingMinimalNodeConfig(gnoRootDir) + genesis := ts.Value(envKeyGenesis).(*gnoland.GnoGenesisState) + genesis.Txs = append(pkgsTxs, genesis.Txs...) + + cfg.Genesis.AppState = *genesis + if *nonVal { + pv := gnoland.NewMockedPrivValidator() + cfg.Genesis.Validators = []bft.GenesisValidator{ + { + Address: pv.GetPubKey().Address(), + PubKey: pv.GetPubKey(), + Power: 10, + Name: "none", + }, + } + } + + ctx, cancel := context.WithTimeout(context.Background(), nodeMaxLifespan) + ts.Defer(cancel) + + dbdir := ts.Getenv("GNO_DBDIR") + priv := ts.Value(envKeyPrivValKey).(ed25519.PrivKeyEd25519) + nodep := setupNode(ts, ctx, &ProcessNodeConfig{ + ValidatorKey: priv, + DBDir: dbdir, + RootDir: gnoRootDir, + TMConfig: cfg.TMConfig, + Genesis: NewMarshalableGenesisDoc(cfg.Genesis), + }) + + nodesManager.Set(sid, &tNodeProcess{NodeProcess: nodep, cfg: cfg}) + ts.Setenv("RPC_ADDR", nodep.Address()) + + // Load user infos + loadUserEnv(ts, nodep.Address()) + + fmt.Fprintln(ts.Stdout(), "node started successfully") + + case "restart": + node, exists := nodesManager.Get(sid) + if !exists { + err = fmt.Errorf("node must be started before being restarted") + break + } + + if err := node.Stop(); err != nil { + err = fmt.Errorf("unable to stop the node gracefully: %w", err) + break + } + + ctx, cancel := context.WithTimeout(context.Background(), nodeMaxLifespan) + ts.Defer(cancel) + + priv := ts.Value(envKeyPrivValKey).(ed25519.PrivKeyEd25519) + dbdir := ts.Getenv("GNO_DBDIR") + nodep := setupNode(ts, ctx, &ProcessNodeConfig{ + ValidatorKey: priv, + DBDir: dbdir, + RootDir: gnoRootDir, + TMConfig: node.cfg.TMConfig, + Genesis: NewMarshalableGenesisDoc(node.cfg.Genesis), + }) + + ts.Setenv("RPC_ADDR", nodep.Address()) + nodesManager.Set(sid, &tNodeProcess{NodeProcess: nodep, cfg: node.cfg}) + + // Load user infos + loadUserEnv(ts, nodep.Address()) + + fmt.Fprintln(ts.Stdout(), "node restarted successfully") + + case "stop": + node, exists := nodesManager.Get(sid) + if !exists { + err = fmt.Errorf("node not started cannot be stopped") + break + } + + if err := node.Stop(); err != nil { + err = fmt.Errorf("unable to stop the node gracefully: %w", err) + break + } + + fmt.Fprintln(ts.Stdout(), "node stopped successfully") + nodesManager.Delete(sid) + + default: + err = fmt.Errorf("not supported command: %q", cmd) + // XXX: support gnoland other commands + } + + tsValidateError(ts, strings.TrimSpace("gnoland "+cmd), neg, err) + } +} + +func gnokeyCmd(nodes *NodesManager) func(ts *testscript.TestScript, neg bool, args []string) { + return func(ts *testscript.TestScript, neg bool, args []string) { + gnoHomeDir := ts.Getenv("GNOHOME") + + sid := getNodeSID(ts) + + args, err := unquote(args) + if err != nil { + tsValidateError(ts, "gnokey", neg, err) + } + + io := commands.NewTestIO() + io.SetOut(commands.WriteNopCloser(ts.Stdout())) + io.SetErr(commands.WriteNopCloser(ts.Stderr())) + cmd := keyscli.NewRootCmd(io, client.DefaultBaseOptions) + + io.SetIn(strings.NewReader("\n")) + defaultArgs := []string{ + "-home", gnoHomeDir, + "-insecure-password-stdin=true", + } + + if n, ok := nodes.Get(sid); ok { + if raddr := n.Address(); raddr != "" { + defaultArgs = append(defaultArgs, "-remote", raddr) + } + + n.nGnoKeyExec++ + } + + args = append(defaultArgs, args...) + + err = cmd.ParseAndRun(context.Background(), args) + tsValidateError(ts, "gnokey", neg, err) + } +} + +func adduserCmd(nodesManager *NodesManager) func(ts *testscript.TestScript, neg bool, args []string) { + return func(ts *testscript.TestScript, neg bool, args []string) { + gnoHomeDir := ts.Getenv("GNOHOME") + + sid := getNodeSID(ts) + if nodesManager.IsNodeRunning(sid) { + 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) + } + + genesis := ts.Value(envKeyGenesis).(*gnoland.GnoGenesisState) + genesis.Balances = append(genesis.Balances, balance) + } +} + +func adduserfromCmd(nodesManager *NodesManager) func(ts *testscript.TestScript, neg bool, args []string) { + return func(ts *testscript.TestScript, neg bool, args []string) { + gnoHomeDir := ts.Getenv("GNOHOME") + + sid := getNodeSID(ts) + if nodesManager.IsNodeRunning(sid) { + tsValidateError(ts, "adduserfrom", neg, errors.New("adduserfrom must be used before starting node")) + return + } + + var account, index uint64 + var err error + + switch len(args) { + case 2: + case 4: + index, err = strconv.ParseUint(args[3], 10, 32) + if err != nil { + ts.Fatalf("invalid index number %s", args[3]) + } + fallthrough + case 3: + account, err = strconv.ParseUint(args[2], 10, 32) + if err != nil { + ts.Fatalf("invalid account number %s", args[2]) + } + default: + ts.Fatalf("to create account from metadatas, user name and mnemonic are required ( account and index are optional )") + } + + kb, err := keys.NewKeyBaseFromDir(gnoHomeDir) + if err != nil { + ts.Fatalf("unable to get keybase") + } + + balance, err := createAccountFrom(ts, kb, args[0], args[1], uint32(account), uint32(index)) + if err != nil { + ts.Fatalf("error creating wallet %s", err) + } + + genesis := ts.Value(envKeyGenesis).(*gnoland.GnoGenesisState) + genesis.Balances = append(genesis.Balances, balance) + + fmt.Fprintf(ts.Stdout(), "Added %s(%s) to genesis", args[0], balance.Address) + } +} + +func patchpkgCmd() func(ts *testscript.TestScript, neg bool, args []string) { + return func(ts *testscript.TestScript, neg bool, args []string) { + args, err := unquote(args) + if err != nil { + tsValidateError(ts, "patchpkg", neg, err) + } + + if len(args) != 2 { + ts.Fatalf("`patchpkg`: should have exactly 2 arguments") + } + + pkgs := ts.Value(envKeyPkgsLoader).(*PkgsLoader) + replace, with := args[0], args[1] + pkgs.SetPatch(replace, with) + } +} + +func loadpkgCmd(gnoRootDir string) func(ts *testscript.TestScript, neg bool, args []string) { + return func(ts *testscript.TestScript, neg bool, args []string) { + workDir := ts.Getenv("WORK") + examplesDir := filepath.Join(gnoRootDir, "examples") + + pkgs := ts.Value(envKeyPkgsLoader).(*PkgsLoader) + + var path, name string + switch len(args) { + case 2: + name = args[0] + path = filepath.Clean(args[1]) + case 1: + path = filepath.Clean(args[0]) + case 0: + ts.Fatalf("`loadpkg`: no arguments specified") + default: + ts.Fatalf("`loadpkg`: too many arguments specified") + } + + if path == "all" { + ts.Logf("warning: loading all packages") + if err := pkgs.LoadAllPackagesFromDir(examplesDir); err != nil { + ts.Fatalf("unable to load packages from %q: %s", examplesDir, err) + } + + return + } + + if !strings.HasPrefix(path, workDir) { + path = filepath.Join(examplesDir, path) + } + + if err := pkgs.LoadPackage(examplesDir, path, name); err != nil { + ts.Fatalf("`loadpkg` unable to load package(s) from %q: %s", args[0], err) + } + + ts.Logf("%q package was added to genesis", args[0]) + } +} + +func loadUserEnv(ts *testscript.TestScript, remote string) error { + const path = "auth/accounts" + + // List all accounts + kb := ts.Value(envKeyBase).(keys.Keybase) + accounts, err := kb.List() + if err != nil { + ts.Fatalf("query accounts: unable to list keys: %s", err) + } + + cli, err := rpcclient.NewHTTPClient(remote) + if err != nil { + return fmt.Errorf("unable create rpc client %q: %w", remote, err) + } + + batch := cli.NewBatch() + for _, account := range accounts { + accountPath := filepath.Join(path, account.GetAddress().String()) + if err := batch.ABCIQuery(accountPath, []byte{}); err != nil { + return fmt.Errorf("unable to create query request: %w", err) + } + } + + batchRes, err := batch.Send(context.Background()) + if err != nil { + return fmt.Errorf("unable to query accounts: %w", err) + } + + if len(batchRes) != len(accounts) { + ts.Fatalf("query accounts: len(res) != len(accounts)") + } + + for i, res := range batchRes { + account := accounts[i] + name := account.GetName() + qres := res.(*ctypes.ResultABCIQuery) + + if err := qres.Response.Error; err != nil { + ts.Fatalf("query account %q error: %s", account.GetName(), err.Error()) + } + + var qret struct{ BaseAccount std.BaseAccount } + if err = amino.UnmarshalJSON(qres.Response.Data, &qret); err != nil { + ts.Fatalf("query account %q unarmshal error: %s", account.GetName(), err.Error()) + } + + strAccountNumber := strconv.Itoa(int(qret.BaseAccount.GetAccountNumber())) + ts.Setenv(name+"_account_num", strAccountNumber) + ts.Logf("[%q] account number: %s", name, strAccountNumber) + + strAccountSequence := strconv.Itoa(int(qret.BaseAccount.GetSequence())) + ts.Setenv(name+"_account_seq", strAccountSequence) + ts.Logf("[%q] account sequence: %s", name, strAccountNumber) + } + + return nil +} + +type tsLogWriter struct { + ts *testscript.TestScript +} + +func (l *tsLogWriter) Write(p []byte) (n int, err error) { + l.ts.Logf(string(p)) + return len(p), nil +} + +func setupNode(ts *testscript.TestScript, ctx context.Context, cfg *ProcessNodeConfig) NodeProcess { + pcfg := ProcessConfig{ + Node: cfg, + Stdout: &tsLogWriter{ts}, + Stderr: ts.Stderr(), + } + + // Setup coverdir provided + if coverdir := ts.Getenv("GOCOVERDIR"); coverdir != "" { + pcfg.CoverDir = coverdir + } + + val := ts.Value(envKeyExecCommand) + + switch cmd := val.(commandkind); cmd { + case commandKindInMemory: + nodep, err := RunInMemoryProcess(ctx, pcfg) + if err != nil { + ts.Fatalf("unable to start in memory node: %s", err) + } + + return nodep + + case commandKindTesting: + if !testing.Testing() { + ts.Fatalf("unable to invoke testing process while not testing") + } + + return runTestingNodeProcess(&testingTS{ts}, ctx, pcfg) + + case commandKindBin: + bin := ts.Value(envKeyExecBin).(string) + nodep, err := RunNodeProcess(ctx, pcfg, bin) + if err != nil { + ts.Fatalf("unable to start process node: %s", err) + } + + return nodep + + default: + ts.Fatalf("unknown command kind: %+v", cmd) + } + + return nil +} + +// createAccount creates a new account with the given name and adds it to the keybase. +func createAccount(ts *testscript.TestScript, 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) + } + + return createAccountFrom(ts, kb, accountName, mnemonic, 0, 0) +} + +// createAccountFrom creates a new account with the given metadata and adds it to the keybase. +func createAccountFrom(ts *testscript.TestScript, kb keys.Keybase, accountName, mnemonic string, account, index uint32) (gnoland.Balance, error) { + var balance gnoland.Balance + + // check if mnemonic is valid + if !bip39.IsMnemonicValid(mnemonic) { + return balance, fmt.Errorf("invalid mnemonic") + } + + keyInfo, err := kb.CreateAccount(accountName, mnemonic, "", "", account, index) + if err != nil { + return balance, fmt.Errorf("unable to create account: %w", err) + } + + address := keyInfo.GetAddress() + ts.Setenv(accountName+"_user_seed", mnemonic) + ts.Setenv(accountName+"_user_addr", address.String()) + + return gnoland.Balance{ + Address: address, + Amount: std.Coins{std.NewCoin(ugnot.Denom, 10e6)}, + }, nil +} + +func buildGnoland(t *testing.T, rootdir string) string { + t.Helper() + + bin := filepath.Join(t.TempDir(), "gnoland-test") + + t.Log("building gnoland integration binary...") + + // Build a fresh gno binary in a temp directory + gnoArgsBuilder := []string{"build", "-o", bin} + + os.Executable() + + // Forward `-covermode` settings if set + if coverMode := testing.CoverMode(); coverMode != "" { + gnoArgsBuilder = append(gnoArgsBuilder, + "-covermode", coverMode, + ) + } + + // Append the path to the gno command source + gnoArgsBuilder = append(gnoArgsBuilder, filepath.Join(rootdir, + "gno.land", "pkg", "integration", "process")) + + t.Logf("build command: %s", strings.Join(gnoArgsBuilder, " ")) + + cmd := exec.Command("go", gnoArgsBuilder...) + + var buff bytes.Buffer + cmd.Stderr, cmd.Stdout = &buff, &buff + defer buff.Reset() + + if err := cmd.Run(); err != nil { + require.FailNowf(t, "unable to build binary", "%q\n%s", + err.Error(), buff.String()) + } + + return bin +} + +// GeneratePrivKeyFromMnemonic generates a crypto.PrivKey from a mnemonic. +func GeneratePrivKeyFromMnemonic(mnemonic, bip39Passphrase string, account, index uint32) (crypto.PrivKey, error) { + // Generate Seed from Mnemonic + seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase) + if err != nil { + return nil, fmt.Errorf("failed to generate seed: %w", err) + } + + // Derive Private Key + coinType := crypto.CoinType // ensure this is set correctly in your context + hdPath := hd.NewFundraiserParams(account, coinType, index) + masterPriv, ch := hd.ComputeMastersFromSeed(seed) + derivedPriv, err := hd.DerivePrivateKeyForPath(masterPriv, ch, hdPath.String()) + if err != nil { + return nil, fmt.Errorf("failed to derive private key: %w", err) + } + + // Convert to secp256k1 private key + privKey := secp256k1.PrivKeySecp256k1(derivedPriv) + return privKey, nil +} + +func getNodeSID(ts *testscript.TestScript) string { + return ts.Getenv("SID") +} + +func tsValidateError(ts *testscript.TestScript, cmd string, neg bool, err error) { + if err != nil { + fmt.Fprintf(ts.Stderr(), "%q error: %+v\n", cmd, err) + if !neg { + ts.Fatalf("unexpected %q command failure: %s", cmd, err) + } + } else { + if neg { + ts.Fatalf("unexpected %q command success", cmd) + } + } +} diff --git a/tests/integration/testscript_testing.go b/tests/integration/testscript_testing.go new file mode 100644 index 000000000..9eed180dd --- /dev/null +++ b/tests/integration/testscript_testing.go @@ -0,0 +1,41 @@ +package integration + +import ( + "errors" + "testing" + + "github.com/rogpeppe/go-internal/testscript" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// This error is from testscript.Fatalf and is needed to correctly +// handle the FailNow method. +// see: https://github.com/rogpeppe/go-internal/blob/32ae33786eccde1672d4ba373c80e1bc282bfbf6/testscript/testscript.go#L799-L812 +var errFailNow = errors.New("fail now!") //nolint:stylecheck + +var ( + _ require.TestingT = (*testingTS)(nil) + _ assert.TestingT = (*testingTS)(nil) + _ TestingTS = &testing.T{} +) + +type TestingTS = require.TestingT + +type testingTS struct { + *testscript.TestScript +} + +func TSTestingT(ts *testscript.TestScript) TestingTS { + return &testingTS{ts} +} + +func (t *testingTS) Errorf(format string, args ...interface{}) { + defer recover() // we can ignore recover result, we just want to catch it up + t.Fatalf(format, args...) +} + +func (t *testingTS) FailNow() { + // unfortunately we can't access underlying `t.t.FailNow` method + panic(errFailNow) +} diff --git a/tests/integration/utils.go b/tests/integration/utils.go new file mode 100644 index 000000000..bc9e7f1e2 --- /dev/null +++ b/tests/integration/utils.go @@ -0,0 +1,73 @@ +package integration + +import ( + "errors" + "fmt" + "strconv" + "strings" +) + +// `unquote` takes a slice of strings, resulting from splitting a string block by spaces, and +// processes them. The function handles quoted phrases and escape characters within these strings. +func unquote(args []string) ([]string, error) { + const quote = '"' + + parts := []string{} + var inQuote bool + + var part strings.Builder + for _, arg := range args { + var escaped bool + for _, c := range arg { + if escaped { + // If the character is meant to be escaped, it is processed with Unquote. + // We use `Unquote` here for two main reasons: + // 1. It will validate that the escape sequence is correct + // 2. It converts the escaped string to its corresponding raw character. + // For example, "\\t" becomes '\t'. + uc, err := strconv.Unquote(`"\` + string(c) + `"`) + if err != nil { + return nil, fmt.Errorf("unhandled escape sequence `\\%c`: %w", c, err) + } + + part.WriteString(uc) + escaped = false + continue + } + + // If we are inside a quoted string and encounter an escape character, + // flag the next character as `escaped` + if inQuote && c == '\\' { + escaped = true + continue + } + + // Detect quote and toggle inQuote state + if c == quote { + inQuote = !inQuote + continue + } + + // Handle regular character + part.WriteRune(c) + } + + // If we're inside a quote, add a single space. + // It reflects one or multiple spaces between args in the original string. + if inQuote { + part.WriteRune(' ') + continue + } + + // Finalize part, add to parts, and reset for next part + parts = append(parts, part.String()) + part.Reset() + } + + // Check if a quote is left open + if inQuote { + return nil, errors.New("unfinished quote") + } + + return parts, nil +} diff --git a/tests/integration/utils_test.go b/tests/integration/utils_test.go new file mode 100644 index 000000000..2c3010649 --- /dev/null +++ b/tests/integration/utils_test.go @@ -0,0 +1,51 @@ +package integration + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUnquote(t *testing.T) { + t.Parallel() + + cases := []struct { + Input string + Expected []string + ShouldFail bool + }{ + {"", []string{""}, false}, + {"g", []string{"g"}, false}, + {"Hello Gno", []string{"Hello", "Gno"}, false}, + {`"Hello" "Gno"`, []string{"Hello", "Gno"}, false}, + {`"Hel lo" "Gno"`, []string{"Hel lo", "Gno"}, false}, + {`"H e l l o\n" \nGno`, []string{"H e l l o\n", "\\nGno"}, false}, + {`"Hel\n"\nlo " ""G"n"o"`, []string{"Hel\n\\nlo", " Gno"}, false}, + {`"He said, \"Hello\"" "Gno"`, []string{`He said, "Hello"`, "Gno"}, false}, + {`"\n \t" \n\t`, []string{"\n \t", "\\n\\t"}, false}, + {`"Hel\\n"\t\\nlo " ""\\nGno"`, []string{"Hel\\n\\t\\\\nlo", " \\nGno"}, false}, + // errors: + {`"Hello Gno`, []string{}, true}, // unfinished quote + {`"Hello\e Gno"`, []string{}, true}, // unhandled escape sequence + } + + for _, tc := range cases { + tc := tc + t.Run(tc.Input, func(t *testing.T) { + t.Parallel() + + // split by whitespace to simulate command-line arguments + args := strings.Split(tc.Input, " ") + unquotedArgs, err := unquote(args) + if tc.ShouldFail { + require.Error(t, err) + return + } + + require.NoError(t, err) + assert.Equal(t, tc.Expected, unquotedArgs) + }) + } +}