#![cfg(unix)]
use std::fs;
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::UnixStream;
use waos_dev_protocol::{Connection, FromWaOS, Listener, OutputStream, PROTOCOL, RunEnd, ToWaOS};
struct TestFolder(PathBuf);
impl TestFolder {
fn new(test: &str) -> TestFolder {
let folder = std::env::temp_dir().join(format!("waos-dev-{}-{test}", std::process::id()));
let _ = fs::remove_dir_all(&folder);
TestFolder(folder)
}
fn socket(&self) -> PathBuf {
self.0.join("dev.sock")
}
}
impl Drop for TestFolder {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
async fn connected(path: &Path) -> (Listener, Connection<UnixStream>, Connection<UnixStream>) {
let listener = Listener::bind(path).await.unwrap();
let (client, server) = tokio::join!(Connection::connect(path), listener.accept());
let (mut client, mut server) = (client.unwrap(), server.unwrap());
let (agreed, agreed_too) = tokio::join!(client.agree_on_protocol(), server.agree_on_protocol());
agreed.unwrap();
agreed_too.unwrap();
(listener, client, server)
}
#[tokio::test]
async fn a_client_of_another_build_is_refused_with_both_numbers() {
let folder = TestFolder::new("other-build");
let socket = folder.socket();
let listener = Listener::bind(&socket).await.unwrap();
let (client, server) = tokio::join!(UnixStream::connect(&socket), listener.accept());
let (mut client, mut server) = (client.unwrap(), server.unwrap());
let theirs = PROTOCOL + 1;
let mut their_frame = 4u32.to_le_bytes().to_vec();
their_frame.extend_from_slice(&theirs.to_le_bytes());
client.write_all(&their_frame).await.unwrap();
let refused = server.agree_on_protocol().await.unwrap_err();
assert_eq!(refused.kind(), ErrorKind::Unsupported);
assert!(
refused.to_string().contains(&format!("protocol {theirs}"))
&& refused
.to_string()
.contains(&format!("protocol {PROTOCOL}")),
"{refused}"
);
drop(server);
let mut ours = [0; 8];
client.read_exact(&mut ours).await.unwrap();
assert_eq!(&ours[..4], 4u32.to_le_bytes());
assert_eq!(u32::from_le_bytes(ours[4..].try_into().unwrap()), PROTOCOL);
assert_eq!(client.read(&mut [0; 1]).await.unwrap(), 0, "WaOS closed");
}
fn every_from_waos() -> Vec<FromWaOS> {
vec![
FromWaOS::Started,
FromWaOS::Output {
stream: OutputStream::Stdout,
bytes: b"hello, ".to_vec(),
},
FromWaOS::Output {
stream: OutputStream::Stderr,
bytes: b"a warning\n".to_vec(),
},
FromWaOS::Ended(RunEnd::Returned { success: true }),
FromWaOS::Ended(RunEnd::Returned { success: false }),
FromWaOS::Ended(RunEnd::Failed("unreachable".into())),
FromWaOS::Ended(RunEnd::Stopped),
FromWaOS::Ended(RunEnd::Replaced),
FromWaOS::Refused("not a component".into()),
]
}
#[tokio::test]
async fn every_message_comes_back_as_sent() {
let folder = TestFolder::new("round-trip");
let (_listener, mut client, mut server) = connected(&folder.socket()).await;
let run = ToWaOS::Run {
id: "dev.example.triangle".parse().unwrap(),
component: (0..=255).cycle().take(3 << 20).collect(),
};
let (sent, received) = tokio::join!(client.send(&run), server.receive::<ToWaOS>());
sent.unwrap();
assert_eq!(received.unwrap().as_ref(), Some(&run));
for message in every_from_waos() {
let (sent, received) = tokio::join!(server.send(&message), client.receive::<FromWaOS>());
sent.unwrap();
assert_eq!(received.unwrap(), Some(message));
}
}
#[tokio::test]
async fn the_halves_send_while_they_watch_for_the_close() {
let folder = TestFolder::new("halves");
let (_listener, mut client, server) = connected(&folder.socket()).await;
let (mut server_in, mut server_out) = server.into_split();
let watching = tokio::spawn(async move { server_in.receive::<ToWaOS>().await });
server_out.send(&FromWaOS::Started).await.unwrap();
assert_eq!(
client.receive::<FromWaOS>().await.unwrap(),
Some(FromWaOS::Started)
);
drop(client);
assert_eq!(watching.await.unwrap().unwrap(), None, "the close is seen");
}
#[tokio::test]
async fn the_other_side_closing_is_none() {
let folder = TestFolder::new("closing");
let (_listener, client, mut server) = connected(&folder.socket()).await;
drop(client);
assert_eq!(server.receive::<ToWaOS>().await.unwrap(), None);
}
#[tokio::test]
async fn a_socket_file_a_dead_waos_left_is_replaced() {
let folder = TestFolder::new("stale");
let socket = folder.socket();
fs::create_dir_all(&folder.0).unwrap();
drop(std::os::unix::net::UnixListener::bind(&socket).unwrap());
assert!(socket.exists());
let (_listener, mut client, mut server) = connected(&socket).await;
let (sent, received) = tokio::join!(server.send(&FromWaOS::Started), client.receive());
sent.unwrap();
assert_eq!(received.unwrap(), Some(FromWaOS::Started));
}
#[tokio::test]
async fn a_second_waos_is_refused_while_the_first_answers() {
let folder = TestFolder::new("second");
let socket = folder.socket();
let first = Listener::bind(&socket).await.unwrap();
let (refused, probe) = tokio::join!(Listener::bind(&socket), first.accept());
assert_eq!(refused.unwrap_err().kind(), ErrorKind::AddrInUse);
let mut probe = probe.unwrap();
assert_eq!(
probe.receive::<ToWaOS>().await.unwrap(),
None,
"the probe is a connection that asked nothing"
);
}
#[tokio::test]
async fn a_listener_dropped_leaves_no_socket_file() {
let folder = TestFolder::new("dropped");
let socket = folder.socket();
drop(Listener::bind(&socket).await.unwrap());
assert!(!socket.exists());
assert_eq!(
Connection::connect(&socket).await.unwrap_err().kind(),
ErrorKind::NotFound
);
}