From 1753e91d5f540542e98890858b2fd3a8e4bffea9 Mon Sep 17 00:00:00 2001 From: Nils Andreas Svee Date: Mon, 17 Aug 2026 09:49:32 +0700 Subject: [PATCH 1/2] fix(tests): break the irtt-client/irtt-server dev-dependency cycle 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. --- Cargo.lock | 11 ++- Cargo.toml | 1 + .../async_client/in_tree_server_support.rs | 82 +++++++++++++++++++ crates/irtt-client/src/async_client/tests.rs | 2 +- crates/irtt-integration-tests/Cargo.toml | 12 +++ .../tests/async_client.rs | 0 .../tests/fill_negotiation.rs} | 0 .../tests/fill_wire_bytes.rs} | 0 .../tests/hmac.rs | 0 .../tests/large_packets.rs | 0 .../tests/negotiation_policy.rs | 0 .../tests/protocol_options.rs | 0 .../tests/protocol_options/backend_smoke.rs | 0 .../tests/support/in_tree_server.rs | 0 .../tests/support/mod.rs | 0 .../tests/support/real_irtt.rs | 0 .../tests/traffic_class.rs | 0 .../tests/wildcard_source.rs | 0 crates/irtt-server/Cargo.toml | 3 - 19 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 crates/irtt-client/src/async_client/in_tree_server_support.rs create mode 100644 crates/irtt-integration-tests/Cargo.toml rename crates/{irtt-server => irtt-integration-tests}/tests/async_client.rs (100%) rename crates/{irtt-client/tests/server_fill.rs => irtt-integration-tests/tests/fill_negotiation.rs} (100%) rename crates/{irtt-server/tests/server_fill.rs => irtt-integration-tests/tests/fill_wire_bytes.rs} (100%) rename crates/{irtt-client => irtt-integration-tests}/tests/hmac.rs (100%) rename crates/{irtt-client => irtt-integration-tests}/tests/large_packets.rs (100%) rename crates/{irtt-server => irtt-integration-tests}/tests/negotiation_policy.rs (100%) rename crates/{irtt-client => irtt-integration-tests}/tests/protocol_options.rs (100%) rename crates/{irtt-client => irtt-integration-tests}/tests/protocol_options/backend_smoke.rs (100%) rename crates/{irtt-client => irtt-integration-tests}/tests/support/in_tree_server.rs (100%) rename crates/{irtt-client => irtt-integration-tests}/tests/support/mod.rs (100%) rename crates/{irtt-client => irtt-integration-tests}/tests/support/real_irtt.rs (100%) rename crates/{irtt-server => irtt-integration-tests}/tests/traffic_class.rs (100%) rename crates/{irtt-server => irtt-integration-tests}/tests/wildcard_source.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 1578b01..9c4c3ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -719,6 +719,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "irtt-integration-tests" +version = "0.0.0" +dependencies = [ + "irtt-client", + "irtt-proto", + "irtt-server", + "tokio", +] + [[package]] name = "irtt-proto" version = "0.5.0" @@ -734,7 +744,6 @@ name = "irtt-server" version = "0.5.0" dependencies = [ "getrandom 0.4.3", - "irtt-client", "irtt-proto", "nix 0.31.3", "socket2", diff --git a/Cargo.toml b/Cargo.toml index d2407dc..c2d8201 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "crates/irtt-server", "crates/irtt-stats", "crates/irtt-cli", + "crates/irtt-integration-tests", ] resolver = "2" diff --git a/crates/irtt-client/src/async_client/in_tree_server_support.rs b/crates/irtt-client/src/async_client/in_tree_server_support.rs new file mode 100644 index 0000000..0375aa3 --- /dev/null +++ b/crates/irtt-client/src/async_client/in_tree_server_support.rs @@ -0,0 +1,82 @@ +//! A real `irtt-server` running in-process, for tests that need to prove +//! `AsyncClient` and the blocking `Client` are equivalent against a real peer. +//! +//! This duplicates `crates/irtt-integration-tests/tests/support/in_tree_server.rs` +//! rather than depending on it: this module backs private (`super::*`) unit +//! tests here, while the integration crate covers black-box client/server +//! behavior, and neither can depend on the other's test-only code. + +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>, + done: Option>>, +} + +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(); + } +} diff --git a/crates/irtt-client/src/async_client/tests.rs b/crates/irtt-client/src/async_client/tests.rs index 9e1278b..eb02a86 100644 --- a/crates/irtt-client/src/async_client/tests.rs +++ b/crates/irtt-client/src/async_client/tests.rs @@ -14,7 +14,7 @@ use irtt_proto::{ }; use tokio::runtime::{Builder, Runtime}; -#[path = "../../tests/support/in_tree_server.rs"] +#[path = "in_tree_server_support.rs"] mod in_tree_server; use super::*; diff --git a/crates/irtt-integration-tests/Cargo.toml b/crates/irtt-integration-tests/Cargo.toml new file mode 100644 index 0000000..198dc7f --- /dev/null +++ b/crates/irtt-integration-tests/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "irtt-integration-tests" +version = "0.0.0" +edition.workspace = true +rust-version.workspace = true +publish = false + +[dependencies] +irtt-client = { workspace = true, features = ["ancillary", "tokio"] } +irtt-server.workspace = true +irtt-proto.workspace = true +tokio = { version = "1.53.1", default-features = false, features = ["rt", "net", "time", "sync", "macros"] } diff --git a/crates/irtt-server/tests/async_client.rs b/crates/irtt-integration-tests/tests/async_client.rs similarity index 100% rename from crates/irtt-server/tests/async_client.rs rename to crates/irtt-integration-tests/tests/async_client.rs diff --git a/crates/irtt-client/tests/server_fill.rs b/crates/irtt-integration-tests/tests/fill_negotiation.rs similarity index 100% rename from crates/irtt-client/tests/server_fill.rs rename to crates/irtt-integration-tests/tests/fill_negotiation.rs diff --git a/crates/irtt-server/tests/server_fill.rs b/crates/irtt-integration-tests/tests/fill_wire_bytes.rs similarity index 100% rename from crates/irtt-server/tests/server_fill.rs rename to crates/irtt-integration-tests/tests/fill_wire_bytes.rs diff --git a/crates/irtt-client/tests/hmac.rs b/crates/irtt-integration-tests/tests/hmac.rs similarity index 100% rename from crates/irtt-client/tests/hmac.rs rename to crates/irtt-integration-tests/tests/hmac.rs diff --git a/crates/irtt-client/tests/large_packets.rs b/crates/irtt-integration-tests/tests/large_packets.rs similarity index 100% rename from crates/irtt-client/tests/large_packets.rs rename to crates/irtt-integration-tests/tests/large_packets.rs diff --git a/crates/irtt-server/tests/negotiation_policy.rs b/crates/irtt-integration-tests/tests/negotiation_policy.rs similarity index 100% rename from crates/irtt-server/tests/negotiation_policy.rs rename to crates/irtt-integration-tests/tests/negotiation_policy.rs diff --git a/crates/irtt-client/tests/protocol_options.rs b/crates/irtt-integration-tests/tests/protocol_options.rs similarity index 100% rename from crates/irtt-client/tests/protocol_options.rs rename to crates/irtt-integration-tests/tests/protocol_options.rs diff --git a/crates/irtt-client/tests/protocol_options/backend_smoke.rs b/crates/irtt-integration-tests/tests/protocol_options/backend_smoke.rs similarity index 100% rename from crates/irtt-client/tests/protocol_options/backend_smoke.rs rename to crates/irtt-integration-tests/tests/protocol_options/backend_smoke.rs diff --git a/crates/irtt-client/tests/support/in_tree_server.rs b/crates/irtt-integration-tests/tests/support/in_tree_server.rs similarity index 100% rename from crates/irtt-client/tests/support/in_tree_server.rs rename to crates/irtt-integration-tests/tests/support/in_tree_server.rs diff --git a/crates/irtt-client/tests/support/mod.rs b/crates/irtt-integration-tests/tests/support/mod.rs similarity index 100% rename from crates/irtt-client/tests/support/mod.rs rename to crates/irtt-integration-tests/tests/support/mod.rs diff --git a/crates/irtt-client/tests/support/real_irtt.rs b/crates/irtt-integration-tests/tests/support/real_irtt.rs similarity index 100% rename from crates/irtt-client/tests/support/real_irtt.rs rename to crates/irtt-integration-tests/tests/support/real_irtt.rs diff --git a/crates/irtt-server/tests/traffic_class.rs b/crates/irtt-integration-tests/tests/traffic_class.rs similarity index 100% rename from crates/irtt-server/tests/traffic_class.rs rename to crates/irtt-integration-tests/tests/traffic_class.rs diff --git a/crates/irtt-server/tests/wildcard_source.rs b/crates/irtt-integration-tests/tests/wildcard_source.rs similarity index 100% rename from crates/irtt-server/tests/wildcard_source.rs rename to crates/irtt-integration-tests/tests/wildcard_source.rs diff --git a/crates/irtt-server/Cargo.toml b/crates/irtt-server/Cargo.toml index 0cb7398..0d1027e 100644 --- a/crates/irtt-server/Cargo.toml +++ b/crates/irtt-server/Cargo.toml @@ -23,6 +23,3 @@ tokio = { version = "1.53.1", default-features = false, features = ["rt", "net", # source-selection path here; elsewhere a wildcard bind is refused. [target.'cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))'.dependencies] nix = { version = "0.31.3", default-features = false, features = ["net", "uio"] } - -[dev-dependencies] -irtt-client = { workspace = true, features = ["ancillary", "tokio"] } -- 2.54.0 From 3f1ac889895be7fab8643685d849175f112e8b4c Mon Sep 17 00:00:00 2001 From: Nils Andreas Svee Date: Mon, 17 Aug 2026 09:51:03 +0700 Subject: [PATCH 2/2] fix(deny): add a license field to irtt-integration-tests cargo-deny's licenses check requires a license expression even on an unpublished crate; add the workspace's Apache-2.0 license so the check passes rather than treating irtt-integration-tests as unlicensed. --- crates/irtt-integration-tests/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/irtt-integration-tests/Cargo.toml b/crates/irtt-integration-tests/Cargo.toml index 198dc7f..f5ca0ec 100644 --- a/crates/irtt-integration-tests/Cargo.toml +++ b/crates/irtt-integration-tests/Cargo.toml @@ -2,6 +2,7 @@ name = "irtt-integration-tests" version = "0.0.0" edition.workspace = true +license.workspace = true rust-version.workspace = true publish = false -- 2.54.0