Mocker
The Mocker is a small harness for running one block directly, without building a full Flowgraph and without starting a Runtime.
This is useful for:
- unit tests for a single block,
- checking edge cases with carefully chosen input samples,
- testing message handlers and message outputs,
- microbenchmarks where the scheduler and graph setup would hide the cost of the block itself.
Mocker is available on native targets through futuresdr::runtime::mocker.
Stream Blocks
For stream blocks, instantiate the block with mocker::Reader<T> and mocker::Writer<T> buffer types. Then set the input samples, reserve output space, and run the block:
use futuresdr::blocks::Apply;
use futuresdr::runtime::mocker::Mocker;
use futuresdr::runtime::mocker::Reader;
use futuresdr::runtime::mocker::Writer;
let block: Apply<_, _, _, Reader<u32>, Writer<u32>> =
Apply::with_buffers(|x: &u32| x + 1);
let mut mocker = Mocker::new(block);
mocker.input().set(vec![1, 2, 3]);
mocker.output().reserve(3);
mocker.run();
let (items, tags) = mocker.output().get();
assert_eq!(items, vec![2, 3, 4]);
assert!(tags.is_empty());
The mock reader exposes the input through the same CpuBufferReader API that a normal block sees at runtime. The mock writer stores produced samples in a vector that can be read with get() or drained with take().
Multiple Runs
run() calls the block’s work() method until the block stops requesting immediate re-entry through WorkIo::call_again. You can update the mocked input and run the same block again:
use futuresdr::blocks::Apply;
use futuresdr::runtime::mocker::Mocker;
use futuresdr::runtime::mocker::Reader;
use futuresdr::runtime::mocker::Writer;
let block: Apply<_, _, _, Reader<u32>, Writer<u32>> =
Apply::with_buffers(|x: &u32| x + 1);
let mut mocker = Mocker::new(block);
mocker.output().reserve(6);
mocker.input().set(vec![1, 2, 3]);
mocker.run();
mocker.input().set(vec![4, 5, 6]);
mocker.run();
let (items, _) = mocker.output().get();
assert_eq!(items, vec![2, 3, 4, 5, 6, 7]);
If the block relies on init() or deinit() state, call those explicitly:
mocker.init();
mocker.run();
mocker.deinit();
Tags
Mock inputs can include item tags:
use futuresdr::runtime::dev::ItemTag;
use futuresdr::runtime::dev::Tag;
mocker.input().set_with_tags(
vec![0.0_f32; 1024],
vec![ItemTag {
index: 256,
tag: Tag::Id(256),
}],
);
The output writer returns both produced samples and output tags:
let (items, tags) = mocker.output().get();
Message Blocks
Mocker can also exercise message handlers. Use post() to call a message handler on the wrapped block:
use futuresdr::blocks::MessageCopy;
use futuresdr::prelude::*;
use futuresdr::runtime::mocker::Mocker;
let mut mocker = Mocker::new(MessageCopy);
mocker.init();
let ret = mocker.post("in", Pmt::Usize(123))?;
assert_eq!(ret, Pmt::Ok);
mocker.run();
let messages = mocker.take_messages();
assert_eq!(messages, vec![vec![Pmt::Usize(123)]]);
Message outputs are captured per output port. Use messages() to clone the currently captured PMTs, or take_messages() to drain them.
Benchmarks
Because Mocker runs a block without a scheduler, it is useful for measuring the cost of one block implementation. The repository’s apply benchmarks use Mocker to compare several ways to apply a simple operation to samples.
For benchmark code that needs to call Kernel::work() directly, parts_mut() returns mutable access to the wrapped kernel, MessageOutputs, and BlockMeta:
let (kernel, message_outputs, meta) = mocker.parts_mut();
Most tests should prefer mocker.run(), since it matches the normal block work loop more closely.