irtt-client and irtt-server mutually dev-depended on each other for cross-crate integration tests. That cycle blocks `cargo publish` for either crate whenever the other doesn't already exist on the registry at the required version, which is exactly the situation for a crate's first-ever release (irtt-server has never been published before). Introduce a new `irtt-integration-tests` crate (publish = false, no library target) to own tests whose actual subject is the interaction between two or more irtt-* production crates: crate-local behavior -> crate's own tests/ cross-crate/system behavior -> irtt-integration-tests Move every test that imports both irtt_client and irtt_server there: the client-side support harness (hmac, large_packets, protocol_options, fill_negotiation) and the server-side real-socket tests that drive a real AsyncClient against a real Server (async_client, negotiation_policy, fill_wire_bytes, traffic_class, wildcard_source). server_set.rs stays in irtt-server/tests since it only uses irtt_proto as test machinery to drive irtt-server directly, which is a normal one-directional test dependency, not a peer relationship. irtt-server's dev-dependency on irtt-client is dropped entirely now that nothing under crates/irtt-server/tests needs it, which fully breaks the cycle from that side. irtt-client keeps a one-directional dev-dependency on irtt-server: a handful of white-box unit tests in src/async_client/tests.rs assert on private AsyncClient internals (socket, remote, test_hooks) while also needing a real in-tree server for blocking/async equivalence checks, so they can't move to an external crate without losing that access. Their InTreeServer helper is duplicated locally rather than shared, matching the existing precedent of irtt-cli's own tests/support/in_tree_server.rs.
75 lines
2.3 KiB
Rust
75 lines
2.3 KiB
Rust
use std::{
|
|
net::{IpAddr, Ipv4Addr, SocketAddr},
|
|
sync::mpsc,
|
|
thread::{self, JoinHandle},
|
|
time::Duration,
|
|
};
|
|
|
|
use irtt_server::{Server, ServerConfig};
|
|
use tokio::sync::oneshot;
|
|
|
|
const STARTUP_TIMEOUT: Duration = Duration::from_secs(2);
|
|
|
|
pub struct InTreeServer {
|
|
pub addr: SocketAddr,
|
|
shutdown: Option<oneshot::Sender<()>>,
|
|
done: Option<JoinHandle<Result<(), irtt_server::ServerRuntimeError>>>,
|
|
}
|
|
|
|
impl InTreeServer {
|
|
pub fn start(config: ServerConfig) -> Self {
|
|
let (startup_tx, startup_rx) = mpsc::sync_channel(1);
|
|
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
|
let done = thread::spawn(move || {
|
|
let runtime = tokio::runtime::Builder::new_current_thread()
|
|
.enable_all()
|
|
.build()
|
|
.expect("failed to build in-tree test server runtime");
|
|
runtime.block_on(async move {
|
|
let bind_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0);
|
|
let mut server = Server::bind(bind_addr, config).await?;
|
|
startup_tx
|
|
.send(server.local_addr()?)
|
|
.expect("test stopped waiting for in-tree server startup");
|
|
server
|
|
.run(async {
|
|
let _ = shutdown_rx.await;
|
|
})
|
|
.await
|
|
})
|
|
});
|
|
|
|
let addr = match startup_rx.recv_timeout(STARTUP_TIMEOUT) {
|
|
Ok(addr) => addr,
|
|
Err(error) => {
|
|
let _ = shutdown_tx.send(());
|
|
let result = done.join().expect("in-tree test server thread panicked");
|
|
panic!("in-tree test server failed before startup ({error}): {result:?}");
|
|
}
|
|
};
|
|
|
|
Self {
|
|
addr,
|
|
shutdown: Some(shutdown_tx),
|
|
done: Some(done),
|
|
}
|
|
}
|
|
|
|
fn stop_inner(&mut self) {
|
|
if let Some(shutdown) = self.shutdown.take() {
|
|
let _ = shutdown.send(());
|
|
}
|
|
if let Some(done) = self.done.take() {
|
|
done.join()
|
|
.expect("in-tree test server thread panicked")
|
|
.expect("in-tree test server stopped with an error");
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for InTreeServer {
|
|
fn drop(&mut self) {
|
|
self.stop_inner();
|
|
}
|
|
}
|