Testing
Components can be tested by connecting them into a small simulation and driving
their ports directly. For simple cases this can be done by hand with
OutPort/InPort, but most component tests need the same testbench structure:
- Create an engine and the device under test (DUT).
- Connect driver ports to DUT input ports.
- Connect receiver ports to DUT output ports.
- Run a sequence of sends, expects, delays, and no-traffic checks.
The build_component_harness! macro will generate the repeated testbench code.
It generates the harness struct, Port/Step enums, helper macros, etc.
Harnesses are usually declared inside a small test module. This keeps generated
names such as Port, Step, and the helper macros local to the harness and
avoids clashes with other harnesses in the same test file.
For example, the harness around a Delay component is created and used below:
#![allow(unused)] fn main() { mod delay_harness { use std::rc::Rc; use gwr_components::build_component_harness; use gwr_components::delay::Delay; use gwr_engine::test_helpers::start_test; build_component_harness! { harness DelayHarness<T> { component: delay: Rc<Delay<T>>, rx ports: { Rx<T> => rx, }, tx ports: { Tx<T> => tx, }, } } #[test] fn delay_forwards_values() { let mut engine = start_test(file!()); let clock = engine.default_clock(); let delay = Delay::new_and_register(&engine, &clock, engine.top(), "delay", 5).unwrap(); let mut harness = DelayHarness::new(engine, delay); harness.run_steps([ send_rx!(10), expect_no_traffic!(&[Port::Tx], 4), expect_tx!(10), ]); } } }
The macro supports scalar RX/TX ports and RX/TX port arrays. Each port section
is optional, so a source-only component can define only tx ports and a
sink-only component can define only rx ports.
Step can be a send, expect, delay, no-traffic check, Seq(Vec<Step<...>>)
that runs child steps in order, or Par(Vec<Step<...>>) that runs child steps
concurrently and waits for all of them before moving on. The generated seq!
and par! helper macros build those recursive control structures and record
their source location, so tests can express parallel sequences on different
ports while keeping error messages tied to the call site.
The harness checks that each step is used on a compatible port; for example, using an expect step on an RX port or a send step on a TX port will fail the test.
Use run_steps([Step<...>]) for fixed test sequences and
run_step_generator(iterator) for stateful generators that yield steps as the
test progresses.