#![cfg(unix)]
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use wcode_harness::actor::SessionActor;
use wcode_harness::agent::{Agent, AgentConfig};
use wcode_harness::event::{AgentEvent, LlmStreamEvent};
use wcode_harness::message::StopReason;
use wcode_harness::protocol::{Request, SessionId};
use wcode_harness::streamfn::{LlmOpts, LlmStream, StreamFn};
use wcode_protocol::Client;
fn agent(script: Vec<Vec<LlmStreamEvent>>) -> Agent {
let script = Arc::new(Mutex::new(VecDeque::from(script)));
let stream_fn: StreamFn = Arc::new(move |_ctx, _sys, _tools, _opts| {
let events = script.lock().unwrap().pop_front().unwrap_or_default();
Box::pin(futures::stream::iter(events)) as LlmStream
});
Agent::new(AgentConfig {
system: "sys".into(),
tools: Vec::new(),
llm: LlmOpts {
model: "m1".into(),
..LlmOpts::default()
},
stream_fn,
hooks: wcode_harness::hooks::HooksSet::default(),
session: None,
context: Vec::new(),
working_dir: std::path::PathBuf::new(),
max_turns: wcode_harness::loop_::DEFAULT_MAX_TURNS,
parallel_tools: true,
compaction: wcode_harness::compaction::CompactionPolicy::default(),
plan_mode: wcode_harness::hooks::PlanModeHandle::new(),
})
}
fn done() -> LlmStreamEvent {
LlmStreamEvent::Done {
stop_reason: StopReason::Stop,
usage: None,
}
}
fn serve_static(
sessions: Vec<(SessionId, wcode_harness::actor::SessionHandle)>,
listener: tokio::net::UnixListener,
) {
let mut sessions = sessions.into_iter();
let root = sessions.next().expect("at least one session");
let rest: Vec<_> = sessions.collect();
tokio::spawn(wcode_protocol::serve(
wcode_protocol::Registry::new(),
tokio::sync::watch::channel(rest).1,
root,
None,
listener,
));
}
fn ids_of(sessions: &[wcode_harness::protocol::SessionInfo]) -> Vec<SessionId> {
sessions.iter().map(|s| s.id.clone()).collect()
}
async fn next_message(
rx: &mut tokio::sync::broadcast::Receiver<AgentEvent>,
) -> (SessionId, String) {
loop {
let event = tokio::time::timeout(Duration::from_secs(5), rx.recv())
.await
.expect("an event")
.expect("open");
if let AgentEvent::MessageReceived { from, content } = event {
return (from, content);
}
}
}
#[tokio::test]
async fn bind_sets_owner_only_mode() {
use std::os::unix::fs::PermissionsExt;
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("wcode.sock");
let _listener = wcode_protocol::bind(&sock).await.unwrap();
let mode = std::fs::metadata(&sock).unwrap().permissions().mode();
assert_eq!(mode & 0o777, 0o600, "socket must be owner-only");
}
#[tokio::test]
async fn a_remote_client_drives_the_session() {
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("wcode.sock");
let handle = SessionActor::spawn(agent(vec![vec![
LlmStreamEvent::TextDelta("hello".into()),
done(),
]]));
let listener = wcode_protocol::bind(&sock).await.unwrap();
serve_static(
vec![(SessionId::new("test"), handle)],
listener,
);
let client = Client::connect(&sock).await.unwrap();
let mut events = client.subscribe();
let reply = client
.ask(Request::Submit { text: "hi".into() })
.await
.unwrap();
assert!(
matches!(
reply,
AgentEvent::Stopped {
stop_reason: StopReason::Stop
}
),
"{reply:?}"
);
let mut saw_text = false;
loop {
let event = tokio::time::timeout(Duration::from_secs(5), events.recv())
.await
.expect("an event")
.expect("open");
match event {
AgentEvent::MessageUpdate { message } if message.as_text() == "hello" => {
saw_text = true;
}
AgentEvent::AgentEnd => break,
_ => {}
}
}
assert!(saw_text, "the streamed text reached the remote client");
let reply = client.ask(Request::GetHistory).await.unwrap();
let AgentEvent::History { messages } = reply else {
panic!("expected a History reply, got {reply:?}");
};
assert!(
messages.iter().any(|m| m.as_text() == "hi"),
"the remote read-back carries the user turn: {messages:?}"
);
}
#[tokio::test]
async fn a_second_client_observes_the_same_session() {
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("wcode.sock");
let handle = SessionActor::spawn(agent(vec![vec![
LlmStreamEvent::TextDelta("shared".into()),
done(),
]]));
let listener = wcode_protocol::bind(&sock).await.unwrap();
serve_static(
vec![(SessionId::new("test"), handle)],
listener,
);
let a = Client::connect(&sock).await.unwrap();
let b = Client::connect(&sock).await.unwrap();
let mut a_events = a.subscribe();
let mut b_events = b.subscribe();
a.ask(Request::Submit { text: "go".into() }).await.unwrap();
for (name, rx) in [("a", &mut a_events), ("b", &mut b_events)] {
let mut saw = false;
loop {
let event = tokio::time::timeout(Duration::from_secs(5), rx.recv())
.await
.unwrap_or_else(|_| panic!("{name}: an event"))
.unwrap_or_else(|_| panic!("{name}: open"));
match event {
AgentEvent::MessageUpdate { message } if message.as_text() == "shared" => {
saw = true
}
AgentEvent::AgentEnd => break,
_ => {}
}
}
assert!(saw, "{name} observed the streamed text");
}
}
#[tokio::test]
async fn a_client_reconnects_after_the_connection_drops() {
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("wcode.sock");
let bad = wcode_protocol::bind(&sock).await.unwrap();
tokio::spawn(async move {
if let Ok((stream, _)) = bad.accept().await {
drop(stream);
}
drop(bad);
});
let client = Client::connect(&sock).await.unwrap();
let mut events = client.subscribe();
let handle = SessionActor::spawn(agent(vec![vec![
LlmStreamEvent::TextDelta("back".into()),
done(),
]]));
let listener = wcode_protocol::bind(&sock).await.unwrap();
serve_static(
vec![(SessionId::new("test"), handle)],
listener,
);
let mut reconnected = false;
for _ in 0..200 {
if client.ask(Request::GetHistory).await.is_ok() {
reconnected = true;
break;
}
tokio::time::sleep(Duration::from_millis(25)).await;
}
assert!(reconnected, "the client reconnected");
client.send(Request::Submit { text: "hi".into() }).unwrap();
let mut saw_text = false;
loop {
let event = tokio::time::timeout(Duration::from_secs(10), events.recv())
.await
.expect("the run streamed after reconnect")
.expect("open");
match event {
AgentEvent::MessageUpdate { message } if message.as_text() == "back" => saw_text = true,
AgentEvent::AgentEnd => break,
_ => {}
}
}
assert!(saw_text, "the request went through after reconnecting");
}
#[tokio::test]
async fn a_notify_crosses_the_socket() {
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("wcode.sock");
let handle = SessionActor::spawn(agent(vec![]));
let listener = wcode_protocol::bind(&sock).await.unwrap();
serve_static(
vec![(SessionId::new("test"), handle)],
listener,
);
let client = Client::connect(&sock).await.unwrap();
let mut events = client.subscribe();
let reply = client
.ask(Request::Notify {
content: "ping".into(),
})
.await
.unwrap();
assert!(matches!(reply, AgentEvent::Ack), "{reply:?}");
let mut saw_message = false;
while let Ok(Ok(event)) = tokio::time::timeout(Duration::from_secs(5), events.recv()).await {
if let AgentEvent::MessageReceived { content, .. } = event {
assert_eq!(content, "ping");
saw_message = true;
break;
}
}
assert!(saw_message, "the remote client saw the inbound message");
let reply = client.ask(Request::GetHistory).await.unwrap();
let AgentEvent::History { messages } = reply else {
panic!("expected a History reply, got {reply:?}");
};
assert!(
messages.iter().any(|m| m.as_text() == "[message from user]\nping"),
"the notify landed in the context: {messages:?}"
);
}
#[tokio::test]
async fn a_remote_peer_delivery_carries_the_sender() {
use wcode_protocol::Registry;
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("w.sock");
let w_handle = SessionActor::spawn(agent(vec![]));
let listener = wcode_protocol::bind(&sock).await.unwrap();
serve_static(
vec![(SessionId::new("w1"), w_handle)],
listener,
);
let orch = SessionId::agent("orchestrator");
let w1 = SessionId::agent("w1");
let registry = Registry::new();
let client = Client::connect(&sock).await.unwrap();
let mut events = client.subscribe();
registry.register_remote(w1.clone(), client);
registry.set_owner(w1.clone(), orch.clone());
registry
.deliver(
&orch,
&w1,
Request::Notify {
content: "hi".into(),
},
)
.unwrap();
let (from, content) = next_message(&mut events).await;
assert!(
from == orch && content == "hi",
"from={from:?} content={content:?}"
);
}
#[tokio::test]
async fn a_lazy_client_connects_once_the_peer_appears() {
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("w.sock");
let client = Client::lazy(&sock);
let mut events = client.subscribe();
client
.send_from(
SessionId::agent("orchestrator"),
Request::Notify {
content: "hi".into(),
},
)
.unwrap();
let handle = SessionActor::spawn(agent(vec![]));
let listener = wcode_protocol::bind(&sock).await.unwrap();
serve_static(
vec![(SessionId::new("w1"), handle)],
listener,
);
let (from, content) = next_message(&mut events).await;
assert!(
from.as_str() == "agent:orchestrator" && content == "hi",
"from={from:?} content={content:?}"
);
}
#[tokio::test]
async fn two_sessions_demux_over_one_socket() {
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("w.sock");
let a = SessionActor::spawn(agent(vec![vec![
LlmStreamEvent::TextDelta("from-a".into()),
done(),
]]));
let b = SessionActor::spawn(agent(vec![vec![
LlmStreamEvent::TextDelta("from-b".into()),
done(),
]]));
let sa = SessionId::agent("a");
let sb = SessionId::agent("b");
let listener = wcode_protocol::bind(&sock).await.unwrap();
serve_static(
vec![(sa.clone(), a), (sb.clone(), b)],
listener,
);
let conn = Client::connect(&sock).await.unwrap();
let va = conn.with_session(sa.clone());
let vb = conn.with_session(sb.clone());
let mut ea = va.subscribe();
let mut eb = vb.subscribe();
let ra = va.ask(Request::Submit { text: "hi-a".into() }).await.unwrap();
let rb = vb.ask(Request::Submit { text: "hi-b".into() }).await.unwrap();
assert!(
matches!(
ra,
AgentEvent::Stopped {
stop_reason: StopReason::Stop
}
),
"{ra:?}"
);
assert!(
matches!(
rb,
AgentEvent::Stopped {
stop_reason: StopReason::Stop
}
),
"{rb:?}"
);
assert!(
drains_text(&mut ea, "from-a", "from-b").await,
"a heard its own stream"
);
assert!(
drains_text(&mut eb, "from-b", "from-a").await,
"b heard its own stream"
);
}
async fn drains_text(
rx: &mut tokio::sync::broadcast::Receiver<AgentEvent>,
want: &str,
foreign: &str,
) -> bool {
let mut saw = false;
loop {
let event = tokio::time::timeout(Duration::from_secs(5), rx.recv())
.await
.expect("an event")
.expect("open");
match event {
AgentEvent::MessageUpdate { message } if message.as_text() == want => saw = true,
AgentEvent::MessageUpdate { message } if message.as_text() == foreign => {
panic!("{foreign:?} leaked onto the {want:?} subscription")
}
AgentEvent::AgentEnd => break,
_ => {}
}
}
saw
}
#[tokio::test]
async fn unknown_session_is_rejected_on_a_multi_session_server() {
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("w.sock");
let listener = wcode_protocol::bind(&sock).await.unwrap();
serve_static(
vec![
(SessionId::agent("a"), SessionActor::spawn(agent(vec![]))),
(SessionId::agent("b"), SessionActor::spawn(agent(vec![]))),
],
listener,
);
let conn = Client::connect(&sock).await.unwrap();
let ghost = conn.with_session(SessionId::agent("ghost"));
let reply = ghost.ask(Request::Submit { text: "?".into() }).await.unwrap();
match reply {
AgentEvent::Error { message } => assert!(message.contains("ghost"), "{message}"),
other => panic!("expected an Error reply, got {other:?}"),
}
}
#[tokio::test]
async fn single_session_server_routes_the_default() {
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("w.sock");
let handle = SessionActor::spawn(agent(vec![vec![
LlmStreamEvent::TextDelta("only".into()),
done(),
]]));
let listener = wcode_protocol::bind(&sock).await.unwrap();
serve_static(
vec![(SessionId::new("test"), handle)],
listener,
);
let client = Client::connect(&sock).await.unwrap();
let mut events = client.subscribe();
let reply = client.ask(Request::Submit { text: "hi".into() }).await.unwrap();
assert!(
matches!(
reply,
AgentEvent::Stopped {
stop_reason: StopReason::Stop
}
),
"{reply:?}"
);
assert!(
drains_text(&mut events, "only", "from-other").await,
"the default view heard the sole session's stream"
);
}
#[tokio::test]
async fn list_sessions_returns_the_roster() {
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("w.sock");
let root = SessionId::new("root");
let a = SessionId::agent("a");
let listener = wcode_protocol::bind(&sock).await.unwrap();
serve_static(
vec![
(root.clone(), SessionActor::spawn(agent(vec![]))),
(a.clone(), SessionActor::spawn(agent(vec![]))),
],
listener,
);
let client = Client::connect(&sock).await.unwrap();
let reply = client.ask(Request::ListSessions).await.unwrap();
let AgentEvent::Sessions { sessions } = reply else {
panic!("expected a Sessions reply, got {reply:?}");
};
assert_eq!(ids_of(&sessions), vec![root, a]);
}
#[tokio::test]
async fn a_session_registered_before_the_server_is_served() {
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("w.sock");
let registry = wcode_protocol::Registry::new();
let root_id = SessionId::new("root");
let w1 = SessionId::agent("w1");
registry.register(w1.clone(), SessionActor::spawn(agent(vec![])));
let root = SessionActor::spawn(agent(vec![]));
let listener = wcode_protocol::bind(&sock).await.unwrap();
tokio::spawn(wcode_protocol::serve(
registry.clone(),
registry.subscribe(),
(root_id.clone(), root),
None,
listener,
));
let client = Client::connect(&sock).await.unwrap();
let reply = client.ask(Request::ListSessions).await.unwrap();
let AgentEvent::Sessions { sessions } = reply else {
panic!("expected a Sessions reply, got {reply:?}");
};
assert_eq!(ids_of(&sessions), vec![root_id, w1]);
}
#[tokio::test]
async fn the_roster_carries_each_sessions_model() {
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("w.sock");
let registry = wcode_protocol::Registry::new();
let root_id = SessionId::new("root");
let root = SessionActor::spawn(agent(vec![]));
let listener = wcode_protocol::bind(&sock).await.unwrap();
tokio::spawn(wcode_protocol::serve(
registry.clone(),
registry.subscribe(),
(root_id.clone(), root),
None,
listener,
));
let w1 = SessionId::agent("w1");
let w2 = SessionId::agent("w2");
registry.set_model(root_id.clone(), "m1");
registry.register(w1.clone(), SessionActor::spawn(agent(vec![])));
registry.set_model(w1.clone(), "m2");
registry.register(w2.clone(), SessionActor::spawn(agent(vec![])));
let client = Client::connect(&sock).await.unwrap();
let reply = client.ask(Request::ListSessions).await.unwrap();
let AgentEvent::Sessions { sessions } = reply else {
panic!("expected a Sessions reply, got {reply:?}");
};
assert_eq!(ids_of(&sessions), vec![root_id.clone(), w1.clone(), w2.clone()]);
let models: Vec<Option<&str>> = sessions.iter().map(|s| s.model.as_deref()).collect();
assert_eq!(models, vec![Some("m1"), Some("m2"), None], "{sessions:?}");
}
#[tokio::test]
async fn a_registration_racing_the_connect_is_served() {
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("w.sock");
let registry = wcode_protocol::Registry::new();
let root_id = SessionId::new("root");
let root = SessionActor::spawn(agent(vec![]));
let listener = wcode_protocol::bind(&sock).await.unwrap();
tokio::spawn(wcode_protocol::serve(
registry.clone(),
registry.subscribe(),
(root_id.clone(), root),
None,
listener,
));
let client = Client::connect(&sock).await.unwrap();
let mut roster = client.subscribe_roster();
let w1 = SessionId::agent("w1");
registry.register(w1.clone(), SessionActor::spawn(agent(vec![])));
tokio::time::timeout(Duration::from_secs(5), roster.changed())
.await
.expect("a roster change")
.expect("open");
assert!(
roster.borrow().iter().any(|s| s.id == w1),
"the pushed roster names the late session: {:?}",
roster.borrow()
);
let mut events = client.with_session(w1.clone()).subscribe();
client
.with_session(w1.clone())
.send(Request::Notify {
content: "hi".into(),
})
.unwrap();
let mut saw = false;
while let Ok(Ok(event)) = tokio::time::timeout(Duration::from_secs(5), events.recv()).await {
if let AgentEvent::MessageReceived { content, .. } = event {
assert_eq!(content, "hi");
saw = true;
break;
}
}
assert!(saw, "an event from the late session reached the client");
}
#[tokio::test]
async fn list_sessions_reads_the_live_roster() {
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("w.sock");
let registry = wcode_protocol::Registry::new();
let root_id = SessionId::new("root");
let root = SessionActor::spawn(agent(vec![]));
let listener = wcode_protocol::bind(&sock).await.unwrap();
tokio::spawn(wcode_protocol::serve(
registry.clone(),
registry.subscribe(),
(root_id.clone(), root),
None,
listener,
));
let client = Client::connect(&sock).await.unwrap();
let w1 = SessionId::agent("w1");
registry.register(w1.clone(), SessionActor::spawn(agent(vec![])));
let mut roster = client.subscribe_roster();
tokio::time::timeout(Duration::from_secs(5), roster.changed())
.await
.expect("a roster change")
.expect("open");
let reply = client.ask(Request::ListSessions).await.unwrap();
let AgentEvent::Sessions { sessions } = reply else {
panic!("expected a Sessions reply, got {reply:?}");
};
assert_eq!(ids_of(&sessions), vec![root_id, w1]);
}
#[tokio::test]
async fn a_growth_push_reaches_an_established_client() {
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("w.sock");
let registry = wcode_protocol::Registry::new();
let root_id = SessionId::new("root");
let root = SessionActor::spawn(agent(vec![]));
let listener = wcode_protocol::bind(&sock).await.unwrap();
tokio::spawn(wcode_protocol::serve(
registry.clone(),
registry.subscribe(),
(root_id.clone(), root),
None,
listener,
));
let client = Client::connect(&sock).await.unwrap();
client.ask(Request::ListSessions).await.unwrap();
let mut roster = client.subscribe_roster();
let w1 = SessionId::agent("w1");
registry.register(w1.clone(), SessionActor::spawn(agent(vec![])));
let sessions = loop {
let sessions = roster.borrow_and_update().clone();
if sessions.iter().any(|s| s.id == w1) {
break sessions;
}
tokio::time::timeout(Duration::from_secs(5), roster.changed())
.await
.expect("a roster change")
.expect("open");
};
assert_eq!(ids_of(&sessions), vec![root_id.clone(), w1.clone()]);
let mut events = client.with_session(w1.clone()).subscribe();
client
.with_session(w1.clone())
.send(Request::Notify {
content: "hi".into(),
})
.unwrap();
let mut saw = false;
while let Ok(Ok(event)) = tokio::time::timeout(Duration::from_secs(5), events.recv()).await {
if let AgentEvent::MessageReceived { content, .. } = event {
assert_eq!(content, "hi");
saw = true;
break;
}
}
assert!(saw, "an event from the grown session reached the client");
}
#[tokio::test]
async fn a_define_request_spawns_on_a_served_peer() {
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("w.sock");
let registry = wcode_protocol::Registry::new();
let root_id = SessionId::new("root");
let root = SessionActor::spawn(agent(vec![]));
let spawn_registry = registry.clone();
let handler: wcode_protocol::DefineHandler = Arc::new(move |args| {
let name = args.name.unwrap_or_else(|| "w1".into());
let id = SessionId::agent(name);
spawn_registry.register(id.clone(), SessionActor::spawn(agent(vec![])));
Ok(id)
});
let listener = wcode_protocol::bind(&sock).await.unwrap();
tokio::spawn(wcode_protocol::serve(
registry.clone(),
registry.subscribe(),
(root_id.clone(), root),
Some(handler),
listener,
));
let client = Client::connect(&sock).await.unwrap();
let mut roster = client.subscribe_roster();
client.ask(Request::ListSessions).await.unwrap();
let reply = client
.ask(Request::Define {
name: Some("w1".into()),
model: Some("m".into()),
role: None,
tools: None,
base_url: None,
api_key: None,
read_only: false,
effort: None,
})
.await
.unwrap();
let AgentEvent::Spawned { worker: id } = reply else {
panic!("expected a Spawned reply, got {reply:?}");
};
assert_eq!(id, SessionId::agent("w1"));
let sessions = loop {
let sessions = roster.borrow_and_update().clone();
if sessions.iter().any(|s| s.id == id) {
break sessions;
}
tokio::time::timeout(Duration::from_secs(5), roster.changed())
.await
.expect("a roster change")
.expect("open");
};
assert_eq!(ids_of(&sessions), vec![root_id.clone(), id.clone()]);
}
#[tokio::test]
async fn define_without_a_handler_is_a_correlated_error() {
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("w.sock");
let root = SessionActor::spawn(agent(vec![]));
let listener = wcode_protocol::bind(&sock).await.unwrap();
tokio::spawn(wcode_protocol::serve(
wcode_protocol::Registry::new(),
tokio::sync::watch::channel(Vec::new()).1,
(SessionId::new("root"), root),
None,
listener,
));
let client = Client::connect(&sock).await.unwrap();
let reply = client
.ask(Request::Define {
name: Some("w1".into()),
model: None,
role: None,
tools: None,
base_url: None,
api_key: None,
read_only: false,
effort: None,
})
.await
.unwrap();
match reply {
AgentEvent::Error { message } => assert!(message.contains("not enabled"), "{message}"),
other => panic!("expected an Error reply, got {other:?}"),
}
}
#[tokio::test]
async fn a_flush_returns_promptly_when_connected() {
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("w.sock");
let handle = SessionActor::spawn(agent(vec![]));
let listener = wcode_protocol::bind(&sock).await.unwrap();
tokio::spawn(wcode_protocol::serve(
wcode_protocol::Registry::new(),
tokio::sync::watch::channel(Vec::new()).1,
(SessionId::new("test"), handle),
None,
listener,
));
let client = Client::connect(&sock).await.unwrap();
let mut events = client.subscribe();
client
.send(Request::Notify {
content: "hi".into(),
})
.unwrap();
let start = std::time::Instant::now();
client.flush(Duration::from_secs(2)).await;
let elapsed = start.elapsed();
assert!(
elapsed < Duration::from_secs(1),
"flush returned promptly when connected: {elapsed:?}"
);
let (_, content) = next_message(&mut events).await;
assert_eq!(content, "hi");
}
#[tokio::test]
async fn flush_is_bounded_when_the_peer_is_unreachable() {
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("nobody.sock");
let client = Client::lazy(&sock);
let timeout = Duration::from_millis(300);
let start = std::time::Instant::now();
client.flush(timeout).await;
let elapsed = start.elapsed();
assert!(
elapsed >= timeout / 2,
"flush waited for the barrier: {elapsed:?}"
);
assert!(
elapsed < Duration::from_secs(2),
"flush gave up within the timeout rather than hanging: {elapsed:?}"
);
}
#[tokio::test]
async fn a_flush_waits_behind_an_unwritten_frame() {
use tokio::io::AsyncReadExt as _;
let dir = tempfile::tempdir().unwrap();
let sock = dir.path().join("w.sock");
let listener = wcode_protocol::bind(&sock).await.unwrap();
let (release_tx, release_rx) = tokio::sync::oneshot::channel::<()>();
tokio::spawn(async move {
let (mut stream, _) = listener.accept().await.unwrap();
let _ = release_rx.await;
let mut buf = [0u8; 8192];
while matches!(stream.read(&mut buf).await, Ok(n) if n > 0) {}
});
let client = Client::connect(&sock).await.unwrap();
client
.send(Request::Notify {
content: "x".repeat(8 << 20),
})
.unwrap();
let timeout = Duration::from_millis(150);
let start = std::time::Instant::now();
client.flush(timeout).await;
let elapsed = start.elapsed();
assert!(
elapsed >= timeout / 2,
"flush waited behind the unwritten frame: {elapsed:?}"
);
let _ = release_tx.send(());
let start = std::time::Instant::now();
client.flush(Duration::from_secs(2)).await;
assert!(
start.elapsed() < Duration::from_secs(1),
"flush completed once the reader drained"
);
}