use std::any::{Any, type_name};
use std::cell::Cell;
use std::fmt;
use std::future::Future;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use bytes::Bytes;
use crate::runtime::HandlerResult;
use crate::{OutgoingMessage, RawMessage};
use super::TestError;
tokio::task_local! {
static HARNESS: HarnessScope;
static PAIRING: Cell<Origin>;
static PUBLISHING: Origin;
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct Origin(Option<usize>);
impl Origin {
fn of<C: ?Sized>(connected: &C) -> Self {
Self(Some(std::ptr::from_ref(connected).cast::<()>() as usize))
}
}
pub(crate) fn paired<C: ?Sized, F: Future>(
connected: &C,
pairing: F,
) -> impl Future<Output = (F::Output, Origin)> {
PAIRING.scope(Cell::new(Origin::of(connected)), async move {
let live = pairing.await;
(live, PAIRING.with(Cell::get))
})
}
pub(crate) fn pairs_against<C: ?Sized>(connected: &C) {
let _ = PAIRING.try_with(|origin| origin.set(Origin::of(connected)));
}
pub(crate) fn publishing_to<F: Future>(
origin: Origin,
publish: F,
) -> impl Future<Output = F::Output> {
PUBLISHING.scope(origin, publish)
}
fn publishing() -> Origin {
PUBLISHING.try_with(|origin| *origin).unwrap_or_default()
}
#[derive(Clone)]
pub(crate) struct HarnessScope {
coordinator: Coordinator,
scope_id: usize,
subscription: usize,
}
impl HarnessScope {
pub(crate) const fn new(
coordinator: Coordinator,
scope_id: usize,
subscription: usize,
) -> Self {
Self {
coordinator,
scope_id,
subscription,
}
}
}
pub(crate) fn record_slot_publish<Options, Payload>(
slot: &'static str,
msg: &OutgoingMessage<'_, Payload>,
options: Option<&Options>,
) where
Options: Clone + Send + Sync + 'static,
Payload: AsRef<[u8]>,
{
let _ = HARNESS.try_with(|scope| {
scope
.coordinator
.record_slot(slot, msg, RecordedOptions::capture(options));
});
}
pub(crate) fn record_reply_publish<Options>(name: &str, options: Option<&Options>)
where
Options: Clone + Send + Sync + 'static,
{
let _ = HARNESS.try_with(|scope| {
scope
.coordinator
.record_reply(name, RecordedOptions::capture(options));
});
}
#[must_use]
pub(crate) struct PipelinePublish(Option<(Coordinator, Origin, RawMessage)>);
impl PipelinePublish {
pub(crate) fn capture<Payload: AsRef<[u8]>>(msg: &OutgoingMessage<'_, Payload>) -> Self {
Self(
HARNESS
.try_with(|scope| (scope.coordinator.clone(), publishing(), raw_of(msg)))
.ok(),
)
}
pub(crate) fn sent(self) {
if let Some((coordinator, origin, message)) = self.0 {
coordinator.record_sent(origin, message);
}
}
}
fn raw_of<Payload: AsRef<[u8]>>(msg: &OutgoingMessage<'_, Payload>) -> RawMessage {
RawMessage::new(msg.name().to_owned(), msg.payload().as_ref().to_vec())
.with_headers(msg.headers().clone())
}
#[derive(Clone)]
pub(crate) struct RecordedOptions {
pub(crate) type_name: &'static str,
pub(crate) value: Option<Arc<dyn Any + Send + Sync>>,
}
impl RecordedOptions {
fn capture<Options: Clone + Send + Sync + 'static>(options: Option<&Options>) -> Self {
Self {
type_name: type_name::<Options>(),
value: options.map(|options| Arc::new(options.clone()) as Arc<dyn Any + Send + Sync>),
}
}
pub(crate) fn downcast<Options: 'static>(&self, channel: &str) -> Option<&Options> {
let value = self.value.as_ref()?;
Some(value.as_ref().downcast_ref::<Options>().unwrap_or_else(|| {
panic!(
"channel {channel:?} published with per-message options of type `{}`, not `{}`",
self.type_name,
type_name::<Options>(),
)
}))
}
}
impl fmt::Debug for RecordedOptions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RecordedOptions")
.field("type_name", &self.type_name)
.field("set", &self.value.is_some())
.finish()
}
}
pub(crate) fn record_batch(name: &str, deliveries: Vec<Delivered>) {
let _ = HARNESS.try_with(|scope| {
scope.coordinator.record(Record {
scope_id: scope.scope_id,
subscription: scope.subscription,
name: name.to_owned(),
deliveries,
panicked: false,
decode_failed: false,
});
});
}
pub(crate) struct PendingRecord(Option<Coordinator>);
impl PendingRecord {
pub(crate) fn new() -> Self {
Self(
HARNESS
.try_with(|scope| {
scope.coordinator.enqueued();
scope.coordinator.clone()
})
.ok(),
)
}
}
impl Drop for PendingRecord {
fn drop(&mut self) {
if let Some(coordinator) = &self.0 {
coordinator.consumed();
}
}
}
pub(crate) async fn in_harness_scope<F: Future>(scope: Option<HarnessScope>, fut: F) -> F::Output {
match scope {
Some(scope) => HARNESS.scope(scope, fut).await,
None => fut.await,
}
}
#[derive(Clone, Debug)]
pub(crate) struct SlotRecord {
pub(crate) slot: &'static str,
pub(crate) message: RawMessage,
pub(crate) options: RecordedOptions,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum Outcome {
Ack,
Nack,
Drop,
DecodeFailed,
Panicked,
}
pub(crate) struct Delivered {
pub(crate) raw: Bytes,
pub(crate) settle: Option<HandlerResult>,
}
pub(crate) struct Record {
pub(crate) scope_id: usize,
pub(crate) subscription: usize,
pub(crate) name: String,
pub(crate) deliveries: Vec<Delivered>,
pub(crate) panicked: bool,
pub(crate) decode_failed: bool,
}
impl Record {
pub(crate) fn outcome(&self) -> Outcome {
if self.panicked {
Outcome::Panicked
} else if self.decode_failed {
Outcome::DecodeFailed
} else {
settled_as(self.deliveries.first().and_then(|one| one.settle))
}
}
}
fn settled_as(settle: Option<HandlerResult>) -> Outcome {
match settle {
Some(HandlerResult::Ack) => Outcome::Ack,
Some(HandlerResult::Nack { requeue: true } | HandlerResult::NackAfter { .. }) => {
Outcome::Nack
}
Some(HandlerResult::Nack { requeue: false }) | None => Outcome::Drop,
}
}
pub(crate) struct TestHooks {
coordinator: OnceLock<Coordinator>,
subscriptions: Mutex<Vec<(usize, String)>>,
}
impl TestHooks {
pub(crate) fn detached() -> Self {
Self {
coordinator: OnceLock::new(),
subscriptions: Mutex::new(Vec::new()),
}
}
pub(crate) fn subscribed(&self, scope_id: usize, name: &str) -> usize {
let mut subscriptions = self
.subscriptions
.lock()
.expect("test hooks subscriptions mutex poisoned");
subscriptions.push((scope_id, name.to_owned()));
subscriptions.len() - 1
}
pub(crate) fn subscriptions(&self) -> Vec<LiveSubscription> {
self.subscriptions
.lock()
.expect("test hooks subscriptions mutex poisoned")
.iter()
.enumerate()
.map(|(id, (scope_id, name))| LiveSubscription {
id,
scope_id: *scope_id,
name: name.clone(),
})
.collect()
}
pub(crate) fn install(&self, coordinator: Coordinator) {
let _ = self.coordinator.set(coordinator);
}
pub(crate) fn coordinator(&self) -> Option<&Coordinator> {
self.coordinator.get()
}
}
#[derive(Clone)]
pub struct Coordinator {
inner: Arc<Inner>,
}
impl fmt::Debug for Coordinator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Coordinator")
.field("in_flight", &self.inner.in_flight.load(Ordering::SeqCst))
.field("processed", &self.inner.processed.load(Ordering::SeqCst))
.finish_non_exhaustive()
}
}
struct Inner {
in_flight: AtomicUsize,
processed: AtomicUsize,
max_steps: usize,
notify: tokio::sync::Notify,
records: Mutex<Vec<Record>>,
slot_records: Mutex<Vec<SlotRecord>>,
reply_records: Mutex<Vec<(String, RecordedOptions)>>,
published: Mutex<Vec<Published>>,
brokers: Mutex<Vec<(usize, usize)>>,
redeliveries: Mutex<Vec<DueRedelivery>>,
timers: Mutex<Vec<Timer>>,
}
struct Published {
scope_id: usize,
message: RawMessage,
}
#[derive(Clone, Debug)]
pub(crate) struct LiveSubscription {
pub(crate) id: usize,
pub(crate) scope_id: usize,
pub(crate) name: String,
}
pub(crate) type Routing<'a> = &'a (dyn Fn(usize, &str, &[&str]) -> Vec<usize> + Sync);
struct Unsettled {
subscription: String,
handled: usize,
expected: usize,
}
struct DueRedelivery {
subscription: usize,
due: tokio::time::Instant,
}
struct Timer {
deadline: tokio::time::Instant,
handle: tokio::task::JoinHandle<()>,
}
pub(crate) struct InFlightHold {
coordinator: Coordinator,
}
impl Drop for InFlightHold {
fn drop(&mut self) {
let inner = &self.coordinator.inner;
inner.in_flight.fetch_sub(1, Ordering::SeqCst);
inner.notify.notify_waiters();
}
}
impl Coordinator {
pub(crate) fn new(max_steps: usize) -> Self {
Self {
inner: Arc::new(Inner {
in_flight: AtomicUsize::new(0),
processed: AtomicUsize::new(0),
max_steps,
notify: tokio::sync::Notify::new(),
records: Mutex::new(Vec::new()),
slot_records: Mutex::new(Vec::new()),
reply_records: Mutex::new(Vec::new()),
published: Mutex::new(Vec::new()),
brokers: Mutex::new(Vec::new()),
redeliveries: Mutex::new(Vec::new()),
timers: Mutex::new(Vec::new()),
}),
}
}
pub fn enqueued(&self) {
self.inner.in_flight.fetch_add(1, Ordering::SeqCst);
self.inner.notify.notify_waiters();
}
pub fn consumed(&self) {
self.inner.processed.fetch_add(1, Ordering::SeqCst);
self.inner.in_flight.fetch_sub(1, Ordering::SeqCst);
self.inner.notify.notify_waiters();
}
pub(crate) fn hold(&self) -> InFlightHold {
self.inner.in_flight.fetch_add(1, Ordering::SeqCst);
InFlightHold {
coordinator: self.clone(),
}
}
pub(crate) fn record(&self, record: Record) {
self.inner
.records
.lock()
.expect("coordinator records mutex poisoned")
.push(record);
}
pub fn schedule_redelivery<F>(&self, delay: Duration, redeliver: F)
where
F: FnOnce() + Send + 'static,
{
let deadline = tokio::time::Instant::now() + delay;
let handle = tokio::spawn(async move {
tokio::time::sleep(delay).await;
redeliver();
});
self.push_timer(deadline, handle);
}
pub(crate) fn schedule_redelivery_future<Fut>(&self, delay: Duration, redeliver: Fut)
where
Fut: Future<Output = ()> + Send + 'static,
{
let deadline = tokio::time::Instant::now() + delay;
let handle = tokio::spawn(async move {
tokio::time::sleep(delay).await;
redeliver.await;
});
self.push_timer(deadline, handle);
}
fn push_timer(&self, deadline: tokio::time::Instant, handle: tokio::task::JoinHandle<()>) {
self.inner
.timers
.lock()
.expect("coordinator timers mutex poisoned")
.push(Timer { deadline, handle });
}
#[allow(clippy::significant_drop_tightening)]
pub(crate) async fn fire_due_timers(&self) {
let now = tokio::time::Instant::now();
let due: Vec<tokio::task::JoinHandle<()>> = {
let mut timers = self
.inner
.timers
.lock()
.expect("coordinator timers mutex poisoned");
let mut due = Vec::new();
let mut i = 0;
while i < timers.len() {
if timers[i].deadline <= now {
due.push(timers.swap_remove(i).handle);
} else {
i += 1;
}
}
due
};
for handle in due {
let _ = handle.await;
}
}
pub(crate) async fn drive(&self) -> Result<(), TestError> {
loop {
let notified = self.inner.notify.notified();
tokio::pin!(notified);
notified.as_mut().enable();
if self.inner.in_flight.load(Ordering::SeqCst) == 0 {
return Ok(());
}
if self.inner.processed.load(Ordering::SeqCst) >= self.inner.max_steps {
return Err(TestError::NotQuiescent {
processed: self.inner.processed.load(Ordering::SeqCst),
});
}
notified.await;
}
}
pub(crate) fn locate<'a>(
&self,
connected: impl IntoIterator<Item = &'a (dyn Any + Send + Sync)>,
) {
*self
.inner
.brokers
.lock()
.expect("coordinator brokers mutex poisoned") = connected
.into_iter()
.enumerate()
.filter_map(|(scope_id, connected)| {
Origin::of::<dyn Any + Send + Sync>(connected)
.0
.map(|address| (address, scope_id))
})
.collect();
}
pub(crate) fn record_published(&self, scope_id: usize, message: RawMessage) {
self.inner
.published
.lock()
.expect("coordinator published mutex poisoned")
.push(Published { scope_id, message });
self.inner.notify.notify_waiters();
}
fn record_sent(&self, origin: Origin, message: RawMessage) {
let scope_id = origin.0.and_then(|address| {
self.inner
.brokers
.lock()
.expect("coordinator brokers mutex poisoned")
.iter()
.find(|(known, _)| *known == address)
.map(|(_, scope_id)| *scope_id)
});
if let Some(scope_id) = scope_id {
self.record_published(scope_id, message);
}
}
pub(crate) fn published(&self, scope_id: usize, name: &str) -> Vec<RawMessage> {
self.inner
.published
.lock()
.expect("coordinator published mutex poisoned")
.iter()
.filter(|published| published.scope_id == scope_id && published.message.name() == name)
.map(|published| published.message.clone())
.collect()
}
pub(crate) fn expect_redelivery(&self, subscription: usize, delay: Duration) {
self.inner
.redeliveries
.lock()
.expect("coordinator redeliveries mutex poisoned")
.push(DueRedelivery {
subscription,
due: tokio::time::Instant::now() + delay,
});
self.inner.notify.notify_waiters();
}
fn owed(&self, subscriptions: &[LiveSubscription], routing: Routing<'_>) -> Option<Unsettled> {
let now = tokio::time::Instant::now();
let mut expected = vec![0; subscriptions.len()];
{
let published = self
.inner
.published
.lock()
.expect("coordinator published mutex poisoned");
for publish in published.iter() {
let on: Vec<usize> = (0..subscriptions.len())
.filter(|&index| subscriptions[index].scope_id == publish.scope_id)
.collect();
if on.is_empty() {
continue;
}
let names: Vec<&str> = on
.iter()
.map(|&index| subscriptions[index].name.as_str())
.collect();
for position in routing(publish.scope_id, publish.message.name(), &names) {
if let Some(&index) = on.get(position) {
expected[index] += 1;
}
}
}
}
{
let redeliveries = self
.inner
.redeliveries
.lock()
.expect("coordinator redeliveries mutex poisoned");
for (index, subscription) in subscriptions.iter().enumerate() {
expected[index] += redeliveries
.iter()
.filter(|due| due.subscription == subscription.id && due.due <= now)
.count();
}
}
let records = self
.inner
.records
.lock()
.expect("coordinator records mutex poisoned");
let owed = subscriptions
.iter()
.zip(expected)
.find_map(|(subscription, expected)| {
let handled = records
.iter()
.filter(|record| record.subscription == subscription.id)
.map(|record| record.deliveries.len())
.sum::<usize>();
(handled < expected).then(|| Unsettled {
subscription: subscription.name.clone(),
handled,
expected,
})
});
drop(records);
owed
}
pub(crate) async fn settle_live(
&self,
subscriptions: &[LiveSubscription],
routing: Routing<'_>,
deadline: Duration,
) -> Result<(), TestError> {
let until = tokio::time::Instant::now() + deadline;
loop {
let notified = self.inner.notify.notified();
tokio::pin!(notified);
notified.as_mut().enable();
let owed = self.owed(subscriptions, routing);
let running = self.inner.in_flight.load(Ordering::SeqCst) > 0;
if owed.is_none() && !running {
return Ok(());
}
if tokio::time::timeout_at(until, notified).await.is_err() {
let (subscription, handled, expected) = owed.map_or((None, 0, 0), |owed| {
(Some(owed.subscription), owed.handled, owed.expected)
});
return Err(TestError::NotSettled {
subscription,
handled,
expected,
deadline,
});
}
}
}
pub(crate) fn record_slot<Payload: AsRef<[u8]>>(
&self,
slot: &'static str,
msg: &OutgoingMessage<'_, Payload>,
options: RecordedOptions,
) {
let message = RawMessage::new(msg.name().to_owned(), msg.payload().to_vec())
.with_headers(msg.headers().clone());
self.inner
.slot_records
.lock()
.expect("coordinator slot records mutex poisoned")
.push(SlotRecord {
slot,
message,
options,
});
}
pub(crate) fn record_reply(&self, name: &str, options: RecordedOptions) {
self.inner
.reply_records
.lock()
.expect("coordinator reply records mutex poisoned")
.push((name.to_owned(), options));
}
pub(crate) fn reply_published(&self, name: &str) -> Vec<RecordedOptions> {
self.inner
.reply_records
.lock()
.expect("coordinator reply records mutex poisoned")
.iter()
.filter(|(channel, _)| channel == name)
.map(|(_, options)| options.clone())
.collect()
}
pub(crate) fn slot_published(&self, slot: &str) -> Vec<SlotRecord> {
self.inner
.slot_records
.lock()
.expect("coordinator slot records mutex poisoned")
.iter()
.filter(|record| record.slot == slot)
.cloned()
.collect()
}
#[allow(clippy::significant_drop_tightening)]
pub(crate) fn with_records<R>(
&self,
scope_id: usize,
name: &str,
f: impl FnOnce(&[&Record]) -> R,
) -> R {
let guard = self
.inner
.records
.lock()
.expect("coordinator records mutex poisoned");
let matching: Vec<&Record> = guard
.iter()
.filter(|r| r.scope_id == scope_id && r.name == name)
.collect();
f(&matching)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn matches(pattern: &str, destination: &str) -> bool {
let mut pattern = pattern.split('.');
let mut subject = destination.split('.');
loop {
match (pattern.next(), subject.next()) {
(None, None) => return true,
(Some(token), Some(part)) if token == "*" || token == part => {}
_ => return false,
}
}
}
fn fan_out(destination: &str, subscriptions: &[&str]) -> Vec<usize> {
(0..subscriptions.len())
.filter(|&position| matches(subscriptions[position], destination))
.collect()
}
fn exact(destination: &str, subscriptions: &[&str]) -> Vec<usize> {
(0..subscriptions.len())
.filter(|&position| subscriptions[position] == destination)
.collect()
}
fn handled(subscription: &LiveSubscription) -> Record {
Record {
scope_id: subscription.scope_id,
subscription: subscription.id,
name: subscription.name.clone(),
deliveries: vec![Delivered {
raw: Bytes::from_static(b"{}"),
settle: Some(HandlerResult::Ack),
}],
panicked: false,
decode_failed: false,
}
}
fn brokers_routing(
east: fn(&str, &[&str]) -> Vec<usize>,
west: fn(&str, &[&str]) -> Vec<usize>,
) -> impl Fn(usize, &str, &[&str]) -> Vec<usize> + Sync {
move |scope_id, destination, subscriptions| {
if scope_id == 0 {
east(destination, subscriptions)
} else {
west(destination, subscriptions)
}
}
}
fn mounted(subscriptions: &[(usize, &str)]) -> Vec<LiveSubscription> {
subscriptions
.iter()
.enumerate()
.map(|(id, (scope_id, name))| LiveSubscription {
id,
scope_id: *scope_id,
name: (*name).to_owned(),
})
.collect()
}
struct Brokers {
east: Box<u8>,
west: Box<u8>,
}
impl Brokers {
fn new() -> Self {
Self {
east: Box::new(0),
west: Box::new(1),
}
}
fn coordinator(&self) -> Coordinator {
let coordinator = Coordinator::new(16);
coordinator.locate([
&*self.east as &(dyn Any + Send + Sync),
&*self.west as &(dyn Any + Send + Sync),
]);
coordinator
}
}
async fn send(coordinator: &Coordinator, consuming: usize, broker: &u8, name: &str) {
let ((), origin) = paired(broker, async {}).await;
in_harness_scope(
Some(HarnessScope::new(coordinator.clone(), consuming, 0)),
publishing_to(origin, async {
let msg: OutgoingMessage<'_> = OutgoingMessage::new(name, b"{}".as_slice());
PipelinePublish::capture(&msg).sent();
}),
)
.await;
}
#[tokio::test]
async fn a_pattern_subscription_is_owed_what_its_pattern_reaches() {
let brokers = Brokers::new();
let coordinator = brokers.coordinator();
let routing = brokers_routing(fan_out, exact);
let subscriptions = mounted(&[(0, "orders.*")]);
send(&coordinator, 0, &brokers.east, "orders.eu").await;
send(&coordinator, 0, &brokers.east, "audit").await;
let owed = coordinator
.owed(&subscriptions, &routing)
.expect("the publish to orders.eu is owed to orders.*");
assert_eq!(
(owed.subscription.as_str(), owed.handled, owed.expected),
("orders.*", 0, 1),
);
coordinator.record(handled(&subscriptions[0]));
assert!(coordinator.owed(&subscriptions, &routing).is_none());
}
#[tokio::test]
async fn a_publish_is_owed_on_the_broker_it_was_paired_against() {
let brokers = Brokers::new();
let coordinator = brokers.coordinator();
let routing = brokers_routing(exact, exact);
let subscriptions = mounted(&[(0, "orders"), (1, "orders")]);
send(&coordinator, 0, &brokers.west, "orders").await;
let owed = coordinator
.owed(&subscriptions, &routing)
.expect("west's subscription is owed the publish");
assert_eq!((owed.handled, owed.expected), (0, 1));
coordinator.record(handled(&subscriptions[0]));
assert!(coordinator.owed(&subscriptions, &routing).is_some());
coordinator.record(handled(&subscriptions[1]));
assert!(coordinator.owed(&subscriptions, &routing).is_none());
assert_eq!(coordinator.published(1, "orders").len(), 1);
assert_eq!(coordinator.published(0, "orders"), Vec::new());
}
#[tokio::test]
async fn a_wildcard_and_exact_names_on_two_brokers_are_owed_apart() {
const EACH: usize = 200;
let brokers = Brokers::new();
let coordinator = brokers.coordinator();
let routing = brokers_routing(fan_out, exact);
let subscriptions = mounted(&[(0, "orders.*"), (1, "orders.eu"), (1, "orders.us")]);
for _ in 0..EACH {
send(&coordinator, 0, &brokers.west, "orders.eu").await;
send(&coordinator, 1, &brokers.east, "orders.us").await;
}
for _ in 0..EACH - 1 {
coordinator.record(handled(&subscriptions[1]));
coordinator.record(handled(&subscriptions[0]));
}
coordinator.record(handled(&subscriptions[1]));
let owed = coordinator
.owed(&subscriptions, &routing)
.expect("the last us is still on its way to the wildcard");
assert_eq!(
(owed.subscription.as_str(), owed.handled, owed.expected),
("orders.*", EACH - 1, EACH),
);
coordinator.record(handled(&subscriptions[0]));
assert!(coordinator.owed(&subscriptions, &routing).is_none());
}
#[tokio::test]
async fn overlapping_subscriptions_owe_what_their_broker_delivers_to_each() {
let brokers = Brokers::new();
let coordinator = brokers.coordinator();
let routing = brokers_routing(fan_out, exact);
let subscriptions = mounted(&[
(0, "orders.*"),
(0, "orders.eu"),
(1, "orders.eu"),
(1, "orders.us"),
]);
send(&coordinator, 0, &brokers.east, "orders.eu").await;
send(&coordinator, 0, &brokers.east, "orders.us").await;
coordinator.record(handled(&subscriptions[0]));
coordinator.record(handled(&subscriptions[1]));
let owed = coordinator
.owed(&subscriptions, &routing)
.expect("the wildcard is owed both publishes");
assert_eq!(
(owed.subscription.as_str(), owed.handled, owed.expected),
("orders.*", 1, 2),
);
coordinator.record(handled(&subscriptions[0]));
assert!(coordinator.owed(&subscriptions, &routing).is_none());
}
#[tokio::test]
async fn a_publisher_the_runtime_did_not_pair_is_not_awaited() {
let brokers = Brokers::new();
let coordinator = brokers.coordinator();
let routing = brokers_routing(exact, exact);
let subscriptions = mounted(&[(0, "orders")]);
in_harness_scope(Some(HarnessScope::new(coordinator.clone(), 0, 0)), async {
let msg: OutgoingMessage<'_> = OutgoingMessage::new("orders", b"{}".as_slice());
PipelinePublish::capture(&msg).sent();
})
.await;
assert!(coordinator.owed(&subscriptions, &routing).is_none());
assert_eq!(coordinator.published(0, "orders"), Vec::new());
}
#[test]
fn the_debug_form_reports_the_quiescence_counters() {
let coordinator = Coordinator::new(16);
let idle = format!("{coordinator:?}");
assert!(idle.contains("in_flight: 0"), "{idle}");
assert!(idle.contains("processed: 0"), "{idle}");
coordinator.enqueued();
let busy = format!("{coordinator:?}");
assert!(busy.contains("in_flight: 1"), "{busy}");
}
#[tokio::test]
async fn a_hold_keeps_the_reaction_open_across_a_settle_and_its_copy() {
let coordinator = Coordinator::new(16);
coordinator.enqueued();
let hold = coordinator.hold();
coordinator.consumed();
let mut drive = Box::pin(coordinator.drive());
assert!(
futures::poll!(drive.as_mut()).is_pending(),
"drive returned between the settle and the copy"
);
coordinator.enqueued();
drop(hold);
assert!(futures::poll!(drive.as_mut()).is_pending());
coordinator.consumed();
drive
.await
.expect("the reaction settles once the copy is handled");
}
}