|
| 1 | + |
| 2 | +## Table of Contents |
| 3 | +1. [How to write tests](#how-to-write-tests) |
| 4 | + - a. [Adding a new test](#adding-a-new-test) |
| 5 | + - b. [Running tests locally](#running-tests-locally) |
| 6 | + - b. [Code samples](#code-samples) |
| 7 | + - [Setup](#setup) |
| 8 | + - [Creating test users](#creating-test-users) |
| 9 | + - [Waiting](#waiting) |
| 10 | + - [Query wallet balances](#query-wallet-balances) |
| 11 | + - [Broadcasting messages](#broadcasting-messages) |
| 12 | + - [Starting the relayer](#starting-the-relayer) |
| 13 | + - [Arbitrary commands](#arbitrary-commands) |
| 14 | + - [IBC transfer](#ibc-transfer) |
| 15 | +2. [Test design](#test-design) |
| 16 | + - a. [ibctest](#ibctest) |
| 17 | + - b. [CI configuration](#ci-configuration) |
| 18 | +3. [Troubleshooting](#troubleshooting) |
| 19 | + |
| 20 | + |
| 21 | +## How to write tests |
| 22 | + |
| 23 | +### Adding a new test |
| 24 | + |
| 25 | +All tests should go under the [e2e](https://github.com/cosmos/ibc-go/tree/main/e2e) directory. When adding a new test, either add a new test function |
| 26 | +to an existing test suite **_in the same file_**, or create a new test suite in a new file and add test functions there. |
| 27 | +New test files should follow the convention of `module_name_test.go`. |
| 28 | + |
| 29 | +New test suites should be composed of `testsuite.E2ETestSuite`. This type has lots of useful helper functionality that will |
| 30 | +be quite common in most tests. |
| 31 | + |
| 32 | +> Note: see [here](#how-tests-are-run) for details about these requirements. |
| 33 | +
|
| 34 | + |
| 35 | +### Running tests locally |
| 36 | + |
| 37 | +Tests can be run using a Makefile target under the e2e directory. `e2e/Makefile` |
| 38 | + |
| 39 | +There are several envinronment variables that alter the behaviour of the make target. |
| 40 | + |
| 41 | +| Environment Variable | Description | Default Value| |
| 42 | +| ----------- | ----------- | ----------- | |
| 43 | +| SIMD_IMAGE | The image that will be used for simd | ibc-go-simd-e2e | |
| 44 | +| SIMD_TAG | The tag used for simd | latest| |
| 45 | +| RLY_TAG | The tag used for the go relayer | main| |
| 46 | + |
| 47 | + |
| 48 | +> Note: when running tests locally, **no images are pushed** to the `ghcr.io/cosmos/ibc-go-simd-e2e` registry. |
| 49 | +The images which are used only exist on your machine. |
| 50 | + |
| 51 | +These environment variables allow us to run tests with arbitrary verions (from branches or released) of simd |
| 52 | +and the go relayer. |
| 53 | + |
| 54 | +Every time changes are pushed to a branch or to `main`, a new `simd` image is built and pushed [here](https://github.com/cosmos/ibc-go/pkgs/container/ibc-go-simd-e2e). |
| 55 | + |
| 56 | + |
| 57 | +#### Example Command: |
| 58 | + |
| 59 | +```sh |
| 60 | +export SIMD_IMAGE="ghcr.io/cosmos/ibc-go-simd-e2e" |
| 61 | +export SIMD_TAG="pr-1650" |
| 62 | +export RLY_TAG="v2.0.0-rc2" |
| 63 | +make e2e-test suite=FeeMiddlewareTestSuite test=TestMultiMsg_MsgPayPacketFeeSingleSender |
| 64 | +``` |
| 65 | + |
| 66 | + |
| 67 | +> Note: sometimes it can be useful to make changes to [ibctest](https://github.com/strangelove-ventures/ibctest) when running tests locally. In order to do this, add the following line to |
| 68 | +e2e/go.mod |
| 69 | + |
| 70 | +`replace github.com/strangelove-ventures/ibctest => ../ibctest` |
| 71 | + |
| 72 | +Or point it to any local checkout you have. |
| 73 | + |
| 74 | +### Code samples |
| 75 | + |
| 76 | +#### Setup |
| 77 | + |
| 78 | +Every standard test will start with this. This creates two chains and a relayer, |
| 79 | +initializes relayer accounts on both chains, establishes a connection and a channel |
| 80 | +between the chains. |
| 81 | + |
| 82 | +Both chains have started, but the relayer is not yet started. |
| 83 | + |
| 84 | +The relayer should be started as part of the test if required. See [Starting the Relayer](#starting-the-relayer) |
| 85 | + |
| 86 | +```go |
| 87 | +relayer, channelA := s.SetupChainsRelayerAndChannel(ctx, feeMiddlewareChannelOptions()) |
| 88 | +chainA, chainB := s.GetChains() |
| 89 | +``` |
| 90 | + |
| 91 | +#### Creating test users |
| 92 | + |
| 93 | +There are helper functions to easily create users on both chains. |
| 94 | + |
| 95 | +```go |
| 96 | +chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) |
| 97 | +chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) |
| 98 | +``` |
| 99 | + |
| 100 | +#### Waiting |
| 101 | + |
| 102 | +We can wait for some number of blocks on the specified chains if required. |
| 103 | + |
| 104 | +```go |
| 105 | +chainA, chainB := s.GetChains() |
| 106 | +err := test.WaitForBlocks(ctx, 1, chainA, chainB) |
| 107 | +s.Require().NoError(err) |
| 108 | +``` |
| 109 | + |
| 110 | +#### Query wallet balances |
| 111 | + |
| 112 | +We can fetch balances of wallets on specific chains. |
| 113 | + |
| 114 | +```go |
| 115 | +chainABalance, err := s.GetChainANativeBalance(ctx, chainAWallet) |
| 116 | +s.Require().NoError(err) |
| 117 | +``` |
| 118 | + |
| 119 | +#### Broadcasting messages |
| 120 | + |
| 121 | +We can broadcast arbitrary messages which are signed on behalf of users created in the test. |
| 122 | + |
| 123 | +This example shows a multi message transaction being broadcast on chainA and signed on behalf of chainAWallet. |
| 124 | + |
| 125 | +```go |
| 126 | +relayer, channelA := s.SetupChainsRelayerAndChannel(ctx, feeMiddlewareChannelOptions()) |
| 127 | +chainA, chainB := s.GetChains() |
| 128 | + |
| 129 | +chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) |
| 130 | +chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) |
| 131 | + |
| 132 | +t.Run("broadcast multi message transaction", func(t *testing.T){ |
| 133 | + payPacketFeeMsg := feetypes.NewMsgPayPacketFee(testFee, channelA.PortID, channelA.ChannelID, chainAWallet.Bech32Address(chainA.Config().Bech32Prefix), nil) |
| 134 | + transferMsg := transfertypes.NewMsgTransfer(channelA.PortID, channelA.ChannelID, transferAmount, chainAWallet.Bech32Address(chainA.Config().Bech32Prefix), chainBWallet.Bech32Address(chainB.Config().Bech32Prefix), clienttypes.NewHeight(1, 1000), 0) |
| 135 | + resp, err := s.BroadcastMessages(ctx, chainA, chainAWallet, payPacketFeeMsg, transferMsg) |
| 136 | + |
| 137 | + s.AssertValidTxResponse(resp) |
| 138 | + s.Require().NoError(err) |
| 139 | +}) |
| 140 | +``` |
| 141 | + |
| 142 | +#### Starting the relayer |
| 143 | + |
| 144 | +The relayer can be started with the following. |
| 145 | + |
| 146 | +```go |
| 147 | +t.Run("start relayer", func(t *testing.T) { |
| 148 | + s.StartRelayer(relayer) |
| 149 | +}) |
| 150 | +``` |
| 151 | + |
| 152 | +#### Arbitrary commands |
| 153 | + |
| 154 | +Arbitrary commands can be executed on a given chain. |
| 155 | + |
| 156 | +> Note: these commands will be fully configured to run on the chain executed on (home directory, ports configured etc.) |
| 157 | +
|
| 158 | +However, it is preferable to [broadcast messages](#broadcasting-messages) or use a gRPC query if possible. |
| 159 | + |
| 160 | +```go |
| 161 | +stdout, stderr, err := chainA.Exec(ctx, []string{"tx", "..."}, nil) |
| 162 | +``` |
| 163 | + |
| 164 | +#### IBC transfer |
| 165 | + |
| 166 | +It is possible to send an IBC transfer in two ways. |
| 167 | + |
| 168 | +Use the ibctest `Chain` interface (this ultimately does a docker exec) |
| 169 | + |
| 170 | +```go |
| 171 | +t.Run("send IBC transfer", func(t *testing.T) { |
| 172 | + chainATx, err = chainA.SendIBCTransfer(ctx, channelA.ChannelID, chainAWallet.KeyName, walletAmount, nil) |
| 173 | + s.Require().NoError(err) |
| 174 | + s.Require().NoError(chainATx.Validate(), "chain-a ibc transfer tx is invalid") |
| 175 | +}) |
| 176 | +``` |
| 177 | + |
| 178 | +Broadcast a `MsgTransfer`. |
| 179 | + |
| 180 | +```go |
| 181 | +t.Run("send IBC transfer", func(t *testing.T){ |
| 182 | + transferMsg := transfertypes.NewMsgTransfer(channelA.PortID, channelA.ChannelID, transferAmount, chainAWallet.Bech32Address(chainA.Config().Bech32Prefix), chainBWallet.Bech32Address(chainB.Config().Bech32Prefix), clienttypes.NewHeight(1, 1000), 0) |
| 183 | + resp, err := s.BroadcastMessages(ctx, chainA, chainAWallet, transferMsg) |
| 184 | + s.AssertValidTxResponse(resp) |
| 185 | + s.Require().NoError(err) |
| 186 | +}) |
| 187 | +``` |
| 188 | + |
| 189 | +## Test design |
| 190 | + |
| 191 | + |
| 192 | +#### ibctest |
| 193 | + |
| 194 | +These E2E tests use the [ibctest framework](https://github.com/strangelove-ventures/ibctest). This framework creates chains and relayers in containers and allows for arbitrary commands to be executed in the chain containers, |
| 195 | +as well as allowing us to broadcast arbitrary messages which are signed on behalf of a user created in the test. |
| 196 | + |
| 197 | + |
| 198 | +#### CI configuration |
| 199 | + |
| 200 | +There are two main github actions for e2e tests. |
| 201 | + |
| 202 | +[e2e.yaml](https://github.com/cosmos/ibc-go/blob/main/.github/workflows/e2e.yaml) which runs when collaborators create branches. |
| 203 | + |
| 204 | +[e2e-fork.yaml](https://github.com/cosmos/ibc-go/blob/main/.github/workflows/e2e-fork.yml) which runs when forks are created. |
| 205 | + |
| 206 | +In `e2e.yaml`, the `simd` image is built and pushed to [a registry](https://github.com/cosmos/ibc-go/pkgs/container/ibc-go-simd-e2e) and every test |
| 207 | +that is run uses the image that was built. |
| 208 | + |
| 209 | +In `e2e-fork.yaml`, images are not pushed to this registry, but instead remain local to the host runner. |
| 210 | + |
| 211 | + |
| 212 | +### How tests are run |
| 213 | + |
| 214 | +The tests use the `matrix` feature of Github Actions. The matrix is |
| 215 | +dynamically generated using [this command](https://github.com/cosmos/ibc-go/blob/main/cmd/build_test_matrix/main.go). |
| 216 | + |
| 217 | +> Note: there is currently a limitation that all tests belonging to a test suite must be in the same file. |
| 218 | +In order to support test functions spread in different files, we would either need to manually maintain a matrix |
| 219 | +or update the script to account for this. The script assumes there is a single test suite per test file to avoid an overly complex |
| 220 | +generation process. |
| 221 | + |
| 222 | +Which looks under the `e2e` directory, and creates a task for each test suite function. |
| 223 | + |
| 224 | +#### Example |
| 225 | + |
| 226 | +```go |
| 227 | +// e2e/file_one_test.go |
| 228 | +package e2e |
| 229 | + |
| 230 | +func TestFeeMiddlewareTestSuite(t *testing.T) { |
| 231 | + suite.Run(t, new(FeeMiddlewareTestSuite)) |
| 232 | +} |
| 233 | + |
| 234 | +type FeeMiddlewareTestSuite struct { |
| 235 | + testsuite.E2ETestSuite |
| 236 | +} |
| 237 | + |
| 238 | +func (s *FeeMiddlewareTestSuite) TestA() {} |
| 239 | +func (s *FeeMiddlewareTestSuite) TestB() {} |
| 240 | +func (s *FeeMiddlewareTestSuite) TestC() {} |
| 241 | + |
| 242 | +``` |
| 243 | + |
| 244 | +```go |
| 245 | +// e2e/file_two_test.go |
| 246 | +package e2e |
| 247 | + |
| 248 | +func TestTransferTestSuite(t *testing.T) { |
| 249 | + suite.Run(t, new(TransferTestSuite)) |
| 250 | +} |
| 251 | + |
| 252 | +type TransferTestSuite struct { |
| 253 | + testsuite.E2ETestSuite |
| 254 | +} |
| 255 | + |
| 256 | +func (s *TransferTestSuite) TestD() {} |
| 257 | +func (s *TransferTestSuite) TestE() {} |
| 258 | +func (s *TransferTestSuite) TestF() {} |
| 259 | + |
| 260 | +``` |
| 261 | + |
| 262 | +In the above example, the following would be generated. |
| 263 | + |
| 264 | +```json |
| 265 | +{ |
| 266 | + "include": [ |
| 267 | + { |
| 268 | + "suite": "FeeMiddlewareTestSuite", |
| 269 | + "test": "TestA" |
| 270 | + }, |
| 271 | + { |
| 272 | + "suite": "FeeMiddlewareTestSuite", |
| 273 | + "test": "TestB" |
| 274 | + }, |
| 275 | + { |
| 276 | + "suite": "FeeMiddlewareTestSuite", |
| 277 | + "test": "TestC" |
| 278 | + }, |
| 279 | + { |
| 280 | + "suite": "TransferTestSuite", |
| 281 | + "test": "TestD" |
| 282 | + }, |
| 283 | + { |
| 284 | + "suite": "TransferTestSuite", |
| 285 | + "test": "TestE" |
| 286 | + }, |
| 287 | + { |
| 288 | + "suite": "TransferTestSuite", |
| 289 | + "test": "TestF" |
| 290 | + } |
| 291 | + ] |
| 292 | +} |
| 293 | +``` |
| 294 | + |
| 295 | +This string is used to generate a test matrix in the Github Action that runs the E2E tests. |
| 296 | + |
| 297 | +All tests will be run on different hosts. |
| 298 | + |
| 299 | + |
| 300 | +#### Misceleneous: |
| 301 | + |
| 302 | +* Gas fees are set to zero to simply calcuations when asserting account balances. |
| 303 | +* When upgrading from e.g. v4 -> v5, in ibc-go, we cannot upgrade the go.mod under `e2e` since v5 will not yet exist. We need to upgrade it in a follow up PR. |
| 304 | + |
| 305 | +### Troubleshooting |
| 306 | + |
| 307 | +* On Mac, after running a lot of tests, it can happen that containers start failing. To fix this, you can try clearing existing containers and restarting the docker daemon. |
| 308 | + |
| 309 | + This generally manifests itself as relayer or simd containers timing out during setup stages of the test. This doesn't happen in CI. |
| 310 | + ```bash |
| 311 | + # delete all images |
| 312 | + docker system prune -af |
| 313 | + ``` |
| 314 | + This issue doesn't seem to occur on other operating systems. |
| 315 | + |
| 316 | + |
0 commit comments