use super::*;
use mdns_proto::{
QueryUpdate,
wire::{ResourceClass, ResourceType},
};
fn answer(tag: u16) -> CollectedAnswer {
CollectedAnswer::from_parts(
ResourceType::Ptr,
ResourceClass::In,
tag.to_be_bytes().to_vec(),
tag as u64,
)
}
#[test]
fn mailbox_coalesces_duplicate_answers() {
let mut mb = QueryMailbox::new();
mb.push_answer(answer(7));
mb.push_answer(answer(7));
assert_eq!(mb.answers.len(), 1);
assert!(matches!(mb.drain(), Drained::Event(QueryEvent::Answer(_))));
assert!(matches!(mb.drain(), Drained::Empty));
}
#[test]
fn mailbox_bounds_backlog_dropping_oldest() {
let mut mb = QueryMailbox::new();
let overflow: u16 = 64;
for i in 0..(MAX_QUERY_EVENT_BACKLOG as u16 + overflow) {
mb.push_answer(answer(i));
}
assert_eq!(mb.answers.len(), MAX_QUERY_EVENT_BACKLOG);
assert_eq!(mb.dropped, u64::from(overflow));
match mb.drain() {
Drained::Event(QueryEvent::Answer(a)) => {
assert_eq!(a.rdata_slice(), &overflow.to_be_bytes()[..]);
}
_ => panic!("expected an answer at the head of the queue"),
}
}
#[test]
fn mailbox_record_dropped_accumulates_with_drop_oldest() {
let mut mb = QueryMailbox::new();
mb.record_dropped(3); for i in 0..(MAX_QUERY_EVENT_BACKLOG as u16 + 2) {
mb.push_answer(answer(i));
}
assert_eq!(mb.dropped, 5);
}
#[test]
fn mailbox_terminal_reserved_under_answer_pressure() {
let mut mb = QueryMailbox::new();
for i in 0..(MAX_QUERY_EVENT_BACKLOG as u16 + 64) {
mb.push_answer(answer(i));
}
mb.set_terminal(QueryUpdate::Done);
let mut answers = 0usize;
let mut got_terminal = false;
loop {
match mb.drain() {
Drained::Event(QueryEvent::Answer(_)) => answers += 1,
Drained::Event(QueryEvent::Terminal(_)) => got_terminal = true,
Drained::Ended | Drained::Empty => break,
}
}
assert_eq!(answers, MAX_QUERY_EVENT_BACKLOG);
assert!(got_terminal, "terminal must survive answer backpressure");
}
#[test]
fn mailbox_drains_answers_then_terminal_then_ends() {
let mut mb = QueryMailbox::new();
mb.push_answer(answer(1));
mb.push_answer(answer(2));
mb.set_terminal(QueryUpdate::Done);
assert!(matches!(mb.drain(), Drained::Event(QueryEvent::Answer(_))));
assert!(matches!(mb.drain(), Drained::Event(QueryEvent::Answer(_))));
assert!(matches!(
mb.drain(),
Drained::Event(QueryEvent::Terminal(_))
));
assert!(matches!(mb.drain(), Drained::Ended));
assert!(matches!(mb.drain(), Drained::Ended));
}
#[tokio::test]
async fn doorbell_wakes_parked_consumer_for_full_batch() {
let (mailbox, doorbell_tx, doorbell_rx) = new_mailbox();
let mb_consumer = Arc::clone(&mailbox);
let consumer = tokio::spawn(async move {
let mut answers = 0usize;
let mut got_terminal = false;
loop {
let drained = lock(&mb_consumer).drain();
match drained {
Drained::Event(QueryEvent::Answer(_)) => answers += 1,
Drained::Event(QueryEvent::Terminal(_)) => got_terminal = true,
Drained::Ended => break,
Drained::Empty => {
if doorbell_rx.recv().await.is_err() {
break;
}
}
}
}
(answers, got_terminal)
});
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
{
let mut mb = lock(&mailbox);
for i in 0..5u16 {
mb.push_answer(answer(i));
}
mb.set_terminal(QueryUpdate::Done);
}
let _ = doorbell_tx.try_send(());
let (answers, got_terminal) = consumer.await.expect("consumer task panicked");
assert_eq!(answers, 5);
assert!(got_terminal);
}
#[test]
fn dropped_handle_tracks_mailbox_drops() {
let (mailbox, _tx, _rx) = new_mailbox();
let handle = DroppedHandle {
mailbox: Arc::clone(&mailbox),
};
assert_eq!(handle.get(), 0);
lock(&mailbox).record_dropped(4);
assert_eq!(handle.get(), 4);
for i in 0..(MAX_QUERY_EVENT_BACKLOG as u16 + 3) {
lock(&mailbox).push_answer(answer(i));
}
assert_eq!(handle.get(), 4 + 3);
}