Write Tests¶
Introduction¶
Testing is a critical step in blockchain development, ensuring reliability, performance, and security. Zombienet simplifies this process with its intuitive Domain Specific Language (DSL), enabling developers to write natural-language test scripts tailored to their network needs.
This guide provides an in-depth look at how to create and execute test scenarios using Zombienet's flexible testing framework. You’ll learn how to define tests for metrics, logs, events, and more, allowing for comprehensive evaluation of your blockchain network’s behavior and performance.
Testing DSL¶
Zombienet provides a Domain Specific Language (DSL) for writing tests. The DSL is designed to be human-readable and allows you to write tests using natural language expressions. You can define assertions and tests against the spawned network using this DSL. This way, users can evaluate different metrics, such as:
- On-chain storage - the storage of each of the chains running via Zombienet
- Metrics - the metrics provided by the nodes
- Histograms - visual representations of metrics data
- Logs - detailed records of system activities and events
- System events - notifications of significant occurrences within the network
- Tracing - detailed analysis of execution paths and operations
- Custom API calls (through Polkadot.js) - personalized interfaces for interacting with the network
- Commands - instructions or directives executed by the network
These abstractions are expressed by sentences defined in a natural language style. Therefore, each test line will be mapped to a test to run. Also, the test file (*.zndsl
) includes pre-defined header fields used to define information about the suite, such as network configuration and credentials location.
For more details about the Zombienet DSL, see the Testing DSL specification.
The Test File¶
The test file is a text file with the extension .zndsl
. It is divided into two parts: the header and the body. The header contains the network configuration and the credentials to use, while the body contains the tests to run.
The header is defined by the following fields:
description
string - long description of the test suite (optional)network
string - path to the network definition file, supported in both.json
and.toml
formatscreds
string - credentials filename or path to use (available only with Kubernetes provider). Looks in the current directory or$HOME/.kube/
if a filename is passed
The body contains the tests to run. Each test is defined by a sentence in the DSL, which is mapped to a test to run. Each test line defines an assertion or a command to be executed against the spawned network.
Name¶
The test name in Zombienet is derived from the filename by removing any leading numeric characters before the first hyphen. For example, a file named 0001-zombienet-test.zndsl
will result in a test name of zombienet-test
, which will be displayed in the test report output of the runner.
Assertions¶
Assertions are defined by sentences in the DSL that evaluate different metrics, such as on-chain storage, metrics, histograms, logs, system events, tracing, and custom API calls. Each assertion is defined by a sentence in the DSL, which is mapped to a test to run.
-
Well known functions
- already mapped test function -
Histogram
- get metrics from Prometheus, calculate the histogram, and assert on the target value -
Metric
- get metric from Prometheus and assert on the target value -
Log line
- get logs from nodes and assert on the matching pattern -
Count of log lines
- get logs from nodes and assert on the number of lines matching pattern -
System events
- find a system event from subscription by matching a pattern -
Tracing
- match an array of span names from the suppliedtraceID
-
Custom JS scripts
- run a custom JavaScript script and assert on the return value -
Custom TS scripts
- run a custom TypeScript script and assert on the return value -
Backchannel
- wait for a value and register to use
Commands¶
Commands allow interaction with the nodes and can run pre-defined commands or an arbitrary command in the node. Commonly used commands are as follows:
-
restart
- stop the process and start again after theX
amount of seconds or immediately -
pause
- pause (SIGSTOP) the process -
resume
- resume (SIGCONT) the process -
sleep
- sleep the test-runner forx
amount of seconds
Running a Test¶
To run a test against the spawned network, you can use the Zombienet DSL to define the test scenario. Follow these steps to create an example test:
-
Create a file named
spawn-a-basic-network-test.zndsl
-
Add the following code to the file you just created.
spawn-a-basic-network-test.zndslDescription = "Test the basic functionality of the network (minimal example)" Network = "./spawn-a-basic-network.toml" Creds = "config" # Alice's tasks [[tasks]] name = "alice" is_up = true parachain_100_registered = { condition = "within", timeout = 225 } parachain_100_block_height = { condition = "at least 10", timeout = 250 } # Bob's tasks [[tasks]] name = "bob" is_up = true parachain_100_registered = { condition = "within", timeout = 225 } parachain_100_block_height = { condition = "at least 10", timeout = 250 } # Metrics [[metrics]] name = "alice" node_roles = 4 sub_libp2p_is_major_syncing = 0 [[metrics]] name = "bob" node_roles = 4 [[metrics]] name = "collator01" node_roles = 4
This test scenario checks to verify the following:
- Nodes are running
- The parachain with ID 100 is registered within a certain timeframe (255 seconds in this example)
- Parachain block height is at least a certain number within a timeframe (in this case, 10 within 255 seconds)
- Nodes are reporting metrics
You can define any test scenario you need following the Zombienet DSL syntax.
To run the test, execute the following command:
This command will execute the test scenario defined in the spawn-a-basic-network-test.zndsl
file on the network. If successful, the terminal will display the test output, indicating whether the test passed or failed.
Example Test Files¶
The following example test files define two tests, a small network test and a big network test. Each test defines a network configuration file and credentials to use.
The tests define assertions to evaluate the network’s metrics and logs. The assertions are defined by sentences in the DSL, which are mapped to tests to run.
Description = "Small Network test"
Network = "./0000-test-config-small-network.toml"
Creds = "config"
# Metrics
[[metrics]]
node_roles = 4
sub_libp2p_is_major_syncing = 0
# Logs
[[logs]]
bob_log_line_glob = "*rted #1*"
bob_log_line_regex = "Imported #[0-9]+"
And the second test file:
Description = "Big Network test"
Network = "./0001-test-config-big-network.toml"
Creds = "config"
# Metrics
[[metrics]]
node_roles = 4
sub_libp2p_is_major_syncing = 0
# Logs
[[logs]]
bob_log_line_glob = "*rted #1*"
bob_log_line_regex = "Imported #[0-9]+"
# Custom JS script
[[custom_scripts]]
alice_js_script = { path = "./0008-custom.js", condition = "return is greater than 1", timeout = 200 }
# Custom TS script
[[custom_scripts]]
alice_ts_script = { path = "./0008-custom-ts.ts", condition = "return is greater than 1", timeout = 200 }
# Backchannel
[[backchannel]]
alice_wait_for_name = { use_as = "X", timeout = 30 }
# Well-known functions
[[functions]]
alice_is_up = true
alice_parachain_100_registered = { condition = "within", timeout = 225 }
alice_parachain_100_block_height = { condition = "at least 10", timeout = 250 }
# Histogram
[[histogram]]
alice_polkadot_pvf_execution_time = { min_samples = 2, buckets = [
"0.1",
"0.25",
"0.5",
"+Inf",
], timeout = 100 }
# System events
[[system_events]]
alice_system_event_matches = { pattern = "\"paraId\":[0-9]+", timeout = 10 }
# Tracing
[[tracing]]
alice_trace = { traceID = "94c1501a78a0d83c498cc92deec264d9", contains = [
"answer-chunk-request",
"answer-chunk-request",
] }
| Created: November 22, 2024