flowrs-engine/crates/flowrs-cli/tests/firmware_explain.rs
flowRS 49c9d04fbc flowRS engine, cut from flowRS bd343e051a2e
Built alone from an empty CARGO_HOME, its tests passed, and flowrsc checked, ran and exported a
document from the conformance corpus; the export built alone.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:37:09 -05:00

150 lines
5 KiB
Rust

//! `flowrsc firmware explain`. Without the `firmexplain` feature it says what is missing; with it,
//! it runs. `flowrsc firmware <graph>` keeps working beside the new subcommand either way.
use clap::Parser;
use flowrs_cli::{Cli, run};
fn flowrsc(args: &[&str]) -> (i32, String, String) {
let cli = Cli::try_parse_from(std::iter::once("flowrsc").chain(args.iter().copied()))
.unwrap_or_else(|e| panic!("{args:?}: {e}"));
let (mut out, mut err) = (Vec::new(), Vec::new());
let code = run(&cli, &mut out, &mut err);
(
code,
String::from_utf8_lossy(&out).into_owned(),
String::from_utf8_lossy(&err).into_owned(),
)
}
const BLINK: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../flowrs-sim/firmware/stm32/blink_pc13.bin"
);
#[test]
fn the_graph_form_still_parses_beside_the_subcommand() {
assert!(
Cli::try_parse_from(["flowrsc", "firmware", "g.fdsl", "-o", "x", "--board", "b"]).is_ok()
);
// All three of them are still required.
assert!(Cli::try_parse_from(["flowrsc", "firmware", "g.fdsl"]).is_err());
assert!(Cli::try_parse_from(["flowrsc", "firmware"]).is_err());
assert!(Cli::try_parse_from(["flowrsc", "firmware", "explain", "image.bin"]).is_ok());
}
#[cfg(not(feature = "firmexplain"))]
#[test]
fn a_build_with_no_twins_says_where_to_find_one() {
let (code, out, err) = flowrsc(&["firmware", "explain", BLINK, "--board", "stm32f103"]);
assert_eq!(code, 2);
assert!(out.is_empty());
assert!(err.contains("--features firmexplain"), "{err}");
assert!(err.contains("stays in your browser"), "{err}");
}
#[cfg(feature = "firmexplain")]
mod with_twins {
use super::*;
/// One twin CPU per process, and the tests here run on threads.
static CPU: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn one_at_a_time() -> std::sync::MutexGuard<'static, ()> {
CPU.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[test]
fn it_reports_the_three_parts_for_a_board() {
let _cpu = one_at_a_time();
let (code, out, err) = flowrsc(&[
"firmware",
"explain",
BLINK,
"--board",
"bluepill_f103c8",
"--cycles",
"3000000",
]);
assert_eq!(code, 0, "{err}");
assert!(out.contains("OBSERVATIONS"), "{out}");
assert!(out.contains("EVIDENCE"), "{out}");
assert!(out.contains("UNKNOWNS"), "{out}");
assert!(out.contains("PC13 (output) toggled at 50.00 Hz"), "{out}");
assert!(out.contains("authorised to examine"), "{out}");
assert!(
out.contains("flowrsc firmware explain") && out.contains("--board bluepill_f103c8"),
"the command that runs it again: {out}"
);
}
#[test]
fn it_answers_in_json_on_request_and_finds_the_board_when_not_told() {
let _cpu = one_at_a_time();
let (code, out, err) = flowrsc(&[
"firmware", "explain", BLINK, "--json", "--cycles", "2000000",
]);
assert_eq!(code, 0, "{err}");
let v: serde_json::Value = serde_json::from_str(&out).unwrap();
assert_eq!(v["ranking"]["schema"], "flowrs.firmexplain.ranking/1");
assert!(v["ranking"]["candidates"].as_array().unwrap().len() > 1);
assert_eq!(v["explanation"]["schema"], "flowrs.firmexplain/1");
}
#[test]
fn what_changed_is_said() {
let _cpu = one_at_a_time();
let (code, out, err) = flowrsc(&[
"firmware",
"explain",
&format!(
"{}/../flowrs-renode/tests/firmware/embassy_f103.bin",
env!("CARGO_MANIFEST_DIR")
),
"--board",
"stm32f103",
"--cycles",
"7200000",
"--compare-with",
"PA0=1@50ms",
]);
assert_eq!(code, 0, "{err}");
assert!(out.contains("WHAT CHANGED"), "{out}");
assert!(out.contains("usb ready"), "{out}");
}
#[test]
fn what_cannot_run_is_a_usage_error() {
let _cpu = one_at_a_time();
for (args, says) in [
(
vec!["firmware", "explain", "no-such-file", "--board", "stm32f4"],
"no-such-file",
),
(
vec!["firmware", "explain", BLINK, "--board", "no-such"],
"no board or twin platform",
),
(
vec!["firmware", "explain", BLINK, "--board", "uno"],
"runs in the browser",
),
(
vec![
"firmware",
"explain",
BLINK,
"--board",
"stm32f4",
"--stimulus",
"PA0",
],
"PA0=1",
),
] {
let (code, _, err) = flowrsc(&args);
assert_eq!(code, 2, "{args:?}: {err}");
assert!(err.contains(says), "{args:?}: {err}");
}
}
}