mod common;
use common::{load, push_record, snapshot, wait_until};
use ek_core::{envelope::Envelope, MainEngine, PlugNotice, PluginState};
use ek_event::Event;
use std::sync::{Arc, Mutex};
use std::time::Duration;
#[derive(Debug)]
pub struct Ping {
pub n: u64,
}
#[derive(Debug, PartialEq)]
pub struct Pong {
pub n: u64,
}
const SVC: &str = "order.place";
type CtxSlot = Arc<Mutex<Option<ek_core::PluginContext>>>;
fn ctx_slot() -> CtxSlot {
Arc::new(Mutex::new(None))
}
#[test]
fn dl1_late_routes_get_wired() {
let eng = MainEngine::new(None);
let c1 = common::counter();
let c1b = Arc::clone(&c1);
eng.add_plugin(
common::test_plugin("c1")
.routes_str(&["eTick."])
.cap(128)
.mail(move |_e: &Event| {
common::bump(&c1b);
})
.build(),
)
.unwrap();
eng.start().unwrap();
let c2 = common::counter();
let c2b = Arc::clone(&c2);
eng.add_plugin(
common::test_plugin("c2")
.routes_str(&["eTick."])
.cap(128)
.mail(move |_e: &Event| {
common::bump(&c2b);
})
.build(),
)
.unwrap();
for i in 0..100u64 {
eng.event_engine().put(Event::with_data("eTick.", Arc::new(i))).unwrap();
}
assert!(wait_until(Duration::from_secs(3), || {
load(&c1) == 100 && load(&c2) == 100
}));
eng.close_all(Duration::from_secs(1));
}
#[test]
fn dl2_same_name_readd_replaces() {
let eng = MainEngine::new(None);
let ups = common::counter();
let u2 = Arc::clone(&ups);
eng.add_plugin(
common::test_plugin("watch")
.routes_str(&["ePlugUp"])
.mail(move |e: &Event| {
if e.data.downcast_ref::<PlugNotice>().map(|n| n.name == "worker").unwrap_or(false) {
common::bump(&u2);
}
})
.build(),
)
.unwrap();
let stop_seen = common::records::<String>();
let s_start = Arc::clone(&stop_seen);
let s_stop = Arc::clone(&stop_seen);
let ticks_a = common::counter();
let ta = Arc::clone(&ticks_a);
eng.add_plugin(
common::test_plugin("worker")
.routes_str(&["eTick."])
.cap(128)
.mail(move |_e: &Event| {
common::bump(&ta);
})
.start_hook(move || push_record(&s_start, "start#1".to_string()))
.stop_hook(move || push_record(&s_stop, "stop#1".to_string()))
.build(),
)
.unwrap();
eng.start().unwrap();
eng.event_engine().put(Event::new("eTick.")).unwrap();
assert!(wait_until(Duration::from_secs(2), || load(&ticks_a) == 1));
eng.remove_plugin("worker").unwrap();
let ticks_b = common::counter();
let tb = Arc::clone(&ticks_b);
eng.add_plugin(
common::test_plugin("worker")
.routes_str(&["eTick."])
.cap(128)
.mail(move |_e: &Event| {
common::bump(&tb);
})
.build(),
)
.unwrap();
eng.event_engine().put(Event::new("eTick.")).unwrap();
eng.event_engine().put(Event::new("eTick.")).unwrap();
assert!(wait_until(Duration::from_secs(3), || load(&ticks_b) == 2));
assert_eq!(load(&ticks_a), 1);
assert_eq!(load(&ups), 2);
assert!(snapshot(&stop_seen).contains(&"stop#1".to_string()));
assert_eq!(eng.get_state("worker"), Some(PluginState::Running));
eng.close_all(Duration::from_secs(1));
}
#[test]
fn dl3_duplicate_while_running() {
let eng = MainEngine::new(None);
let c1 = common::counter();
let c1b = Arc::clone(&c1);
eng.add_plugin(
common::test_plugin("worker")
.routes_str(&["eTick."])
.mail(move |_e: &Event| {
common::bump(&c1b);
})
.build(),
)
.unwrap();
eng.start().unwrap();
match eng.add_plugin(common::test_plugin("worker").build()) {
Err(ek_core::PluginError::Duplicate(n)) => assert_eq!(n, "worker"),
other => panic!("expected Duplicate, got {other:?}"),
}
for i in 0..5u64 {
eng.event_engine().put(Event::with_data("eTick.", Arc::new(i))).unwrap();
}
assert!(wait_until(Duration::from_secs(2), || load(&c1) == 5));
assert_eq!(eng.get_state("worker"), Some(PluginState::Running));
eng.close_all(Duration::from_secs(1));
}
#[test]
fn dl4_remove_unknown() {
let eng = MainEngine::new(None);
match eng.remove_plugin("no_such") {
Err(ek_core::PluginError::UnknownName(n)) => assert_eq!(n, "no_such"),
other => panic!("expected UnknownName, got {other:?}"),
}
eng.start().unwrap();
eng.close_all(Duration::from_secs(1));
match eng.remove_plugin("x") {
Err(ek_core::PluginError::Stopped) => {}
other => panic!("expected Stopped, got {other:?}"),
}
}
#[test]
fn dl5_remove_ready_no_stop_hook() {
let eng = MainEngine::new(None);
let stopped = common::counter();
let s_stop = Arc::clone(&stopped);
eng.add_plugin(
common::test_plugin("ghostborn")
.stop_hook(move || {
common::bump(&s_stop);
})
.build(),
)
.unwrap();
assert_eq!(eng.get_state("ghostborn"), Some(PluginState::Ready));
eng.remove_plugin("ghostborn").unwrap();
assert_eq!(eng.get_state("ghostborn"), None);
std::thread::sleep(Duration::from_millis(100));
assert_eq!(load(&stopped), 0, "Ready 不调 on_stop(D4 判据)");
eng.close_all(Duration::from_secs(1));
}
#[test]
fn dl6_busy_remove_inflight_ask_delivered() {
let eng = MainEngine::new(None);
let started_flag = common::counter();
let s_flag = Arc::clone(&started_flag);
eng.add_plugin(
common::test_plugin("broker")
.routes_str(&["eReq.broker"])
.services(vec![ek_core::ServiceDesc::describe::<Ping, Pong>(
SVC,
" slow echo: 600ms per req",
)])
.mail(move |e: &Event| {
if load(&s_flag) == 0 {
common::bump(&s_flag);
}
Envelope::handle::<Ping, Pong>(e, |q| {
std::thread::sleep(Duration::from_millis(600));
Pong { n: q.n }
});
})
.build(),
)
.unwrap();
let a_ctx = ctx_slot();
let a_init = Arc::clone(&a_ctx);
let a_mail = Arc::clone(&a_ctx);
let a_seen = common::records::<String>();
let a2 = Arc::clone(&a_seen);
eng.add_plugin(
common::test_plugin("a")
.routes_str(&["eKick"])
.init_hook(move |ctx| {
*a_init.lock().unwrap() = Some(ctx.clone());
})
.mail(move |e: &Event| {
if e.event_type != "eKick" {
return;
}
let ctx = a_mail.lock().unwrap().as_ref().expect("ctx").clone();
let r = ctx.ask_service::<Ping, Pong>(SVC, Ping { n: 7 }, Duration::from_secs(3));
push_record(
&a2,
match r {
Ok(p) => format!("ok:{}", p.n),
Err(ek_core::AskError::UnknownService { service }) => {
assert_eq!(service, SVC);
"unknown".to_string()
}
other => panic!("unexpected {other:?}"),
},
);
})
.build(),
)
.unwrap();
eng.start().unwrap();
eng.event_engine().put(Event::with_data("eKick", Arc::new(0u8))).unwrap();
assert!(wait_until(Duration::from_millis(500), || load(&started_flag) == 1));
eng.remove_plugin("broker").unwrap();
assert!(wait_until(Duration::from_secs(3), || snapshot(&a_seen) == vec!["ok:7".to_string()]));
eng.event_engine().put(Event::with_data("eKick", Arc::new(1u8))).unwrap();
assert!(
wait_until(Duration::from_secs(3), || snapshot(&a_seen)
== vec!["ok:7".to_string(), "unknown".to_string()]),
"{:?}",
snapshot(&a_seen)
);
eng.close_all(Duration::from_secs(1));
}
#[test]
fn dl7_two_late_and_reverse_close() {
let eng = MainEngine::new(None);
let seq = common::records::<String>();
let f_start = Arc::clone(&seq);
let f_stop = Arc::clone(&seq);
eng.add_plugin(
common::test_plugin("first")
.start_hook(move || push_record(&f_start, "start:first".into()))
.stop_hook(move || push_record(&f_stop, "stop:first".into()))
.build(),
)
.unwrap();
eng.start().unwrap();
let s1 = Arc::clone(&seq);
let s1b = Arc::clone(&seq);
eng.add_plugin(
common::test_plugin("late1")
.start_hook(move || push_record(&s1, "start:late1".into()))
.stop_hook(move || push_record(&s1b, "stop:late1".into()))
.build(),
)
.unwrap();
let s2a = Arc::clone(&seq);
let s2b = Arc::clone(&seq);
eng.add_plugin(
common::test_plugin("late2")
.start_hook(move || push_record(&s2a, "start:late2".into()))
.stop_hook(move || push_record(&s2b, "stop:late2".into()))
.build(),
)
.unwrap();
assert_eq!(eng.get_state("late1"), Some(PluginState::Running));
assert_eq!(eng.get_state("late2"), Some(PluginState::Running));
eng.close(Duration::from_secs(2));
let got = snapshot(&seq);
assert!(got.contains(&"start:first".into()) && got.contains(&"start:late1".into()) && got.contains(&"start:late2".into()));
assert!(got.contains(&"stop:late2".into()) && got.contains(&"stop:late1".into()) && got.contains(&"stop:first".into()));
let p = |name: &str| got.iter().position(|s| s == name).unwrap();
assert!(p("stop:late2") < p("stop:late1") && p("stop:late1") < p("stop:first"), "{got:?}");
}