#![forbid(unsafe_code)]
use ftui_core::event::Event;
use ftui_render::frame::Frame;
use ftui_runtime::program::{Cmd, Model, RuntimeLane};
use ftui_runtime::simulator::ProgramSimulator;
use std::time::Duration;
struct MatrixModel {
trace: Vec<String>,
value: i32,
}
#[derive(Debug)]
enum MMsg {
Step(String),
Batch(Vec<String>),
Seq(Vec<String>),
Task(String),
TaskDone(String),
Nested(u32),
Quit,
}
impl From<Event> for MMsg {
fn from(_: Event) -> Self {
MMsg::Step("event".into())
}
}
impl Model for MatrixModel {
type Message = MMsg;
fn init(&mut self) -> Cmd<Self::Message> {
self.trace.push("init".into());
Cmd::none()
}
fn update(&mut self, msg: Self::Message) -> Cmd<Self::Message> {
match msg {
MMsg::Step(s) => {
self.value += 1;
self.trace.push(format!("step:{s}"));
Cmd::none()
}
MMsg::Batch(items) => {
self.trace.push(format!("batch:{}", items.len()));
Cmd::batch(items.into_iter().map(|s| Cmd::msg(MMsg::Step(s))).collect())
}
MMsg::Seq(items) => {
self.trace.push(format!("seq:{}", items.len()));
Cmd::sequence(items.into_iter().map(|s| Cmd::msg(MMsg::Step(s))).collect())
}
MMsg::Task(l) => {
self.trace.push(format!("task:{l}"));
let lc = l.clone();
Cmd::task(move || MMsg::TaskDone(lc))
}
MMsg::TaskDone(l) => {
self.trace.push(format!("done:{l}"));
Cmd::none()
}
MMsg::Nested(d) => {
self.trace.push(format!("n:{d}"));
if d > 0 {
Cmd::msg(MMsg::Nested(d - 1))
} else {
Cmd::none()
}
}
MMsg::Quit => {
self.trace.push("quit".into());
Cmd::quit()
}
}
}
fn view(&self, _frame: &mut Frame) {}
fn on_shutdown(&mut self) -> Cmd<Self::Message> {
self.trace.push("shutdown".into());
Cmd::none()
}
}
fn new_sim() -> ProgramSimulator<MatrixModel> {
let mut sim = ProgramSimulator::new(MatrixModel {
trace: vec![],
value: 0,
});
sim.init();
sim
}
#[test]
fn i01_batch_ordering() {
let mut sim = new_sim();
sim.send(MMsg::Batch(vec!["a".into(), "b".into(), "c".into()]));
let t = &sim.model().trace;
let a = t.iter().position(|s| s == "step:a").unwrap();
let b = t.iter().position(|s| s == "step:b").unwrap();
let c = t.iter().position(|s| s == "step:c").unwrap();
assert!(
a < b && b < c,
"I01: batch must preserve order: a={a} b={b} c={c}"
);
}
#[test]
fn i02_sequence_ordering() {
let mut sim = new_sim();
sim.send(MMsg::Seq(vec!["x".into(), "y".into(), "z".into()]));
let t = &sim.model().trace;
let x = t.iter().position(|s| s == "step:x").unwrap();
let y = t.iter().position(|s| s == "step:y").unwrap();
let z = t.iter().position(|s| s == "step:z").unwrap();
assert!(x < y && y < z, "I02: sequence must preserve order");
}
#[test]
fn i03_task_routes_through_update() {
let mut sim = new_sim();
sim.send(MMsg::Task("t1".into()));
assert!(
sim.model().trace.contains(&"done:t1".to_string()),
"I03: task result must route through update"
);
}
#[test]
fn i04_quit_halts_batch() {
struct QuitBatchModel {
trace: Vec<String>,
}
#[derive(Debug)]
enum QBMsg {
Step(&'static str),
Go,
}
impl From<Event> for QBMsg {
fn from(_: Event) -> Self {
QBMsg::Step("e")
}
}
impl Model for QuitBatchModel {
type Message = QBMsg;
fn update(&mut self, msg: QBMsg) -> Cmd<QBMsg> {
match msg {
QBMsg::Step(s) => {
self.trace.push(s.into());
Cmd::none()
}
QBMsg::Go => Cmd::batch(vec![
Cmd::msg(QBMsg::Step("pre")),
Cmd::quit(),
Cmd::msg(QBMsg::Step("post")),
]),
}
}
fn view(&self, _: &mut Frame) {}
}
let mut s = ProgramSimulator::new(QuitBatchModel { trace: vec![] });
s.init();
s.send(QBMsg::Go);
assert!(
s.model().trace.contains(&"pre".to_string()),
"I04: pre-quit must run"
);
assert!(
!s.model().trace.contains(&"post".to_string()),
"I04: post-quit must not run"
);
}
#[test]
fn i05_no_processing_after_quit() {
let mut sim = new_sim();
sim.send(MMsg::Step("a".into()));
sim.send(MMsg::Quit);
sim.send(MMsg::Step("b".into()));
assert!(
!sim.model().trace.contains(&"step:b".to_string()),
"I05: post-quit must be dropped"
);
}
#[test]
fn i06_init_before_updates() {
let sim = new_sim();
assert_eq!(sim.model().trace[0], "init", "I06: init must be first");
}
#[test]
fn i07_shutdown_after_quit() {
let mut sim = new_sim();
sim.send(MMsg::Quit);
let _ = sim.model_mut().on_shutdown();
let t = &sim.model().trace;
let q = t.iter().position(|s| s == "quit").unwrap();
let s = t.iter().position(|s| s == "shutdown").unwrap();
assert!(s > q, "I07: shutdown must follow quit");
}
#[test]
fn i08_subscription_stop_timeout_constant() {
use ftui_runtime::subscription::StopSignal;
let _: fn(&StopSignal) -> bool = StopSignal::is_stopped;
let _: fn(&StopSignal, Duration) -> bool = StopSignal::wait_timeout;
}
#[test]
fn i09_stop_signal_has_cancellation_token() {
use ftui_runtime::subscription::StopSignal;
let _: fn(&StopSignal) -> &ftui_runtime::CancellationToken = StopSignal::cancellation_token;
}
#[test]
fn i10_process_subscription_api_exists() {
use ftui_runtime::process_subscription::{ProcessEvent, ProcessSubscription};
let _killed = ProcessEvent::Killed;
let _exited = ProcessEvent::Exited(0);
let _signaled = ProcessEvent::Signaled(15);
let _error = ProcessEvent::Error("test".into());
let _sub: ProcessSubscription<String> =
ProcessSubscription::new("echo", |e| format!("{e:?}")).timeout(Duration::from_secs(1));
}
#[test]
fn i11_process_exit_code_variant() {
use ftui_runtime::process_subscription::ProcessEvent;
let event = ProcessEvent::Exited(42);
assert_eq!(
event,
ProcessEvent::Exited(42),
"I11: exit code must be captured"
);
}
#[test]
fn i12_effect_counters_monotonic() {
let before = ftui_runtime::effect_system::effects_command_total();
ftui_runtime::effect_system::record_command_effect("matrix-test", 0);
let after = ftui_runtime::effect_system::effects_command_total();
assert_eq!(
after,
before + 1,
"I12: command counter must increment by 1"
);
}
#[test]
fn i13_inline_gauge_api_exists() {
let _ = ftui_runtime::inline_active_widgets();
}
#[test]
fn i14_i15_i16_terminal_writer_modes() {
use ftui_runtime::ScreenMode;
let _inline = ScreenMode::Inline { ui_height: 5 };
let _auto = ScreenMode::InlineAuto {
min_height: 3,
max_height: 10,
};
let _alt = ScreenMode::AltScreen;
}
#[test]
fn i17_default_lane_structured() {
assert_eq!(RuntimeLane::default(), RuntimeLane::Structured, "I17");
}
#[test]
fn i18_asupersync_fallback() {
assert_eq!(
RuntimeLane::Asupersync.resolve(),
RuntimeLane::Structured,
"I18"
);
}
#[test]
fn i19_shadow_lane_equivalence() {
fn run_workload() -> Vec<String> {
let mut sim = new_sim();
sim.send(MMsg::Step("a".into()));
sim.send(MMsg::Batch(vec!["b".into(), "c".into()]));
sim.send(MMsg::Task("t".into()));
sim.model().trace.clone()
}
let r1 = run_workload();
let r2 = run_workload();
assert_eq!(
r1, r2,
"I19: identical workloads must produce identical output"
);
}
#[test]
fn i20_deterministic_10_runs() {
let reference = {
let mut sim = new_sim();
sim.send(MMsg::Step("x".into()));
sim.send(MMsg::Batch(vec!["y".into(), "z".into()]));
sim.send(MMsg::Task("t".into()));
sim.send(MMsg::Nested(3));
sim.model().trace.clone()
};
for i in 1..10 {
let mut sim = new_sim();
sim.send(MMsg::Step("x".into()));
sim.send(MMsg::Batch(vec!["y".into(), "z".into()]));
sim.send(MMsg::Task("t".into()));
sim.send(MMsg::Nested(3));
assert_eq!(sim.model().trace, reference, "I20: run {i} diverged");
}
}
#[test]
fn i21_deep_recursion() {
let mut sim = new_sim();
sim.send(MMsg::Nested(50));
assert!(
sim.model().trace.contains(&"n:0".to_string()),
"I21: must reach depth 0"
);
}
#[test]
fn i22_large_batch_ordered() {
let items: Vec<String> = (0..200).map(|i| format!("{i}")).collect();
let mut sim = new_sim();
sim.send(MMsg::Batch(items));
let first = sim
.model()
.trace
.iter()
.position(|s| s == "step:0")
.unwrap();
let last = sim
.model()
.trace
.iter()
.position(|s| s == "step:199")
.unwrap();
assert!(first < last, "I22: items must be ordered");
}
#[test]
fn i23_cancellation_stops_work() {
use ftui_runtime::cancellation::CancellationSource;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
let source = CancellationSource::new();
let token = source.token();
let done = Arc::new(AtomicBool::new(false));
let d = done.clone();
let h = std::thread::spawn(move || {
while !token.is_cancelled() {
std::thread::sleep(Duration::from_millis(5));
}
d.store(true, Ordering::SeqCst);
});
std::thread::sleep(Duration::from_millis(30));
source.cancel();
h.join().unwrap();
assert!(
done.load(Ordering::SeqCst),
"I23: cancellation must stop work"
);
}
#[test]
fn i24_drop_source_no_cancel() {
use ftui_runtime::cancellation::CancellationSource;
let source = CancellationSource::new();
let token = source.token();
drop(source);
assert!(!token.is_cancelled(), "I24: drop must not cancel");
}
#[test]
fn matrix_has_24_invariants() {
let invariant_count = 24;
assert_eq!(invariant_count, 24, "matrix must cover 24 invariants");
}