use spate_core::feed::{
Confirmation, Confirmer, Delivery, Depth, DepthOf, DepthReading, Feed, FeedError,
FeedErrorKind, FeedWaker, Receipt, Unit, confirmation, depth_reading,
};
use spate_core::hash::{FastMap, FastSet};
use std::collections::{BTreeMap, VecDeque};
use std::sync::{Arc, Mutex, MutexGuard};
use std::time::Duration;
#[derive(Clone, Copy, Debug)]
enum Call {
Receive,
Renew,
Acknowledge,
Fail,
}
impl Call {
fn name(self) -> &'static str {
match self {
Call::Receive => "receive",
Call::Renew => "renew",
Call::Acknowledge => "acknowledge",
Call::Fail => "fail",
}
}
}
#[derive(Default)]
struct State {
queued: VecDeque<Delivery>,
held: BTreeMap<Receipt, Delivery>,
counts: FastMap<String, u32>,
scripted: FastSet<Receipt>,
next_receipt: u64,
delivered: Vec<Delivery>,
acknowledged: Vec<Receipt>,
failed: Vec<(Receipt, String)>,
renewed: Vec<Receipt>,
gone: Vec<Receipt>,
published: Vec<Unit>,
dead_lettered: Vec<Unit>,
publish_outcomes: VecDeque<FeedErrorKind>,
answers: [VecDeque<FeedErrorKind>; 4],
hold_publishes: bool,
held_publishes: Vec<(Unit, Confirmer)>,
feed_depths: VecDeque<Result<Depth, FeedError>>,
dead_letter_depths: VecDeque<Result<Depth, FeedError>>,
feed_depth_reads: usize,
dead_letter_depth_reads: usize,
hands_back: bool,
stopped_receiving: usize,
handed_back: Vec<Receipt>,
closes: Vec<Duration>,
waker: Option<FeedWaker>,
}
impl State {
fn queue(&mut self, unit: Unit, deliveries: u32) -> Receipt {
self.next_receipt += 1;
let receipt = Receipt(self.next_receipt);
let delivery = Delivery::new(unit, receipt, deliveries);
self.queued.push_back(delivery);
receipt
}
fn lapse(&mut self, receipt: Receipt) -> Option<Delivery> {
if let Some(delivery) = self.held.remove(&receipt) {
return Some(delivery);
}
let at = self.queued.iter().position(|d| d.receipt == receipt)?;
self.queued.remove(at)
}
fn on_held(
&mut self,
receipt: Receipt,
f: impl FnOnce(&mut State) -> Result<(), FeedError>,
) -> Result<(), FeedError> {
if self.held.contains_key(&receipt) {
f(self)
} else {
self.gone.push(receipt);
Err(FeedError::new(
FeedErrorKind::Gone,
format!("{receipt} is not held by the scripted feed"),
))
}
}
fn answer(&mut self, call: Call) -> Result<(), FeedError> {
match self.answers[call as usize].pop_front() {
Some(kind) => Err(FeedError::new(kind, "refused by the feed script")),
None => Ok(()),
}
}
fn wake(&self) {
if let Some(waker) = &self.waker {
waker.wake();
}
}
}
impl std::fmt::Debug for State {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("State")
.field("queued", &self.queued.len())
.field("held", &self.held.len())
.field("acknowledged", &self.acknowledged.len())
.finish_non_exhaustive()
}
}
#[derive(Debug)]
pub struct ScriptedFeed {
state: Arc<Mutex<State>>,
}
#[derive(Clone, Debug)]
pub struct FeedScript {
state: Arc<Mutex<State>>,
}
#[must_use]
pub fn scripted_feed() -> (ScriptedFeed, FeedScript) {
let state = Arc::new(Mutex::new(State::default()));
(
ScriptedFeed {
state: Arc::clone(&state),
},
FeedScript { state },
)
}
impl FeedScript {
pub fn deliver(&self, unit: Unit) -> Receipt {
let mut state = self.lock();
let count = state.counts.entry(unit.id.clone()).or_default();
*count += 1;
let deliveries = *count;
let receipt = state.queue(unit, deliveries);
state.scripted.insert(receipt);
state.wake();
receipt
}
pub fn redeliver(&self, receipt: Receipt) -> Receipt {
let mut state = self.lock();
let Some(delivery) = state.lapse(receipt) else {
panic!("redeliver: {receipt} is neither queued nor held");
};
let scripted = state.scripted.remove(&receipt);
let deliveries = match state.counts.get_mut(&delivery.unit.id) {
Some(count) if scripted => {
*count += 1;
*count
}
_ => delivery.deliveries + 1,
};
let receipt = state.queue(delivery.unit, deliveries);
if scripted {
state.scripted.insert(receipt);
}
state.wake();
receipt
}
pub fn dead_letter(&self, receipt: Receipt) {
let mut state = self.lock();
let Some(delivery) = state.lapse(receipt) else {
panic!("dead_letter: {receipt} is neither queued nor held");
};
state.dead_lettered.push(delivery.unit);
state.wake();
}
pub fn refuse_next_publish(&self, kind: FeedErrorKind) {
let mut state = self.lock();
state.publish_outcomes.push_back(kind);
state.wake();
}
pub fn refuse_next_receive(&self, kind: FeedErrorKind) {
self.refuse_next(Call::Receive, kind);
}
pub fn refuse_next_renew(&self, kind: FeedErrorKind) {
self.refuse_next(Call::Renew, kind);
}
pub fn refuse_next_acknowledge(&self, kind: FeedErrorKind) {
self.refuse_next(Call::Acknowledge, kind);
}
pub fn refuse_next_fail(&self, kind: FeedErrorKind) {
self.refuse_next(Call::Fail, kind);
}
#[must_use]
pub fn refusals_left(&self) -> usize {
let state = self.lock();
state.publish_outcomes.len() + state.answers.iter().map(VecDeque::len).sum::<usize>()
}
fn refuse_next(&self, call: Call, kind: FeedErrorKind) {
let why = match call {
Call::Receive => "Gone has no meaning for receive",
_ => {
"the feed answers Gone itself for a receipt it does not hold; \
lapse one with redeliver or dead_letter"
}
};
assert!(
kind != FeedErrorKind::Gone,
"refuse_next_{}: {why}",
call.name()
);
let mut state = self.lock();
state.answers[call as usize].push_back(kind);
state.wake();
}
pub fn hold_publishes(&self) {
let mut state = self.lock();
state.hold_publishes = true;
state.wake();
}
pub fn confirm_held_publishes(&self) {
let mut state = self.lock();
state.hold_publishes = false;
for (unit, confirmer) in std::mem::take(&mut state.held_publishes) {
confirmer.confirm();
state.queue(unit, 1);
}
state.wake();
}
pub fn answer_depth(&self, of: DepthOf, answer: Result<Depth, FeedError>) {
let mut state = self.lock();
match of {
DepthOf::DeadLetters => state.dead_letter_depths.push_back(answer),
_ => state.feed_depths.push_back(answer),
}
state.wake();
}
pub fn hand_back_on_stop(&self) {
let mut state = self.lock();
state.hands_back = true;
state.wake();
}
#[must_use]
pub fn stopped_receiving(&self) -> usize {
self.lock().stopped_receiving
}
#[must_use]
pub fn handed_back(&self) -> Vec<Receipt> {
self.lock().handed_back.clone()
}
#[must_use]
pub fn closes(&self) -> Vec<Duration> {
self.lock().closes.clone()
}
#[must_use]
pub fn depth_reads(&self, of: DepthOf) -> usize {
let state = self.lock();
match of {
DepthOf::DeadLetters => state.dead_letter_depth_reads,
_ => state.feed_depth_reads,
}
}
#[must_use]
pub fn delivered(&self) -> Vec<Delivery> {
self.lock().delivered.clone()
}
#[must_use]
pub fn held(&self) -> Vec<Receipt> {
self.lock().held.keys().copied().collect()
}
#[must_use]
pub fn acknowledged(&self) -> Vec<Receipt> {
self.lock().acknowledged.clone()
}
#[must_use]
pub fn failed(&self) -> Vec<(Receipt, String)> {
self.lock().failed.clone()
}
#[must_use]
pub fn renewed(&self) -> Vec<Receipt> {
self.lock().renewed.clone()
}
#[must_use]
pub fn gone(&self) -> Vec<Receipt> {
self.lock().gone.clone()
}
#[must_use]
pub fn published(&self) -> Vec<Unit> {
self.lock().published.clone()
}
#[must_use]
pub fn dead_lettered(&self) -> Vec<Unit> {
self.lock().dead_lettered.clone()
}
fn lock(&self) -> MutexGuard<'_, State> {
self.state.lock().expect("feed script poisoned")
}
}
impl ScriptedFeed {
fn lock(&self) -> MutexGuard<'_, State> {
self.state.lock().expect("feed script poisoned")
}
}
impl Feed for ScriptedFeed {
fn backend(&self) -> &str {
"scripted"
}
fn set_waker(&self, waker: FeedWaker) {
let mut state = self.lock();
if !state.queued.is_empty() {
waker.wake();
}
state.waker = Some(waker);
}
fn receive(&self, max: usize) -> Result<Vec<Delivery>, FeedError> {
let mut state = self.lock();
state.answer(Call::Receive)?;
let take = max.min(state.queued.len());
let out: Vec<Delivery> = state.queued.drain(..take).collect();
for delivery in &out {
state.held.insert(delivery.receipt, delivery.clone());
state.delivered.push(delivery.clone());
}
if take > 0 && !state.queued.is_empty() {
state.wake();
}
Ok(out)
}
fn renew(&self, receipt: Receipt) -> Result<(), FeedError> {
let mut state = self.lock();
state.answer(Call::Renew)?;
state.on_held(receipt, |state| {
state.renewed.push(receipt);
Ok(())
})
}
fn acknowledge(&self, receipt: Receipt) -> Result<(), FeedError> {
let mut state = self.lock();
state.answer(Call::Acknowledge)?;
state.on_held(receipt, |state| {
state.held.remove(&receipt);
state.acknowledged.push(receipt);
Ok(())
})
}
fn fail(&self, receipt: Receipt, reason: &str) -> Result<(), FeedError> {
let mut state = self.lock();
state.answer(Call::Fail)?;
state.on_held(receipt, |state| {
state.held.remove(&receipt);
state.failed.push((receipt, reason.to_owned()));
Ok(())
})
}
fn publish(&self, unit: Unit) -> Confirmation {
let mut state = self.lock();
state.published.push(unit.clone());
let (confirmer, confirmation) = confirmation();
if let Some(kind) = state.publish_outcomes.pop_front() {
confirmer.reject(FeedError::new(kind, "refused by the feed script"));
} else if state.hold_publishes {
state.held_publishes.push((unit, confirmer));
} else {
confirmer.confirm();
state.queue(unit, 1);
state.wake();
}
confirmation
}
fn read_depth(&self, of: DepthOf) -> Option<DepthReading> {
let mut state = self.lock();
let answer = match of {
DepthOf::DeadLetters => {
state.dead_letter_depth_reads += 1;
state.dead_letter_depths.pop_front().unwrap_or_else(|| {
let mut depth = Depth::new("the scripted dead-letter destination", false);
depth.visible = state.dead_lettered.len() as u64;
Ok(depth)
})
}
_ => {
state.feed_depth_reads += 1;
state.feed_depths.pop_front().unwrap_or_else(|| {
let mut depth = Depth::new("the scripted feed", false);
depth.visible = state.queued.len() as u64;
depth.in_flight = state.held.len() as u64;
Ok(depth)
})
}
};
let (reporter, reading) = depth_reading();
reporter.report(answer);
Some(reading)
}
fn hands_back(&self) -> bool {
self.lock().hands_back
}
fn stop_receiving(&self) {
self.lock().stopped_receiving += 1;
}
fn hand_back(&self, receipt: Receipt) -> Result<(), FeedError> {
self.lock().on_held(receipt, |state| {
state.held.remove(&receipt);
state.handed_back.push(receipt);
Ok(())
})
}
fn close(&self, within: Duration) -> Option<Confirmation> {
self.lock().closes.push(within);
let (confirmer, confirmation) = confirmation();
confirmer.confirm();
Some(confirmation)
}
}
#[cfg(test)]
mod tests {
use super::*;
use spate_core::feed::wake_channel;
use std::time::Duration;
fn unit(id: &str) -> Unit {
Unit::new(id, id.as_bytes().to_vec())
}
fn kind(result: Result<(), FeedError>) -> FeedErrorKind {
result.unwrap_err().kind
}
#[test]
fn a_scripted_feed_reports_depth_from_its_own_state() {
let (feed, script) = scripted_feed();
let read = |of| {
feed.read_depth(of)
.expect("the scripted feed reports depth")
.try_outcome()
.expect("settled at once")
.expect("read")
};
assert!(read(DepthOf::Feed).is_empty());
let held = script.deliver(unit("held"));
let dead: Vec<Receipt> = ["dead-1", "dead-2", "dead-3"]
.into_iter()
.map(|id| script.deliver(unit(id)))
.collect();
script.deliver(unit("queued-1"));
script.deliver(unit("queued-2"));
let got = feed.receive(1).unwrap();
assert_eq!(got[0].receipt, held);
for receipt in dead {
script.dead_letter(receipt);
}
let depth = read(DepthOf::Feed);
assert_eq!(
(depth.visible, depth.in_flight, depth.delayed, depth.unsent),
(2, 1, 0, 0)
);
assert!(!depth.approximate);
let dead = read(DepthOf::DeadLetters);
assert_eq!((dead.visible, dead.in_flight), (3, 0));
assert_eq!(script.depth_reads(DepthOf::Feed), 2);
assert_eq!(script.depth_reads(DepthOf::DeadLetters), 1);
}
#[test]
fn a_scripted_depth_answer_sequence_is_served_in_order() {
let (feed, script) = scripted_feed();
let (waker, park) = wake_channel();
feed.set_waker(waker);
let mut first = Depth::new("q", true);
first.delayed = 3;
script.answer_depth(DepthOf::Feed, Ok(first.clone()));
assert!(
park.park(Duration::ZERO),
"scripting did not wake the reader"
);
script.answer_depth(
DepthOf::Feed,
Err(FeedError::new(FeedErrorKind::Fatal, "gone")),
);
let mut dead = Depth::new("dlq", true);
dead.visible = 2;
script.answer_depth(DepthOf::DeadLetters, Ok(dead.clone()));
let read = |of| feed.read_depth(of).unwrap().try_outcome().unwrap();
assert_eq!(read(DepthOf::DeadLetters).unwrap(), dead);
assert_eq!(read(DepthOf::Feed).unwrap(), first);
assert_eq!(read(DepthOf::Feed).unwrap_err().kind, FeedErrorKind::Fatal);
assert!(read(DepthOf::Feed).unwrap().is_empty());
assert!(read(DepthOf::DeadLetters).unwrap().is_empty());
}
#[test]
fn a_redelivery_lapses_the_old_receipt() {
let (feed, script) = scripted_feed();
let first = script.deliver(unit("a"));
assert_eq!(feed.receive(8).unwrap()[0].receipt, first);
assert_eq!(script.held(), vec![first]);
let second = script.redeliver(first);
assert!(script.held().is_empty(), "the lapsed receipt is still held");
let again = feed.receive(8).unwrap();
assert_eq!(again.len(), 1);
assert_eq!((again[0].receipt, again[0].deliveries), (second, 2));
assert_eq!(kind(feed.renew(first)), FeedErrorKind::Gone);
assert_eq!(kind(feed.acknowledge(first)), FeedErrorKind::Gone);
assert_eq!(kind(feed.fail(first, "late")), FeedErrorKind::Gone);
assert_eq!(script.gone(), vec![first, first, first]);
feed.renew(second).unwrap();
feed.acknowledge(second).unwrap();
assert_eq!(script.renewed(), vec![second]);
assert_eq!(script.acknowledged(), vec![second]);
assert!(script.failed().is_empty());
assert_eq!(script.delivered().len(), 2);
}
#[test]
fn a_duplicate_is_held_beside_the_first_delivery() {
let (feed, script) = scripted_feed();
let first = script.deliver(unit("a"));
let _ = feed.receive(8).unwrap();
let second = script.deliver(unit("a"));
let dup = feed.receive(8).unwrap();
assert_eq!((dup[0].receipt, dup[0].deliveries), (second, 2));
assert_eq!(script.held(), vec![first, second]);
feed.acknowledge(first).unwrap();
feed.fail(second, "poison").unwrap();
assert_eq!(script.acknowledged(), vec![first]);
assert_eq!(script.failed(), vec![(second, "poison".to_owned())]);
assert!(script.held().is_empty());
assert!(feed.receive(8).unwrap().is_empty(), "fail redelivered");
}
#[test]
fn a_dead_lettered_unit_lapses_its_receipt() {
let (feed, script) = scripted_feed();
let queued = script.deliver(unit("q"));
script.dead_letter(queued);
assert!(feed.receive(8).unwrap().is_empty());
let held = script.deliver(unit("h"));
let _ = feed.receive(8).unwrap();
script.dead_letter(held);
assert_eq!(kind(feed.acknowledge(held)), FeedErrorKind::Gone);
assert_eq!(script.dead_lettered(), vec![unit("q"), unit("h")]);
}
#[test]
fn publishes_are_confirmed_refused_or_held_as_scripted() {
let (feed, script) = scripted_feed();
let mut plain = feed.publish(unit("plain"));
assert!(matches!(plain.try_outcome(), Some(Ok(()))));
script.refuse_next_publish(FeedErrorKind::Refused);
let mut refused = feed.publish(unit("refused"));
assert_eq!(
refused.try_outcome().unwrap().unwrap_err().kind,
FeedErrorKind::Refused
);
script.hold_publishes();
let mut held = feed.publish(unit("held"));
assert!(held.try_outcome().is_none());
let ids = |d: Vec<Delivery>| d.into_iter().map(|d| d.unit.id).collect::<Vec<_>>();
assert_eq!(ids(feed.receive(8).unwrap()), vec!["plain"]);
script.confirm_held_publishes();
assert!(matches!(held.try_outcome(), Some(Ok(()))));
assert_eq!(ids(feed.receive(8).unwrap()), vec!["held"]);
assert_eq!(
script.published(),
vec![unit("plain"), unit("refused"), unit("held")]
);
}
#[test]
fn scripting_wakes_the_reader() {
let (feed, script) = scripted_feed();
let early = script.deliver(unit("early"));
let (waker, park) = wake_channel();
feed.set_waker(waker);
assert!(
park.park(Duration::ZERO),
"set_waker missed a queued delivery"
);
let _ = feed.receive(8).unwrap();
assert!(!park.park(Duration::ZERO));
let again = script.redeliver(early);
assert!(park.park(Duration::ZERO), "redeliver did not wake");
script.dead_letter(again);
assert!(park.park(Duration::ZERO), "dead_letter did not wake");
script.hold_publishes();
assert!(park.park(Duration::ZERO), "hold_publishes did not wake");
let _held = feed.publish(unit("p"));
assert!(!park.park(Duration::ZERO), "a held publish woke the reader");
script.confirm_held_publishes();
assert!(
park.park(Duration::ZERO),
"confirm_held_publishes did not wake"
);
}
#[test]
fn a_partial_receive_leaves_a_wake_pending() {
let (feed, script) = scripted_feed();
let (waker, park) = wake_channel();
feed.set_waker(waker);
script.deliver(unit("a"));
script.deliver(unit("b"));
assert!(park.park(Duration::ZERO));
assert_eq!(feed.receive(1).unwrap().len(), 1);
assert!(
park.park(Duration::ZERO),
"a delivery is queued but the reader's park was not signalled"
);
assert!(feed.receive(0).unwrap().is_empty());
assert!(
!park.park(Duration::ZERO),
"receive(0) at capacity re-signalled"
);
}
#[test]
fn a_confirmed_publish_wakes_the_reader() {
let (feed, _script) = scripted_feed();
let (waker, park) = wake_channel();
feed.set_waker(waker);
assert!(!park.park(Duration::ZERO));
drop(feed.publish(unit("p")));
assert!(park.park(Duration::ZERO), "publish did not wake the reader");
}
#[test]
fn refusing_the_next_publish_wakes_the_reader() {
let (feed, script) = scripted_feed();
let (waker, park) = wake_channel();
feed.set_waker(waker);
assert!(!park.park(Duration::ZERO));
script.refuse_next_publish(FeedErrorKind::Refused);
assert!(
park.park(Duration::ZERO),
"refuse_next_publish did not wake the reader"
);
}
fn call(feed: &ScriptedFeed, call: Call, receipt: Receipt) -> Result<(), FeedError> {
match call {
Call::Receive => feed.receive(0).map(drop),
Call::Renew => feed.renew(receipt),
Call::Acknowledge => feed.acknowledge(receipt),
Call::Fail => feed.fail(receipt, "poison"),
}
}
fn refuse(script: &FeedScript, call: Call, kind: FeedErrorKind) {
match call {
Call::Receive => script.refuse_next_receive(kind),
Call::Renew => script.refuse_next_renew(kind),
Call::Acknowledge => script.refuse_next_acknowledge(kind),
Call::Fail => script.refuse_next_fail(kind),
}
}
const CALLS: [Call; 4] = [Call::Receive, Call::Renew, Call::Acknowledge, Call::Fail];
#[test]
fn scripted_answers_are_served_in_order_then_spent() {
for c in CALLS {
let (feed, script) = scripted_feed();
let receipt = script.deliver(unit("a"));
let _ = feed.receive(8).unwrap();
refuse(&script, c, FeedErrorKind::Retryable);
refuse(&script, c, FeedErrorKind::Fatal);
assert_eq!(script.refusals_left(), 2);
assert_eq!(
kind(call(&feed, c, receipt)),
FeedErrorKind::Retryable,
"{c:?}"
);
assert_eq!(script.refusals_left(), 1);
assert_eq!(kind(call(&feed, c, receipt)), FeedErrorKind::Fatal, "{c:?}");
assert_eq!(script.refusals_left(), 0);
call(&feed, c, receipt).unwrap_or_else(|e| panic!("{c:?} after the script: {e}"));
}
}
#[test]
fn a_scripted_error_changes_nothing() {
let (feed, script) = scripted_feed();
let receipt = script.deliver(unit("a"));
script.deliver(unit("b"));
let _ = feed.receive(1).unwrap();
for c in [Call::Renew, Call::Acknowledge, Call::Fail] {
refuse(&script, c, FeedErrorKind::Retryable);
assert_eq!(kind(call(&feed, c, receipt)), FeedErrorKind::Retryable);
assert_eq!(script.held(), vec![receipt], "{c:?} changed the held set");
assert!(script.renewed().is_empty(), "{c:?} recorded a renewal");
assert!(script.acknowledged().is_empty(), "{c:?} recorded an ack");
assert!(script.failed().is_empty(), "{c:?} recorded a fail");
assert!(script.gone().is_empty(), "{c:?} recorded Gone");
}
feed.renew(receipt).unwrap();
feed.acknowledge(receipt).unwrap();
assert_eq!(script.renewed(), vec![receipt]);
assert_eq!(script.acknowledged(), vec![receipt]);
}
#[test]
fn a_scripted_receive_error_takes_nothing() {
let (feed, script) = scripted_feed();
let queued = script.deliver(unit("a"));
script.refuse_next_receive(FeedErrorKind::Retryable);
assert_eq!(feed.receive(8).unwrap_err().kind, FeedErrorKind::Retryable);
assert!(script.held().is_empty());
assert!(script.delivered().is_empty());
let got = feed.receive(8).unwrap();
assert_eq!(got.len(), 1);
assert_eq!(got[0].receipt, queued);
}
#[test]
fn a_scripted_answer_waits_for_its_own_call() {
for scripted in CALLS {
let (feed, script) = scripted_feed();
for id in ["a", "b", "c", "d"] {
script.deliver(unit(id));
}
let receipts: Vec<Receipt> =
feed.receive(4).unwrap().iter().map(|d| d.receipt).collect();
refuse(&script, scripted, FeedErrorKind::Fatal);
for (i, c) in CALLS.into_iter().enumerate() {
if c as usize != scripted as usize {
call(&feed, c, receipts[i])
.unwrap_or_else(|e| panic!("{c:?} took {scripted:?}'s answer: {e}"));
}
}
assert_eq!(script.refusals_left(), 1);
let own = receipts[scripted as usize];
assert_eq!(kind(call(&feed, scripted, own)), FeedErrorKind::Fatal);
}
}
#[test]
fn a_scripted_answer_goes_to_a_lapsed_receipt_first() {
for c in [Call::Renew, Call::Acknowledge, Call::Fail] {
let (feed, script) = scripted_feed();
let old = script.deliver(unit("a"));
let _ = feed.receive(8).unwrap();
script.redeliver(old);
refuse(&script, c, FeedErrorKind::Retryable);
assert_eq!(kind(call(&feed, c, old)), FeedErrorKind::Retryable, "{c:?}");
assert!(script.gone().is_empty(), "{c:?} recorded Gone");
assert_eq!(script.refusals_left(), 0, "{c:?}");
assert_eq!(kind(call(&feed, c, old)), FeedErrorKind::Gone, "{c:?}");
assert_eq!(script.gone(), vec![old], "{c:?}");
}
}
#[test]
fn a_refused_receive_sends_no_signal() {
let (feed, script) = scripted_feed();
let (waker, park) = wake_channel();
feed.set_waker(waker);
script.deliver(unit("a"));
script.refuse_next_receive(FeedErrorKind::Retryable);
assert!(park.park(Duration::ZERO));
assert_eq!(feed.receive(8).unwrap_err().kind, FeedErrorKind::Retryable);
assert!(
!park.park(Duration::ZERO),
"a refused receive signalled the reader"
);
}
#[test]
fn refusals_left_counts_unserved_publish_refusals() {
let (feed, script) = scripted_feed();
script.refuse_next_publish(FeedErrorKind::Refused);
script.refuse_next_receive(FeedErrorKind::Retryable);
assert_eq!(script.refusals_left(), 2);
let mut refused = feed.publish(unit("a"));
assert_eq!(
refused.try_outcome().unwrap().unwrap_err().kind,
FeedErrorKind::Refused
);
assert_eq!(script.refusals_left(), 1);
}
#[test]
#[should_panic(expected = "refuse_next_receive: Gone has no meaning for receive")]
fn scripting_gone_panics_for_receive() {
scripted_feed().1.refuse_next_receive(FeedErrorKind::Gone);
}
#[test]
#[should_panic(expected = "refuse_next_renew: the feed answers Gone")]
fn scripting_gone_panics_for_renew() {
scripted_feed().1.refuse_next_renew(FeedErrorKind::Gone);
}
#[test]
#[should_panic(expected = "refuse_next_acknowledge: the feed answers Gone")]
fn scripting_gone_panics_for_acknowledge() {
scripted_feed()
.1
.refuse_next_acknowledge(FeedErrorKind::Gone);
}
#[test]
#[should_panic(expected = "refuse_next_fail: the feed answers Gone")]
fn scripting_gone_panics_for_fail() {
scripted_feed().1.refuse_next_fail(FeedErrorKind::Gone);
}
#[test]
fn refusing_the_next_call_wakes_the_reader() {
for c in CALLS {
let (feed, script) = scripted_feed();
let (waker, park) = wake_channel();
feed.set_waker(waker);
assert!(!park.park(Duration::ZERO));
refuse(&script, c, FeedErrorKind::Retryable);
assert!(
park.park(Duration::ZERO),
"refusing the next {c:?} did not wake the reader"
);
}
}
#[test]
fn a_later_set_waker_replaces_the_waker() {
let (feed, script) = scripted_feed();
let (first, first_park) = wake_channel();
feed.set_waker(first);
let (second, second_park) = wake_channel();
feed.set_waker(second);
script.deliver(unit("a"));
assert!(
second_park.park(Duration::ZERO),
"deliver signalled the replaced waker"
);
assert!(!first_park.park(Duration::ZERO));
}
}