use std::time::Duration;
use lmflow::{Graph, Packet, State, Timestamp};
#[test]
fn passthrough_forwards_unchanged() {
let g = Graph::from_yaml(
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"]
"#,
)
.unwrap();
let out = g.add_poller("out").unwrap();
g.start().unwrap();
let inp = g.input("in").unwrap();
for i in 0..5i64 {
inp.send(Packet::from_i64(i * 10).at(Timestamp(i))).unwrap();
}
g.close_all_inputs();
let mut got = Vec::new();
while got.len() < 5 {
match out.next() {
Some(p) => got.push(p.as_i64().unwrap()),
None => break,
}
}
assert_eq!(got, vec![0, 10, 20, 30, 40], "两级 PassThrough 应原样透传");
g.wait_done_timeout(Duration::from_secs(5)).unwrap();
assert_eq!(g.state(), State::Terminated);
}
#[test]
fn sink_consumes_and_counts() {
let g = Graph::from_yaml(
r#"
nodes:
- { name: k, kernel: Sink, input_ports: ["in"], output_ports: [] }
input_ports: ["in"]
output_ports: []
"#,
)
.unwrap();
g.start().unwrap();
let inp = g.input("in").unwrap();
for i in 0..3i64 {
inp.send(Packet::from_i64(i).at(Timestamp(i))).unwrap();
}
g.close_all_inputs();
g.wait_done_timeout(Duration::from_secs(5)).unwrap();
assert_eq!(g.counter_value("sink.packets"), 3);
assert_eq!(g.counter_value("sink.closed"), 1);
assert_eq!(g.state(), State::Terminated);
}
#[test]
fn defaults_are_registered() {
lmflow::builtin::register_defaults();
let names = lmflow::kernel::registered_names();
for want in ["PassThrough", "Sink"] {
assert!(names.iter().any(|n| n == want), "{want} 应已注册:{names:?}");
}
}
#[test]
fn fanout_is_native_no_split_kernel_needed() {
let g = Graph::from_yaml(
r#"
nodes:
- { name: a, kernel: PassThrough, input_ports: ["in"], output_ports: ["mid"] }
- { name: b, kernel: PassThrough, input_ports: ["mid"], output_ports: ["x"] }
- { name: c, kernel: PassThrough, input_ports: ["mid"], output_ports: ["y"] }
input_ports: ["in"]
output_ports: ["x", "y"]
"#,
)
.unwrap();
let px = g.add_poller("x").unwrap();
let py = g.add_poller("y").unwrap();
g.start().unwrap();
g.input("in")
.unwrap()
.send(Packet::from_i64(7).at(Timestamp(0)))
.unwrap();
g.close_all_inputs();
assert_eq!(px.next().unwrap().as_i64(), Some(7));
assert_eq!(
py.next().unwrap().as_i64(),
Some(7),
"同一条边的两个消费者都应收到"
);
g.wait_done_timeout(Duration::from_secs(5)).unwrap();
}
#[test]
fn log_fast_path_does_not_swallow_logs() {
use std::ffi::{c_char, c_void, CStr};
use std::sync::atomic::{AtomicUsize, Ordering};
static SEEN: AtomicUsize = AtomicUsize::new(0);
unsafe extern "C" fn sink_cb(_u: *mut c_void, _lvl: i32, msg: *const c_char) {
if !msg.is_null() && !unsafe { CStr::from_ptr(msg) }.to_bytes().is_empty() {
SEEN.fetch_add(1, Ordering::Relaxed);
}
}
assert!(
!lmflow::runtime::log_enabled(),
"默认不该有 log sink(快路应生效,算子可跳过 format!)"
);
lmflow::ffi::lmflow_set_log_callback(Some(sink_cb), std::ptr::null_mut());
assert!(lmflow::runtime::log_enabled(), "装上 sink 后快路须让路");
let g = Graph::from_yaml(
r#"
nodes:
- { name: k, kernel: Sink, input_ports: ["in"], output_ports: [] }
input_ports: ["in"]
output_ports: []
"#,
)
.unwrap();
g.start().unwrap();
let inp = g.input("in").unwrap();
for i in 0..3i64 {
inp.send(Packet::from_i64(i).at(Timestamp(i))).unwrap();
}
g.close_all_inputs();
g.wait_done_timeout(Duration::from_secs(5)).unwrap();
lmflow::ffi::lmflow_set_log_callback(None, std::ptr::null_mut());
assert!(!lmflow::runtime::log_enabled(), "卸掉 sink 后应回到快路");
assert!(
SEEN.load(Ordering::Relaxed) >= 3,
"装了 sink 就必须真收到日志(每包一条 + close 一条),实际 {}",
SEEN.load(Ordering::Relaxed)
);
}