github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/misc/docker-integration/integration_test.go (about) 1 //go:build docker 2 3 package integration 4 5 import ( 6 "encoding/json" 7 "fmt" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/gnolang/gno/gno.land/pkg/gnoland" 16 "github.com/gnolang/gno/gno.land/pkg/sdk/vm" 17 "github.com/gnolang/gno/tm2/pkg/amino" 18 "github.com/gnolang/gno/tm2/pkg/std" 19 "github.com/stretchr/testify/require" 20 "gopkg.in/yaml.v3" 21 ) 22 23 const ( 24 gnolandContainerName = "int_gnoland" 25 26 test1Addr = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" 27 test1Seed = "source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast" 28 dockerWaitTimeout = 30 29 ) 30 31 func TestDockerIntegration(t *testing.T) { 32 t.Parallel() 33 34 tmpdir, err := os.MkdirTemp(os.TempDir(), "*-gnoland-integration") 35 require.NoError(t, err) 36 37 checkDocker(t) 38 cleanupGnoland(t) 39 buildDockerImage(t) 40 startGnoland(t) 41 waitGnoland(t) 42 43 runSuite(t, tmpdir) 44 } 45 46 func runSuite(t *testing.T, tempdir string) { 47 t.Helper() 48 49 // add test1 account to docker container keys with "pass" password 50 dockerExec(t, fmt.Sprintf( 51 `echo "pass\npass\n%s\n" | gnokey add -recover -insecure-password-stdin test1`, 52 test1Seed, 53 )) 54 // assert test1 account exists 55 var acc gnoland.GnoAccount 56 dockerExec_gnokeyQuery(t, "auth/accounts/"+test1Addr, &acc) 57 require.Equal(t, test1Addr, acc.Address.String(), "test1 account not found") 58 minCoins := std.MustParseCoins("9990000000000ugnot") // This value is chosen arbitrarily and may not be optimal. Feel free to update it to a more suitable amount 59 require.True(t, acc.Coins.IsAllGTE(minCoins), 60 "test1 account coins expected at least %s, got %s", minCoins, acc.Coins) 61 62 // add gno.land/r/demo/tests package as tests_copy 63 dockerExec(t, 64 `echo 'pass' | gnokey maketx addpkg -insecure-password-stdin \ 65 -gas-fee 1000000ugnot -gas-wanted 2000000 \ 66 -broadcast -chainid dev \ 67 -pkgdir /opt/gno/src/examples/gno.land/r/demo/tests/ \ 68 -pkgpath gno.land/r/demo/tests_copy \ 69 -deposit 100000000ugnot \ 70 test1`, 71 ) 72 // assert gno.land/r/demo/tests_copy has been added 73 var qfuncs vm.FunctionSignatures 74 dockerExec_gnokeyQuery(t, `-data "gno.land/r/demo/tests_copy" vm/qfuncs`, &qfuncs) 75 require.True(t, len(qfuncs) > 0, "gno.land/r/demo/tests_copy not added") 76 77 // broadcast a package TX 78 dockerExec(t, 79 `echo 'pass' | gnokey maketx call -insecure-password-stdin \ 80 -gas-fee 1000000ugnot -gas-wanted 2000000 \ 81 -broadcast -chainid dev \ 82 -pkgpath "gno.land/r/demo/tests_copy" -func "InitTestNodes" \ 83 test1`, 84 ) 85 } 86 87 func checkDocker(t *testing.T) { 88 t.Helper() 89 output, err := createCommand(t, []string{"docker", "info"}).CombinedOutput() 90 require.NoError(t, err, "docker daemon not running: %s", string(output)) 91 } 92 93 func buildDockerImage(t *testing.T) { 94 t.Helper() 95 96 cmd := createCommand(t, []string{ 97 "docker", 98 "build", 99 "-t", "gno:integration", 100 filepath.Join("..", ".."), 101 }) 102 output, err := cmd.CombinedOutput() 103 require.NoError(t, err, string(output)) 104 } 105 106 // dockerExec runs docker exec with cmd as argument 107 func dockerExec(t *testing.T, cmd string) []byte { 108 t.Helper() 109 110 cmds := append( 111 []string{"docker", "exec", gnolandContainerName, "sh", "-c"}, 112 cmd, 113 ) 114 bz, err := createCommand(t, cmds).CombinedOutput() 115 require.NoError(t, err, string(bz)) 116 return bz 117 } 118 119 // dockerExec_gnokeyQuery runs dockerExec with gnokey query prefix and parses 120 // the command output to out. 121 func dockerExec_gnokeyQuery(t *testing.T, cmd string, out any) { 122 t.Helper() 123 124 output := dockerExec(t, "gnokey query "+cmd) 125 // parses the output of gnokey query: 126 // height: h 127 // data: { JSON } 128 var resp struct { 129 Height int64 `yaml:"height"` 130 Data any `yaml:"data"` 131 } 132 err := yaml.Unmarshal(output, &resp) 133 require.NoError(t, err) 134 bz, err := json.Marshal(resp.Data) 135 require.NoError(t, err) 136 err = amino.UnmarshalJSON(bz, out) 137 require.NoError(t, err) 138 } 139 140 func createCommand(t *testing.T, args []string) *exec.Cmd { 141 t.Helper() 142 msg := strings.Join(args, " ") 143 t.Log(msg) 144 return exec.Command(args[0], args[1:]...) 145 } 146 147 func startGnoland(t *testing.T) { 148 t.Helper() 149 150 cmd := createCommand(t, []string{ 151 "docker", "run", 152 "-d", 153 "--name", gnolandContainerName, 154 "-w", "/opt/gno/src/gno.land", 155 "gno:integration", 156 "gnoland", 157 "start", 158 }) 159 output, err := cmd.CombinedOutput() 160 require.NoError(t, err) 161 require.NotEmpty(t, string(output)) // should be the hash of the container. 162 163 // t.Cleanup(func() { cleanupGnoland(t) }) 164 } 165 166 func waitGnoland(t *testing.T) { 167 t.Helper() 168 t.Log("waiting...") 169 for i := 0; i < dockerWaitTimeout; i++ { 170 output, _ := createCommand(t, 171 []string{"docker", "logs", gnolandContainerName}, 172 ).CombinedOutput() 173 if strings.Contains(string(output), "Committed state") { 174 // ok blockchain is ready 175 t.Log("gnoland ready") 176 return 177 } 178 time.Sleep(time.Second) 179 } 180 // cleanupGnoland(t) 181 panic("gnoland start timeout") 182 } 183 184 func cleanupGnoland(t *testing.T) { 185 t.Helper() 186 createCommand(t, []string{"docker", "rm", "-f", gnolandContainerName}).Run() 187 }