The TLDR for getting started is that these tests are based on a TAP compatible tool called Bats. All of the tests are small bash scripts. If the test exits with a non-zero exit code, the test fails. If you’re new to bash or Bats, these are some useful links.
- https://devhints.io/bash
- https://google.github.io/styleguide/shellguide.html
- https://bats-core.readthedocs.io/en/stable/
There are two easy ways to run your test cases. The easiest way is to execute the ./test-runner.sh script. That script has several conveniences that will be important for test automation. If you’d like to run a single test file, or use Bats directly, it’s pretty easy as well:
# You'll want to set whatever environment variables your test needs. If you want to use defaults you could skip this.
set -a
source tests/.env
set +a
# Some tests will use helpers. If your test does, you'll want to add that to the bats lib path.
export BATS_LIB_PATH="$PWD/core/helpers/lib"
# Now you can run bats
bats tests/agglayer/bridges.bats --filter-tags agglayer,rpc
Separation of concerns between infrastructure and test logic is a major goal of this project. We want to have a wide variety of tests. The tests themselves shouldn’t be concerned with the details of the infrastructure on which the execute. A single test should be configurable based on environment variables and should be able to run against a Kurtosis devnet or a mainnet RPC.
Ideally this repository is self documenting. For our test cases, that means they should be thoughtfully named and tagged. In the case of a failure, the error messaging and naming should be it very clear what failed and what it might mean.
# Bad example
@test "send bridge" {
if ! bridge ; then {
echo "bridge failed"
exit 1
}
}
# Better example
@test "bridge native eth from l1 to l2" {
if ! bridge ; then {
echo "Bridge deposit was not claimed despite being finalized on L1. Check that bridge service is running properly"
exit 1
}
}
The “better” example above has a descriptive name that indicates what type of bridge is being tested. It also has a lot more context if and when there is a failure.
Another major goal of this project is to have a simple developer experience. Making that happen isn’t easy, but we want to have shared environment variables that are common across test cases. Similarly, we want to have tags and a project layout that’s intuitive.
As a test developer, you can define whatever environment variables you like, but in order for the test cases to be executed via generic automations, it’s critical that you’re aware of commonly used environment variables and to use them whenever possible.
Variable | Purpose |
---|---|
RPC_URL | A generic JSON RPC URL for tests that do not require L1/L2 context |
L1_RPC_URL | An RPC URL that serves as the L1 for some rollup or validium |
L2_RPC_URL | An RPC URL for a rollup that’s anchored on L1 |
PRIVATE_KEY | A private key that’s funded for RPC_URL |
L1_PRIVATE_KEY | A private key that’s funded for L1_RPC_URL |
L2_PRIVATE_KEY | A private key that’s funded for L2_RPC_URL |
SEQUENCER_RPC_URL | An RPC URL that’s directly connected to a sequencer |
GAS_TOKEN_ADDR | The L1 address of a custom gas token |
BRIDGE_SERVICE_URL | The URL of the bridge service used for claiming deposits |
L1_BRIDGE_ADDR | The address of the bridge on L1 |
L2_BRIDGE_ADDR | The address of the bridge on L2 |
A few points on the design and thinking. In general, we’re going to
prefer deriving rather than specifying everything. Rather than
specifying an L1_ETH_ADDRESS
variable that can be set, we would
derive this value from the L1_PRIVATE_KEY
. Similarly, rather than
specifying the ~networkID~ with something like L2_NETWORK_ID
, we
would rather read this value from the bridge.
The test cases aren’t meant for a specific environment, but in many cases the default values for environment variables will target the kurtosis-cdk package or the kurtosis-polygon-pos package. For example, if you startup the kurtosis package like this:
kurtosis run --enclave cdk --args-file .github/tests/combinations/fork12-cdk-erigon-sovereign.yml .
Many tests will assume the default target of the test is kurtosis and define the keys and URLs accordingly.
Consistent and clear test naming is critical for maintaining readability, ensuring searchability, and improving test result clarity. We will enforce these naming standards during code review to maintain consistency across our test suite. Naming Standard
Each test should follow this pattern:
@test "<action> <test scope> <conditions or properties> [expected outcome]"
Where:
<action>
– What the test is doing (e.g., bridge, send, claim, create).<test scope>
– The subject of the test (e.g., native ETH, ERC20, contract, RPC call).<conditions or properties>
(optional) – Any constraints or test conditions (e.g., with low gas, after).[expected outcome]
(only if needed) – If success/failure isn’t obvious (e.g., fails if contract is paused).
Examples:
@test "bridge native ETH from L2 to L1"
@test "bridge native ETH from L2 to L1 without initial deposit fails"
@test "withdraw ERC20 and finalize after challenge period"
@test "deposit ETH on L2 with custom gas limit"
@test "replay transaction on L1 with same nonce reverts"
@test "bridge fails when contract is paused"
@test "query interop_getLatestSettledCertificateHeader on agglayer RPC returns expected fields"
Best Practices:
- Start with a clear action (e.g., bridge, deposit, send).
- Be specific but concise—avoid vague test names.
- Do not include “test” in the name (it’s redundant).
- Use present tense (“bridge native ETH” not “bridging native ETH”).
- Failure states should be explicit (e.g., “deposit fails when network ID is the current network”).
Test names should be reviewed for clarity and adherence to this standard before merging. Future linting may enforce a predefined set of allowed actions to further standardize test naming.
TODO - This is still a work in progress. All of the tests live in the ./tests folder, but there isn’t a clear standard right now in terms of sub-folders, the names of files, how many cases should exist in a single file. Additionally, the standards for individual bats files are a bit arbitrary.
TODO - Like the overall project organization, there’s more thinking needed for test tags. Each test probably needs a least two tags to be useful. E.g:
- Target (i.e. what is being tested)
agglayer
lxly
erigon
evm
pos
heimdall
- Type of test
regression
smoke
acceptance
stress
load
TODO - We need to document the various helper functions. Some helpers might be mandatory (enforced by code review) while others are there for your convenience.