mod common;
use lmflow::packet::type_id;
use lmflow::{register_kernel, Kernel, KernelContract, KernelCtx, Packet, Timestamp};
#[derive(Default)]
struct DeclaresHostObject;
impl Kernel for DeclaresHostObject {
fn get_contract(c: &mut KernelContract) {
c.input_type(0, type_id::HOST_OBJECT);
}
fn process(&mut self, cc: &mut KernelCtx) -> lmflow::Result<()> {
cc.forward(0, 0)
}
}
#[derive(Default)]
struct EmitsHostObject;
impl Kernel for EmitsHostObject {
fn get_contract(c: &mut KernelContract) {
c.input_any(0);
c.output_type(0, type_id::HOST_OBJECT);
}
fn process(&mut self, cc: &mut KernelCtx) -> lmflow::Result<()> {
cc.forward(0, 0)
}
}
#[derive(Default)]
struct HostObjectSource;
impl Kernel for HostObjectSource {
fn process(&mut self, cc: &mut KernelCtx) -> lmflow::Result<()> {
let ptr = Box::into_raw(Box::new(1i64)) as *mut std::ffi::c_void;
unsafe extern "C" fn drop_i64(p: *mut std::ffi::c_void) {
drop(unsafe { Box::from_raw(p as *mut i64) });
}
cc.emit(0, unsafe {
Packet::from_foreign(ptr, type_id::HOST_OBJECT, Some(drop_i64))
})?;
cc.source_done();
Ok(())
}
}
#[derive(Default)]
struct HostObjectOnClose;
impl Kernel for HostObjectOnClose {
fn process(&mut self, _cc: &mut KernelCtx) -> lmflow::Result<()> {
Ok(())
}
fn close(&mut self, cc: &mut KernelCtx) -> lmflow::Result<()> {
let ptr = Box::into_raw(Box::new(1i64)) as *mut std::ffi::c_void;
unsafe extern "C" fn drop_i64(p: *mut std::ffi::c_void) {
drop(unsafe { Box::from_raw(p as *mut i64) });
}
cc.emit(0, unsafe {
Packet::from_foreign(ptr, type_id::HOST_OBJECT, Some(drop_i64))
})
}
}
fn one_node(kernel: &str) -> String {
format!(
r#"
nodes:
- {{ name: n, kernel: {kernel}, input_ports: ["in"], output_ports: ["out"] }}
input_ports: ["in"]
output_ports: ["out"]
"#
)
}
#[test]
fn contract_declaring_host_object_is_rejected_at_build() {
let _ = register_kernel::<DeclaresHostObject>("DeclaresHostObject");
let err = common::graph_from_yaml(&one_node("DeclaresHostObject")).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("HOST_OBJECT") && msg.contains("not enabled"),
"应指明该类型未启用: {msg}"
);
assert!(
msg.contains("input port 0"),
"应指出是哪个方向的哪个口: {msg}"
);
assert!(
msg.contains("LMFLOW_TYPE_BUFFER") && msg.contains("LMFLOW_TYPE_STR"),
"应给出替代方案(BUFFER / STR+JSON): {msg}"
);
}
#[test]
fn output_contract_declaring_host_object_is_rejected() {
let _ = register_kernel::<EmitsHostObject>("EmitsHostObject");
let msg = common::graph_from_yaml(&one_node("EmitsHostObject"))
.unwrap_err()
.to_string();
assert!(
msg.contains("HOST_OBJECT") && msg.contains("output port 0"),
"输出口方向也要拒: {msg}"
);
}
#[test]
fn packet_carrying_host_object_is_rejected_at_runtime() {
unsafe extern "C" fn drop_i64(p: *mut std::ffi::c_void) {
drop(unsafe { Box::from_raw(p as *mut i64) });
}
let graph = common::graph_from_yaml(&one_node("PassThrough")).unwrap();
graph.add_poller("out").unwrap();
graph.start().unwrap();
let ptr = Box::into_raw(Box::new(1i64)) as *mut std::ffi::c_void;
let pkt =
unsafe { Packet::from_foreign(ptr, type_id::HOST_OBJECT, Some(drop_i64)) }.at(Timestamp(0));
graph.input("in").unwrap().send(pkt).unwrap();
graph.close_all_inputs();
let msg = graph.wait_done().unwrap_err().to_string();
assert!(
msg.contains("HOST_OBJECT") && msg.contains("not enabled"),
"any 端口也必须拒掉带 HOST_OBJECT 的包: {msg}"
);
assert!(
msg.contains("LMFLOW_TYPE_BUFFER"),
"同样要给替代方案: {msg}"
);
}
#[test]
fn any_port_still_accepts_normal_packets() {
let graph = common::graph_from_yaml(&one_node("PassThrough")).unwrap();
let poller = graph.add_poller("out").unwrap();
graph.start().unwrap();
graph
.input("in")
.unwrap()
.send(Packet::from_i64(9).at(Timestamp(0)))
.unwrap();
graph.close_all_inputs();
graph.wait_done().unwrap();
assert_eq!(poller.next().and_then(|p| p.as_i64()), Some(9));
}
#[test]
fn source_output_carrying_host_object_is_rejected_before_dispatch() {
let _ = register_kernel::<HostObjectSource>("HostObjectSource");
let graph = common::graph_from_yaml(
r#"
executors:
- { name: pool, type: ThreadPoolExecutor, num_threads: 1 }
nodes:
- { name: source, kernel: HostObjectSource, input_ports: [], output_ports: [out], executor: pool }
output_ports: [out]
"#,
)
.unwrap();
let poller = graph.add_poller("out").unwrap();
graph.start().unwrap();
let msg = graph.wait_done().unwrap_err().to_string();
assert!(
msg.contains("HOST_OBJECT") && msg.contains("output port `out`"),
"应在输出派发前指出非法类型和端口: {msg}"
);
assert!(
poller.try_next().is_none(),
"非法 HOST_OBJECT 不得到达图输出"
);
}
#[test]
fn close_output_carrying_host_object_is_rejected_before_dispatch() {
let _ = register_kernel::<HostObjectOnClose>("HostObjectOnClose");
let graph = common::graph_from_yaml(&one_node("HostObjectOnClose")).unwrap();
let poller = graph.add_poller("out").unwrap();
graph.start().unwrap();
graph
.input("in")
.unwrap()
.send(Packet::from_i64(1).at(Timestamp(0)))
.unwrap();
graph.close_all_inputs();
let msg = graph.wait_done().unwrap_err().to_string();
assert!(
msg.contains("HOST_OBJECT") && msg.contains("output port `out`"),
"close 产出也应在派发前被拒: {msg}"
);
assert!(
poller.try_next().is_none(),
"close 产出的非法 HOST_OBJECT 不得到达图输出"
);
}
#[test]
fn side_packet_carrying_host_object_is_rejected() {
let graph = common::graph_from_yaml(&one_node("PassThrough")).unwrap();
let ptr = Box::into_raw(Box::new(1i64)) as *mut std::ffi::c_void;
unsafe extern "C" fn drop_i64(p: *mut std::ffi::c_void) {
drop(unsafe { Box::from_raw(p as *mut i64) });
}
let err = graph
.set_side_packet("native", unsafe {
Packet::from_foreign(ptr, type_id::HOST_OBJECT, Some(drop_i64))
})
.unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("side packet `native`") && msg.contains("HOST_OBJECT"),
"应指出非法 side packet: {msg}"
);
}