use jerrycan_core::{App, get};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::time::Duration;
async fn poll_until(max: usize, mut cond: impl FnMut() -> bool) -> bool {
for _ in 0..max {
if cond() {
return true;
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
cond()
}
#[tokio::test]
async fn background_tasks_start_on_serve_and_drain_on_shutdown() {
let started = Arc::new(AtomicBool::new(false));
let stopped = Arc::new(AtomicBool::new(false));
let (s2, p2) = (started.clone(), stopped.clone());
let app = App::new().on_serve("probe", move |_deps, mut shutdown| async move {
s2.store(true, Ordering::SeqCst);
let _ = shutdown.changed().await;
p2.store(true, Ordering::SeqCst);
});
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let (tx, rx) = tokio::sync::oneshot::channel::<()>();
let h = tokio::spawn(app.serve_with_shutdown(listener, async {
let _ = rx.await;
}));
assert!(
poll_until(100, || started.load(Ordering::SeqCst)).await,
"background task must start when serve begins"
);
assert!(!stopped.load(Ordering::SeqCst), "must wait for shutdown");
tx.send(()).unwrap();
h.await.unwrap().unwrap();
assert!(
stopped.load(Ordering::SeqCst),
"drain must complete before serve returns"
);
}
#[tokio::test]
async fn background_task_resolves_app_level_deps_via_task_context() {
#[derive(Clone)]
struct Counter(Arc<AtomicUsize>);
let seen = Arc::new(AtomicUsize::new(0));
let observed = seen.clone();
let app = App::new().provide(Counter(seen.clone())).on_serve(
"counter",
move |mut deps, mut shutdown| async move {
let counter = deps.resolve::<Counter>().await.unwrap();
counter.0.fetch_add(1, Ordering::SeqCst);
let _ = shutdown.changed().await;
},
);
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let (tx, rx) = tokio::sync::oneshot::channel::<()>();
let h = tokio::spawn(app.serve_with_shutdown(listener, async {
let _ = rx.await;
}));
assert!(
poll_until(100, || observed.load(Ordering::SeqCst) == 1).await,
"task must resolve and bump the provided counter"
);
tx.send(()).unwrap();
h.await.unwrap().unwrap();
}
#[tokio::test]
async fn test_app_does_not_run_background_tasks() {
let ran = Arc::new(AtomicBool::new(false));
let flag = ran.clone();
let t = App::new()
.route("/ping", get(|| async { "pong" }))
.on_serve("never", move |_deps, _shutdown| {
let flag = flag.clone();
async move {
flag.store(true, Ordering::SeqCst);
}
})
.into_test();
assert_eq!(t.get("/ping").await.text(), "pong");
tokio::time::sleep(Duration::from_millis(20)).await;
assert!(
!ran.load(Ordering::SeqCst),
"TestApp must not run background tasks"
);
}