diff --git a/chain/cosmos/chain_node.go b/chain/cosmos/chain_node.go index 93e28f372..00e46ac39 100644 --- a/chain/cosmos/chain_node.go +++ b/chain/cosmos/chain_node.go @@ -537,7 +537,7 @@ func (tn *ChainNode) ExecTx(ctx context.Context, keyName string, command ...stri tn.lock.Lock() defer tn.lock.Unlock() - stdout, _, err := tn.Exec(ctx, tn.TxCommand(keyName, command...), nil) + stdout, _, err := tn.Exec(ctx, tn.TxCommand(keyName, command...), tn.Chain.Config().Env) if err != nil { return "", err } @@ -617,7 +617,7 @@ func (tn *ChainNode) BinCommand(command ...string) []string { // pass ("keys", "show", "key1") for command to execute the command against the node. // Will include additional flags for home directory and chain ID. func (tn *ChainNode) ExecBin(ctx context.Context, command ...string) ([]byte, []byte, error) { - return tn.Exec(ctx, tn.BinCommand(command...), nil) + return tn.Exec(ctx, tn.BinCommand(command...), tn.Chain.Config().Env) } // QueryCommand is a helper to retrieve the full query command. For example, @@ -636,7 +636,7 @@ func (tn *ChainNode) QueryCommand(command ...string) []string { // pass ("gov", "params") for command to execute the query against the node. // Returns response in json format. func (tn *ChainNode) ExecQuery(ctx context.Context, command ...string) ([]byte, []byte, error) { - return tn.Exec(ctx, tn.QueryCommand(command...), nil) + return tn.Exec(ctx, tn.QueryCommand(command...), tn.Chain.Config().Env) } // CondenseMoniker fits a moniker into the cosmos character limit for monikers. @@ -731,7 +731,7 @@ func (tn *ChainNode) RecoverKey(ctx context.Context, keyName, mnemonic string) e tn.lock.Lock() defer tn.lock.Unlock() - _, _, err := tn.Exec(ctx, command, nil) + _, _, err := tn.Exec(ctx, command, tn.Chain.Config().Env) return err } @@ -827,7 +827,7 @@ func (tn *ChainNode) CollectGentxs(ctx context.Context) error { tn.lock.Lock() defer tn.lock.Unlock() - _, _, err := tn.Exec(ctx, command, nil) + _, _, err := tn.Exec(ctx, command, tn.Chain.Config().Env) return err } @@ -1076,7 +1076,7 @@ func (tn *ChainNode) UnsafeResetAll(ctx context.Context) error { command = append(command, "unsafe-reset-all", "--home", tn.HomeDir()) - _, _, err := tn.Exec(ctx, command, nil) + _, _, err := tn.Exec(ctx, command, tn.Chain.Config().Env) return err } @@ -1326,7 +1326,7 @@ func (tn *ChainNode) KeyBech32(ctx context.Context, name string, bech string) (s command = append(command, "--bech", bech) } - stdout, stderr, err := tn.Exec(ctx, command, nil) + stdout, stderr, err := tn.Exec(ctx, command, tn.Chain.Config().Env) if err != nil { return "", fmt.Errorf("failed to show key %q (stderr=%q): %w", name, stderr, err) } diff --git a/chain/internal/tendermint/tendermint_node.go b/chain/internal/tendermint/tendermint_node.go index 5c927f935..d334a7f8a 100644 --- a/chain/internal/tendermint/tendermint_node.go +++ b/chain/internal/tendermint/tendermint_node.go @@ -253,7 +253,7 @@ func (tn *TendermintNode) InitHomeFolder(ctx context.Context, mode string) error command := []string{tn.Chain.Config().Bin, "init", mode, "--home", tn.HomeDir(), } - _, _, err := tn.Exec(ctx, command, nil) + _, _, err := tn.Exec(ctx, command, tn.Chain.Config().Env) return err } diff --git a/dockerutil/file.go b/dockerutil/file.go index 0780030b7..78cdd5353 100644 --- a/dockerutil/file.go +++ b/dockerutil/file.go @@ -1,9 +1,19 @@ package dockerutil import ( + "archive/tar" + "bytes" + "context" "fmt" "io" "os" + "path" + "path/filepath" + "strings" + "testing" + + "github.com/docker/docker/client" + "github.com/stretchr/testify/require" ) func CopyFile(src, dst string) (int64, error) { @@ -30,3 +40,41 @@ func CopyFile(src, dst string) (int64, error) { nBytes, err := io.Copy(destination, source) return nBytes, err } + +func CopyCoverageFromContainer(ctx context.Context, t *testing.T, client *client.Client, containerId string, internalGoCoverDir string, extHostGoCoverDir string) { + r, _, err := client.CopyFromContainer(ctx, containerId, internalGoCoverDir) + require.NoError(t, err) + defer r.Close() + + err = os.MkdirAll(extHostGoCoverDir, os.ModePerm) + require.NoError(t, err) + + tr := tar.NewReader(r) + for { + hdr, err := tr.Next() + if err == io.EOF { + break // End of archive + } + require.NoError(t, err) + + var fileBuff bytes.Buffer + _, err = io.Copy(&fileBuff, tr) + require.NoError(t, err) + + name := hdr.Name + extractedFileName := path.Base(name) + + //Only extract coverage files + if !strings.HasPrefix(extractedFileName, "cov") { + continue + } + isDirectory := extractedFileName == "" + if isDirectory { + continue + } + + filePath := filepath.Join(extHostGoCoverDir, extractedFileName) + err = os.WriteFile(filePath, fileBuff.Bytes(), os.ModePerm) + require.NoError(t, err) + } +} diff --git a/examples/cosmos/code_coverage_test.go b/examples/cosmos/code_coverage_test.go new file mode 100644 index 000000000..d870cec71 --- /dev/null +++ b/examples/cosmos/code_coverage_test.go @@ -0,0 +1,91 @@ +package cosmos_test + +import ( + "context" + "os" + "testing" + + "github.com/strangelove-ventures/interchaintest/v8" + "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" + "github.com/strangelove-ventures/interchaintest/v8/dockerutil" + "github.com/strangelove-ventures/interchaintest/v8/ibc" + "github.com/strangelove-ventures/interchaintest/v8/testreporter" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest" +) + +func TestCodeCoverage(t *testing.T) { + t.Parallel() + + var ( + ctx = context.Background() + ExternalGoCoverDir = "/tmp/interchaintest-app-coverage" + Denom = "umfx" + vals = 1 + fullNodes = 0 + ) + + cfgA := ibc.ChainConfig{ + Type: "cosmos", + Name: "manifest", + ChainID: "manifest-2", + Images: []ibc.DockerImage{ + { + Repository: "ghcr.io/liftedinit/manifest-ledger", + Version: "v0.0.1-alpha.10", + UidGid: "1025:1025", + }, + }, + Bin: "manifestd", + Bech32Prefix: "manifest", + Denom: Denom, + GasPrices: "0" + Denom, + GasAdjustment: 1.3, + TrustingPeriod: "508h", + NoHostMount: false, + } + + cfgA.WithCodeCoverage() + + cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t, zaptest.Level(zapcore.DebugLevel)), []*interchaintest.ChainSpec{ + { + Name: "manifest", + Version: cfgA.Images[0].Version, + ChainName: cfgA.ChainID, + NumValidators: &vals, + NumFullNodes: &fullNodes, + ChainConfig: cfgA, + }, + }) + + chains, err := cf.Chains(t.Name()) + require.NoError(t, err) + chainA := chains[0].(*cosmos.CosmosChain) + + client, network := interchaintest.DockerSetup(t) + + ic := interchaintest.NewInterchain(). + AddChain(chainA) + + rep := testreporter.NewNopReporter() + eRep := rep.RelayerExecReporter(t) + + // Build interchain + require.NoError(t, ic.Build(ctx, eRep, interchaintest.InterchainBuildOptions{ + TestName: t.Name(), + Client: client, + NetworkID: network, + SkipPathCreation: false, + })) + + t.Cleanup(func() { + dockerutil.CopyCoverageFromContainer(ctx, t, client, chainA.GetNode().ContainerID(), chainA.HomeDir(), ExternalGoCoverDir) + + files, err := os.ReadDir(ExternalGoCoverDir) + require.NoError(t, err) + require.NotEmpty(t, files) + + _ = ic.Close() + }) +} diff --git a/ibc/types.go b/ibc/types.go index 226a1c667..332263dfd 100644 --- a/ibc/types.go +++ b/ibc/types.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "path" "reflect" "strconv" "strings" @@ -226,6 +227,14 @@ func (c ChainConfig) MergeChainSpecConfig(other ChainConfig) ChainConfig { return c } +// WithCodeCoverage enables Go Code Coverage from the chain node directory. +func (c *ChainConfig) WithCodeCoverage(override ...string) { + c.Env = append(c.Env, fmt.Sprintf("GOCOVERDIR=%s", path.Join("/var/cosmos-chain", c.ChainID))) + if len(override) > 0 { + c.Env = append(c.Env, override[0]) + } +} + // IsFullyConfigured reports whether all required fields have been set on c. // It is possible for some fields, such as GasAdjustment and NoHostMount, // to be their respective zero values and for IsFullyConfigured to still report true.