hilt 0.2.0

Renode-based hardware-in-the-loop test fixtures for embedded Rust projects
Documentation
//! `hilt` — hardware-in-the-loop test fixtures for embedded Rust.
//!
//! Runs compiled firmware inside a [Renode](https://renode.io) simulation in a
//! Podman/Docker container, captures its output, and lets host tests assert on
//! it. It supports three firmware-verification styles, and mixes them freely:
//!
//! * **Self-reporting firmware** — a generic Cortex-M board ([`CpuInit::VectorTable`]).
//!   `hilt` reads the ELF's vector table, sets `SP`/`PC`, and hooks a marker
//!   symbol to log `HIL OK` (and a fail symbol / the panic handler to log
//!   `HIL FAIL`). Fail ends the run at once, as does pass for a lone
//!   machine with no host bridge. Assert with
//!   [`HilOutput::passed`] / [`HilOutput::failed`] / [`assert_hil_ok!`];
//!   console UART text is in [`HilOutput::uart`]. Great for one firmware
//!   image per test.
//! * **Live CAN interaction** — board-described platforms ([`CpuInit::Board`])
//!   wired into a CAN hub, with any machine optionally bridged to a host
//!   SocketCAN interface ([`MachineSpec::with_socketcan_bridge`]). Host code
//!   then exchanges frames with the firmware over `vcan` and asserts on
//!   protocol behavior. Multiple machines are supported; Linux hosts only.
//! * **Live UART interaction** — any machine's UART exposed to the host as a
//!   raw TCP socket ([`MachineSpec::with_uart_bridge`]). Start the run with
//!   [`RenodeRunner::spawn`], connect with [`RunningHil::connect_uart`], and
//!   exchange bytes with the firmware's serial line; works on macOS/Windows
//!   hosts too.
//!
//! # Quick start (single self-reporting firmware)
//!
//! ```no_run
//! use hilt::{HilConfig, Platform, RenodeRunner, assert_hil_ok};
//!
//! let output = RenodeRunner::new(HilConfig::single(
//!     Platform::rp2040(),
//!     "target/thumbv6m-none-eabi/release/examples/hil_keyboard",
//! ))
//! .run();
//! assert_hil_ok!(output);
//! ```
//!
//! # Quick start (multi-machine CAN with a host bridge)
//!
//! ```no_run
//! use hilt::{HilConfig, MachineSpec, Platform, RenodeRunner};
//!
//! let machines = vec![
//!     MachineSpec::new("controller", "controller.elf", Platform::stm32h7())
//!         .with_socketcan_bridge(),
//!     MachineSpec::new("sensor", "sensor.elf", Platform::stm32h7()),
//! ];
//! // hilt::setup_vcan("vcan0");  // once, with privileges
//! let run = RenodeRunner::new(HilConfig::multi(machines)).spawn();
//! // ... open vcan0 and exercise the firmware ...
//! let output = run.wait();
//! ```
//!
//! External requirements: `podman` or `docker` (or a native `renode`). The
//! firmware ELF's vector table and symbols are read in pure Rust.
//!
//! [`CpuInit::VectorTable`]: crate::CpuInit::VectorTable
//! [`CpuInit::Board`]: crate::CpuInit::Board

#![warn(missing_docs)]

mod config;
#[cfg(feature = "matrix")]
mod matrix;
mod output;
mod platform;
mod runner;

pub use config::{
    default_renode_image, HilConfig, MachineSpec, UartBridge, DEFAULT_FAIL_MARKER, DEFAULT_MARKER,
    DEFAULT_RENODE_IMAGE, DEFAULT_SOCKETCAN_IFACE, RENODE_ARM64_IMAGE, RENODE_IMAGE_ENV,
};
#[cfg(feature = "matrix")]
pub use matrix::{
    build_test_plan, generate_matrix_test_resc, parse_key_log, parse_tapdance_log, MatrixTestKey,
    MatrixTestPlan,
};
pub use output::{HilOutput, HIL_FAIL_MARKER, HIL_OK_MARKER};
pub use platform::{CpuInit, Platform, ReplSource};
pub use runner::{
    container_runtime_available, detect_container_runtime, extract_elf_info, find_symbol_address,
    interface_available, renode_available, renode_bin, run_renode_container,
    run_with_timeout_and_cleanup, setup_vcan, Artifact, ElfInfo, GuestBuild, RenodeRunner,
    RunningHil, RENODE_ARM64_VERSION, RUNTIME_ENV,
};
// `assert_hil_ok!` is exported at the crate root by `#[macro_export]`.

/// Serializes tests that read or mutate process-global env vars (which are
/// shared across cargo's parallel test threads).
#[cfg(test)]
pub(crate) fn test_env_lock() -> std::sync::MutexGuard<'static, ()> {
    static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
    LOCK.lock()
        .unwrap_or_else(std::sync::PoisonError::into_inner)
}