#![cfg_attr(not(test), allow(dead_code, unused_imports))]
use ruststream::memory::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Deserialize, Serialize, Outgoing)]
struct Order {
id: u64,
}
#[derive(Debug, PartialEq, Deserialize, Serialize, Outgoing)]
#[outgoing(name = "receipts")]
struct Receipt {
order_id: u64,
}
mod start {
use std::error::Error;
use std::future::Future;
use std::pin::pin;
use std::time::Duration;
use futures::StreamExt;
use ruststream::Subscriber;
use ruststream::memory::MemorySubscriber;
use ruststream::memory::prelude::*;
use ruststream::runtime::HealthState;
use tokio::time::timeout;
use super::{Order, Receipt};
#[subscriber("orders", reply)]
async fn issue_receipt(order: &Order) -> Receipt {
Receipt { order_id: order.id }
}
fn app(broker: MemoryBroker) -> impl App {
RustStream::new(AppInfo::new("orders", "0.1.0")).with_broker(broker, |b| {
b.include(issue_receipt).out_reply(Publish);
})
}
async fn serve_forever(broker: MemoryBroker) -> Result<(), Box<dyn Error>> {
app(broker).run().await?;
Ok(())
}
async fn serve_until(
broker: MemoryBroker,
work: impl Future<Output = ()> + Send,
) -> Result<(), Box<dyn Error>> {
app(broker).run_until(work).await?;
Ok(())
}
async fn serve(
broker: MemoryBroker,
work: impl Future<Output = ()> + Send,
) -> Result<(), Box<dyn Error>> {
let running = app(broker).start().await?;
work.await;
running.shutdown().await?;
Ok(())
}
async fn stop(running: RunningApp) -> Result<(), Box<dyn Error>> {
let health = running.health();
running.shutdown().await?;
tracing::info!(state = ?health.state(), "stopped");
Ok(())
}
async fn next_receipt(receipts: &mut MemorySubscriber) -> Option<Receipt> {
let mut stream = pin!(receipts.stream());
match timeout(Duration::from_secs(5), stream.next()).await {
Ok(Some(Ok(delivery))) => serde_json::from_slice(delivery.payload()).ok(),
_ => None,
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn serve_stops_the_service_after_the_work() -> Result<(), Box<dyn Error>> {
let broker = MemoryBroker::new();
let mut receipts = broker.subscribe("receipts");
let orders = broker.publisher();
let mut receipt = None;
let work = async {
let sent = orders
.message(&Order { id: 7 })
.to("orders")
.publish()
.await;
if sent.is_ok() {
receipt = next_receipt(&mut receipts).await;
}
};
serve(broker, work).await?;
assert_eq!(receipt, Some(Receipt { order_id: 7 }));
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn start_runs_the_service_beside_the_caller() -> Result<(), Box<dyn Error>> {
let broker = MemoryBroker::new();
let mut receipts = broker.subscribe("receipts");
let orders = broker.publisher();
let running = app(broker).start().await?;
let health = running.health();
assert_eq!(health.state(), HealthState::Running);
orders
.message(&Order { id: 7 })
.to("orders")
.publish()
.await?;
assert_eq!(
next_receipt(&mut receipts).await,
Some(Receipt { order_id: 7 })
);
stop(running).await?;
assert_eq!(health.state(), HealthState::Stopped);
Ok(())
}
#[cfg(unix)]
fn raise_sigterm() -> Result<(), Box<dyn Error>> {
let status = std::process::Command::new("sh")
.arg("-c")
.arg(format!("kill -TERM {}", std::process::id()))
.status()?;
assert!(status.success(), "kill -TERM failed: {status:?}");
Ok(())
}
#[cfg(unix)]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn run_serves_until_a_termination_signal() -> Result<(), Box<dyn Error>> {
let _guard = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?;
let broker = MemoryBroker::new();
let mut receipts = broker.subscribe("receipts");
let orders = broker.publisher();
let mut run = tokio::spawn(async move {
serve_forever(broker)
.await
.map_err(|error| error.to_string())
});
let receipt = timeout(Duration::from_secs(30), async {
loop {
orders
.message(&Order { id: 7 })
.to("orders")
.publish()
.await?;
let answered = timeout(Duration::from_millis(100), next_receipt(&mut receipts));
if let Ok(Some(receipt)) = answered.await {
return Ok::<_, Box<dyn Error>>(receipt);
}
}
})
.await??;
assert_eq!(receipt, Receipt { order_id: 7 });
let stopped = timeout(Duration::from_secs(30), async {
loop {
raise_sigterm()?;
if let Ok(joined) = timeout(Duration::from_millis(50), &mut run).await {
return Ok::<_, Box<dyn Error>>(joined?);
}
}
})
.await??;
assert_eq!(stopped, Ok(()), "the signalled shutdown must be graceful");
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn run_until_stops_when_the_future_resolves() -> Result<(), Box<dyn Error>> {
let broker = MemoryBroker::new();
let mut receipts = broker.subscribe("receipts");
let orders = broker.publisher();
let mut receipt = None;
let first_receipt = async {
let sent = orders
.message(&Order { id: 7 })
.to("orders")
.publish()
.await;
if sent.is_ok() {
receipt = next_receipt(&mut receipts).await;
}
};
serve_until(broker, first_receipt).await?;
assert_eq!(receipt, Some(Receipt { order_id: 7 }));
Ok(())
}
}
fn main() {}