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.
96 lines
3.3 KiB
Rust
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();
|
|
}
|