Testing with BTest

Zeek’s main regression tests are btest-based.

Testing protocol parsers (also called analyzers) and scripting behavior is usually done by having Zeek read packet traces (PCAP files) to produce logs. These logs are then baselined. Baselines are stored in the testing/btest/Baseline directory and checked into the repository.

There’s also a Baseline.zam directory for alternative baselines when running Zeek with ZAM enabled.

All btests run in CI on various platforms with and without ZAM enabled.

Packet Traces (PCAP Files)

We store packet traces in testing/btest/Traces/. The README file represents an index where PCAPs came from or how they were created to keep a bit of lineage available. PCAP filenames always end with .pcap or .pcapng. Usually PCAP files are stored uncompressed, except for some larger but highly compressible examples.

There are generally two approaches to create new packet traces in isolation if you cannot share a packet capture from a production network. Either install and run the software yourself and capture the traffic with tcpdump in a lab or virtual environment, or create a Python script that produces packet traces using Scapy. In 2026, LLM agents are very effective for the latter. Including packet traces from real software or actual production networks is more realistic and if available, preferred over Scapy-generated traces. For edge case testing of parsers, Scapy-generated, Scapy-edited, or hex-edited capture files are all fair game. Keep a note in the README what was done to create a certain trace.

The testing/btest/Traces directory is mostly structured by protocol. When adding a Scapy-generated trace, <name>.pcap, put a <name>.pcap.py file next to it. Running the Python script should generate the <name>.pcap file next to <name>.pcap.py reproducibly. Running the script a year later should produce the exact same result.

Multi-word PCAP names should be all lower-case and use dashes for separation. The PCAP’s name should include the thing or scenario being tested. For example, ftp/ftp-with-numbers-in-filename.pcap or ssh/server-pre-banner-data.pcap. Try to keep PCAP files to a few kilobytes in size, 50KB or more should be an exception. Including PCAPs with valid checksums is preferred. Use tcprewrite to correct them if needed.

Creating new Tests

Tests are stored in the testing/btest/ directory. There are subdirectories for individual Zeek components and protocols. Recursively looking at directory and test names should help you orient. Older or more integration-like tests end with .test or .sh. Most tests using packet traces and baselining or testing Zeek language features should end with .zeek for better language server support.

Every test should have a commented @TEST-DOC line at the top describing what is being tested, followed by some @TEST-EXEC lines with the actual commands.

# @TEST-DOC: Verify a basic HTTP GET request.
#
# @TEST-EXEC: zeek -b -r $TRACES/http/get.pcap %INPUT
#
# @TEST-EXEC: btest-diff-cut -m uid service history conn.log
# @TEST-EXEC: btest-diff-cut -m http.log

@load base/protocols/conn
@load base/protocols/http

In tests, prefer to use zeek -b to invoke Zeek in bare mode to reduce Zeek’s initialization time and reduce the potential for unintended side-effects. Load all required scripts and packages via explicit @load directives within the test’s content. The magic %INPUT variable expands to a filename containing the content of the test (see the BTest documentation for more details).

The $TRACES environment variable is set in testing/btest/btest.cfg and expands to testing/btest/Traces. That’s all there is to it.

For baseline testing, @TEST-EXEC: btest-diff has been used historically, with btest-diff-cut being a recent addition that allows to easily create smaller baselines by selecting relevant columns to diff. Prefer to use the btest-diff-cut helper script and only include columns in the baselines that are relevant to the test. This hardens your test against irrelevant baseline deviations introduced by unrelated future changes to Zeek. Avoid the verbose TSV header by passing -m to btest-diff or btest-diff-cut.

To baseline all columns of path.log, use btest-diff-cut -m path.log. A full baseline is recommended for protocol logs in tests that are specific to that protocol. For example, in HTTP tests, baseline the full http.log, but only select certain columns from conn.log as shown above.

The conn.log’s uid service and history columns are generally interesting, even if the test does not strictly need all of them. Checksum errors are quickly recognized as c or C in the history column. The service entry is also useful to verify a protocol is present (and wasn’t removed due to an analyzer violation).

If you baseline weird.log, minimally include the columns uid, name, addl and source. Include @load base/frameworks/notice/weird in the test. By default, weirds are not logged when running in bare mode.

When testing very specific analyzer confirmation and violation behavior, load the frameworks/analyzer/debug-logging.zeek and baseline the created analyzer_debug.log using btest-diff-cut -m:

# @TEST-DOC: Verify the SSH analyzer's confirmation and violation behavior. Regression test for #1234.
#
# @TEST-EXEC: zeek -r $TRACES/ssh/ssh.server-side-half-duplex.pcap %INPUT
#
# @TEST-EXEC: btest-diff-cut -m ssh.log
# @TEST-EXEC: btest-diff-cut -m uid service history conn.log
# @TEST-EXEC: btest-diff-cut -m analyzer_debug.log

@load base/protocols/ssh
@load frameworks/analyzer/debug-logging.zeek

If you explicitly want to test for no weirds or no analyzer violations, use test ! -f weird.log:

# ...
# @TEST-EXEC: btest-diff-cut -m uid service history conn.log
#
# @TEST-EXEC: test ! -f weird.log
# @TEST-EXEC: test ! -f analyzer.log

@load base/protocols/conn
@load base/protocols/http
@load base/frameworks/notice/weird

The btest-diff and btest-diff-cut commands support TEST_DIFF_CANONFIER to canonify a baseline, i.e., normalize content that will change from invocation to invocation. It defaults to testing/scripts/diff-canonifier, which is set in testing/btest/btest.cfg and canonifies timestamps to a XXXX.XXX pattern.

Verification

Always verify that new tests pass by running them with btest -d path/to/test in the top-level testing/btest directory. Ensure all required files are included in a commit and PR submission (new PCAP files, new baselines, new tests).

Finally, also verify that all tests still pass by running btest -d -j in the top-level testing/btest directory. When adding new scripts or fields to record types, some tests in ./coverage require updates. To only run the coverage tests, pass the directory to btest: btest -d -j ./coverage.

To update baselines, run btest -d -U path/to/test. Note that this batch updates all btest-diff and btest-diff-cut baselines at once. Use btest -d -u for interactive prompts. In either case, verify with git diff or git diff --word-diff after running an update to validate the baseline changes are reasonable.

To run all tests with ZAM, use btest -d -j -a zam. Look into btest’s environment concept and check testing/btest/btest.cfg for the extra environment variables and settings used for running tests under ZAM.

Non-PCAP Tests

Note that not all tests are packet trace based. Many of the cluster tests instead use btest-bg-run, btest-bg-wait and remote events for testing. Review them first if you work on cluster functionality. There are also the ./bifs and ./language directories that usually do not involve packet traces unless required for driving network time for timer or table expiration testing. Deep dive into tests if you need to learn how exactly they work.