use std::time::{Duration, Instant};
use thal::value::Value;
use thal::Reactor;
const PROGRAM: &str = r#"
reaction Echo {
when: Tick(t)
where: t.sequence == 1
emit: Process { cmd: "echo", args: ["hello, thal"] }
}
reaction Init {
when: Boot(b)
emit: Timer { interval: 50ms }
}
"#;
#[tokio::test(flavor = "multi_thread")]
async fn process_actor_runs_echo() {
let program = thal::load_str(PROGRAM).expect("load");
let reactor = Reactor::new(program);
let store = reactor.store();
let task = tokio::spawn(reactor.run());
let deadline = Instant::now() + Duration::from_millis(500);
loop {
if Instant::now() > deadline {
break;
}
let processes = store.scan_by_name("Process");
if processes
.iter()
.any(|p| matches!(p.fields.get("status"), Some(Value::String(s)) if s == "Done"))
{
break;
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
task.abort();
let processes = store.scan_by_name("Process");
assert_eq!(
processes.len(),
1,
"expected exactly one Process molecule, got {}",
processes.len()
);
let p = &processes[0];
assert_eq!(p.fields["cmd"], Value::String("echo".into()));
assert_eq!(
p.fields["status"],
Value::String("Done".into()),
"actor should have driven Pending -> Done"
);
assert_eq!(p.fields["exit_code"], Value::Int(0));
let stdout = p.fields["stdout"]
.as_string()
.expect("stdout is a String");
assert!(
stdout.contains("hello, thal"),
"expected echo output to contain the argument, got {stdout:?}"
);
match &p.fields["args"] {
Value::List(items) => {
assert_eq!(items.len(), 1);
assert_eq!(items[0], Value::String("hello, thal".into()));
}
other => panic!("expected args to be a List, got {}", other.type_name()),
}
}