#![cfg(feature = "memory")]
use std::convert::Infallible;
use std::pin::pin;
use futures::{FutureExt, Stream, StreamExt};
use ruststream::memory::{MemoryBroker, MemoryError, MemoryMessage};
use ruststream::{
Broker, ConnectedBroker, IncomingMessage, OutgoingMessage, OwnedTransactions, Publisher,
Subscriber, Transaction,
};
async fn drain_next<S>(stream: &mut S) -> Option<Vec<u8>>
where
S: Stream<Item = Result<MemoryMessage, Infallible>> + Unpin,
{
let msg = stream
.next()
.now_or_never()
.flatten()?
.expect("memory subscriber never errors");
let payload = msg.payload().to_vec();
msg.ack().await.expect("ack failed");
Some(payload)
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn concurrent_transactions_commit_independently_and_atomically() {
let broker = MemoryBroker::new();
let mut subscriber = broker.subscribe("orders");
let publisher = broker.publisher();
let mut first = publisher.transaction().await.expect("first transaction");
let mut second = publisher.transaction().await.expect("second transaction");
first
.publish(OutgoingMessage::new("orders", b"first-a".as_slice()))
.await
.expect("publish into first failed");
second
.publish(OutgoingMessage::new("orders", b"second-a".as_slice()))
.await
.expect("publish into second failed");
first
.publish(OutgoingMessage::new("orders", b"first-b".as_slice()))
.await
.expect("publish into first failed");
publisher
.publish(OutgoingMessage::new("orders", b"direct".as_slice()))
.await
.expect("direct publish failed");
let mut stream = pin!(subscriber.stream());
assert_eq!(
drain_next(&mut stream).await.as_deref(),
Some(b"direct".as_slice())
);
assert_eq!(
drain_next(&mut stream).await,
None,
"buffered publishes became visible before commit"
);
second.commit().await.expect("second commit failed");
assert_eq!(
drain_next(&mut stream).await.as_deref(),
Some(b"second-a".as_slice())
);
assert_eq!(
drain_next(&mut stream).await,
None,
"the sibling transaction leaked into the commit"
);
first.commit().await.expect("first commit failed");
for expected in [b"first-a".as_slice(), b"first-b".as_slice()] {
assert_eq!(drain_next(&mut stream).await.as_deref(), Some(expected));
}
assert_eq!(drain_next(&mut stream).await, None);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn abort_discards_the_owned_buffer() {
let broker = MemoryBroker::new();
let mut subscriber = broker.subscribe("orders");
let publisher = broker.publisher();
let mut txn = publisher.transaction().await.expect("transaction failed");
txn.publish(OutgoingMessage::new("orders", b"gone".as_slice()))
.await
.expect("publish into the transaction failed");
txn.abort().await.expect("abort failed");
let mut stream = pin!(subscriber.stream());
assert_eq!(
drain_next(&mut stream).await,
None,
"aborted publish became visible"
);
publisher
.publish(OutgoingMessage::new("orders", b"kept".as_slice()))
.await
.expect("direct publish failed");
assert_eq!(
drain_next(&mut stream).await.as_deref(),
Some(b"kept".as_slice())
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn commit_against_a_shut_down_bus_errors() {
let broker = MemoryBroker::new();
let publisher = broker.publisher();
let mut txn = publisher.transaction().await.expect("transaction failed");
txn.publish(OutgoingMessage::new("orders", b"buffered".as_slice()))
.await
.expect("publish into the transaction failed");
let connected = broker.connect().await.expect("connect failed");
connected.shutdown().await.expect("shutdown failed");
assert_eq!(txn.commit().await, Err(MemoryError::ShutDown));
}
#[cfg(feature = "json")]
mod typed {
use ruststream::codec::{Codec, DefaultCodec};
use ruststream::runtime::TypedPublisher;
use serde::{Deserialize, Serialize};
use super::*;
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Order {
id: u32,
}
fn decode_order(payload: &[u8]) -> Order {
DefaultCodec::default()
.decode(payload)
.expect("decoding a committed payload failed")
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn typed_publish_round_trips_through_the_default_codec() {
let broker = MemoryBroker::new();
let mut subscriber = broker.subscribe("orders");
let publisher = TypedPublisher::new(broker.publisher());
let mut txn = publisher.transaction().await.expect("transaction failed");
txn.publish("orders", &Order { id: 7 })
.await
.expect("typed publish into the transaction failed");
let mut stream = pin!(subscriber.stream());
assert_eq!(
drain_next(&mut stream).await,
None,
"buffered typed publish became visible before commit"
);
txn.commit().await.expect("commit failed");
let payload = drain_next(&mut stream)
.await
.expect("committed publish missing");
assert_eq!(decode_order(&payload), Order { id: 7 });
assert_eq!(drain_next(&mut stream).await, None);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn concurrent_typed_scopes_commit_independently() {
let broker = MemoryBroker::new();
let mut subscriber = broker.subscribe("orders");
let publisher = TypedPublisher::new(broker.publisher());
let mut first = publisher.transaction().await.expect("first transaction");
let mut second = publisher.transaction().await.expect("second transaction");
first
.publish("orders", &Order { id: 1 })
.await
.expect("publish into first failed");
second
.publish("orders", &Order { id: 2 })
.await
.expect("publish into second failed");
second.commit().await.expect("second commit failed");
let mut stream = pin!(subscriber.stream());
let payload = drain_next(&mut stream)
.await
.expect("second scope's publish missing");
assert_eq!(decode_order(&payload), Order { id: 2 });
assert_eq!(
drain_next(&mut stream).await,
None,
"the sibling scope leaked into the commit"
);
first.commit().await.expect("first commit failed");
let payload = drain_next(&mut stream)
.await
.expect("first scope's publish missing");
assert_eq!(decode_order(&payload), Order { id: 1 });
assert_eq!(drain_next(&mut stream).await, None);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn typed_commit_against_a_shut_down_bus_errors() {
let broker = MemoryBroker::new();
let publisher = TypedPublisher::new(broker.publisher());
let mut txn = publisher.transaction().await.expect("transaction failed");
txn.publish("orders", &Order { id: 9 })
.await
.expect("typed publish into the transaction failed");
let connected = broker.connect().await.expect("connect failed");
connected.shutdown().await.expect("shutdown failed");
assert_eq!(txn.commit().await, Err(MemoryError::ShutDown));
}
}