Testing Guide
The four layers
| Layer | Where | Run with |
|---|---|---|
| Rust unit tests | inline #[cfg(test)] modules in every crate's src/ | cargo test --all |
| In-process protocol/integration tests | spvirit-tools/tests/ (30 test files + shared harnesses, ~100 test fns), plus per-crate tests/ integration suites in spvirit-client, -server, -ioc, -gateway and -calc | cargo test -p spvirit-tools (or -p <crate>) |
| Cross-implementation interop tests | spvirit-tools/tests/interop_*.rs + tests/interop/ | env-gated; see below |
| Python tests | spvirit-py/tests/test_*.py (6 files) | run each file directly with the venv python (test_ioc_host_api.py: python -m pytest) |
| spvirit-py embedded-interpreter unit tests | spvirit-py/src/ | cargo test -p spvirit-py --no-default-features --features test-embed |
There are no benchmarks (no benches/, no criterion). There is a small
set of golden spec vectors (spvirit-tools/tests/protocol/spec_vectors/*.bin,
9 files, decoded by golden_spec_vectors_decode), but no corpus of packet
captures from real servers. Most codec tests are inline-bytes and
round-trip based.
Rust unit tests
Every crate keeps tests at the bottom of each source file. Notable suites:
spvirit-codec: spvd_encode (39), spvd_decode (38), spvirit_encode (25), epics_decode (19), monitor (12), segment (11), error (6).spvirit-server: simple_store (MDEL, timestamps, all 12 array element types, put_nt, validator rejection), pv (constructors, attach guards), pva_server (builder wiring, links), monitor (delta-frame semantics), db, record_fields, group.spvirit-client: ~130 tests (search target math, parsing, cache/pool and retry behaviour, round-trips).
In-process protocol tests (spvirit-tools/tests/)
Two harnesses under tests/protocol/:
frame_harness.rs—TestServer/TestSession: spawns actual workspace binaries (locatestarget/<profile>/) and speaks raw frames.scenario_harness.rs—ScenarioSession: higher-level connect/handshake/get/put helpers.
Used by spvirit_protocol_* (codec, lifecycle, matrix, unsupported-command
handling), spvirit_pvlist, spvirit_monitor_fields (nested pvRequest
selection), spvirit_monitor_pipeline (flow control), spvirit_put_timestamp,
spvirit_server_startup and spvirit_explore_client. Other integration files
(ioc_fields, pv_handle_api, spvirit_nt_lifecycle,
spvirit_record_array, spvirit_search_resilience, spvirit_get incl.
read-only/simulation PUT authorization) start their own server process and
drive it through the client library.
Interop tests (against real EPICS implementations)
tests/interop/harness.rs provides ProcessGuard, LocalServerFixture,
free-port helpers, and sets EPICS_PVA_ADDR_LIST=127.0.0.1,
EPICS_PVA_AUTO_ADDR_LIST=NO, EPICS_PVA_BROADCAST_PORT for hermetic runs.
All interop tests are env-gated and skip silently unless enabled:
| Suite | Needs | Enable |
|---|---|---|
| p4p (pvxs) | pip install p4p | PVA_TEST_P4P=1, P4P_PROVIDER_CMD → tests/interop/p4p_server.py, P4P_TEST_SERVER=127.0.0.1:5075 |
| EPICS Base | EPICS Base install | its own enable vars (see interop_epics_base.rs) |
| PVAccessJava | Gradle project | see interop_pvaccess_java.rs |
| Generic external server | any PVA server | PVA_TEST_SERVER, PVA_TEST_PV, PVA_TEST_MONITOR, PVA_TEST_* addr/port vars |
CI runs the p4p matrix on every push/PR (see chapter 07). The command:
PVA_TEST_P4P=1 P4P_PROVIDER_CMD="python spvirit-tools/tests/interop/p4p_server.py" \
cargo test -p spvirit-tools --test interop_tool_matrix -- p4p_provider_matrix
Python tests
Five of the six files are plain-assert scripts with a __main__ loop that
calls every test_* function, run directly with the venv's python. The
exception is test_ioc_host_api.py, which uses pytest.raises and must be
run with python -m pytest tests/test_ioc_host_api.py. Conventions:
- Every test that starts a
Serveruses its own unique port pair (test_pv_handles.py: 15075–15246;test_value_types.py: 16060–16083, within its reserved 16060–16099 range). Pick unused ranges for new files. - Build first:
.\.venv\Scripts\maturin.exe develop(debug is fine). - Run:
.\.venv\Scripts\python.exe tests\test_pv_handles.py→ expectALL OK.
Conventions for new work
- TDD is the house style: write the failing test first, verify it fails, implement, verify it passes, commit per task. Follow the same rhythm.
- Timestamps in tests: always set explicit
NtTimeStamps on payloads you assert against — aNonetimestamp is stamped at encode time and makes monitor-delta assertions flaky (see chapter 02). - When touching the codec, remember the four duplicated size codecs and the absence of a capture corpus — add a round-trip test in the same file as the change, and consider checking real captures from pvxs/p4p if the change is wire-visible.