use std::path::PathBuf;
use crate::platform::Platform;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UartBridge {
pub host_port: u16,
pub uart: String,
}
#[derive(Debug, Clone)]
pub struct MachineSpec {
pub name: String,
pub firmware_elf: PathBuf,
pub platform: Platform,
pub socketcan_bridge: bool,
pub uart_bridge: Option<UartBridge>,
}
impl MachineSpec {
#[must_use]
pub fn new(
name: impl Into<String>,
firmware_elf: impl Into<PathBuf>,
platform: Platform,
) -> Self {
Self {
name: name.into(),
firmware_elf: firmware_elf.into(),
platform,
socketcan_bridge: false,
uart_bridge: None,
}
}
#[must_use]
pub fn with_socketcan_bridge(mut self) -> Self {
self.socketcan_bridge = true;
self
}
#[must_use]
pub fn with_uart_bridge(mut self, host_port: u16, uart: impl Into<String>) -> Self {
self.uart_bridge = Some(UartBridge {
host_port,
uart: uart.into(),
});
self
}
}
#[derive(Debug, Clone)]
pub struct HilConfig {
pub machines: Vec<MachineSpec>,
pub timeout_secs: u32,
pub socketcan_iface: String,
pub stubs_repl: Option<PathBuf>,
pub image: String,
pub marker: String,
}
pub const DEFAULT_RENODE_IMAGE: &str = "docker.io/antmicro/renode:latest";
pub const RENODE_ARM64_IMAGE: &str = "localhost/hilt-renode:arm64";
pub const RENODE_IMAGE_ENV: &str = "HILT_RENODE_IMAGE";
pub const DEFAULT_SOCKETCAN_IFACE: &str = "vcan0";
pub const DEFAULT_MARKER: &str = "hil_marker";
#[must_use]
pub fn default_renode_image() -> String {
if let Ok(image) = std::env::var(RENODE_IMAGE_ENV) {
return image;
}
if cfg!(target_arch = "aarch64") {
RENODE_ARM64_IMAGE.to_string()
} else {
DEFAULT_RENODE_IMAGE.to_string()
}
}
impl HilConfig {
#[must_use]
pub fn new(platform: Platform, firmware_elf: impl Into<PathBuf>) -> Self {
Self::single(platform, firmware_elf)
}
#[must_use]
pub fn single(platform: Platform, firmware_elf: impl Into<PathBuf>) -> Self {
let timeout_secs = platform.default_timeout_secs;
Self {
machines: vec![MachineSpec::new("hil", firmware_elf, platform)],
timeout_secs,
socketcan_iface: DEFAULT_SOCKETCAN_IFACE.to_string(),
stubs_repl: None,
image: default_renode_image(),
marker: DEFAULT_MARKER.to_string(),
}
}
#[must_use]
pub fn multi(machines: Vec<MachineSpec>) -> Self {
Self {
machines,
timeout_secs: 15,
socketcan_iface: DEFAULT_SOCKETCAN_IFACE.to_string(),
stubs_repl: None,
image: default_renode_image(),
marker: DEFAULT_MARKER.to_string(),
}
}
#[must_use]
pub fn timeout(mut self, secs: u32) -> Self {
self.timeout_secs = secs;
self
}
#[must_use]
pub fn socketcan_iface(mut self, iface: impl Into<String>) -> Self {
self.socketcan_iface = iface.into();
self
}
#[must_use]
pub fn stubs_repl(mut self, path: impl Into<PathBuf>) -> Self {
self.stubs_repl = Some(path.into());
self
}
#[must_use]
pub fn image(mut self, image: impl Into<String>) -> Self {
self.image = image.into();
self
}
#[must_use]
pub fn marker(mut self, marker: impl Into<String>) -> Self {
self.marker = marker.into();
self
}
#[must_use]
pub fn needs_host_network(&self) -> bool {
self.machines.iter().any(|m| m.socketcan_bridge)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_defaults() {
let cfg = HilConfig::single(Platform::rp2040(), "/tmp/fw.elf");
assert_eq!(cfg.machines.len(), 1);
assert_eq!(cfg.machines[0].name, "hil");
assert_eq!(cfg.timeout_secs, 10);
assert_eq!(cfg.socketcan_iface, "vcan0");
assert!(cfg.stubs_repl.is_none());
assert!(!cfg.needs_host_network());
}
#[test]
fn single_uses_platform_timeout() {
assert_eq!(
HilConfig::single(Platform::stm32h7(), "/tmp/fw.elf").timeout_secs,
15
);
}
#[test]
fn multi_and_bridge_flags() {
let machines = vec![
MachineSpec::new("a", "/tmp/a.elf", Platform::stm32h7()).with_socketcan_bridge(),
MachineSpec::new("b", "/tmp/b.elf", Platform::stm32h7()),
];
let cfg = HilConfig::multi(machines);
assert_eq!(cfg.machines.len(), 2);
assert_eq!(cfg.timeout_secs, 15);
assert!(cfg.needs_host_network());
}
#[test]
fn uart_bridge_builder() {
let machines = vec![
MachineSpec::new("a", "/tmp/a.elf", Platform::rp2040())
.with_uart_bridge(3456, "sysbus.uart"),
MachineSpec::new("b", "/tmp/b.elf", Platform::rp2040()),
];
let cfg = HilConfig::multi(machines);
assert!(!cfg.needs_host_network());
assert_eq!(
cfg.machines[0].uart_bridge,
Some(UartBridge {
host_port: 3456,
uart: "sysbus.uart".to_string(),
})
);
assert!(cfg.machines[1].uart_bridge.is_none());
}
#[test]
fn default_image_arch_selected_and_env_override() {
let _guard = crate::test_env_lock();
let saved = std::env::var(RENODE_IMAGE_ENV).ok();
std::env::remove_var(RENODE_IMAGE_ENV);
let img = default_renode_image();
if cfg!(target_arch = "aarch64") {
assert_eq!(img, RENODE_ARM64_IMAGE);
} else {
assert_eq!(img, DEFAULT_RENODE_IMAGE);
}
assert_eq!(
HilConfig::single(Platform::rp2040(), "/tmp/fw.elf").image,
img
);
std::env::set_var(RENODE_IMAGE_ENV, "localhost/custom:tag");
assert_eq!(default_renode_image(), "localhost/custom:tag");
assert_eq!(
HilConfig::single(Platform::rp2040(), "/tmp/fw.elf").image,
"localhost/custom:tag"
);
match saved {
Some(v) => std::env::set_var(RENODE_IMAGE_ENV, v),
None => std::env::remove_var(RENODE_IMAGE_ENV),
}
}
#[test]
fn builder_overrides() {
let cfg = HilConfig::single(Platform::stm32h7(), "/tmp/fw.elf")
.timeout(30)
.socketcan_iface("vcan1")
.stubs_repl("/tmp/stubs.repl")
.image("localhost/renode:dev")
.marker("done_marker");
assert_eq!(cfg.timeout_secs, 30);
assert_eq!(cfg.socketcan_iface, "vcan1");
assert_eq!(cfg.stubs_repl, Some(PathBuf::from("/tmp/stubs.repl")));
assert_eq!(cfg.image, "localhost/renode:dev");
assert_eq!(cfg.marker, "done_marker");
}
}