1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
//! The routing suite against a stand-in whose transport cannot acknowledge.
//!
//! Several transports settle nothing: `ZeroMQ` acknowledges nothing at all, MQTT `QoS 0` and Redis
//! pub/sub have no acknowledgement to give, and a file or stdio transport has nobody to give it
//! to. Their deliveries report [`AckError::Unsupported`], and the in-process stand-in a broker
//! crate ships must answer the same way - a stand-in that succeeds instead passes a handler's
//! retry in a test and loses the message in production.
//!
//! Both halves are pinned here: the honest stand-in passes the suite, and the one claiming
//! settlements it never performs still fails it.
#![cfg(all(feature = "conformance", feature = "memory", feature = "testing"))]
use std::{
future::{Future, ready},
marker::PhantomData,
};
use futures::{Stream, StreamExt};
use ruststream::{
AckError, AddressedCopies, Broker, ConnectedBroker, HeaderMap, IncomingMessage,
OutgoingMessage, RawMessage, Subscribe, Subscriber,
conformance::harness,
memory::{
ClosedMemoryBroker, ConnectedMemoryBroker, MemoryBroker, MemoryError, MemoryMessage,
MemorySubscriber, Retaining, Retention,
},
nonzero,
testing::{Coordinator, InProcess, TestableBroker},
};
/// What a stand-in's deliveries answer when they are settled.
///
/// A type parameter rather than a field, so the stand-ins below cannot drift into each other's
/// answer and every scenario runs against each of them.
trait Settlement: Send + Sync + 'static {
/// The answer `ack` and `nack` give.
fn answer() -> Result<(), AckError>;
}
/// A transport with no acknowledgement at all, answering as `ZeroMQ`, MQTT `QoS 0` and Redis
/// pub/sub answer.
struct Refuses;
impl Settlement for Refuses {
fn answer() -> Result<(), AckError> {
Err(AckError::Unsupported)
}
}
/// The same transport claiming every settlement worked: the stand-in the suite must still catch.
struct Claims;
impl Settlement for Claims {
fn answer() -> Result<(), AckError> {
Ok(())
}
}
/// A transport whose settlement genuinely fails. The suite admits an unsupported settlement, not
/// any answer at all, so this one must still fail.
struct Fails;
impl Settlement for Fails {
fn answer() -> Result<(), AckError> {
Err(AckError::Timeout)
}
}
/// A stand-in transport: the in-memory broker's routing, with `S`'s answer to `ack` and `nack`.
///
/// Every stand-in behaves identically on the wire - a settled delivery is simply gone, and nothing
/// is ever redelivered - so what the suite reads is only what each one claims.
struct Standin<S> {
broker: MemoryBroker<Retaining>,
settlement: PhantomData<S>,
}
impl<S> Standin<S> {
fn new() -> Self {
Self {
// The routing suite reads the publish log back, which is the retaining form's job.
broker: MemoryBroker::retaining(Retention::Messages(nonzero!(64))),
settlement: PhantomData,
}
}
}
impl<S: Settlement> Broker for Standin<S> {
type Error = MemoryError;
type Connected = ConnectedStandin<S>;
async fn connect(self) -> Result<Self::Connected, Self::Error> {
self.broker.connect().await.map(|inner| ConnectedStandin {
inner,
settlement: PhantomData,
})
}
}
/// The stand-in has no server either, so the transition the suite connects through is its
/// ordinary `connect`.
impl<S: Settlement> InProcess for Standin<S> {
fn connect_in_process(
self,
) -> impl Future<Output = Result<Self::Connected, Self::Error>> + Send {
self.connect()
}
}
/// The connected form of a [`Standin`], which is what the suite drives.
struct ConnectedStandin<S> {
inner: ConnectedMemoryBroker<Retaining>,
settlement: PhantomData<S>,
}
impl<S: Settlement> ConnectedBroker for ConnectedStandin<S> {
type Error = MemoryError;
type Closed = ClosedMemoryBroker;
fn shutdown(self) -> impl Future<Output = Result<Self::Closed, Self::Error>> + Send {
self.inner.shutdown()
}
}
impl<S: Settlement> TestableBroker for ConnectedStandin<S> {
fn install_coordinator(&self, coordinator: Coordinator) {
self.inner.install_coordinator(coordinator);
}
fn inject(&self, message: OutgoingMessage<'_>) {
self.inner.inject(message);
}
fn published(&self, name: &str) -> Vec<RawMessage> {
self.inner.published(name)
}
}
impl<S: Settlement> Subscribe for ConnectedStandin<S> {
// One name is both ends of this stand-in, so a publish under it reaches the subscription.
type Copies = AddressedCopies;
type Subscriber = StandinSubscriber<S>;
fn subscribe(
&self,
name: &str,
) -> impl Future<Output = Result<Self::Subscriber, Self::Error>> + Send {
let opened = Subscribe::subscribe(&self.inner, name);
async move {
opened.await.map(|inner| StandinSubscriber {
inner,
settlement: PhantomData,
})
}
}
}
/// The stand-in's subscriber: the memory subscriber's stream, one wrapper per delivery.
struct StandinSubscriber<S> {
inner: MemorySubscriber<Retaining>,
settlement: PhantomData<S>,
}
impl<S: Settlement> Subscriber for StandinSubscriber<S> {
type Message = StandinMessage<S>;
type Error = <MemorySubscriber<Retaining> as Subscriber>::Error;
fn stream(&mut self) -> impl Stream<Item = Result<Self::Message, Self::Error>> + Send + '_ {
self.inner.stream().map(|delivery| {
delivery.map(|inner| StandinMessage {
inner,
settlement: PhantomData,
})
})
}
}
/// One delivery from a stand-in, answering settlement as `S` does.
struct StandinMessage<S> {
inner: MemoryMessage<Retaining>,
settlement: PhantomData<S>,
}
impl<S: Settlement> IncomingMessage for StandinMessage<S> {
fn payload(&self) -> &[u8] {
self.inner.payload()
}
fn headers(&self) -> &HeaderMap {
self.inner.headers()
}
/// Taking the wrapped delivery apart here releases it without requeueing, which is what a
/// transport that settles nothing does with every message it hands over.
fn ack(self) -> impl Future<Output = Result<(), AckError>> + Send {
let _ = self.inner.into_raw();
ready(S::answer())
}
fn nack(self, _requeue: bool) -> impl Future<Output = Result<(), AckError>> + Send {
let _ = self.inner.into_raw();
ready(S::answer())
}
}
/// The honest stand-in passes: every routing guarantee is checked, and the settlement it refuses
/// is accepted as an answer rather than failing the run.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_standin_that_cannot_acknowledge_passes_the_suite() {
harness::run_suite(Standin::<Refuses>::new).await;
}
/// The liar is still caught, at the one scenario whose assertion IS the settlement: it claims the
/// requeue worked, and the redelivery that claim promises never arrives. The panic this test
/// expects is printed by the run; it is the suite working.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[should_panic(expected = "nack_with_requeue second: stream timed out")]
async fn a_standin_claiming_a_settlement_it_never_performs_fails_the_suite() {
harness::run_suite(Standin::<Claims>::new).await;
}
/// A settlement that fails for any other reason is still a failed run: what the suite accepts is
/// the one answer that names a transport without acknowledgement, not every error.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[should_panic(expected = "ack must succeed or be unsupported, got: Timeout")]
async fn a_standin_whose_settlement_fails_still_fails_the_suite() {
harness::run_suite(Standin::<Fails>::new).await;
}