use std::time::Duration;
use lmflow::{Graph, Packet, Timestamp};
const CHAIN: &str = r#"
nodes:
- { name: a, kernel: PassThrough, input_ports: ["in"], output_ports: ["mid"] }
- { name: b, kernel: PassThrough, input_ports: ["mid"], output_ports: ["out"] }
input_ports: ["in"]
output_ports: ["out"]
"#;
fn run_chain(n: i64) -> Graph {
let g = Graph::from_yaml(CHAIN).unwrap();
let out = g.add_poller("out").unwrap();
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 = 0;
while got < n {
match out.next() {
Some(_) => got += 1,
None => break,
}
}
g.wait_done_timeout(Duration::from_secs(5)).unwrap();
assert_eq!(got, n, "应收到全部输出");
g
}
#[test]
fn counts_packets_in_and_out() {
let g = run_chain(6);
for i in 0..2 {
let st = g.node_stats(i).expect("node stats");
assert_eq!(st.processed, 6, "{} 应处理 6 次", st.node_name);
assert_eq!(st.packets_in, 6, "{} 收 6 个包", st.node_name);
assert_eq!(st.packets_out, 6, "{} 发 6 个包", st.node_name);
assert_eq!(st.errors, 0);
assert!(!st.running, "{} 已跑完", st.node_name);
assert_eq!(st.running_for_us, 0, "不在跑时 running_for_us 应为 0");
assert_eq!(st.queued, 0, "队列应排空");
}
}
#[test]
fn peak_queue_depth_is_a_high_water_mark() {
let g = run_chain(6);
let b = g.node_stats(1).expect("node stats");
assert_eq!(b.queued, 0);
assert!(
b.peak_queue_depth >= 1,
"峰值应至少为 1(排空后仍保留),实际 {}",
b.peak_queue_depth
);
}
#[test]
fn total_us_is_consistent_with_processed() {
let g = run_chain(4);
let st = g.node_stats(0).unwrap();
assert!(
st.total_process_us >= 0 && st.max_process_us >= 0,
"耗时不应为负"
);
assert!(
st.max_process_us <= st.total_process_us,
"单次最慢不可能超过累计:max={} total={}",
st.max_process_us,
st.total_process_us
);
}
#[test]
fn dot_with_stats_annotates_and_keeps_structure() {
let g = run_chain(3);
let plain = g.to_dot();
let stats = g.to_dot_with_stats();
assert!(!plain.contains("pkts"), "普通版不应带统计");
assert!(stats.contains("3 pkts"), "应标出处理包数:\n{stats}");
assert!(stats.contains("peakQ"), "应标出队列峰值");
assert!(stats.contains("in 3 / out 3"), "应标出收发包数");
assert!(stats.starts_with("digraph"));
let count = |s: &str, pat: &str| s.matches(pat).count();
assert_eq!(
count(&plain, "->"),
count(&stats, "->"),
"边数不应因统计模式改变"
);
assert_eq!(
count(&plain, "[label="),
count(&stats, "[label="),
"节点数不应因统计模式改变"
);
}
#[test]
fn dot_with_stats_keeps_subgraph_clusters() {
let g = Graph::from_yaml(
r#"
subgraphs:
inner:
nodes:
- { name: p, kernel: PassThrough, input_ports: ["sin"], output_ports: ["sout"] }
input_ports: ["sin"]
output_ports: ["sout"]
nodes:
- { name: sub, type: inner, input_ports: ["in"], output_ports: ["out"] }
input_ports: ["in"]
output_ports: ["out"]
"#,
)
.unwrap();
let dot = g.to_dot_with_stats();
assert!(dot.contains("cluster_"), "子图 cluster 应保留:\n{dot}");
}
#[test]
fn stats_timing_off_zeroes_only_timing_fields() {
let g = Graph::from_yaml(
r#"
stats_timing: false
nodes:
- { name: a, kernel: PassThrough, input_ports: ["in"], output_ports: ["out"] }
input_ports: ["in"]
output_ports: ["out"]
"#,
)
.unwrap();
let out = g.add_poller("out").unwrap();
g.start().unwrap();
let inp = g.input("in").unwrap();
for i in 0..4i64 {
inp.send(Packet::from_i64(i).at(Timestamp(i))).unwrap();
}
g.close_all_inputs();
let mut n = 0;
while n < 4 {
match out.next() {
Some(_) => n += 1,
None => break,
}
}
g.wait_done_timeout(Duration::from_secs(5)).unwrap();
let st = g.node_stats(0).unwrap();
assert_eq!(st.processed, 4, "计数不受计时开关影响");
assert_eq!(st.packets_in, 4);
assert_eq!(st.packets_out, 4);
assert_eq!(st.total_process_us, 0, "关了计时,累计耗时应为 0");
assert_eq!(st.max_process_us, 0, "关了计时,最慢一次应为 0");
assert_eq!(st.running_for_us, 0);
let dot = g.to_dot_with_stats();
assert!(dot.contains("4 pkts"), "包数仍应标出:\n{dot}");
}
#[test]
fn watchdog_forces_timing_on() {
#[derive(Default)]
struct Slow;
impl lmflow::Kernel for Slow {
fn process(&mut self, cc: &mut lmflow::KernelCtx) -> lmflow::Result<()> {
std::thread::sleep(Duration::from_millis(2));
cc.forward(0, 0)
}
}
lmflow::register_kernel::<Slow>("SlowForWatchdogTest").unwrap();
let g = Graph::from_yaml(
r#"
stats_timing: false
watchdog_ms: 1
nodes:
- { name: a, kernel: SlowForWatchdogTest, input_ports: ["in"], output_ports: ["out"] }
input_ports: ["in"]
output_ports: ["out"]
"#,
)
.unwrap();
let out = g.add_poller("out").unwrap();
g.start().unwrap();
g.input("in")
.unwrap()
.send(Packet::from_i64(1).at(Timestamp(0)))
.unwrap();
g.close_all_inputs();
let _ = out.next();
g.wait_done_timeout(Duration::from_secs(5)).unwrap();
let st = g.node_stats(0).unwrap();
assert_eq!(st.processed, 1);
assert!(
st.max_process_us >= 1000,
"watchdog_ms>0 必须强制开启计时(睡了 2ms,应测到 >=1000µs);实测 {}µs —— \
若为 0 说明计时被 stats_timing=false 关掉了,watchdog 会静默失效",
st.max_process_us
);
}