package e2e_test import ( "context" "os" "sort" "sync" "testing" "github.com/stretchr/testify/require" rpchttp "github.com/tendermint/tendermint/rpc/client/http" rpctypes "github.com/tendermint/tendermint/rpc/coretypes" e2e "github.com/tendermint/tendermint/test/e2e/pkg" "github.com/tendermint/tendermint/types" ) func init() { // This can be used to manually specify a testnet manifest and/or node to // run tests against. The testnet must have been started by the runner first. // os.Setenv("E2E_MANIFEST", "networks/ci.toml") // os.Setenv("E2E_NODE", "validator01") } var ( testnetCache = map[string]e2e.Testnet{} testnetCacheMtx = sync.Mutex{} blocksCache = map[string][]*types.Block{} blocksCacheMtx = sync.Mutex{} ) // testNode runs tests for testnet nodes. The callback function is // given a single stateful node to test, running as a subtest in // parallel with other subtests. // // The testnet manifest must be given as the envvar E2E_MANIFEST. If not set, // these tests are skipped so that they're not picked up during normal unit // test runs. If E2E_NODE is also set, only the specified node is tested, // otherwise all nodes are tested. func testNode(t *testing.T, testFunc func(context.Context, *testing.T, e2e.Node)) { t.Helper() testnet := loadTestnet(t) nodes := testnet.Nodes if name := os.Getenv("E2E_NODE"); name != "" { node := testnet.LookupNode(name) require.NotNil(t, node, "node %q not found in testnet %q", name, testnet.Name) nodes = []*e2e.Node{node} } else { sort.Slice(nodes, func(i, j int) bool { return nodes[i].Name < nodes[j].Name }) } for _, node := range nodes { node := *node if node.Stateless() { continue } t.Run(node.Name, func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() testFunc(ctx, t, node) }) } } // loadTestnet loads the testnet based on the E2E_MANIFEST envvar. func loadTestnet(t *testing.T) e2e.Testnet { t.Helper() manifest := os.Getenv("E2E_MANIFEST") if manifest == "" { t.Skip("E2E_MANIFEST not set, not an end-to-end test run") } testnetCacheMtx.Lock() defer testnetCacheMtx.Unlock() if testnet, ok := testnetCache[manifest]; ok { return testnet } testnet, err := e2e.LoadTestnet(manifest) require.NoError(t, err) testnetCache[manifest] = *testnet return *testnet } // fetchBlockChain fetches a complete, up-to-date block history from // the freshest testnet archive node. func fetchBlockChain(ctx context.Context, t *testing.T) []*types.Block { t.Helper() testnet := loadTestnet(t) // Find the freshest archive node var ( client *rpchttp.HTTP status *rpctypes.ResultStatus ) for _, node := range testnet.ArchiveNodes() { c, err := node.Client() require.NoError(t, err) s, err := c.Status(ctx) require.NoError(t, err) if status == nil || s.SyncInfo.LatestBlockHeight > status.SyncInfo.LatestBlockHeight { client = c status = s } } require.NotNil(t, client, "couldn't find an archive node") // Fetch blocks. Look for existing block history in the block cache, and // extend it with any new blocks that have been produced. blocksCacheMtx.Lock() defer blocksCacheMtx.Unlock() from := status.SyncInfo.EarliestBlockHeight to := status.SyncInfo.LatestBlockHeight blocks, ok := blocksCache[testnet.Name] if !ok { blocks = make([]*types.Block, 0, to-from+1) } if len(blocks) > 0 { from = blocks[len(blocks)-1].Height + 1 } for h := from; h <= to; h++ { resp, err := client.Block(ctx, &(h)) require.NoError(t, err) require.NotNil(t, resp.Block) require.Equal(t, h, resp.Block.Height, "unexpected block height %v", resp.Block.Height) blocks = append(blocks, resp.Block) } require.NotEmpty(t, blocks, "blockchain does not contain any blocks") blocksCache[testnet.Name] = blocks return blocks }