diff --git a/tests/fifo_async_tests.rs b/tests/fifo_async_tests.rs index 4b44e26..86cfb71 100644 --- a/tests/fifo_async_tests.rs +++ b/tests/fifo_async_tests.rs @@ -8,6 +8,9 @@ #[cfg(feature = "async-mio")] use mio::{Events, Poll, Token}; +#[cfg(feature = "async-mio")] +use std::{slice::Iter, time::Duration}; + #[cfg(feature = "async-mio")] use ssip_client::*; @@ -18,86 +21,143 @@ mod server; use server::Server; #[cfg(feature = "async-mio")] -mod utils { +enum Answer { + Str(&'static str), + Int(i8), +} + +#[cfg(feature = "async-mio")] +struct State<'a, 'b> { + pub done: bool, + pub countdown: usize, + pub writable: bool, + pub start_get: bool, + pub iter_requests: Iter<'a, Request>, + pub iter_answers: Iter<'b, Answer>, +} - use ssip_client::*; +#[cfg(feature = "async-mio")] +impl<'a, 'b> State<'a, 'b> { + fn new(iter_requests: Iter<'a, Request>, iter_answers: Iter<'b, Answer>) -> Self { + State { + done: false, + countdown: 50, + writable: false, + start_get: false, + iter_requests, + iter_answers, + } + } - const MAX_RETRIES: u16 = 10; + fn terminated(&self) -> bool { + self.done || self.countdown == 0 + } - pub struct Controler { - step: u16, - retry: u16, + fn must_send(&self) -> bool { + self.writable && self.countdown > 0 } - impl Controler { - pub fn new() -> Controler { - Controler { - step: 0, - retry: MAX_RETRIES, - } + fn next_request(&mut self) -> Option<&Request> { + if self.start_get { + self.iter_requests.next() + } else { + None } + } - pub fn step(&self) -> u16 { - self.step + fn assert_string(&mut self, val: &str) { + match self.iter_answers.next() { + Some(Answer::Str(expected_val)) => assert_eq!(expected_val, &val), + Some(Answer::Int(expected_val)) => panic!( + "expecting integer {} instead of string '{}'", + expected_val, val + ), + None => panic!("no more answers"), } + } - pub fn check_result(&mut self, result: ClientResult) -> Option { - match result { - Ok(value) => { - self.step += 1; - self.retry = MAX_RETRIES; - Some(value) - } - Err(ClientError::NotReady) if self.retry > 0 => { - self.retry -= 1; - None - } - Err(err) => panic!("{:?}", err), - } + fn assert_integer(&mut self, val: i8) { + match self.iter_answers.next() { + Some(Answer::Int(expected_val)) => assert_eq!(expected_val, &val), + Some(Answer::Str(expected_val)) => panic!( + "expecting string '{}' instead of integer {}", + expected_val, val + ), + None => panic!("no more answers"), } } } -#[cfg(feature = "async-mio")] -use utils::Controler; - #[test] #[cfg(feature = "async-mio")] -fn basic_async_communication() -> std::io::Result<()> { - const COMMUNICATION: [(&str, &str); 1] = [( - "SET self CLIENT_NAME test:test:main\r\n", - "208 OK CLIENT NAME SET\r\n", - )]; +fn basic_async_communication() -> ClientResult<()> { + const COMMUNICATION: [(&str, &str); 5] = [ + ( + "SET self CLIENT_NAME test:test:main\r\n", + "208 OK CLIENT NAME SET\r\n", + ), + ("SET self LANGUAGE en\r\n", "201 OK LANGUAGE SET\r\n"), + ("STOP self\r\n", "210 OK STOPPED\r\n"), + ( + "GET OUTPUT_MODULE\r\n", + "251-espeak\r\n251 OK GET RETURNED\r\n", + ), + ("GET RATE\r\n", "251-10\r\n251 OK GET RETURNED\r\n"), + ]; - let socket_path = Server::temporary_path(); + let get_requests = vec![Request::GetOutputModule, Request::GetRate]; + let get_answers = vec![Answer::Str("espeak"), Answer::Int(10)]; + let mut state = State::new(get_requests.iter(), get_answers.iter()); + + let socket_dir = tempfile::tempdir()?; + let socket_path = socket_dir.path().join("basic_async_communication.socket"); assert!(!socket_path.exists()); - let server_path = socket_path.clone(); - let result = std::panic::catch_unwind(move || -> std::io::Result { - let handle = Server::run(&server_path, &COMMUNICATION); - let mut poll = Poll::new()?; - let mut events = Events::with_capacity(128); - let mut client = fifo::Builder::new().path(&server_path).build().unwrap(); - let input_token = Token(0); - let output_token = Token(1); - client.register(&poll, input_token, output_token).unwrap(); - let mut controler = Controler::new(); - while controler.step() < 2 { - poll.poll(&mut events, None)?; - for event in &events { - if event.token() == output_token && event.is_writable() && controler.step() == 0 { - controler.check_result(client.set_client_name(ClientName::new("test", "test"))); - } else if event.token() == input_token - && event.is_readable() - && controler.step() == 1 - { - controler.check_result(client.check_client_name_set()); + let handle = Server::run(&socket_path, &COMMUNICATION); + let mut poll = Poll::new()?; + let mut events = Events::with_capacity(128); + let mut client = AsyncClient::new(fifo::Builder::new().path(&socket_path).build()?); + let input_token = Token(0); + let output_token = Token(1); + let timeout = Duration::new(0, 500 * 1000 * 1000 /* 500 ms */); + client.register(&poll, input_token, output_token).unwrap(); + client.push(Request::SetName(ClientName::new("test", "test"))); + while !state.terminated() { + if !state.writable || !client.has_next() { + poll.poll(&mut events, Some(timeout))?; + } + state.countdown -= 1; + for event in &events { + let token = event.token(); + if token == input_token { + match dbg!(client.receive_next()?) { + Response::ClientNameSet => { + client.push(Request::SetLanguage(ClientScope::Current, "en".to_string())) + } + Response::LanguageSet => client.push(Request::Stop(MessageScope::Last)), + Response::Stopped => state.start_get = true, + Response::GetString(val) => state.assert_string(&val), + Response::GetInteger(val) => state.assert_integer(val), + result => panic!("Unexpected response: {:?}", result), + } + if let Some(request) = state.next_request() { + client.push(request.clone()); + } else if state.start_get { + state.done = true; // No more get request } + } else if token == output_token { + state.writable = true; } } - handle.join().unwrap().unwrap(); - Ok(controler.step()) - }); - std::fs::remove_file(socket_path)?; - assert_eq!(2, result.unwrap().unwrap()); + if state.must_send() { + match client.send_next() { + Ok(()) => (), + Err(ClientError::NotReady) => state.writable = false, + err => return err, + } + } + } + handle.join().unwrap().unwrap(); + assert!(state.countdown > 0); + socket_dir.close()?; Ok(()) } diff --git a/tests/fifo_sync_tests.rs b/tests/fifo_sync_tests.rs index 02577aa..41920ed 100644 --- a/tests/fifo_sync_tests.rs +++ b/tests/fifo_sync_tests.rs @@ -24,30 +24,25 @@ use server::Server; fn test_client( communication: &'static [(&'static str, &'static str)], process: F, -) -> io::Result<()> +) -> ClientResult<()> where F: FnMut(&mut Client) -> io::Result<()>, { - let socket_path = Server::temporary_path(); + let socket_dir = tempfile::tempdir()?; + let socket_path = socket_dir.path().join("test_client.socket"); assert!(!socket_path.exists()); let server_path = socket_path.clone(); let mut process_wrapper = std::panic::AssertUnwindSafe(process); - let result = std::panic::catch_unwind(move || { - let handle = Server::run(&server_path, communication); - let mut client = ssip_client::fifo::Builder::new() - .path(&server_path) - .build() - .unwrap(); - client - .set_client_name(ClientName::new("test", "test")) - .unwrap() - .check_client_name_set() - .unwrap(); - process_wrapper(&mut client).unwrap(); - handle.join().unwrap() - }); - std::fs::remove_file(socket_path)?; - result.unwrap().unwrap(); + let handle = Server::run(&server_path, communication); + let mut client = ssip_client::fifo::Builder::new() + .path(&server_path) + .build()?; + client + .set_client_name(ClientName::new("test", "test"))? + .check_client_name_set()?; + process_wrapper(&mut client)?; + handle.join().unwrap().unwrap(); + socket_dir.close()?; Ok(()) } @@ -59,7 +54,7 @@ const SET_CLIENT_COMMUNICATION: (&str, &str) = ( #[test] #[cfg(not(feature = "async-mio"))] -fn connect_and_quit() -> io::Result<()> { +fn connect_and_quit() -> ClientResult<()> { test_client( &[ SET_CLIENT_COMMUNICATION, @@ -74,7 +69,7 @@ fn connect_and_quit() -> io::Result<()> { #[test] #[cfg(not(feature = "async-mio"))] -fn say_one_line() -> io::Result<()> { +fn say_one_line() -> ClientResult<()> { test_client( &[ SET_CLIENT_COMMUNICATION, @@ -106,7 +101,7 @@ macro_rules! test_setter { ($setter:ident, $question:expr, $answer:expr, $code:expr, $($arg:tt)*) => { #[test] #[cfg(not(feature = "async-mio"))] - fn $setter() -> io::Result<()> { + fn $setter() -> ClientResult<()> { test_client( &[SET_CLIENT_COMMUNICATION, ($question, $answer)], |client| { @@ -122,7 +117,7 @@ macro_rules! test_getter { ($getter:ident, $receive:ident, $arg:tt, $question:expr, $answer:expr, $value:expr) => { #[test] #[cfg(not(feature = "async-mio"))] - fn $getter() -> io::Result<()> { + fn $getter() -> ClientResult<()> { test_client( &[SET_CLIENT_COMMUNICATION, ($question, $answer)], |client| { @@ -142,7 +137,7 @@ macro_rules! test_list { ($getter:ident, $question:expr, $answer:expr, $code:expr, $values:expr) => { #[test] #[cfg(not(feature = "async-mio"))] - fn $getter() -> io::Result<()> { + fn $getter() -> ClientResult<()> { test_client( &[SET_CLIENT_COMMUNICATION, ($question, $answer)], |client| { @@ -165,7 +160,7 @@ test_setter!( #[test] #[cfg(not(feature = "async-mio"))] -fn set_debug() -> io::Result<()> { +fn set_debug() -> ClientResult<()> { test_client( &[ SET_CLIENT_COMMUNICATION, @@ -341,7 +336,7 @@ test_list!( #[test] #[cfg(not(feature = "async-mio"))] -fn list_synthesis_voices() -> io::Result<()> { +fn list_synthesis_voices() -> ClientResult<()> { test_client( &[ SET_CLIENT_COMMUNICATION, @@ -367,7 +362,7 @@ fn list_synthesis_voices() -> io::Result<()> { #[test] #[cfg(not(feature = "async-mio"))] -fn receive_notification() -> io::Result<()> { +fn receive_notification() -> ClientResult<()> { test_client( &[ SET_CLIENT_COMMUNICATION, diff --git a/tests/server.rs b/tests/server.rs index 788175c..dee7e30 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -7,7 +7,7 @@ // modified, or distributed except according to those terms. use std::io::{self, BufRead, BufReader, BufWriter, Write}; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::thread; use std::os::unix::net::UnixListener; @@ -24,13 +24,13 @@ impl Server { /// Argument `communication` is an array of pairs. The first item is a list of strings /// the server will receive and the second item is the answer. pub fn new

( - socket_path: &P, + socket_path: P, communication: &[(&'static str, &'static str)], ) -> io::Result where P: AsRef, { - let listener = UnixListener::bind(socket_path)?; + let listener = UnixListener::bind(socket_path.as_ref())?; Ok(Server { listener, communication: communication.to_vec(), @@ -56,7 +56,7 @@ impl Server { if line != *question { return Err(io::Error::new( io::ErrorKind::InvalidInput, - format!("read <{}> instead of <{}>", dbg!(line), *question), + format!("read <{}> instead of <{}>", line, *question), )); } } @@ -66,11 +66,6 @@ impl Server { Ok(()) } - pub fn temporary_path() -> PathBuf { - let tid = unsafe { libc::pthread_self() } as u64; - std::env::temp_dir().join(format!("ssip-client-test-{}-{}", std::process::id(), tid)) - } - pub fn run

( socket_path: P, communication: &'static [(&'static str, &'static str)],