use super::*;
#[test]
fn test_pool_checkout_blocking_fast_path() {
let pool = Pool::new(vec![42u32]);
let guard = pool.checkout_blocking().expect("checkout_blocking");
assert_eq!(*guard, 42);
drop(guard);
assert_eq!(pool.available(), 1);
}
#[test]
fn test_pool_checkout_blocking_closed() {
let pool = Pool::<u32>::new(vec![]);
pool.close();
assert!(matches!(pool.checkout_blocking(), Err(PoolError::Closed)));
}
#[test]
fn test_pool_checkout_blocking_slow_path() {
let pool = std::sync::Arc::new(Pool::new(vec![42u32]));
let primary = pool.checkout_blocking().unwrap();
let handle = std::thread::spawn({
let pool = pool.clone();
move || pool.checkout_blocking()
});
std::thread::sleep(std::time::Duration::from_millis(50));
drop(primary);
let guard = handle.join().expect("join").expect("checkout");
assert_eq!(*guard, 42);
drop(guard);
assert_eq!(pool.available(), 1);
}
#[test]
fn test_pool_error_display() {
assert_eq!(format!("{}", PoolError::Closed), "session pool is closed");
}
#[tokio::test]
#[cfg_attr(miri, ignore = "tokio runtime is unsupported under Miri")]
async fn test_pool_guard_returns_triplet_on_normal_drop() {
let pool = Pool::new(vec![1u32, 2, 3]);
assert_eq!(pool.available(), 3);
{
let _guard = pool.checkout().await.expect("checkout");
assert_eq!(pool.available(), 2);
}
assert_eq!(pool.available(), 3);
}
#[tokio::test]
#[cfg_attr(miri, ignore = "tokio runtime is unsupported under Miri")]
async fn test_pool_guard_returns_triplet_on_panic_unwind() {
let pool = std::sync::Arc::new(Pool::new(vec![1u32]));
assert_eq!(pool.available(), 1);
let pool_clone = pool.clone();
let result = tokio::spawn(async move {
let _guard = pool_clone.checkout().await.expect("checkout");
assert_eq!(pool_clone.available(), 0);
panic!("synthetic inference panic");
})
.await;
assert!(result.is_err(), "spawned task must report the panic");
assert_eq!(pool.available(), 1);
}
#[tokio::test]
#[cfg_attr(miri, ignore = "tokio runtime is unsupported under Miri")]
async fn test_pool_close_wakes_waiters_with_closed() {
let pool = std::sync::Arc::new(Pool::<u32>::new(vec![]));
let waiter = tokio::spawn({
let pool = pool.clone();
async move { pool.checkout().await.map(|_g| ()) }
});
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
pool.close();
let res = waiter.await.expect("join");
assert!(matches!(res, Err(PoolError::Closed)));
}
#[tokio::test]
#[cfg_attr(miri, ignore = "tokio runtime is unsupported under Miri")]
async fn test_pool_fifo_under_contention() {
let pool = std::sync::Arc::new(Pool::new(vec![0u32]));
let primary = pool.checkout().await.expect("primary checkout");
assert_eq!(pool.available(), 0);
let waker_log = std::sync::Arc::new(tokio::sync::Mutex::new(Vec::new()));
let mut handles = Vec::new();
for id in 0u32..3 {
let pool = pool.clone();
let log = waker_log.clone();
handles.push(tokio::spawn(async move {
let g = pool.checkout().await.expect("checkout");
log.lock().await.push(id);
drop(g);
}));
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
}
drop(primary);
for h in handles {
h.await.expect("join");
}
let log = waker_log.lock().await.clone();
assert_eq!(log, vec![0, 1, 2], "waiters must wake in FIFO order");
}
#[tokio::test]
#[cfg_attr(miri, ignore = "tokio runtime is unsupported under Miri")]
async fn test_into_owned_for_spawn_blocking() {
let pool = std::sync::Arc::new(Pool::new(vec![String::from("triplet")]));
let guard = pool.checkout().await.expect("checkout");
let reservation = guard.into_owned();
let result = tokio::task::spawn_blocking(move || {
assert_eq!(*reservation, "triplet");
reservation.checkin();
"done"
})
.await
.expect("join");
assert_eq!(pool.available(), 1);
assert_eq!(result, "done");
}
#[tokio::test]
#[cfg_attr(miri, ignore = "tokio runtime is unsupported under Miri")]
async fn test_owned_reservation_returns_on_spawn_blocking_panic() {
let pool = std::sync::Arc::new(Pool::new(vec![String::from("triplet")]));
let guard = pool.checkout().await.expect("checkout");
let reservation = guard.into_owned();
let result = tokio::task::spawn_blocking(move || {
let _reservation = reservation;
panic!("simulated inference panic");
})
.await;
assert!(result.is_err(), "spawn_blocking must report the panic");
assert_eq!(
pool.available(),
1,
"reservation must be returned after panic"
);
}
#[tokio::test]
#[cfg_attr(miri, ignore = "tokio runtime is unsupported under Miri")]
async fn test_owned_reservation_drop_returns_item() {
let pool = std::sync::Arc::new(Pool::new(vec![String::from("triplet")]));
let guard = pool.checkout().await.expect("checkout");
let reservation = guard.into_owned();
tokio::task::spawn_blocking(move || {
let _reservation = reservation;
})
.await
.expect("join");
assert_eq!(pool.available(), 1);
}
#[tokio::test]
#[cfg_attr(miri, ignore = "tokio runtime is unsupported under Miri")]
async fn test_pool_close_is_idempotent() {
let pool = Pool::<u32>::new(vec![]);
pool.close();
pool.close();
}
#[tokio::test]
#[cfg_attr(miri, ignore = "tokio runtime is unsupported under Miri")]
async fn test_pool_waiters_count() {
let pool = std::sync::Arc::new(Pool::<u32>::new(vec![]));
let w1 = tokio::spawn({
let p = pool.clone();
async move { p.checkout().await.map(|_| ()) }
});
let w2 = tokio::spawn({
let p = pool.clone();
async move { p.checkout().await.map(|_| ()) }
});
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
assert_eq!(pool.waiters(), 2, "both blocked tasks must be waiters");
pool.close();
let _ = w1.await;
let _ = w2.await;
}
#[tokio::test]
#[cfg_attr(miri, ignore = "tokio runtime is unsupported under Miri")]
async fn test_owned_reservation_round_trip_through_option() {
let pool = std::sync::Arc::new(Pool::new(vec![42u32]));
let guard = pool.checkout().await.expect("checkout");
let mut reservation: Option<OwnedReservation<u32>> = Some(guard.into_owned());
let (res_back, val) = tokio::task::spawn_blocking(move || {
let mut r = reservation.take().unwrap();
*r += 1;
let v = *r;
(r, v)
})
.await
.expect("join");
reservation = Some(res_back);
assert_eq!(val, 43);
drop(reservation);
assert_eq!(pool.available(), 1);
}
#[tokio::test]
#[cfg_attr(miri, ignore = "tokio runtime is unsupported under Miri")]
async fn test_pool_slot_not_leaked_on_cancelled_checkout() {
let pool = std::sync::Arc::new(Pool::new(vec![42u32]));
let primary = pool.checkout().await.expect("checkout");
let aborted = tokio::spawn({
let pool = pool.clone();
async move { pool.checkout().await }
});
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
aborted.abort();
let _ = aborted.await;
assert_eq!(pool.waiters(), 1);
drop(primary);
assert_eq!(pool.available(), 1, "item must return to pool, not leak");
assert_eq!(pool.waiters(), 0, "dead waiter must be removed");
}
#[tokio::test]
#[cfg_attr(miri, ignore = "tokio runtime is unsupported under Miri")]
async fn test_pool_slot_not_leaked_on_timeout_checkout() {
let pool = std::sync::Arc::new(Pool::new(vec![42u32]));
let primary = pool.checkout().await.expect("checkout");
let result = tokio::time::timeout(std::time::Duration::from_millis(10), pool.checkout()).await;
assert!(result.is_err(), "checkout must time out");
assert_eq!(pool.waiters(), 1);
drop(primary);
assert_eq!(
pool.available(),
1,
"item must return to pool after timeout"
);
assert_eq!(pool.waiters(), 0, "dead waiter must be removed");
}
#[tokio::test]
#[cfg_attr(miri, ignore = "tokio runtime is unsupported under Miri")]
async fn test_pool_multiple_dead_waiters_are_skipped() {
let pool = std::sync::Arc::new(Pool::new(vec![0u32]));
let primary = pool.checkout().await.expect("checkout");
let mut handles = Vec::new();
for _ in 0..3 {
handles.push(tokio::spawn({
let pool = pool.clone();
async move { pool.checkout().await }
}));
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
}
for h in handles {
h.abort();
let _ = h.await;
}
assert_eq!(pool.waiters(), 3);
drop(primary);
assert_eq!(
pool.available(),
1,
"item returned after skipping 3 dead waiters"
);
assert_eq!(pool.waiters(), 0);
}
#[test]
fn test_pool_sequential_checkouts_visit_every_item() {
let pool = Pool::new(vec![1u32, 2, 3]);
let mut seen = Vec::new();
for _ in 0..pool.total() {
let guard = pool.checkout_blocking().expect("checkout");
seen.push(*guard);
}
seen.sort_unstable();
assert_eq!(seen, vec![1, 2, 3]);
}