//! `flowrsc firmware explain`. Without the `firmexplain` feature it says what is missing; with it, //! it runs. `flowrsc firmware ` 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}"); } } }