mod common;
use std::time::Duration;
use common::Server;
use weida::{Error, ErrorCode, TransferMeta};
const DEADLINE: Duration = Duration::from_secs(15);
async fn within<F: Future>(f: F) -> F::Output {
tokio::time::timeout(DEADLINE, f)
.await
.expect("operation timed out")
}
fn answer_with(respondent: weida::Respondent, body: &'static [u8]) -> tokio::task::JoinHandle<()> {
tokio::spawn(async move {
while let Ok(mut request) = respondent.accept().await {
let _ = request.body().read_capped(1024).await;
let Ok(mut reply) = request.reply(TransferMeta::default()).await else {
return;
};
if reply.write_all(body).await.is_err() {
return;
}
let _ = reply.finish();
}
})
}
#[tokio::test]
async fn every_respondent_answers_within_the_deadline() {
let certs = common::Certs::generate();
let server = weida::Runtime::new(weida::RuntimeConfig::default()).expect("runtime");
let mut answering = Vec::new();
let mut urls = Vec::new();
let mut bindings = Vec::new();
for body in [&b"a"[..], &b"b"[..]] {
let listener = server.listener();
let binding = listener
.bind_quic("127.0.0.1:0".parse().expect("loopback"), certs.server_tls())
.await
.expect("bind");
urls.push(format!(
"weida://127.0.0.1:{}/poll",
binding.local_addr().port()
));
let respondent = listener.respondent("/poll").expect("respondent");
answering.push(answer_with(
respondent,
if body == b"a" { b"a" } else { b"b" },
));
bindings.push((listener, binding));
}
let client = weida::Runtime::new(weida::RuntimeConfig::default()).expect("client runtime");
let surveyor = client.surveyor(certs.client_tls());
for url in &urls {
within(surveyor.connect(url)).await.expect("connect");
}
assert_eq!(surveyor.peer_count(), 2);
let mut run = within(surveyor.survey(b"who is there", Duration::from_secs(5)))
.await
.expect("survey");
assert_eq!(run.respondents(), 2, "one exchange per respondent");
let mut answers = Vec::new();
while let Some(answer) = within(run.next(1024)).await {
answers.push(answer.expect("a reply, not an error"));
}
answers.sort();
assert_eq!(answers, vec![b"a".to_vec(), b"b".to_vec()]);
assert_eq!(run.late(), 0);
for task in answering {
task.abort();
}
client.shutdown().await;
server.shutdown().await;
}
#[tokio::test]
async fn a_late_reply_is_counted_and_not_delivered() {
let server = Server::start().await;
let respondent = server.listener.respondent("/poll").expect("respondent");
let slow = tokio::spawn(async move {
let mut request = respondent.accept().await.expect("accept");
let _ = request.body().read_capped(1024).await;
tokio::time::sleep(Duration::from_millis(600)).await;
let Ok(mut reply) = request.reply(TransferMeta::default()).await else {
return;
};
let _ = reply.write_all(b"too late").await;
let _ = reply.finish();
});
let client = server.client_runtime();
let surveyor = client.surveyor(server.trust());
within(surveyor.connect(&server.url("/poll")))
.await
.expect("connect");
let mut run = within(surveyor.survey(b"quick", Duration::from_millis(100)))
.await
.expect("survey");
assert_eq!(run.respondents(), 1);
assert!(
within(run.next(1024)).await.is_none(),
"the answer is later than the deadline"
);
slow.await.expect("respondent");
tokio::time::sleep(Duration::from_millis(100)).await;
assert_eq!(run.late(), 1, "a late answer is counted, not delivered");
client.shutdown().await;
}
#[tokio::test]
async fn an_answer_that_arrived_in_time_survives_a_caller_that_reads_late() {
let server = Server::start().await;
let respondent = server.listener.respondent("/poll").expect("respondent");
let answering = answer_with(respondent, b"prompt");
let client = server.client_runtime();
let surveyor = client.surveyor(server.trust());
within(surveyor.connect(&server.url("/poll")))
.await
.expect("connect");
for round in 0..2 {
let mut run = within(surveyor.survey(b"who is there", Duration::from_millis(40)))
.await
.expect("survey");
tokio::time::sleep(Duration::from_millis(120)).await;
let answer = within(run.next(1024))
.await
.unwrap_or_else(|| panic!("round {round}: the answer arrived inside the deadline"));
assert_eq!(answer.expect("a reply, not an error"), b"prompt");
assert_eq!(run.late(), 0, "round {round}: it was not late");
}
answering.abort();
client.shutdown().await;
}
#[tokio::test]
async fn a_respondent_that_never_reads_cannot_hold_the_survey_open() {
let server = Server::start().await;
let respondent = server.listener.respondent("/poll").expect("respondent");
let silent = tokio::spawn(async move {
let mut held = Vec::new();
while let Ok(request) = respondent.accept().await {
held.push(request);
}
});
let client = server.client_runtime();
let surveyor = client.surveyor(server.trust());
within(surveyor.connect(&server.url("/poll")))
.await
.expect("connect");
let question = vec![0u8; 4 * 1024 * 1024];
let run = within(surveyor.survey(&question, Duration::from_millis(300)))
.await
.expect("the survey returns at its deadline");
assert_eq!(
run.respondents(),
0,
"a respondent that could not be asked inside the deadline was not asked"
);
silent.abort();
client.shutdown().await;
}
#[tokio::test]
async fn dropping_a_run_ends_the_exchanges_it_was_collecting() {
let server = Server::start().await;
let respondent = server.listener.respondent("/poll").expect("respondent");
let (tx, rx) = tokio::sync::oneshot::channel();
let silent = tokio::spawn(async move {
let mut request = respondent.accept().await.expect("accept");
let _ = request.body().read_capped(1024).await;
request.canceled().await;
let _ = tx.send(());
});
let client = server.client_runtime();
let surveyor = client.surveyor(server.trust());
within(surveyor.connect(&server.url("/poll")))
.await
.expect("connect");
let run = within(surveyor.survey(b"who is there", Duration::from_secs(300)))
.await
.expect("survey");
assert_eq!(run.respondents(), 1);
drop(run);
within(rx)
.await
.expect("the respondent learns the surveyor walked away");
silent.abort();
client.shutdown().await;
}
#[tokio::test]
async fn a_respondent_that_refuses_is_one_error_among_replies() {
let server = Server::start().await;
let respondent = server.listener.respondent("/poll").expect("respondent");
let refusing = tokio::spawn(async move {
let request = respondent.accept().await.expect("accept");
request.refuse(ErrorCode::Rejected).await;
});
let client = server.client_runtime();
let surveyor = client.surveyor(server.trust());
within(surveyor.connect(&server.url("/poll")))
.await
.expect("connect");
let mut run = within(surveyor.survey(b"who is there", Duration::from_secs(5)))
.await
.expect("survey");
let outcome = within(run.next(1024)).await.expect("an outcome");
let err = outcome.expect_err("the respondent refused");
assert!(matches!(err, Error::Rejected), "{err:?}");
assert!(within(run.next(1024)).await.is_none());
refusing.await.expect("respondent");
client.shutdown().await;
}
#[tokio::test]
async fn a_respondent_that_dies_mid_reply_does_not_end_the_survey() {
let server = Server::start().await;
let respondent = server.listener.respondent("/poll").expect("respondent");
let dying = tokio::spawn(async move {
let mut request = respondent.accept().await.expect("accept");
let _ = request.body().read_capped(1024).await;
let mut reply = request.reply(TransferMeta::default()).await.expect("reply");
reply.write_all(b"half").await.expect("write");
reply.cancel();
});
let client = server.client_runtime();
let surveyor = client.surveyor(server.trust());
within(surveyor.connect(&server.url("/poll")))
.await
.expect("connect");
let mut run = within(surveyor.survey(b"who is there", Duration::from_secs(5)))
.await
.expect("survey");
let outcome = within(run.next(1024)).await.expect("an outcome");
assert!(
outcome.is_err(),
"an interrupted reply is never a complete answer: {outcome:?}"
);
assert!(within(run.next(1024)).await.is_none());
dying.await.expect("respondent");
client.shutdown().await;
}
#[tokio::test]
async fn a_survey_with_no_respondents_is_empty_not_an_error() {
let server = Server::start().await;
let client = server.client_runtime();
let surveyor = client.surveyor(server.trust());
let mut run = within(surveyor.survey(b"anyone?", Duration::from_secs(5)))
.await
.expect("a survey with no respondents is not an error");
assert_eq!(run.respondents(), 0);
assert!(within(run.next(1024)).await.is_none());
assert_eq!(run.late(), 0);
client.shutdown().await;
}