Write a Transport Test Application
Learn how to create a libp2p application that can be tested with the transport interoperability test suite.
How to Write a Transport Test Application
You want to write a new transport test do you? You've come to the correct place.
This document will describe exactly how to write an application and define a
Dockerfile so that it can be run by the transport test in this repo.
The Goals of These Transport Tests
The transport test (i.e. the test executed by the transport/run.sh script)
seeks to measure the following:
- Dial success
- Handshake latency
- Ping latency
Currently, the test framework runs both the dialer and the listener applications on the same host and docker network.
Measuring dial success
This is trivial, if the dialer successfully handshakes, then it successfully dialed the listener. The exit status of the test reflects the success of the dial operation. If the dial fails, then the test is marked as failed.
Measuring the Latency
To measure the latency, we calculate the time to complete the handshake and also record the ping latency measurement.
Test Setup
The testing script executes the transport test using Docker Compose. It
generates a docker-compose.yaml file for each test. The docker-compose.yaml
file passes to the listener and dialer a set of environment variables that
they will use to know how to execute the test.
Example Generated docker-compose.yaml
name: rust-v0_56_x_rust-v0_56__quic-v1_
networks:
transport-network:
external: true
services:
listener:
image: transport-rust-v0.56
container_name: rust-v0_56_x_rust-v0_56__quic-v1__listener
init: true
networks:
- transport-network
environment:
- IS_DIALER=false
- REDIS_ADDR=transport-redis:6379
- TEST_KEY=a5b50d5e
- TRANSPORT=quic-v1
- LISTENER_IP=0.0.0.0
- DEBUG=false
dialer:
image: transport-rust-v0.56
container_name: rust-v0_56_x_rust-v0_56__quic-v1__dialer
depends_on:
- listener
networks:
- transport-network
environment:
- IS_DIALER=true
- REDIS_ADDR=transport-redis:6379
- TEST_KEY=a5b50d5e
- TRANSPORT=quic-v1
- LISTENER_IP=0.0.0.0
- DEBUG=false
When docker compose is executed, it brings up the listener and dialer
docker images and attaches them to the transport-network that has already been
created in the "start global services" step of the test pass. There is a global
Redis server already running in the transport-network and its address is passed to
both services using the REDIS_ADDR environment variable. Both services are
assigned an IP address dynamically and both have access to the DNS server
running in the network; that is how transport-redis resolution happens.
Test Execution
Typically you only need to write one application that can function both as the
listener and the dialer. The dialer is responsible for connecting to the
listener and running the ping protocol with the listener.
Please note that all logging and debug messages must be send to stderr. The stdout stream is only used for reporting the results in YAML format.
The typical high-level flow for any transport test application is as follows:
-
Your application reads the common environment variables:
DEBUG=false # boolean value, either true or false IS_DIALER=true # boolean value, either true or false REDIS_ADDR=transport-redis:6379 # URL and port: transport-redis:6379 TEST_KEY=a5b50d5e # 8-character hexidecimal string TRANSPORT=tcp # transport name: tcp, quic-v2, ws, webrtc, etc SECURE_CHANNEL=noise # secure channel name: noise, tls MUXER=yamux # muxer name: yamux, noiseNOTE: The
SECURE_CHANNELandMUXERenvironment variables are not set when theTRANSPORTis a "standalone" transport such as "quic-v1", etc.NOTE: The
TEST_KEYvalue is the first 8 hexidecimal characters of the sha2 256 hash of the test name. This is used for namespacing the key(s) used when interacting with the global redis server for coordination.NOTE: The
DEBUGvalue is set to true when the test was run with--debug. This is to signal to the test applications to generate verbose logging for debug purposes. -
If
IS_DIALERis true, run thedialercode, else, run thelistenercode (see below).
dialer Application Flow
-
When your test application is run in
dialermode, there are no other environment variables needed. -
Connect to the Redis server at
REDIS_ADDRand poll it asking for the value associated with the<TEST_KEY>_listener_multiaddrkey. -
Dial the
listenerat the multiaddr you received from the Redis server. -
Calculate the time it took to dial, connect, and complete the handshake with the
listener. -
Handle the ping protocol responses and record the round trip latency.
-
Print to stdout, the results in YAML format (see the section "Results Schema" below).
-
Exit cleanly with an exit code of 0. If there are any errors, exit with a non-zero exit code to signal test failure.
listener Application Flow
-
When your test application is run in
listenermode, it will be passed the following environment variables that are unique to thelistener. Your application must read these as well:LISTENER_IP=0.0.0.0NOTE: The
LISTENER_IPis somewhat historical and is always set to0.0.0.0to get the test application to bind to all interfaces. it is up to your application to detect the non-localhost interface your application is bound to so that it can properly calculate its address to send to Redis. -
Listen on the non-localhost network interface and calculate your multiaddr.
-
Connect to the Redis server at the
REDIS_ADDRlocation and set the value for the key<TEST_KEY>_listener_multiaddrto your multiaddr value.NOTE: The use of the
TEST_KEYvalue in the key name effectively namespaces the key-value pair used for each test. Since we typically run multiple tests in parallel, this keeps the tests isolated from each other on the global Redis server. -
Run the event loop so that libp2p can complete the handshake and the ping protocol runs.
-
The
listenermust run until it is shutdown by Docker. Don't worry about exiting logic. When thedialerexits, thelistenercontainer is automatically shut down.
Results Schema
To report the results of the transport test in a way that the test scripts
understand, your test application must output the results of the handshake
latency and the ping latency in YAML format by simply printing it to stdout.
The transport scripts read the stdout from the dialer and save it into a
per-test results.yaml file for later consolidation into the global results.yaml
file for the full test run.
Below is an example of a valid results report printed to stdout:
# Measurements from dialer
latency:
handshake_plus_one_rtt: 5.011
ping_rtt: 0.599
unit: ms
NOTE: The transport/lib/run-signle-test.sh script handles adding the metadata
for the results file in each test. It writes out something like the following
and then appends the data your test application writes to stdout after it:
test: rust-v0.56 x rust-v0.56 (quic-v1)
dialer: rust-v0.56
listener: rust-v0.56
transport: quic-v1
secureChannel: null
muxer: null
status: pass
duration: 1
NOTE: the status value of pass or fail is determined by the exit code of
your test application in dialer mode. If that exits with '0' then status
will be set to pass and the test will be reported as passing. Any other value
will cause status to be set to fail and the test will be reported as
failing.