#![cfg(unix)]
use std::io::{self, BufRead, BufReader, Read, Write};
use std::net::Shutdown;
use std::ops::{Deref, DerefMut};
use std::os::unix::fs::PermissionsExt;
use std::os::unix::net::UnixStream;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc;
use std::thread;
use std::time::{Duration, Instant};
use taria::wire::{AppToBridge, BridgeToApp, InputId, InputStatus};
use taria::{AgentInput, Node, PROTOCOL_VERSION, Role, Snapshot};
use taria_ratatui::TariaLayer;
const TIMEOUT: Duration = Duration::from_secs(5);
struct TestLayer {
layer: TariaLayer,
_dir: tempfile::TempDir,
}
impl Deref for TestLayer {
type Target = TariaLayer;
fn deref(&self) -> &TariaLayer {
&self.layer
}
}
impl DerefMut for TestLayer {
fn deref_mut(&mut self) -> &mut TariaLayer {
&mut self.layer
}
}
fn bind_layer(label: &str) -> TestLayer {
let dir = tempfile::Builder::new()
.prefix("taria-it-")
.permissions(std::fs::Permissions::from_mode(0o700))
.tempdir()
.expect("create test socket dir");
let layer = TariaLayer::bind_at(label, dir.path().join(format!("{label}.sock"))).unwrap();
TestLayer { layer, _dir: dir }
}
struct Client {
reader: BufReader<UnixStream>,
writer: UnixStream,
}
impl Client {
fn connect(layer: &TariaLayer) -> Client {
let stream = UnixStream::connect(layer.socket_path()).unwrap();
stream.set_read_timeout(Some(TIMEOUT)).unwrap();
let writer = stream.try_clone().unwrap();
Client {
reader: BufReader::new(stream),
writer,
}
}
fn read_message(&mut self) -> AppToBridge {
let mut line = String::new();
let n = self.reader.read_line(&mut line).unwrap();
assert!(n > 0, "server closed the connection unexpectedly");
serde_json::from_str(&line).unwrap()
}
fn write_line(&mut self, line: &str) {
self.writer.write_all(line.as_bytes()).unwrap();
self.writer.write_all(b"\n").unwrap();
}
fn send(&mut self, msg: &BridgeToApp) {
let line = serde_json::to_string(msg).unwrap();
self.write_line(&line);
}
fn send_input(&mut self, id: u64, input: AgentInput) {
self.send(&BridgeToApp::input(InputId::new(id), input));
}
}
fn write_input(stream: &mut UnixStream, id: u64, input: AgentInput) {
let mut line = serde_json::to_string(&BridgeToApp::input(InputId::new(id), input)).unwrap();
line.push('\n');
stream.write_all(line.as_bytes()).unwrap();
}
fn key(name: &str) -> AgentInput {
AgentInput::key(name)
}
fn ack(id: u64, status: InputStatus) -> AppToBridge {
AppToBridge::ack(InputId::new(id), status)
}
fn poll_try_recv(layer: &TariaLayer) -> Option<(InputId, AgentInput)> {
let deadline = Instant::now() + TIMEOUT;
while Instant::now() < deadline {
if let Some(pair) = layer.try_recv() {
return Some(pair);
}
thread::sleep(Duration::from_millis(5));
}
None
}
fn poll_input(layer: &TariaLayer) -> Option<AgentInput> {
poll_try_recv(layer).map(|(_, input)| input)
}
fn publish_single_node(layer: &mut TariaLayer, id: &str) {
let mut rec = layer.frame();
rec.push(Node::new(id, Role::Text));
rec.publish();
}
const DROP_BOUND: Duration = Duration::from_millis(50 + 5_000 + 250);
const DROP_MARGIN: Duration = Duration::from_millis(1_000);
fn time_drop(layer: TariaLayer) -> Duration {
let (tx, rx) = mpsc::channel();
let started = Instant::now();
thread::spawn(move || {
drop(layer);
let _ = tx.send(started.elapsed());
});
let limit = DROP_BOUND + DROP_MARGIN;
rx.recv_timeout(limit)
.unwrap_or_else(|_| panic!("dropping the layer took longer than {limit:?}"))
}
fn publish_900_kb(layer: &mut TariaLayer) {
layer.publish([Node::new("draft", Role::TextInput).value("d".repeat(900 * 1024))]);
}
#[test]
fn hello_then_snapshots_stream_to_client() {
let mut layer = bind_layer("stream");
let mut client = Client::connect(&layer);
let hello = client.read_message();
assert_eq!(
hello,
AppToBridge::hello("stream", PROTOCOL_VERSION).stamps(true)
);
publish_single_node(&mut layer, "first");
let AppToBridge::Snapshot(snapshot) = client.read_message() else {
panic!("expected a snapshot after publish");
};
assert_eq!(snapshot.seq, 1);
assert_eq!(snapshot.root.children[0].id.0, "first");
publish_single_node(&mut layer, "second");
let AppToBridge::Snapshot(snapshot) = client.read_message() else {
panic!("expected a second snapshot");
};
assert_eq!(snapshot.seq, 2);
assert_eq!(snapshot.root.children[0].id.0, "second");
}
#[test]
fn latest_snapshot_is_replayed_on_connect() {
let mut layer = bind_layer("replay");
publish_single_node(&mut layer, "pre");
let mut client = Client::connect(&layer);
assert!(matches!(client.read_message(), AppToBridge::Hello { .. }));
let AppToBridge::Snapshot(snapshot) = client.read_message() else {
panic!("expected the latest snapshot right after hello");
};
assert_eq!(snapshot.root.children[0].id.0, "pre");
}
#[test]
fn an_oversized_tree_is_replayed_as_a_line_the_bridge_can_read() {
use taria::wire::MAX_LINE_BYTES;
let mut layer = bind_layer("oversized");
let draft = "d".repeat(MAX_LINE_BYTES + 1);
let frame = || {
[Node::new("draft", Role::TextInput)
.value(draft.clone())
.focused(true)]
};
layer.publish(frame());
let mut client = Client::connect(&layer);
assert!(matches!(client.read_message(), AppToBridge::Hello { .. }));
let mut line = String::new();
client.reader.read_line(&mut line).unwrap();
let content = line
.strip_suffix('\n')
.expect("the snapshot is one whole line");
assert!(
content.len() <= MAX_LINE_BYTES,
"{} bytes, over the {MAX_LINE_BYTES} a bridge reads",
content.len()
);
let AppToBridge::Snapshot(snapshot) = serde_json::from_str(content).unwrap() else {
panic!("expected the latest snapshot right after hello");
};
let value = snapshot.root.children[0].value.as_deref().unwrap();
assert!(value.ends_with('\u{2026}'), "the cut is marked");
assert!(draft.starts_with(value.trim_end_matches('\u{2026}')));
assert_eq!(layer.stats().oversized_snapshots, 1);
client.send_input(1, key("q"));
assert_eq!(poll_input(&layer), Some(key("q")));
assert_eq!(client.read_message(), ack(1, InputStatus::Delivered));
for _ in 0..3 {
layer.publish(frame());
}
let AppToBridge::Snapshot(restamped) = client.read_message() else {
panic!("expected the tree again, stamped");
};
assert_eq!(restamped.seq, 1, "a stamp moving is not a new snapshot");
assert_eq!(restamped.after_input, Some(InputId::new(1)));
assert_eq!(
restamped.root, snapshot.root,
"the tree is the one already cut"
);
assert_eq!(
layer.stats().oversized_snapshots,
1,
"the resend is not cut again"
);
layer.publish([Node::new("draft", Role::TextInput)
.value(format!("x{draft}"))
.focused(true)]);
let AppToBridge::Snapshot(snapshot) = client.read_message() else {
panic!("expected the changed tree");
};
assert_eq!(snapshot.seq, 2, "the redraws published nothing");
assert_eq!(snapshot.after_input, Some(InputId::new(1)));
let value = snapshot.root.children[0].value.as_deref().unwrap();
assert!(value.starts_with('x') && value.ends_with('\u{2026}'));
assert_eq!(layer.stats().oversized_snapshots, 2);
}
#[test]
fn inputs_reach_the_app_acked_delivered_and_malformed_lines_are_ignored() {
let layer = bind_layer("input");
let mut client = Client::connect(&layer);
assert!(matches!(client.read_message(), AppToBridge::Hello { .. }));
assert_eq!(layer.try_recv(), None);
client.write_line("not json at all");
client.write_line(r#"{"type":"unknown"}"#);
let sent = key("q");
client.send_input(1, sent.clone());
assert_eq!(poll_input(&layer), Some(sent));
assert_eq!(client.read_message(), ack(1, InputStatus::Delivered));
let act = AgentInput::act(taria::NodeId("btn".into()), taria::Action::Activate, None);
client.send_input(2, act.clone());
assert_eq!(layer.recv_timeout(TIMEOUT), Some((InputId::new(2), act)));
assert_eq!(client.read_message(), ack(2, InputStatus::Delivered));
}
#[test]
fn an_input_kind_this_build_cannot_read_is_acked_ignored_without_reaching_the_app() {
let layer = bind_layer("unknowninput");
let mut client = Client::connect(&layer);
assert!(matches!(client.read_message(), AppToBridge::Hello { .. }));
client.write_line(r#"{"type":"input","id":41,"input":{"kind":"paste","text":"hi"}}"#);
assert_eq!(client.read_message(), ack(41, InputStatus::Ignored));
assert_eq!(
poll_input(&layer),
None,
"an unreadable input must not be queued for the app"
);
assert_eq!(layer.stats().unknown_inputs, 1);
assert_eq!(
layer.stats().dropped_inputs,
0,
"it was answered, not dropped for want of queue space"
);
let sent = key("q");
client.send_input(42, sent.clone());
assert_eq!(poll_input(&layer), Some(sent));
assert_eq!(client.read_message(), ack(42, InputStatus::Delivered));
assert_eq!(layer.stats().unknown_inputs, 1);
}
#[test]
fn recv_timeout_survives_a_timeout_no_clock_can_hold() {
let layer = bind_layer("hugetimeout");
let mut client = Client::connect(&layer);
assert!(matches!(client.read_message(), AppToBridge::Hello { .. }));
client.send_input(1, key("j"));
assert_eq!(
layer.recv_timeout(Duration::MAX),
Some((InputId::new(1), key("j")))
);
assert_eq!(client.read_message(), ack(1, InputStatus::Delivered));
}
#[test]
fn ack_can_be_refined_to_ignored() {
let layer = bind_layer("ackignored");
let mut client = Client::connect(&layer);
assert!(matches!(client.read_message(), AppToBridge::Hello { .. }));
client.send_input(9, key("q"));
let (id, input) = poll_try_recv(&layer).expect("input should reach the app");
assert_eq!((id, input), (InputId::new(9), key("q")));
assert_eq!(client.read_message(), ack(9, InputStatus::Delivered));
layer.ack(id, InputStatus::Ignored);
assert_eq!(client.read_message(), ack(9, InputStatus::Ignored));
}
#[test]
fn a_late_ack_never_answers_the_next_bridge() {
let layer = bind_layer("lateack");
let mut first = Client::connect(&layer);
assert!(matches!(first.read_message(), AppToBridge::Hello { .. }));
first.send_input(5, key("q"));
let (id, _) = poll_try_recv(&layer).expect("the input reaches the app");
assert_eq!(id, InputId::new(5));
assert_eq!(first.read_message(), ack(5, InputStatus::Delivered));
drop(first);
let mut second = Client::connect(&layer);
assert!(matches!(second.read_message(), AppToBridge::Hello { .. }));
layer.ack(InputId::new(5), InputStatus::Ignored);
second
.reader
.get_ref()
.set_read_timeout(Some(Duration::from_millis(250)))
.unwrap();
let mut line = String::new();
let err = second
.reader
.read_line(&mut line)
.expect_err("the new bridge must hear nothing about an input it never sent");
assert!(
matches!(
err.kind(),
io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut
),
"expected the read to time out, got {err:?} with {line:?}"
);
second
.reader
.get_ref()
.set_read_timeout(Some(TIMEOUT))
.unwrap();
second.send_input(5, key("j"));
let (id, input) = poll_try_recv(&layer).expect("the new bridge's input");
assert_eq!((id, input), (InputId::new(5), key("j")));
assert_eq!(second.read_message(), ack(5, InputStatus::Delivered));
layer.ack(InputId::new(5), InputStatus::Ignored);
assert_eq!(second.read_message(), ack(5, InputStatus::Ignored));
}
#[test]
fn ack_precedes_the_snapshot_published_after_it() {
let mut layer = bind_layer("ackorder");
let mut client = Client::connect(&layer);
assert!(matches!(client.read_message(), AppToBridge::Hello { .. }));
client.send_input(3, key("j"));
assert_eq!(poll_input(&layer), Some(key("j")));
publish_single_node(&mut layer, "moved");
assert_eq!(client.read_message(), ack(3, InputStatus::Delivered));
let AppToBridge::Snapshot(snapshot) = client.read_message() else {
panic!("expected the snapshot after the ack");
};
assert_eq!(snapshot.root.children[0].id.0, "moved");
}
#[test]
fn a_try_recv_loop_hands_over_every_queued_input_and_acks_each() {
let layer = bind_layer("recvloop");
let mut client = Client::connect(&layer);
assert!(matches!(client.read_message(), AppToBridge::Hello { .. }));
for id in 0..3 {
client.send_input(id, key(&format!("k{id}")));
}
let mut got = Vec::new();
let deadline = Instant::now() + TIMEOUT;
while got.len() < 3 && Instant::now() < deadline {
while let Some((id, input)) = layer.try_recv() {
got.push((id.get(), input));
}
thread::sleep(Duration::from_millis(5));
}
assert_eq!(
got,
vec![(0, key("k0")), (1, key("k1")), (2, key("k2"))],
"the loop must hand over every queued input, in order"
);
for id in 0..3 {
assert_eq!(client.read_message(), ack(id, InputStatus::Delivered));
}
}
#[test]
fn drain_sends_the_refinements_and_nothing_else() {
let mut layer = bind_layer("drain");
let mut client = Client::connect(&layer);
assert!(matches!(client.read_message(), AppToBridge::Hello { .. }));
for id in 0..3 {
client.send_input(id, key(&format!("k{id}")));
}
let mut got = Vec::new();
let deadline = Instant::now() + TIMEOUT;
while got.len() < 3 && Instant::now() < deadline {
layer.drain(|input| {
let status = if input == key("k1") {
InputStatus::Ignored
} else {
InputStatus::Delivered
};
got.push(input);
status
});
thread::sleep(Duration::from_millis(5));
}
assert_eq!(got, vec![key("k0"), key("k1"), key("k2")]);
publish_single_node(&mut layer, "after");
assert_eq!(client.read_message(), ack(0, InputStatus::Delivered));
assert_eq!(client.read_message(), ack(1, InputStatus::Delivered));
assert_eq!(
client.read_message(),
ack(1, InputStatus::Ignored),
"the refinement follows its own input's dequeue ack, before the next input's"
);
assert_eq!(client.read_message(), ack(2, InputStatus::Delivered));
let AppToBridge::Snapshot(snapshot) = client.read_message() else {
panic!("a returned Delivered must not send a second ack ahead of the snapshot");
};
assert_eq!(snapshot.root.children[0].id.0, "after");
}
#[test]
fn client_can_reconnect_after_disconnect() {
let mut layer = bind_layer("reconnect");
publish_single_node(&mut layer, "n");
let mut first = Client::connect(&layer);
assert!(matches!(first.read_message(), AppToBridge::Hello { .. }));
assert!(matches!(first.read_message(), AppToBridge::Snapshot(_)));
drop(first);
let mut second = Client::connect(&layer);
assert!(matches!(second.read_message(), AppToBridge::Hello { .. }));
assert!(matches!(second.read_message(), AppToBridge::Snapshot(_)));
second.send_input(1, key("esc"));
assert_eq!(
layer.recv_timeout(TIMEOUT),
Some((InputId::new(1), key("esc")))
);
assert_eq!(second.read_message(), ack(1, InputStatus::Delivered));
}
#[test]
fn oversized_line_disconnects_the_client() {
let layer = bind_layer("linecap");
let mut client = Client::connect(&layer);
assert!(matches!(client.read_message(), AppToBridge::Hello { .. }));
client.writer.set_write_timeout(Some(TIMEOUT)).unwrap();
let chunk = [b'a'; 64 * 1024];
let mut disconnected_while_writing = false;
for _ in 0..48 {
if client.writer.write_all(&chunk).is_err() {
disconnected_while_writing = true;
break;
}
}
if !disconnected_while_writing {
let mut line = String::new();
match client.reader.read_line(&mut line) {
Ok(0) => {}
Ok(n) => panic!("expected disconnect, read {n} bytes"),
Err(err)
if err.kind() == io::ErrorKind::WouldBlock
|| err.kind() == io::ErrorKind::TimedOut =>
{
panic!("server kept the connection open after an oversized line")
}
Err(_) => {} }
}
let mut second = Client::connect(&layer);
assert!(matches!(second.read_message(), AppToBridge::Hello { .. }));
}
#[test]
fn input_flood_is_bounded_acked_dropped_and_never_blocks_the_socket_thread() {
const FLOOD: u64 = 2000;
const QUEUE: u64 = 256;
let mut layer = bind_layer("inputflood");
assert_eq!(layer.stats().dropped_inputs, 0, "no drops before any flood");
let stream = UnixStream::connect(layer.socket_path()).unwrap();
stream.set_read_timeout(Some(TIMEOUT)).unwrap();
let mut writer = stream.try_clone().unwrap();
let mut reader = BufReader::new(stream);
let mut hello = String::new();
reader.read_line(&mut hello).unwrap();
assert!(
matches!(
serde_json::from_str::<AppToBridge>(&hello).unwrap(),
AppToBridge::Hello { .. }
),
"the handshake is always first"
);
let (alive_tx, alive_rx) = mpsc::channel();
let collector = thread::spawn(move || {
let mut messages = Vec::new();
let mut line = String::new();
loop {
line.clear();
match reader.read_line(&mut line) {
Ok(0) => break, Ok(_) => {
let msg = serde_json::from_str::<AppToBridge>(&line).unwrap();
if matches!(
&msg,
AppToBridge::Snapshot(snapshot) if snapshot.root.children[0].id.0 == "alive"
) {
let _ = alive_tx.send(());
}
messages.push(msg);
}
Err(err) => panic!("client read failed: {err}"),
}
}
messages
});
for id in 0..FLOOD {
write_input(&mut writer, id, key(&format!("k{id}")));
}
publish_single_node(&mut layer, "alive");
assert!(
alive_rx.recv_timeout(TIMEOUT).is_ok(),
"a publish mid-flood must still reach the client"
);
writer.shutdown(Shutdown::Write).unwrap();
let messages = collector.join().expect("collector thread");
let mut received = 0;
while layer.try_recv().is_some() {
received += 1;
}
assert_eq!(
received, 0,
"inputs queued on a dead connection must never be handed to the app"
);
assert_eq!(
layer.stats().stale_inputs,
QUEUE,
"the queue held {QUEUE} inputs, and each discard must be counted once"
);
assert_eq!(
layer.stats().dropped_inputs,
FLOOD - QUEUE,
"each dropped input must be counted exactly once"
);
let dropped: Vec<InputId> = messages
.iter()
.filter_map(|msg| match msg {
AppToBridge::Ack {
id,
status: InputStatus::Dropped,
..
} => Some(*id),
_ => None,
})
.collect();
assert!(!dropped.is_empty(), "dropped inputs must be acked dropped");
assert!(
dropped.len() as u64 <= FLOOD - QUEUE,
"more drops acked ({}) than dropped",
dropped.len()
);
assert!(
dropped.iter().all(|id| id.get() < FLOOD),
"a dropped ack must name an input the client actually sent"
);
let other_acks = messages
.iter()
.filter(
|msg| matches!(msg, AppToBridge::Ack { status, .. } if *status != InputStatus::Dropped),
)
.count();
assert_eq!(other_acks, 0, "the only acks during a flood are drops");
let mut second = Client::connect(&layer);
assert!(matches!(second.read_message(), AppToBridge::Hello { .. }));
assert!(matches!(second.read_message(), AppToBridge::Snapshot(_)));
second.send_input(0, key("after"));
assert_eq!(
layer.recv_timeout(TIMEOUT),
Some((InputId::new(0), key("after")))
);
assert_eq!(second.read_message(), ack(0, InputStatus::Delivered));
assert_eq!(layer.stats().dropped_inputs, FLOOD - QUEUE);
assert_eq!(layer.stats().stale_inputs, QUEUE);
}
#[test]
fn inputs_from_a_dead_connection_are_discarded_and_the_next_ones_are_not() {
let layer = bind_layer("staleinput");
let mut client = Client::connect(&layer);
assert!(matches!(client.read_message(), AppToBridge::Hello { .. }));
client.send_input(1, key("q"));
client.writer.shutdown(Shutdown::Write).unwrap();
let mut line = String::new();
loop {
line.clear();
match client.reader.read_line(&mut line) {
Ok(0) => break,
Ok(_) => {}
Err(err) => panic!("client read failed: {err}"),
}
}
const WAIT: Duration = Duration::from_millis(150);
let started = Instant::now();
assert_eq!(
layer.recv_timeout(WAIT),
None,
"an input from a dead connection must never reach the app"
);
let waited = started.elapsed();
assert!(
waited >= WAIT,
"recv_timeout returned after {waited:?}, short of its {WAIT:?} timeout"
);
assert_eq!(layer.stats().stale_inputs, 1, "the discard must be counted");
let mut second = Client::connect(&layer);
assert!(matches!(second.read_message(), AppToBridge::Hello { .. }));
second.send_input(1, key("j"));
assert_eq!(
layer.recv_timeout(TIMEOUT),
Some((InputId::new(1), key("j")))
);
assert_eq!(second.read_message(), ack(1, InputStatus::Delivered));
assert_eq!(
layer.stats().stale_inputs,
1,
"a live input is not a stale one"
);
}
#[test]
fn a_second_client_waits_until_the_first_is_gone() {
let mut layer = bind_layer("oneatatime");
let mut first = Client::connect(&layer);
assert!(matches!(first.read_message(), AppToBridge::Hello { .. }));
let mut second = Client::connect(&layer);
second
.reader
.get_ref()
.set_read_timeout(Some(Duration::from_millis(250)))
.unwrap();
publish_single_node(&mut layer, "while-first");
let AppToBridge::Snapshot(snapshot) = first.read_message() else {
panic!("the served client must still receive publishes");
};
assert_eq!(snapshot.root.children[0].id.0, "while-first");
let mut line = String::new();
let err = second
.reader
.read_line(&mut line)
.expect_err("a waiting client must receive nothing at all");
assert!(
matches!(
err.kind(),
io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut
),
"expected the read to time out, got {err:?} with {line:?}"
);
drop(first);
second
.reader
.get_ref()
.set_read_timeout(Some(TIMEOUT))
.unwrap();
assert_eq!(
second.read_message(),
AppToBridge::hello("oneatatime", PROTOCOL_VERSION).stamps(true),
"the waiting client must be served in full once its turn comes"
);
let AppToBridge::Snapshot(snapshot) = second.read_message() else {
panic!("the newly served client must get the latest snapshot");
};
assert_eq!(
snapshot.root.children[0].id.0, "while-first",
"the tree published while it waited is the one it starts from"
);
second.send_input(1, key("j"));
assert_eq!(
layer.recv_timeout(TIMEOUT),
Some((InputId::new(1), key("j")))
);
assert_eq!(second.read_message(), ack(1, InputStatus::Delivered));
}
#[test]
fn input_a_waiting_client_wrote_before_its_hello_never_reaches_the_app() {
let mut layer = bind_layer("prehello");
publish_single_node(&mut layer, "tree");
let mut first = Client::connect(&layer);
assert!(matches!(first.read_message(), AppToBridge::Hello { .. }));
assert!(matches!(first.read_message(), AppToBridge::Snapshot(_)));
let mut second = Client::connect(&layer);
second.send_input(1, key("q"));
second.writer.write_all(b" ").unwrap();
let mut third = Client::connect(&layer);
third.send_input(1, key("d"));
drop(first);
assert_eq!(
second.read_message(),
AppToBridge::hello("prehello", PROTOCOL_VERSION).stamps(true)
);
assert!(matches!(second.read_message(), AppToBridge::Snapshot(_)));
second.send_input(2, key("y"));
second.send_input(3, key("j"));
assert_eq!(
layer.recv_timeout(TIMEOUT),
Some((InputId::new(3), key("j"))),
"neither the blind input nor the rest of the cut line may reach the app"
);
assert_eq!(
second.read_message(),
ack(3, InputStatus::Delivered),
"nothing is acked for the blind input or the cut line: the first ack \
of the session answers the first input written after its hello"
);
assert_eq!(layer.try_recv(), None, "nothing else was queued");
assert_eq!(
layer.stats().stale_inputs,
1,
"the whole blind input is counted once; the cut line is not counted"
);
assert_eq!(layer.stats().unknown_inputs, 0);
assert_eq!(layer.stats().dropped_inputs, 0);
drop(second);
assert_eq!(
third.read_message(),
AppToBridge::hello("prehello", PROTOCOL_VERSION).stamps(true)
);
assert!(matches!(third.read_message(), AppToBridge::Snapshot(_)));
third.send_input(2, key("k"));
assert_eq!(
layer.recv_timeout(TIMEOUT),
Some((InputId::new(2), key("k"))),
"a client served straight after another is drained like it"
);
assert_eq!(third.read_message(), ack(2, InputStatus::Delivered));
assert_eq!(layer.try_recv(), None);
assert_eq!(
layer.stats().stale_inputs,
2,
"the third's blind input is counted"
);
}
#[test]
fn a_lone_client_that_writes_before_reading_its_hello_keeps_its_input() {
let layer = bind_layer("blindlone");
let mut client = Client::connect(&layer);
client.send_input(1, key("j"));
assert_eq!(
client.read_message(),
AppToBridge::hello("blindlone", PROTOCOL_VERSION).stamps(true)
);
assert_eq!(
poll_input(&layer),
Some(key("j")),
"the first connection of a process waited behind nobody"
);
assert_eq!(client.read_message(), ack(1, InputStatus::Delivered));
assert_eq!(layer.stats().stale_inputs, 0, "nothing was discarded");
}
#[test]
fn a_client_that_finds_the_app_idle_after_a_session_keeps_its_blind_input() {
let layer = bind_layer("blindagain");
let mut first = Client::connect(&layer);
assert!(matches!(first.read_message(), AppToBridge::Hello { .. }));
first.writer.shutdown(Shutdown::Write).unwrap();
let mut line = String::new();
while first.reader.read_line(&mut line).unwrap() > 0 {
line.clear();
}
thread::sleep(Duration::from_millis(250));
let mut second = Client::connect(&layer);
second.send_input(1, key("j"));
assert!(matches!(second.read_message(), AppToBridge::Hello { .. }));
assert_eq!(
poll_input(&layer),
Some(key("j")),
"a client that connected to an idle app waited behind nobody"
);
assert_eq!(second.read_message(), ack(1, InputStatus::Delivered));
assert_eq!(layer.stats().stale_inputs, 0, "nothing was discarded");
}
#[test]
fn acks_queued_as_the_layer_drops_still_reach_the_bridge() {
let layer = bind_layer("exitflush");
let mut client = Client::connect(&layer);
assert!(matches!(client.read_message(), AppToBridge::Hello { .. }));
client.send_input(1, key("q"));
let mut handled = 0;
let deadline = Instant::now() + TIMEOUT;
while handled == 0 && Instant::now() < deadline {
layer.drain(|_| {
handled += 1;
InputStatus::Ignored
});
if handled == 0 {
thread::sleep(Duration::from_millis(1));
}
}
assert_eq!(handled, 1, "the input reaches the app");
drop(layer);
assert_eq!(client.read_message(), ack(1, InputStatus::Delivered));
assert_eq!(client.read_message(), ack(1, InputStatus::Ignored));
let mut line = String::new();
assert_eq!(
client.reader.read_line(&mut line).unwrap(),
0,
"the connection closes after the acks, with nothing else: {line:?}"
);
}
#[test]
fn drop_is_bounded_against_a_bridge_that_stopped_reading_mid_snapshot() {
let TestLayer { mut layer, _dir } = bind_layer("stuckdrop");
let mut client = Client::connect(&layer);
assert!(matches!(client.read_message(), AppToBridge::Hello { .. }));
publish_900_kb(&mut layer);
thread::sleep(Duration::from_millis(100));
let took = time_drop(layer);
assert!(took <= DROP_BOUND + DROP_MARGIN, "took {took:?}");
drop(client);
}
#[test]
fn drop_is_bounded_against_a_bridge_that_reads_too_slowly() {
let TestLayer { mut layer, _dir } = bind_layer("slowdrop");
let mut client = Client::connect(&layer);
assert!(matches!(client.read_message(), AppToBridge::Hello { .. }));
let stop = Arc::new(AtomicBool::new(false));
let trickle = thread::spawn({
let stop = Arc::clone(&stop);
move || {
let mut chunk = Vec::with_capacity(64 * 1024);
while !stop.load(Ordering::SeqCst) {
chunk.clear();
match (&mut client.reader).take(64 * 1024).read_to_end(&mut chunk) {
Ok(0) | Err(_) => return,
Ok(_) => {}
}
for _ in 0..30 {
if stop.load(Ordering::SeqCst) {
return;
}
thread::sleep(Duration::from_millis(100));
}
}
}
});
publish_900_kb(&mut layer);
thread::sleep(Duration::from_millis(100));
let took = time_drop(layer);
assert!(took <= DROP_BOUND + DROP_MARGIN, "took {took:?}");
stop.store(true, Ordering::SeqCst);
trickle.join().expect("the slow reader");
}
#[test]
fn drop_removes_the_socket_file() {
let bound = bind_layer("cleanup");
let path = bound.socket_path().to_path_buf();
assert!(path.exists());
let TestLayer { layer, _dir } = bound;
drop(layer);
assert!(!path.exists(), "socket file should be removed on drop");
}
fn read_snapshot(client: &mut Client) -> Snapshot {
match client.read_message() {
AppToBridge::Snapshot(snapshot) => snapshot,
other => panic!("expected a snapshot, got {other:?}"),
}
}
fn expect_silence(client: &mut Client, window: Duration) {
client
.reader
.get_ref()
.set_read_timeout(Some(window))
.unwrap();
let mut line = String::new();
let read = client.reader.read_line(&mut line);
assert!(
matches!(
&read,
Err(err) if matches!(err.kind(), io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut)
),
"expected nothing to be written, got {read:?} with {line:?}"
);
client
.reader
.get_ref()
.set_read_timeout(Some(TIMEOUT))
.unwrap();
}
const QUIET: Duration = Duration::from_millis(200);
fn drain_one(layer: &TariaLayer, mut f: impl FnMut(AgentInput) -> InputStatus) -> AgentInput {
let deadline = Instant::now() + TIMEOUT;
let mut got = None;
while got.is_none() && Instant::now() < deadline {
layer.drain(|input| {
got = Some(input.clone());
f(input)
});
thread::sleep(Duration::from_millis(5));
}
got.expect("an input should reach the drain")
}
#[test]
fn every_way_of_taking_an_input_stamps_the_next_publish() {
let mut layer = bind_layer("stampapis");
let mut client = Client::connect(&layer);
assert_eq!(
client.read_message(),
AppToBridge::hello("stampapis", PROTOCOL_VERSION).stamps(true),
"the hello says this peer stamps"
);
publish_single_node(&mut layer, "before");
let snapshot = read_snapshot(&mut client);
assert_eq!(snapshot.after_input, None, "nothing has been dequeued yet");
client.send_input(11, key("a"));
assert_eq!(poll_try_recv(&layer), Some((InputId::new(11), key("a"))));
publish_single_node(&mut layer, "after-try-recv");
assert_eq!(client.read_message(), ack(11, InputStatus::Delivered));
let snapshot = read_snapshot(&mut client);
assert_eq!(
(snapshot.seq, snapshot.after_input),
(2, Some(InputId::new(11)))
);
client.send_input(12, key("b"));
assert_eq!(
layer.recv_timeout(TIMEOUT),
Some((InputId::new(12), key("b")))
);
publish_single_node(&mut layer, "after-recv-timeout");
assert_eq!(client.read_message(), ack(12, InputStatus::Delivered));
let snapshot = read_snapshot(&mut client);
assert_eq!(
(snapshot.seq, snapshot.after_input),
(3, Some(InputId::new(12)))
);
client.send_input(13, key("c"));
assert_eq!(drain_one(&layer, |_| InputStatus::Delivered), key("c"));
publish_single_node(&mut layer, "after-drain");
assert_eq!(client.read_message(), ack(13, InputStatus::Delivered));
let snapshot = read_snapshot(&mut client);
assert_eq!(
(snapshot.seq, snapshot.after_input),
(4, Some(InputId::new(13)))
);
assert_eq!(snapshot.root.children[0].id.0, "after-drain");
}
#[test]
fn an_unchanged_frame_after_a_dequeue_is_sent_again_under_the_same_seq() {
let mut layer = bind_layer("restamp");
publish_single_node(&mut layer, "tree");
let mut client = Client::connect(&layer);
assert!(matches!(client.read_message(), AppToBridge::Hello { .. }));
let first = read_snapshot(&mut client);
assert_eq!((first.seq, first.after_input), (1, None));
for _ in 0..3 {
publish_single_node(&mut layer, "tree");
}
expect_silence(&mut client, QUIET);
client.send_input(5, key("x"));
assert_eq!(poll_input(&layer), Some(key("x")));
assert_eq!(client.read_message(), ack(5, InputStatus::Delivered));
publish_single_node(&mut layer, "tree");
let again = read_snapshot(&mut client);
assert_eq!(again.seq, 1, "a stamp moving is not a new snapshot");
assert_eq!(again.after_input, Some(InputId::new(5)));
assert_eq!(again.root, first.root);
for _ in 0..3 {
publish_single_node(&mut layer, "tree");
}
expect_silence(&mut client, QUIET);
publish_single_node(&mut layer, "changed");
let changed = read_snapshot(&mut client);
assert_eq!(
(changed.seq, changed.after_input),
(2, Some(InputId::new(5)))
);
}
#[test]
fn inputs_the_app_never_took_never_stamp() {
const QUEUE: u64 = 256;
let mut layer = bind_layer("nostamp");
let mut client = Client::connect(&layer);
assert!(matches!(client.read_message(), AppToBridge::Hello { .. }));
publish_single_node(&mut layer, "tree");
assert_eq!(read_snapshot(&mut client).after_input, None);
client.write_line(r#"{"type":"input","id":41,"input":{"kind":"paste","text":"hi"}}"#);
assert_eq!(client.read_message(), ack(41, InputStatus::Ignored));
assert_eq!(layer.try_recv(), None);
publish_single_node(&mut layer, "tree");
expect_silence(&mut client, QUIET);
for id in 1..=QUEUE + 1 {
client.send_input(id, key("k"));
}
assert_eq!(client.read_message(), ack(QUEUE + 1, InputStatus::Dropped));
let mut taken = 0;
while layer.try_recv().is_some() {
taken += 1;
}
assert_eq!(taken, QUEUE);
publish_single_node(&mut layer, "tree");
for id in 1..=QUEUE {
assert_eq!(client.read_message(), ack(id, InputStatus::Delivered));
}
let restamped = read_snapshot(&mut client);
assert_eq!(restamped.after_input, Some(InputId::new(QUEUE)));
let mut waiting = Client::connect(&layer);
waiting.send_input(1, key("blind"));
client.send_input(QUEUE + 2, key("late"));
drop(client);
assert!(matches!(waiting.read_message(), AppToBridge::Hello { .. }));
assert_eq!(
read_snapshot(&mut waiting).after_input,
None,
"a new connection's first tree is unstamped"
);
assert_eq!(layer.try_recv(), None, "both were discarded");
assert_eq!(layer.stats().stale_inputs, 2);
publish_single_node(&mut layer, "next");
let next = read_snapshot(&mut waiting);
assert_eq!(next.root.children[0].id.0, "next");
assert_eq!(next.after_input, None);
assert_eq!(layer.stats().dropped_inputs, 1);
assert_eq!(layer.stats().unknown_inputs, 1);
}
#[test]
fn a_new_connection_starts_unstamped() {
let mut layer = bind_layer("stampconn");
let mut first = Client::connect(&layer);
assert!(matches!(first.read_message(), AppToBridge::Hello { .. }));
first.send_input(3, key("a"));
assert_eq!(poll_input(&layer), Some(key("a")));
assert_eq!(first.read_message(), ack(3, InputStatus::Delivered));
publish_single_node(&mut layer, "one");
let stamped = read_snapshot(&mut first);
assert_eq!(
(stamped.seq, stamped.after_input),
(1, Some(InputId::new(3)))
);
drop(first);
let mut second = Client::connect(&layer);
assert!(matches!(second.read_message(), AppToBridge::Hello { .. }));
let greeted = read_snapshot(&mut second);
assert_eq!((greeted.seq, greeted.after_input), (1, None));
assert_eq!(greeted.root, stamped.root);
publish_single_node(&mut layer, "one");
expect_silence(&mut second, QUIET);
second.send_input(3, key("b"));
assert_eq!(poll_input(&layer), Some(key("b")));
assert_eq!(second.read_message(), ack(3, InputStatus::Delivered));
publish_single_node(&mut layer, "one");
let restamped = read_snapshot(&mut second);
assert_eq!(
(restamped.seq, restamped.after_input),
(1, Some(InputId::new(3))),
"the same id on a new connection is a new input"
);
}
#[test]
fn an_ignored_ack_precedes_the_stamped_snapshot() {
let mut layer = bind_layer("stampignored");
let mut client = Client::connect(&layer);
assert!(matches!(client.read_message(), AppToBridge::Hello { .. }));
publish_single_node(&mut layer, "tree");
assert_eq!(read_snapshot(&mut client).seq, 1);
client.send_input(9, key("x"));
assert_eq!(drain_one(&layer, |_| InputStatus::Ignored), key("x"));
publish_single_node(&mut layer, "tree");
assert_eq!(client.read_message(), ack(9, InputStatus::Delivered));
assert_eq!(client.read_message(), ack(9, InputStatus::Ignored));
let snapshot = read_snapshot(&mut client);
assert_eq!(
(snapshot.seq, snapshot.after_input),
(1, Some(InputId::new(9)))
);
}