add tests for unix client

main
Laurent Pelecq 3 years ago
parent 0914f8b191
commit cab51e0fc6

@ -11,3 +11,6 @@ description = "Client API for Speech Dispatcher"
dirs = "3"
thiserror = "1"
[dev-dependencies]
libc = "0"
tempfile = "3"

@ -1,7 +1,7 @@
use ssip_client::ClientResult;
fn main() -> ClientResult<()> {
let mut client = ssip_client::new_unix("joe", "list", "main")?;
let mut client = ssip_client::new_unix_client("joe", "list", "main")?;
let status = client.quit()?;
println!("status: {}", status);
Ok(())

@ -5,4 +5,4 @@ mod client;
mod unix;
pub use client::{ClientResult, ClientStatus};
pub use unix::new as new_unix;
pub use unix::new_client as new_unix_client;

@ -40,28 +40,21 @@ pub(crate) fn send_command(output: &mut dyn Write, command: &str) -> ClientResul
Ok(())
}
/// Strip prefix if found
fn strip_prefix(line: &str, prefix: &str) -> String {
line.strip_prefix(prefix).unwrap_or(line).to_string()
}
/// Parse the status line "OK msg" or "ERR msg"
fn parse_status_line(code: u16, line: &str) -> ClientStatus {
const TOKEN_OK: &str = "OK ";
const OFFSET_OK: usize = TOKEN_OK.len();
const TOKEN_ERR: &str = "ERR ";
const OFFSET_ERR: usize = TOKEN_ERR.len();
if line.starts_with(TOKEN_OK) {
let message = line[OFFSET_OK..].to_string();
Ok(StatusLine { code, message })
} else if line.starts_with(TOKEN_ERR) {
let message = line[OFFSET_ERR..].to_string();
if (300..700).contains(&code) {
const TOKEN_ERR: &str = "ERR ";
let message = strip_prefix(line, TOKEN_ERR);
Err(ClientError::Ssip(StatusLine { code, message }))
} else {
let status = StatusLine {
code,
message: line.to_string(),
};
if (300..700).contains(&code) {
Err(ClientError::Ssip(status))
} else {
Ok(status)
}
const TOKEN_OK: &str = "OK ";
let message = strip_prefix(line, TOKEN_OK);
Ok(StatusLine { code, message })
}
}

@ -1,6 +1,7 @@
use std::io;
use std::io::{self, BufReader, BufWriter};
use std::os::unix::net::UnixStream;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::time::Duration;
use crate::client::{Client, ClientResult};
@ -20,10 +21,122 @@ fn speech_dispatcher_socket() -> io::Result<PathBuf> {
}
}
pub fn new(user: &str, application: &str, component: &str) -> ClientResult<Client<UnixStream>> {
let socket_path = speech_dispatcher_socket()?;
fn new_pair<P>(
socket_path: P,
read_timeout: Option<Duration>,
) -> io::Result<(BufReader<UnixStream>, BufWriter<UnixStream>)>
where
P: AsRef<Path>,
{
let stream = UnixStream::connect(socket_path)?;
let input = io::BufReader::new(stream.try_clone()?);
let output = io::BufWriter::new(stream);
stream.set_read_timeout(read_timeout)?;
Ok((BufReader::new(stream.try_clone()?), BufWriter::new(stream)))
}
pub fn new_client(
user: &str,
application: &str,
component: &str,
) -> ClientResult<Client<UnixStream>> {
let socket_path = speech_dispatcher_socket()?;
let (input, output) = new_pair(&socket_path, None)?;
Client::new(input, output, user, application, component)
}
#[cfg(test)]
mod tests {
use std::io::{self, BufRead, BufReader, BufWriter, Write};
use std::os::unix::net::UnixStream;
use std::path::Path;
use std::thread;
use std::time::Duration;
use super::{new_pair, Client};
struct Server {
input: BufReader<UnixStream>,
output: BufWriter<UnixStream>,
answers: Vec<&'static str>,
}
impl Server {
fn new<P>(socket_path: &P, answers: &[&'static str]) -> io::Result<Self>
where
P: AsRef<Path>,
{
let timeout = Duration::new(5, 0);
let (input, output) = new_pair(&socket_path, Some(timeout))?;
Ok(Server {
input,
output,
answers: answers.to_vec(),
})
}
fn serve(&mut self) -> io::Result<()> {
for answer in self.answers.iter() {
let mut line = String::new();
self.input.read_line(&mut line)?;
self.output.write_all(answer.as_bytes())?;
self.output.flush()?;
}
Ok(())
}
fn temporary_path() -> io::Result<tempfile::TempPath> {
let tfile = tempfile::NamedTempFile::new()?;
Ok(tfile.into_temp_path())
}
fn run<P>(
socket_path: P,
answers: &'static [&'static str],
) -> io::Result<thread::JoinHandle<io::Result<()>>>
where
P: AsRef<Path>,
{
let unix_path = socket_path.as_ref().to_str().unwrap().to_string();
unsafe {
libc::mkfifo(unix_path.as_ptr() as *const libc::c_char, 0o700);
}
let server_path = socket_path.as_ref().to_path_buf();
Ok(thread::spawn(move || -> io::Result<()> {
let mut server = Server::new(&server_path, answers)?;
server.serve()?;
Ok(())
}))
}
}
/// Create a server and run the client
fn test_client<F>(answers: &'static [&'static str], process: F) -> io::Result<()>
where
F: FnMut(&mut Client<UnixStream>) -> io::Result<()>,
{
let socket_path = Server::temporary_path()?;
let server_path = socket_path.to_path_buf();
let mut process_wrapper = std::panic::AssertUnwindSafe(process);
let result = std::panic::catch_unwind(move || {
let handle = Server::run(&server_path, answers).unwrap();
let (input, output) = new_pair(&server_path, None)?;
let mut client = Client::new(input, output, "test", "test", "main").unwrap();
process_wrapper(&mut client).unwrap();
handle.join().unwrap()
});
socket_path.close().unwrap();
assert!(result.is_ok());
Ok(())
}
#[test]
fn connect_and_quit() -> io::Result<()> {
test_client(
&["208 OK CLIENT NAME SET\r\n", "231 HAPPY HACKING\r\n"],
|client| {
assert_eq!(231, client.quit().unwrap().code);
Ok(())
},
)
}
}

Loading…
Cancel
Save