use std::collections::HashSet;
use std::time::{Duration, Instant};
use keywatch::channel_with_starting_keys;
use keywatch::{TryRecvError, Update, channel};
#[tokio::test]
async fn add_and_recv_basic() {
let (tx, mut rx) = channel::<&'static str, i32>(Duration::ZERO);
tx.send("a", Update::Add(1)).unwrap();
assert_eq!(rx.try_recv().unwrap(), ("a", Update::Add(1)));
assert!(matches!(rx.try_recv(), Err(TryRecvError::Empty)));
}
#[tokio::test]
async fn delete_updates_state() {
let (tx, mut rx) = channel::<&'static str, i32>(Duration::ZERO);
tx.send("k", Update::Add(10)).unwrap();
assert_eq!(rx.recv().await, Some(("k", Update::Add(10))));
tx.send("k", Update::Delete).unwrap();
assert_eq!(rx.recv().await, Some(("k", Update::Delete)));
}
#[tokio::test]
async fn clear_generates_deletes_for_existing_keys() {
let (tx, mut rx) = channel::<&'static str, i32>(Duration::ZERO);
tx.send("a", Update::Add(1)).unwrap();
tx.send("b", Update::Add(2)).unwrap();
let mut seen = Vec::new();
seen.push(rx.recv().await.unwrap());
seen.push(rx.recv().await.unwrap());
seen.sort_by_key(|(k, _)| *k);
assert_eq!(seen, vec![("a", Update::Add(1)), ("b", Update::Add(2))]);
tx.clear().unwrap();
let mut deletes = vec![rx.recv().await.unwrap(), rx.recv().await.unwrap()];
deletes.sort_by_key(|(k, _)| *k);
assert_eq!(deletes, vec![("a", Update::Delete), ("b", Update::Delete)]);
}
#[tokio::test]
async fn cooldown_coalesces_updates_until_sent() {
let (tx, mut rx) = channel::<&'static str, i32>(Duration::from_millis(50));
tx.send("k", Update::Add(1)).unwrap();
assert_eq!(rx.recv().await, Some(("k", Update::Add(1))));
tx.send("k", Update::Add(2)).unwrap();
assert!(matches!(rx.try_recv(), Err(TryRecvError::Empty)));
tokio::time::sleep(Duration::from_millis(60)).await;
assert_eq!(rx.recv().await, Some(("k", Update::Add(2))));
}
#[tokio::test]
async fn delete_during_cooldown_supersedes_pending_add() {
let (tx, mut rx) = channel::<&'static str, i32>(Duration::from_millis(50));
tx.send("k", Update::Add(1)).unwrap();
assert_eq!(rx.recv().await, Some(("k", Update::Add(1))));
tx.send("k", Update::Add(2)).unwrap();
tx.send("k", Update::Delete).unwrap();
tokio::time::sleep(Duration::from_millis(60)).await;
assert_eq!(rx.recv().await, Some(("k", Update::Delete)));
assert!(matches!(rx.try_recv(), Err(TryRecvError::Empty)));
}
#[tokio::test]
async fn disconnect_behavior() {
let (tx, mut rx) = channel::<&'static str, i32>(Duration::ZERO);
drop(tx);
assert!(matches!(rx.try_recv(), Err(TryRecvError::Disconnected)));
}
#[tokio::test]
async fn add_then_delete_before_receive_results_in_no_event() {
let (tx, mut rx) = channel::<&'static str, i32>(Duration::ZERO);
tx.send("k", Update::Add(1)).unwrap();
tx.send("k", Update::Delete).unwrap();
assert!(matches!(rx.try_recv(), Err(TryRecvError::Empty)));
tokio::select! {
v = rx.recv() => panic!("unexpected event: {:?}", v),
_ = tokio::time::sleep(Duration::from_millis(20)) => {}
}
}
#[tokio::test]
async fn delete_then_add_before_receive_results_in_add_only() {
let (tx, mut rx) = channel::<&'static str, i32>(Duration::ZERO);
tx.send("k", Update::Add(1)).unwrap();
assert_eq!(rx.recv().await, Some(("k", Update::Add(1))));
tx.send("k", Update::Delete).unwrap();
tx.send("k", Update::Add(42)).unwrap();
assert_eq!(rx.try_recv().unwrap(), ("k", Update::Add(42)));
assert!(matches!(rx.try_recv(), Err(TryRecvError::Empty)));
tokio::select! {
v = rx.recv() => panic!("unexpected extra event: {:?}", v),
_ = tokio::time::sleep(Duration::from_millis(20)) => {}
}
}
#[tokio::test]
async fn delete_twice_before_receive_results_in_single_delete() {
let (tx, mut rx) = channel::<&'static str, i32>(Duration::ZERO);
tx.send("k", Update::Add(7)).unwrap();
assert_eq!(rx.recv().await, Some(("k", Update::Add(7))));
tx.send("k", Update::Delete).unwrap();
tx.send("k", Update::Delete).unwrap();
assert_eq!(rx.try_recv().unwrap(), ("k", Update::Delete));
assert!(matches!(rx.try_recv(), Err(TryRecvError::Empty)));
tokio::select! {
v = rx.recv() => panic!("unexpected extra event after double delete: {:?}", v),
_ = tokio::time::sleep(Duration::from_millis(20)) => {}
}
}
#[tokio::test]
async fn add_second_then_delete_before_second_seen_results_in_delete() {
let (tx, mut rx) = channel::<&'static str, i32>(Duration::ZERO);
tx.send("k", Update::Add(1)).unwrap();
assert_eq!(rx.recv().await, Some(("k", Update::Add(1))));
tx.send("k", Update::Add(2)).unwrap();
tx.send("k", Update::Delete).unwrap();
assert_eq!(rx.recv().await, Some(("k", Update::Delete)));
assert!(matches!(rx.try_recv(), Err(TryRecvError::Empty)));
}
#[tokio::test]
async fn clear_discards_cooldown_value_and_emits_delete() {
let cooldown = Duration::from_millis(100);
let (tx, mut rx) = channel::<&'static str, i32>(cooldown);
tx.send("k", Update::Add(1)).unwrap();
assert_eq!(rx.recv().await, Some(("k", Update::Add(1))));
tx.send("k", Update::Add(2)).unwrap();
tokio::time::sleep(Duration::from_millis(10)).await;
tx.clear().unwrap();
assert_eq!(rx.recv().await, Some(("k", Update::Delete)));
tokio::time::sleep(cooldown).await;
assert!(matches!(rx.try_recv(), Err(TryRecvError::Empty)));
}
#[tokio::test]
async fn clear_mixed_after_second_send_before_other_matures() {
let cooldown = Duration::from_millis(100);
let (tx, mut rx) = channel::<&'static str, i32>(cooldown);
tx.send("A", Update::Add(1)).unwrap();
assert_eq!(rx.recv().await, Some(("A", Update::Add(1))));
tokio::time::sleep(Duration::from_millis(20)).await; tx.send("B", Update::Add(10)).unwrap();
assert_eq!(rx.recv().await, Some(("B", Update::Add(10))));
tokio::time::sleep(Duration::from_millis(50)).await; tx.send("A", Update::Add(2)).unwrap();
tokio::time::sleep(Duration::from_millis(35)).await;
tx.clear().unwrap();
let mut deletes = vec![rx.recv().await.unwrap(), rx.recv().await.unwrap()];
deletes.sort_by_key(|(k, _)| *k);
assert_eq!(deletes, vec![("A", Update::Delete), ("B", Update::Delete)]);
assert!(matches!(rx.try_recv(), Err(TryRecvError::Empty)));
}
#[tokio::test]
async fn recv_waits_for_cooldown_expiry() {
let cooldown = Duration::from_millis(120);
let (tx, mut rx) = channel::<&'static str, i32>(cooldown);
tx.send("k", Update::Add(1)).unwrap();
assert_eq!(rx.recv().await, Some(("k", Update::Add(1))));
tx.send("k", Update::Add(2)).unwrap();
let start = Instant::now();
let received = rx.recv().await;
let elapsed = start.elapsed();
assert_eq!(received, Some(("k", Update::Add(2))));
assert!(
elapsed >= cooldown - Duration::from_millis(25),
"recv returned too early: {:?} < {:?}",
elapsed,
cooldown
);
}
#[tokio::test]
async fn recv_wakes_on_notification() {
let cooldown = Duration::from_secs(2);
let (tx, rx) = channel::<&'static str, i32>(cooldown);
let start = Instant::now();
let handle = tokio::spawn(async move {
let mut rx = rx;
rx.recv().await
});
tokio::time::sleep(Duration::from_millis(30)).await;
tx.send("k", Update::Add(1)).unwrap();
let out = handle.await.unwrap();
let elapsed = start.elapsed();
assert_eq!(out, Some(("k", Update::Add(1))));
assert!(
elapsed < cooldown / 4,
"recv waited too long: {:?}",
elapsed
);
}
#[tokio::test]
async fn recv_wakes_on_clear() {
use std::time::Instant;
let cooldown = Duration::from_secs(5); let (tx, mut rx) = channel::<&'static str, i32>(cooldown);
tx.send("k", Update::Add(1)).unwrap();
assert_eq!(rx.recv().await, Some(("k", Update::Add(1))));
let start = Instant::now();
let recv_fut = tokio::spawn(async move { rx.recv().await });
tokio::time::sleep(Duration::from_millis(40)).await;
tx.clear().unwrap();
let out = recv_fut.await.unwrap();
let elapsed = start.elapsed();
assert_eq!(out, Some(("k", Update::Delete)));
assert!(
elapsed < cooldown / 10,
"recv waited too long for clear notification: {:?}",
elapsed
);
}
#[tokio::test]
async fn send_fails_after_receiver_drop() {
let (tx, rx) = channel::<&'static str, i32>(Duration::ZERO);
drop(rx); let err = tx
.send("k", Update::Add(42))
.expect_err("expected send error");
match err.0 {
Update::Add(v) => assert_eq!(v, 42),
other => panic!("unexpected update in error: {:?}", other),
}
}
#[tokio::test]
async fn clear_fails_after_receiver_drop() {
let (tx, rx) = channel::<&'static str, i32>(Duration::ZERO);
drop(rx);
let err = tx.clear().expect_err("expected clear error");
match err.0 {
Update::Delete => {}
other => panic!("unexpected update in clear error: {:?}", other),
}
}
#[tokio::test]
async fn starting_key_delete_emitted_unknown_key_delete_suppressed() {
let (tx, mut rx) = channel_with_starting_keys::<&'static str, i32>(
HashSet::from_iter(["known"]),
Duration::ZERO,
);
tx.send("known", Update::Delete).unwrap();
assert_eq!(rx.try_recv().unwrap(), ("known", Update::Delete));
tx.send("unknown", Update::Delete).unwrap();
assert_eq!(rx.try_recv(), Err(TryRecvError::Empty));
}
#[tokio::test]
async fn dropped_sender_ignores_cooldown_for_pending_value() {
let cooldown = Duration::from_millis(150);
let (tx, mut rx) = channel::<&'static str, i32>(cooldown);
tx.send("k", Update::Add(1)).unwrap();
assert_eq!(rx.recv().await, Some(("k", Update::Add(1))));
tx.send("k", Update::Add(2)).unwrap();
assert!(matches!(rx.try_recv(), Err(TryRecvError::Empty)));
drop(tx);
assert_eq!(rx.try_recv().unwrap(), ("k", Update::Add(2)));
assert!(matches!(rx.try_recv(), Err(TryRecvError::Disconnected)));
}
#[tokio::test]
async fn dropped_sender_releases_initial_pending_add() {
let cooldown = Duration::from_millis(200);
let (tx, mut rx) = channel::<&'static str, i32>(cooldown);
tx.send("k", Update::Add(10)).unwrap();
drop(tx);
assert_eq!(rx.try_recv().unwrap(), ("k", Update::Add(10)));
assert!(matches!(rx.try_recv(), Err(TryRecvError::Disconnected)));
}
#[tokio::test]
async fn sender_closed_waits_for_receiver_drop() {
let (tx, rx) = channel::<&'static str, i32>(Duration::from_millis(50));
let handle = tokio::spawn(async move {
tx.closed().await;
});
tokio::time::sleep(Duration::from_millis(30)).await;
assert!(
!handle.is_finished(),
"Sender::closed() returned before channel was closed"
);
drop(rx);
tokio::time::timeout(Duration::from_millis(100), handle)
.await
.expect("Sender::closed() did not complete after receiver drop")
.unwrap();
}