mod common;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use lmflow::{register_kernel, Graph, Kernel, KernelCtx, Packet, State, Timestamp};
static OPENS: AtomicUsize = AtomicUsize::new(0);
#[derive(Default)]
struct CountOpen;
impl Kernel for CountOpen {
fn open(&mut self, _cc: &mut KernelCtx) -> lmflow::Result<()> {
OPENS.fetch_add(1, Ordering::SeqCst);
Ok(())
}
fn process(&mut self, cc: &mut KernelCtx) -> lmflow::Result<()> {
cc.forward(0, 0)
}
}
#[derive(Default)]
struct FailOnFive;
impl Kernel for FailOnFive {
fn process(&mut self, cc: &mut KernelCtx) -> lmflow::Result<()> {
let v = cc.input(0).and_then(|p| p.as_i64()).unwrap_or(0);
if v == 5 {
return Err(cc.fail("boom on 5"));
}
cc.forward(0, 0)
}
}
fn reg() {
let _ = register_kernel::<CountOpen>("CountOpenK");
let _ = register_kernel::<FailOnFive>("FailOnFiveK");
}
fn one_node(kernel: &str) -> String {
format!(
"nodes:\n - {{ name: k, kernel: {kernel}, input_ports: [\"in\"], output_ports: [\"out\"] }}\n\
input_ports: [\"in\"]\noutput_ports: [\"out\"]\n"
)
}
fn run_round(g: &Graph, out: &lmflow::Poller, n: i64) -> Vec<i64> {
g.start().unwrap();
let inp = g.input("in").unwrap();
for i in 0..n {
inp.send(Packet::from_i64(i).at(Timestamp(i))).unwrap();
}
g.close_all_inputs();
let mut got = Vec::new();
while (got.len() as i64) < n {
match out.next() {
Some(p) => got.push(p.as_i64().unwrap()),
None => break,
}
}
g.wait_done_timeout(Duration::from_secs(5)).unwrap();
got
}
#[test]
fn reset_allows_clean_rerun() {
reg();
OPENS.store(0, Ordering::SeqCst);
let g = Graph::from_yaml(&one_node("CountOpenK")).unwrap();
let out = g.add_poller("out").unwrap();
let r1 = run_round(&g, &out, 5);
assert_eq!(r1, vec![0, 1, 2, 3, 4], "第一轮");
assert_eq!(g.state(), State::Terminated);
let opens_after_r1 = OPENS.load(Ordering::SeqCst);
assert_eq!(
opens_after_r1, 1,
"open 应只在首轮调一次(实际 {opens_after_r1})"
);
assert_eq!(g.node_stats(0).unwrap().processed, 5);
g.reset().expect("Terminated 后应能 reset");
assert_eq!(g.state(), State::Initialized, "reset 后回到可 start");
assert_eq!(g.node_stats(0).unwrap().processed, 0, "reset 必须清零统计");
let r2 = run_round(&g, &out, 5);
assert_eq!(r2, vec![0, 1, 2, 3, 4], "第二轮(时间戳复用,last_sent 已清)");
assert_eq!(
OPENS.load(Ordering::SeqCst),
1,
"reset **不该**重跑 open —— 算子实例被保留(这正是 reset 的价值)"
);
assert_eq!(g.node_stats(0).unwrap().processed, 5, "第二轮又处理 5 个");
}
#[test]
fn reset_clears_prior_error() {
reg();
let g = Graph::from_yaml(&one_node("FailOnFiveK")).unwrap();
let out = g.add_poller("out").unwrap();
g.start().unwrap();
let inp = g.input("in").unwrap();
for i in 0..8i64 {
let _ = inp.send(Packet::from_i64(i).at(Timestamp(i)));
}
let _ = out.next();
g.close_all_inputs();
let err = g.wait_done_timeout(Duration::from_secs(5));
assert!(err.is_err(), "第一轮应因 5 而出错");
g.reset().expect("错误终止后也应能 reset");
let out2 = g.add_poller("out").unwrap();
g.start().unwrap();
let inp = g.input("in").unwrap();
for i in [0i64, 1, 2, 3] {
inp.send(Packet::from_i64(i).at(Timestamp(i))).unwrap();
}
g.close_all_inputs();
let mut got = Vec::new();
while got.len() < 4 {
match out2.next() {
Some(p) => got.push(p.as_i64().unwrap()),
None => break,
}
}
g.wait_done_timeout(Duration::from_secs(5))
.expect("reset 后第二轮不该带着上一轮的错误");
assert_eq!(got, vec![0, 1, 2, 3]);
}
#[test]
fn reset_on_running_is_rejected() {
reg();
let g = common::graph_from_yaml(&one_node("PassThrough")).unwrap();
g.add_poller("out").unwrap();
g.start().unwrap();
let err = g.reset().expect_err("Running 上 reset 必须报错");
assert!(err.to_string().contains("Terminated"), "{err}");
g.close_all_inputs();
let _ = g.wait_done_timeout(Duration::from_secs(5));
}