Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

AreaBehaviourControl and inspect
MotorsMovements with a rotation limit finish instantly and update all counters; unlimited runs stay “running”output(port)
Analog sensorsReport the raw and scaled values you setset_sensor(port, raw, scaled), sensor_config(port)
I²CEach address has a 256-byte register file; an ultrasonic sensor appears when a port is set to 9 V low-speedset_i2c_registers, i2c_registers, set_ultrasonic_distance
FilesIn-memory flash with real handle semantics (and a handle limit)add_file, file, file_names, open_handles
MailboxesHost messages land in inboxes; you play the on-brick programinbox(mailbox), push_outbox(mailbox, msg)
Programs, soundStart/stop and tones are recordedprogram(), tones(), sound()
DisplayAn 800-byte screen bufferset_screen(bytes)
BrickName and batteryset_name, set_battery
EverythingEvery request telegram is loggedrequests()

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.