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  }