use std::{fmt, future::Future, time::Duration};
use futures::{Stream, StreamExt};
use tokio::time::timeout;
use super::harness::expect_next;
use crate::{AckError, IncomingMessage, OutgoingMessage, Publisher, Seekable, Seeker, Subscriber};
#[cfg(all(test, feature = "memory"))]
mod tests;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(2);
const STEP_TIMEOUT: Duration = Duration::from_secs(10);
const MISS_TIMEOUT: Duration = Duration::from_millis(100);
const REDELIVERY_TIMEOUT: Duration = Duration::from_secs(10);
const RESUBSCRIBE_TIMEOUT: Duration = Duration::from_secs(10);
const AFTER_SHUTDOWN_LIMIT: Duration = Duration::from_secs(5);
const AFTER_SHUTDOWN_REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
type SeekPosition<S> = <<S as Seekable>::Seeker as Seeker>::Position;
type SubscriberMessage<S> = <S as Subscriber>::Message;
mod batches;
mod carries;
mod request_reply;
mod seeking;
mod transactions;
pub use batches::{batch_seeking, batches};
#[doc(hidden)]
pub use carries::CopyPathCheck;
pub use carries::{carries, carries_batch};
pub use request_reply::request_reply;
pub use seeking::{seeking, seeking_unknown_position};
pub use transactions::{owned_transactions, transactions};
async fn collect_payloads<S, M, E>(stream: &mut S, count: usize, label: &str) -> Vec<Vec<u8>>
where
S: Stream<Item = Result<M, E>> + Unpin,
M: IncomingMessage,
E: fmt::Debug,
{
let mut payloads = Vec::with_capacity(count);
for _ in 0..count {
let msg = expect_next(&mut *stream, label).await;
payloads.push(msg.payload().to_vec());
ack_or_unsupported(msg, label).await;
}
payloads
}
async fn publish_all<Pub: Publisher>(publisher: &Pub, subject: &str, payloads: &[&[u8]]) {
for payload in payloads {
within(
publisher.publish(OutgoingMessage::new(subject, payload), None),
"a publish",
)
.await
.expect("publish failed");
}
}
async fn within<Work: Future>(work: Work, what: &str) -> Work::Output {
timeout(STEP_TIMEOUT, work)
.await
.unwrap_or_else(|_| panic!("{what} hung for {STEP_TIMEOUT:?}"))
}
async fn ack_or_unsupported<M: IncomingMessage>(msg: M, label: &str) {
match within(msg.ack(), &format!("{label}: an ack")).await {
Ok(()) | Err(AckError::Unsupported) => {}
Err(other) => panic!("{label}: ack must succeed or be unsupported, got: {other:?}"),
}
}
async fn nack_requeue<M: IncomingMessage>(msg: M, label: &str) -> bool {
match within(msg.nack(true), &format!("{label}: a nack with requeue")).await {
Ok(()) => true,
Err(AckError::Unsupported) => false,
Err(other) => panic!("{label}: nack must succeed or be unsupported, got: {other:?}"),
}
}
fn payloads<M: IncomingMessage>(messages: &[M]) -> Vec<Vec<u8>> {
messages.iter().map(|msg| msg.payload().to_vec()).collect()
}
async fn expect_within<S, M, E>(stream: &mut S, within: Duration, label: &str, expected: &str) -> M
where
S: Stream<Item = Result<M, E>> + Unpin,
M: IncomingMessage,
E: fmt::Debug,
{
timeout(within, stream.next())
.await
.unwrap_or_else(|_| panic!("{label}: nothing arrived within {within:?}; {expected}"))
.unwrap_or_else(|| panic!("{label}: the stream ended; {expected}"))
.unwrap_or_else(|err| panic!("{label}: the stream yielded an error: {err:?}"))
}
async fn collect_payloads_within<S, M, E>(
stream: &mut S,
count: usize,
label: &str,
expected: &str,
) -> Vec<Vec<u8>>
where
S: Stream<Item = Result<M, E>> + Unpin,
M: IncomingMessage,
E: fmt::Debug,
{
let mut payloads = Vec::with_capacity(count);
for _ in 0..count {
let msg = expect_within(&mut *stream, DEFAULT_TIMEOUT, label, expected).await;
payloads.push(msg.payload().to_vec());
ack_or_unsupported(msg, label).await;
}
payloads
}
async fn collect_batched<S, Batch, M, E>(
stream: &mut S,
count: usize,
within: Duration,
label: &str,
expected: &str,
) -> Vec<M>
where
S: Stream<Item = Result<Batch, E>> + Unpin,
Batch: IntoIterator<Item = M>,
E: fmt::Debug,
{
collect_batches(stream, count, within, label, expected)
.await
.into_iter()
.flatten()
.collect()
}
async fn collect_batches<S, Batch, M, E>(
stream: &mut S,
count: usize,
within: Duration,
label: &str,
expected: &str,
) -> Vec<Vec<M>>
where
S: Stream<Item = Result<Batch, E>> + Unpin,
Batch: IntoIterator<Item = M>,
E: fmt::Debug,
{
let mut batches = Vec::new();
let mut delivered = 0;
while delivered < count {
let batch: Vec<M> = timeout(within, stream.next())
.await
.unwrap_or_else(|_| {
panic!(
"{label}: {delivered} of {count} deliveries arrived, then nothing within \
{within:?}; {expected}",
)
})
.unwrap_or_else(|| panic!("{label}: the batch stream ended; {expected}"))
.unwrap_or_else(|err| panic!("{label}: the batch stream yielded an error: {err:?}"))
.into_iter()
.collect();
delivered += batch.len();
batches.push(batch);
}
batches
}
async fn expect_no_batch<S, Batch, E>(stream: &mut S, label: &str)
where
S: Stream<Item = Result<Batch, E>> + Unpin,
{
let result = timeout(MISS_TIMEOUT, stream.next()).await;
assert!(
result.is_err(),
"{label}: expected no further batches within {MISS_TIMEOUT:?}",
);
}
async fn promptly<Work: Future>(work: Work, label: &str) -> Work::Output {
timeout(AFTER_SHUTDOWN_LIMIT, work)
.await
.unwrap_or_else(|_| {
panic!(
"{label}: still pending after {AFTER_SHUTDOWN_LIMIT:?}; a call after shutdown must \
return an error at once, not wait on the dead connection",
)
})
}