use std::net::SocketAddr;
use rstest::fixture;
use tokio::{net::TcpStream, sync::oneshot};
use wireframe::server::WireframeServer;
pub use wireframe_testing::TestResult;
use wireframe_testing::{TestApp, unused_listener};
#[derive(Debug)]
struct PanicServer {
addr: SocketAddr,
shutdown: Option<oneshot::Sender<()>>,
handle: tokio::task::JoinHandle<()>,
}
impl PanicServer {
#[expect(
clippy::expect_used,
reason = "panic world should fail loudly if the panic app cannot be built"
)]
async fn spawn() -> TestResult<Self> {
let factory = || {
TestApp::new()
.and_then(|app| app.on_connection_setup(|| async { panic!("boom") }))
.expect("failed to build panic app")
};
let listener = unused_listener()?;
let server = WireframeServer::new(factory)
.workers(1)
.bind_existing_listener(listener)?;
let addr = server.local_addr().ok_or("Failed to get server address")?;
let (tx_shutdown, rx_shutdown) = oneshot::channel();
let (tx_ready, rx_ready) = oneshot::channel();
let handle = tokio::spawn(async move {
if let Err(err) = server
.ready_signal(tx_ready)
.run_with_shutdown(async {
let _ = rx_shutdown.await;
})
.await
{
tracing::error!("server task failed: {err}");
}
});
rx_ready.await.map_err(|_| "Server did not signal ready")?;
Ok(Self {
addr,
shutdown: Some(tx_shutdown),
handle,
})
}
}
impl Drop for PanicServer {
fn drop(&mut self) {
use std::{thread, time::Duration};
if let Some(tx) = self.shutdown.take() {
let _ = tx.send(());
}
let timeout = Duration::from_secs(5);
let handle = self.handle.abort_handle();
thread::spawn(move || {
thread::sleep(timeout);
handle.abort();
});
}
}
#[derive(Debug, Default)]
pub struct PanicWorld {
server: Option<PanicServer>,
attempts: usize,
}
#[rustfmt::skip]
#[fixture]
pub fn panic_world() -> PanicWorld {
PanicWorld::default()
}
impl PanicWorld {
pub async fn start_panic_server(&mut self) -> TestResult {
let server = PanicServer::spawn().await?;
self.server.replace(server);
Ok(())
}
pub async fn connect_once(&mut self) -> TestResult {
let addr = self.server.as_ref().ok_or("Server not started")?.addr;
TcpStream::connect(addr).await?;
self.attempts += 1;
Ok(())
}
pub async fn verify_and_shutdown(&mut self) -> TestResult {
if self.attempts != 2 {
return Err("expected two successful connection attempts".into());
}
self.server.take();
tokio::task::yield_now().await;
Ok(())
}
}