Files
irtt-rs/crates/irtt-integration-tests/tests/async_client.rs
T
Nils Andreas Svee 1753e91d5f 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.
2026-08-17 09:49:32 +07:00

96 lines
3.3 KiB
Rust

use std::time::Duration;
use irtt_client::{AsyncClient, ClientConfig, ClientEvent, OpenOutcome, SocketConfig};
use irtt_proto::{Clock, ReceivedStats, StampAt};
use irtt_server::{Server, ServerConfig};
use tokio::{sync::oneshot, time::timeout};
#[tokio::test(flavor = "current_thread")]
async fn async_client_opens_echoes_receives_and_closes() {
timeout(Duration::from_secs(2), exercise_client_server())
.await
.expect("client/server integration exceeded its bounded runtime");
}
async fn exercise_client_server() {
let mut server = Server::bind("127.0.0.1:0".parse().unwrap(), ServerConfig::default())
.await
.unwrap();
let server_addr = server.local_addr().unwrap();
let (shutdown_tx, shutdown_rx) = oneshot::channel();
let server_task = tokio::spawn(async move {
server
.run(async {
let _ = shutdown_rx.await;
})
.await
});
let config = ClientConfig {
server_addr: server_addr.to_string(),
duration: Some(Duration::from_secs(1)),
interval: Duration::from_millis(100),
length: 64,
received_stats: ReceivedStats::Both,
stamp_at: StampAt::Both,
clock: Clock::Both,
open_timeouts: vec![Duration::from_millis(200)],
socket_config: SocketConfig {
recv_timeout: Some(Duration::from_millis(200)),
..SocketConfig::default()
},
probe_timeout: Duration::from_millis(200),
..ClientConfig::default()
};
let mut client = AsyncClient::connect(config).await.unwrap();
let opened = client.open().await.unwrap();
match &opened {
OpenOutcome::Started {
remote,
token,
negotiated,
event,
} => {
assert_eq!(*remote, server_addr);
assert_ne!(*token, 0);
assert_eq!(negotiated.params.received_stats, ReceivedStats::Both);
assert_eq!(negotiated.params.stamp_at, StampAt::Both);
assert_eq!(negotiated.params.clock, Clock::Both);
// A default client requests no fill, and this open succeeded under
// the default strict negotiation policy — which it could not have
// if the server had answered an absent request with its own default
// descriptor. The server still fills the payload with it.
assert_eq!(negotiated.params.server_fill, None);
assert!(matches!(event, ClientEvent::SessionStarted { .. }));
}
OpenOutcome::NoTestCompleted { .. } => panic!("normal client unexpectedly ran no-test"),
}
assert!(matches!(
client.send_probe().await.unwrap().as_slice(),
[ClientEvent::EchoSent {
seq: 0,
remote,
..
}] if *remote == server_addr
));
assert!(matches!(
client.recv().await.unwrap().as_slice(),
[ClientEvent::EchoReply {
seq: 0,
remote,
server_timing: Some(_),
received_stats: Some(_),
..
}] if *remote == server_addr
));
assert!(matches!(
client.close().await.unwrap().as_slice(),
[ClientEvent::SessionClosed { remote, .. }] if *remote == server_addr
));
shutdown_tx.send(()).unwrap();
server_task.await.unwrap().unwrap();
}