use super::*;
use crate::service_protocol::decode_service;
use std::io::{BufRead, BufReader};
const QUICK_BEAT: Duration = Duration::from_millis(20);
const BEATS_WANTED: usize = 3;
fn connected() -> (Reply, std::thread::JoinHandle<()>, BufReader<UnixStream>) {
let (service, client) = UnixStream::pair().expect("a connected pair");
let (reply, messages) = Reply::new();
let writer = std::thread::spawn(move || { write_until_answered(&service, &messages, QUICK_BEAT); });
return (reply, writer, BufReader::new(client));
}
fn next(client: &mut BufReader<UnixStream>) -> ServiceMessage {
let mut line: String = String::new();
client.read_line(&mut line).expect("a line arrives");
return decode_service(line.trim_end_matches('\n')).expect("a message");
}
#[test]
fn a_waiting_client_is_sent_heartbeats_until_it_is_answered() {
let (reply, writer, mut client) = connected();
for _ in 0..BEATS_WANTED {
assert_eq!(next(&mut client), ServiceMessage::Heartbeat);
}
let landed: ServiceMessage = ServiceMessage::Landed { id: String::from("x"), commit: String::from("c"), already: false };
reply.send(&landed);
loop {
let heard: ServiceMessage = next(&mut client);
if heard != ServiceMessage::Heartbeat {
assert_eq!(heard, landed);
break;
}
}
drop(reply);
writer.join().expect("the writer ends once the reply is gone");
}
#[test]
fn the_beat_ends_with_the_reply_and_the_connection_closes() {
let (reply, writer, mut client) = connected();
drop(reply);
writer.join().expect("the writer ends");
let mut rest: String = String::new();
while client.read_line(&mut rest).expect("read") > 0 {
assert!(rest.contains("heartbeat"), "only beats may arrive after the reply is dropped: {rest}");
rest.clear();
}
}
#[test]
fn a_queued_answer_is_written_before_the_connection_closes() {
let (reply, writer, mut client) = connected();
let landed: ServiceMessage = ServiceMessage::Landed { id: String::from("x"), commit: String::from("c"), already: false };
reply.send(&landed);
drop(reply);
writer.join().expect("the writer ends");
assert_eq!(next(&mut client), landed);
}
#[test]
fn the_beat_ends_when_the_client_is_gone() {
let (reply, writer, client) = connected();
drop(client);
writer.join().expect("a write to a closed client ends the writer");
drop(reply);
}
#[test]
fn beats_and_answers_never_interleave_their_bytes_and_keep_their_order() {
let (reply, writer, mut client) = connected();
let long_text: String = "w".repeat(200_000);
let answerer = std::thread::spawn(move || {
for number in 0..BEATS_WANTED * 10 {
reply.send(&ServiceMessage::Waiting { text: format!("{number}:{long_text}") });
}
});
let mut waits: usize = 0;
while waits < BEATS_WANTED * 10 {
match next(&mut client) {
ServiceMessage::Heartbeat => {}
ServiceMessage::Waiting { text } => {
assert!(text.starts_with(format!("{waits}:").as_str()), "answers keep their order: {}", &text[..8]);
waits += 1;
}
other => panic!("unexpected {other:?}"),
}
}
answerer.join().expect("answerer");
writer.join().expect("the writer ends when the reply is dropped with the answerer");
}