From 4e9bd4e0ec4f14cb9a701281108a690916b398cb Mon Sep 17 00:00:00 2001 From: Nikolas De Giorgis Date: Thu, 11 Apr 2024 10:30:44 +0100 Subject: [PATCH] tests: add option to dump genesis files in E2E tests (#6100) Co-authored-by: Nikolas De Giorgis Co-authored-by: Cian Hatton Co-authored-by: DimitrisJim --- e2e/internal/directories/directories.go | 40 +++++++++++++ e2e/testsuite/diagnostics/diagnostics.go | 28 +--------- e2e/testsuite/testconfig.go | 71 +++++++++++++++++++++++- e2e/testsuite/testsuite.go | 60 +++++++++++++++++--- 4 files changed, 164 insertions(+), 35 deletions(-) create mode 100644 e2e/internal/directories/directories.go diff --git a/e2e/internal/directories/directories.go b/e2e/internal/directories/directories.go new file mode 100644 index 00000000000..b27590b51bc --- /dev/null +++ b/e2e/internal/directories/directories.go @@ -0,0 +1,40 @@ +package directories + +import ( + "fmt" + "os" + "path" + "strings" + "testing" +) + +const ( + e2eDir = "e2e" + + // DefaultGenesisExportPath is the default path to which Genesis debug files will be exported to. + DefaultGenesisExportPath = "diagnostics/genesis.json" +) + +// E2E finds the e2e directory above the test. +func E2E(t *testing.T) (string, error) { + t.Helper() + + wd, err := os.Getwd() + if err != nil { + return "", err + } + + const maxAttempts = 100 + count := 0 + for ; !strings.HasSuffix(wd, e2eDir) || count > maxAttempts; wd = path.Dir(wd) { + count++ + } + + // arbitrary value to avoid getting stuck in an infinite loop if this is called + // in a context where the e2e directory does not exist. + if count > maxAttempts { + return "", fmt.Errorf("unable to find e2e directory after %d tries", maxAttempts) + } + + return wd, nil +} diff --git a/e2e/testsuite/diagnostics/diagnostics.go b/e2e/testsuite/diagnostics/diagnostics.go index 38ae500947e..4b24a1cfc9f 100644 --- a/e2e/testsuite/diagnostics/diagnostics.go +++ b/e2e/testsuite/diagnostics/diagnostics.go @@ -13,11 +13,11 @@ import ( dockerclient "github.com/docker/docker/client" "github.com/cosmos/ibc-go/e2e/dockerutil" + "github.com/cosmos/ibc-go/e2e/internal/directories" ) const ( dockerInspectFileName = "docker-inspect.json" - e2eDir = "e2e" defaultFilePerm = 0o750 ) @@ -37,7 +37,7 @@ func Collect(t *testing.T, dc *dockerclient.Client, debugModeEnabled bool, chain t.Logf("writing logs for test: %s", t.Name()) ctx := context.TODO() - e2eDir, err := getE2EDir(t) + e2eDir, err := directories.E2E(t) if err != nil { t.Logf("failed finding log directory: %s", err) return @@ -161,27 +161,3 @@ func relayerDiagnosticAbsoluteFilePaths() []string { "/home/hermes/.hermes/config.toml", } } - -// getE2EDir finds the e2e directory above the test. -func getE2EDir(t *testing.T) (string, error) { - t.Helper() - - wd, err := os.Getwd() - if err != nil { - return "", err - } - - const maxAttempts = 100 - count := 0 - for ; !strings.HasSuffix(wd, e2eDir) || count > maxAttempts; wd = ospath.Dir(wd) { - count++ - } - - // arbitrary value to avoid getting stuck in an infinite loop if this is called - // in a context where the e2e directory does not exist. - if count > maxAttempts { - return "", fmt.Errorf("unable to find e2e directory after %d tries", maxAttempts) - } - - return wd, nil -} diff --git a/e2e/testsuite/testconfig.go b/e2e/testsuite/testconfig.go index 685e23b27ca..b1050388e77 100644 --- a/e2e/testsuite/testconfig.go +++ b/e2e/testsuite/testconfig.go @@ -70,6 +70,9 @@ const ( defaultConfigFileName = ".ibc-go-e2e-config.yaml" ) +// defaultChainNames contains the default name for chainA and chainB. +var defaultChainNames = []string{"simapp-a", "simapp-b"} + func getChainImage(binary string) string { if binary == "" { binary = defaultBinary @@ -103,6 +106,10 @@ func (tc TestConfig) Validate() error { if err := tc.validateRelayers(); err != nil { return fmt.Errorf("invalid relayer configuration: %w", err) } + + if err := tc.validateGenesisDebugConfig(); err != nil { + return fmt.Errorf("invalid Genesis debug configuration: %w", err) + } return nil } @@ -163,6 +170,31 @@ func (tc TestConfig) validateRelayers() error { return nil } +// GetChainIndex returns the index of the chain with the given name, if it +// exists. +func (tc TestConfig) GetChainIndex(name string) (int, error) { + for i := range tc.ChainConfigs { + chainName := tc.GetChainName(i) + if chainName == name { + return i, nil + } + } + return -1, fmt.Errorf("chain %s not found in chain configs", name) +} + +// validateGenesisDebugConfig validates configuration of Genesis debug options/ +func (tc TestConfig) validateGenesisDebugConfig() error { + cfg := tc.DebugConfig.GenesisDebug + if !cfg.DumpGenesisDebugInfo { + return nil + } + + // Verify that the provided chain exists in our config + _, err := tc.GetChainIndex(tc.GetGenesisChainName()) + + return err +} + // GetActiveRelayerConfig returns the currently specified relayer config. func (tc TestConfig) GetActiveRelayerConfig() *relayer.Config { for _, r := range tc.RelayerConfigs { @@ -207,6 +239,26 @@ func (tc TestConfig) GetChainBID() string { return "chainB-1" } +// GetChainName returns the name of the chain given an index. +func (tc TestConfig) GetChainName(idx int) string { + // Assumes that only valid indices are provided. We do the same in several other places. + chainName := tc.ChainConfigs[idx].Name + if chainName == "" { + chainName = defaultChainNames[idx] + } + return chainName +} + +// GetGenesisChainName returns the name of the chain for which to dump Genesis files. +// If no chain is provided, it uses the default one (chainA). +func (tc TestConfig) GetGenesisChainName() string { + name := tc.DebugConfig.GenesisDebug.ChainName + if name == "" { + return tc.GetChainName(0) + } + return name +} + // UpgradeConfig holds values relevant to upgrade tests. type UpgradeConfig struct { PlanName string `yaml:"planName"` @@ -216,6 +268,7 @@ type UpgradeConfig struct { // ChainConfig holds information about an individual chain used in the tests. type ChainConfig struct { ChainID string `yaml:"chainId"` + Name string `yaml:"name"` Image string `yaml:"image"` Tag string `yaml:"tag"` Binary string `yaml:"binary"` @@ -227,9 +280,23 @@ type CometBFTConfig struct { LogLevel string `yaml:"logLevel"` } +type GenesisDebugConfig struct { + // DumpGenesisDebugInfo enables the output of Genesis debug files. + DumpGenesisDebugInfo bool `yaml:"dumpGenesisDebugInfo"` + + // ExportFilePath specifies which path to export Genesis debug files to. + ExportFilePath string `yaml:"filePath"` + + // ChainName represent which chain to get Genesis debug info for. + ChainName string `yaml:"chainName"` +} + type DebugConfig struct { // DumpLogs forces the logs to be collected before removing test containers. DumpLogs bool `yaml:"dumpLogs"` + + // GenesisDebug contains debug information specific to Genesis. + GenesisDebug GenesisDebugConfig `yaml:"genesis"` } // LoadConfig attempts to load a atest configuration from the default file path. @@ -471,8 +538,8 @@ type ChainOptionConfiguration func(options *ChainOptions) func DefaultChainOptions() ChainOptions { tc := LoadConfig() - chainACfg := newDefaultSimappConfig(tc.ChainConfigs[0], "simapp-a", tc.GetChainAID(), "atoma", tc.CometBFTConfig) - chainBCfg := newDefaultSimappConfig(tc.ChainConfigs[1], "simapp-b", tc.GetChainBID(), "atomb", tc.CometBFTConfig) + chainACfg := newDefaultSimappConfig(tc.ChainConfigs[0], tc.GetChainName(0), tc.GetChainAID(), "atoma", tc.CometBFTConfig) + chainBCfg := newDefaultSimappConfig(tc.ChainConfigs[1], tc.GetChainName(1), tc.GetChainBID(), "atomb", tc.CometBFTConfig) chainAVal, chainAFn := getValidatorsAndFullNodes(0) chainBVal, chainBFn := getValidatorsAndFullNodes(1) diff --git a/e2e/testsuite/testsuite.go b/e2e/testsuite/testsuite.go index 48dbc13adcc..f7aed822821 100644 --- a/e2e/testsuite/testsuite.go +++ b/e2e/testsuite/testsuite.go @@ -4,6 +4,8 @@ import ( "context" "errors" "fmt" + "os" + "path" "strings" dockerclient "github.com/docker/docker/client" @@ -19,6 +21,7 @@ import ( govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/cosmos/ibc-go/e2e/internal/directories" "github.com/cosmos/ibc-go/e2e/relayer" "github.com/cosmos/ibc-go/e2e/testsuite/diagnostics" feetypes "github.com/cosmos/ibc-go/v8/modules/apps/29-fee/types" @@ -67,6 +70,49 @@ func newPath(chainA, chainB ibc.Chain) pathPair { } } +func (s *E2ETestSuite) SetupTest() { + s.configureGenesisDebugExport() +} + +// configureGenesisDebugExport sets, if needed, env variables to enable exporting of Genesis debug files. +func (s *E2ETestSuite) configureGenesisDebugExport() { + tc := LoadConfig() + t := s.T() + cfg := tc.DebugConfig.GenesisDebug + if !cfg.DumpGenesisDebugInfo { + return + } + + // Set the export path. + exportPath := cfg.ExportFilePath + + // If no path is provided, use the default (e2e/diagnostics/genesis.json). + if exportPath == "" { + e2eDir, err := directories.E2E(t) + s.Require().NoError(err, "can't get e2edir") + exportPath = path.Join(e2eDir, directories.DefaultGenesisExportPath) + } + + if !path.IsAbs(exportPath) { + wd, err := os.Getwd() + s.Require().NoError(err, "can't get working directory") + exportPath = path.Join(wd, exportPath) + } + + // This env variables are set by the interchain test code: + // https://github.com/strangelove-ventures/interchaintest/blob/7aa0fd6487f76238ab44231fdaebc34627bc5990/chain/cosmos/cosmos_chain.go#L1007-L1008 + t.Setenv("EXPORT_GENESIS_FILE_PATH", exportPath) + + chainName := tc.GetGenesisChainName() + chainIdx, err := tc.GetChainIndex(chainName) + s.Require().NoError(err) + + // Interchaintest adds a suffix (https://github.com/strangelove-ventures/interchaintest/blob/a3f4c7bcccf1925ffa6dc793a298f15497919a38/chainspec.go#L125) + // to the chain name, so we need to do the same. + genesisChainName := fmt.Sprintf("%s-%d", chainName, chainIdx+1) + t.Setenv("EXPORT_GENESIS_CHAIN", genesisChainName) +} + // GetRelayerUsers returns two ibc.Wallet instances which can be used for the relayer users // on the two chains. func (s *E2ETestSuite) GetRelayerUsers(ctx context.Context, chainOpts ...ChainOptionConfiguration) (ibc.Wallet, ibc.Wallet) { @@ -173,9 +219,9 @@ func (s *E2ETestSuite) SetupSingleChain(ctx context.Context) ibc.Chain { // generatePathName generates the path name using the test suites name func (s *E2ETestSuite) generatePathName() string { - path := s.GetPathName(s.pathNameIndex) + pathName := s.GetPathName(s.pathNameIndex) s.pathNameIndex++ - return path + return pathName } // GetPathName returns the name of a path at a specific index. This can be used in tests @@ -219,9 +265,9 @@ func (s *E2ETestSuite) GetChains(chainOpts ...ChainOptionConfiguration) (ibc.Cha s.paths = map[string]pathPair{} } - path, ok := s.paths[s.T().Name()] + suitePath, ok := s.paths[s.T().Name()] if ok { - return path.chainA, path.chainB + return suitePath.chainA, suitePath.chainB } chainOptions := DefaultChainOptions() @@ -230,8 +276,8 @@ func (s *E2ETestSuite) GetChains(chainOpts ...ChainOptionConfiguration) (ibc.Cha } chainA, chainB := s.createChains(chainOptions) - path = newPath(chainA, chainB) - s.paths[s.T().Name()] = path + suitePath = newPath(chainA, chainB) + s.paths[s.T().Name()] = suitePath if s.proposalIDs == nil { s.proposalIDs = map[string]uint64{} @@ -240,7 +286,7 @@ func (s *E2ETestSuite) GetChains(chainOpts ...ChainOptionConfiguration) (ibc.Cha s.proposalIDs[chainA.Config().ChainID] = 1 s.proposalIDs[chainB.Config().ChainID] = 1 - return path.chainA, path.chainB + return suitePath.chainA, suitePath.chainB } // GetRelayerWallets returns the ibcrelayer wallets associated with the chains.