Testing
Robot code is hard to test on a robot. nxt-rs ships two kinds of mock brick so you can test on your laptop and in CI. Both are always available, with no feature flag needed.
Write code against Brick<T>
Make your robot logic generic over the transport, so it runs on both the real and the mock brick:
use nxt_rs::{prelude::*, transport::Transport};
/// Drives forward until the bumper is hit; returns the distance in degrees.
async fn drive_to_wall<T: Transport>(brick: &Brick<T>) -> nxt_rs::Result<i32> {
let (motor, bumper) = brick.take((A.motor(), S1.touch())).await?;
let start = motor.tacho().await?.rotation_count;
while !bumper.read().await? {
motor.run(power!(50)).degrees(degrees!(30)).wait().await?;
}
Ok(motor.tacho().await?.rotation_count - start)
}
fn main() {}
The simulator
Simulator models a brick in memory. Keep a clone to arrange the scene and
check the results. A test for the function above:
use nxt_rs::{prelude::*, transport::Transport};
async fn drive_to_wall<T: Transport>(brick: &Brick<T>) -> nxt_rs::Result<i32> {
let (motor, bumper) = brick.take((A.motor(), S1.touch())).await?;
let start = motor.tacho().await?.rotation_count;
while !bumper.read().await? {
motor.run(power!(50)).degrees(degrees!(30)).wait().await?;
}
Ok(motor.tacho().await?.rotation_count - start)
}
use nxt_rs::transport::mock::Simulator;
// #[test]
fn stops_at_wall() {
let sim = Simulator::new();
sim.set_sensor(InputPort::S1, 1023, 1); // the bumper is already pressed
let brick = Brick::mock_with(sim.clone());
let distance = drive_to_wall(&brick).block().unwrap();
assert_eq!(distance, 0);
assert_eq!(sim.output(OutputPort::A).rotation_count, 0);
assert_eq!(sim.sensor_config(InputPort::S1).0, nxt_rs::protocol::SensorType::Switch);
}
fn main() { stops_at_wall() }
What the simulator does:
| Area | Behaviour | Control and inspect |
|---|---|---|
| Motors | Movements with a rotation limit finish instantly and update all counters; unlimited runs stay “running” | output(port) |
| Analog sensors | Report the raw and scaled values you set | set_sensor(port, raw, scaled), sensor_config(port) |
| I²C | Each address has a 256-byte register file; an ultrasonic sensor appears when a port is set to 9 V low-speed | set_i2c_registers, i2c_registers, set_ultrasonic_distance |
| Files | In-memory flash with real handle semantics (and a handle limit) | add_file, file, file_names, open_handles |
| Mailboxes | Host messages land in inboxes; you play the on-brick program | inbox(mailbox), push_outbox(mailbox, msg) |
| Programs, sound | Start/stop and tones are recorded | program(), tones(), sound() |
| Display | An 800-byte screen buffer | set_screen(bytes) |
| Brick | Name and battery | set_name, set_battery |
| Everything | Every request telegram is logged | requests() |
sim.open_handles() is handy for checking that your code doesn’t leak file
handles.
Scripts: exact bytes
When you care about the exact telegrams, for example in a custom
transport or a custom command, use a Script. It expects each request in
order and answers with the reply you give:
use nxt_rs::{Brick, blocking::Block, transport::mock::Script, units::Millivolts};
let script = Script::new()
.expect([0x00, 0x0B], [0x02, 0x0B, 0x00, 0x4C, 0x1D]) // GetBatteryLevel → 7500 mV
.expect_no_reply([0x80, 0x0C]); // StopSoundPlayback, no reply
let brick = Brick::mock_script(script.clone());
assert_eq!(brick.battery().block().unwrap(), Millivolts(7500));
brick.raw_send(&[0x80, 0x0C]).block().unwrap();
script.assert_done();
An unexpected request makes the call fail with a transport error that shows the expected and actual bytes.
Tests on real hardware
Mark hardware tests #[ignore] and opt in with an environment variable, so
they never run by accident:
use nxt_rs::prelude::*;
#[test]
#[ignore = "needs an NXT on USB"]
fn battery_is_fine() {
if std::env::var_os("NXT_TEST_USB").is_none() { return; }
let brick = Brick::usb_first().block().unwrap();
assert!(brick.battery().block().unwrap().0 > 6000);
}
fn main() {}
NXT_TEST_USB=1 cargo test -- --ignored --test-threads=1
--test-threads=1 matters: only one test can open the brick at a time.