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]>
150 lines
5 KiB
Rust
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}");
|
|
}
|
|
}
|
|
}
|