From 86e224b8aa2e0c07d9cbb5aa1b64321fa7042979 Mon Sep 17 00:00:00 2001 From: Aaron Weiss Date: Wed, 21 Jun 2017 21:50:38 -0400 Subject: [PATCH] Fixed unit tests for async changes. --- Cargo.toml | 2 +- src/client/conn.rs | 51 +++++- src/client/server/mod.rs | 357 +++++++++++++++++++++++-------------- src/client/server/utils.rs | 67 ++++--- src/client/transport.rs | 96 ++++++++++ src/error.rs | 6 + 6 files changed, 404 insertions(+), 175 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2724c1a..230b6ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,5 +29,5 @@ serde_derive = "1.0" serde_json = "1.0" tokio-core = "0.1" tokio-io = "0.1" -tokio-mockstream = "1.0" +tokio-mockstream = "1.1" tokio-tls = "0.1" diff --git a/src/client/conn.rs b/src/client/conn.rs index 74d7a5e..ff1c6a2 100644 --- a/src/client/conn.rs +++ b/src/client/conn.rs @@ -1,14 +1,17 @@ //! A module providing IRC connections for use by `IrcServer`s. -use std::fmt; +use std::{fmt, io}; use error; use client::data::Config; -use client::transport::IrcTransport; +use client::transport::{IrcTransport, LogView, Logged}; use proto::{IrcCodec, Message}; +use encoding::{EncoderTrap}; +use encoding::label::encoding_from_whatwg_label; use futures::{Async, Poll, Future, Sink, StartSend, Stream}; use native_tls::TlsConnector; use tokio_core::reactor::Handle; use tokio_core::net::{TcpStream, TcpStreamNew}; use tokio_io::AsyncRead; +use tokio_mockstream::MockStream; use tokio_tls::{TlsConnectorExt, TlsStream}; /// An IRC connection used internally by `IrcServer`. @@ -17,6 +20,8 @@ pub enum Connection { Unsecured(IrcTransport), #[doc(hidden)] Secured(IrcTransport>), + #[doc(hidden)] + Mock(Logged), } impl fmt::Debug for Connection { @@ -27,6 +32,7 @@ impl fmt::Debug for Connection { match *self { Connection::Unsecured(_) => "Connection::Unsecured(...)", Connection::Secured(_) => "Connection::Secured(...)", + Connection::Mock(_) => "Connection::Mock(...)", } ) } @@ -41,6 +47,8 @@ pub enum ConnectionFuture<'a> { Unsecured(&'a Config, TcpStreamNew), #[doc(hidden)] Secured(&'a Config, TlsFuture), + #[doc(hidden)] + Mock(&'a Config), } impl<'a> Future for ConnectionFuture<'a> { @@ -61,6 +69,29 @@ impl<'a> Future for ConnectionFuture<'a> { Ok(Async::Ready(Connection::Secured(transport))) } + &mut ConnectionFuture::Mock(ref config) => { + let enc: error::Result<_> = encoding_from_whatwg_label(config.encoding()).ok_or( + io::Error::new( + io::ErrorKind::InvalidInput, + &format!("Attempted to use unknown codec {}.", config.encoding())[..], + ).into(), + ); + let encoding = enc?; + let init_str = config.mock_initial_value(); + let initial: error::Result<_> = { + encoding.encode(&init_str, EncoderTrap::Replace).map_err(|data| { + io::Error::new( + io::ErrorKind::InvalidInput, + &format!("Failed to encode {} as {}.", data, encoding.name())[..], + ).into() + }) + }; + + let framed = MockStream::new(&initial?).framed(IrcCodec::new(config.encoding())?); + let transport = IrcTransport::new(config, framed); + + Ok(Async::Ready(Connection::Mock(Logged::wrap(transport)))) + } } } } @@ -68,7 +99,9 @@ impl<'a> Future for ConnectionFuture<'a> { impl Connection { /// Creates a new `Connection` using the specified `Config` and `Handle`. pub fn new<'a>(config: &'a Config, handle: &Handle) -> error::Result> { - if config.use_ssl() { + if config.use_mock_connection() { + Ok(ConnectionFuture::Mock(config)) + } else if config.use_ssl() { let domain = format!("{}:{}", config.server(), config.port()); let connector = TlsConnector::builder()?.build()?; let stream = TcpStream::connect(&config.socket_addr(), handle) @@ -90,6 +123,15 @@ impl Connection { )) } } + + /// Gets a view of the internal logging if and only if this connection is using a mock stream. + /// Otherwise, this will always return `None`. This is used for unit testing. + pub fn log_view(&self) -> Option { + match self { + &Connection::Mock(ref inner) => Some(inner.view()), + _ => None + } + } } impl Stream for Connection { @@ -100,6 +142,7 @@ impl Stream for Connection { match self { &mut Connection::Unsecured(ref mut inner) => inner.poll(), &mut Connection::Secured(ref mut inner) => inner.poll(), + &mut Connection::Mock(ref mut inner) => inner.poll(), } } } @@ -112,6 +155,7 @@ impl Sink for Connection { match self { &mut Connection::Unsecured(ref mut inner) => inner.start_send(item), &mut Connection::Secured(ref mut inner) => inner.start_send(item), + &mut Connection::Mock(ref mut inner) => inner.start_send(item), } } @@ -119,6 +163,7 @@ impl Sink for Connection { match self { &mut Connection::Unsecured(ref mut inner) => inner.poll_complete(), &mut Connection::Secured(ref mut inner) => inner.poll_complete(), + &mut Connection::Mock(ref mut inner) => inner.poll_complete(), } } } diff --git a/src/client/server/mod.rs b/src/client/server/mod.rs index 7e2a27a..69651c9 100644 --- a/src/client/server/mod.rs +++ b/src/client/server/mod.rs @@ -8,8 +8,8 @@ use client::conn::Connection; use client::data::{Command, Config, Message, Response, User}; use client::data::Command::{JOIN, NICK, NICKSERV, PART, PRIVMSG, MODE, QUIT}; use client::server::utils::ServerExt; +use client::transport::LogView; use futures::{Async, Poll, Future, Sink, Stream}; -use futures::future; use futures::stream::SplitStream; use futures::sync::mpsc; use futures::sync::oneshot; @@ -427,6 +427,8 @@ impl ServerState { pub struct IrcServer { /// The internal, thread-safe server state. state: Arc, + /// A view of the logs for a mock connection. + view: Option, } impl Server for IrcServer { @@ -492,9 +494,10 @@ impl IrcServer { /// Creates a new IRC server connection from the specified configuration, connecting /// immediately. pub fn from_config(config: Config) -> error::Result { - // Setting up a remote reactor running forever. + // Setting up a remote reactor running for the length of the connection. let (tx_outgoing, rx_outgoing) = mpsc::unbounded(); let (tx_incoming, rx_incoming) = oneshot::channel(); + let (tx_view, rx_view) = oneshot::channel(); let cfg = config.clone(); let _ = thread::spawn(move || { @@ -502,39 +505,49 @@ impl IrcServer { // Setting up internal processing stuffs. let handle = reactor.handle(); - let (sink, stream) = reactor + let conn = reactor .run(Connection::new(&cfg, &handle).unwrap()) - .unwrap() - .split(); + .unwrap(); + + tx_view.send(conn.log_view()).unwrap(); + let (sink, stream) = conn.split(); let outgoing_future = sink.send_all(rx_outgoing.map_err(|_| { let res: error::Error = error::ErrorKind::ChannelError.into(); res - })); - handle.spawn(outgoing_future.map(|_| ()).map_err(|_| ())); + })).map(|_| ()).map_err(|_| ()); // Send the stream half back to the original thread. tx_incoming.send(stream).unwrap(); - reactor.run(future::empty::<(), ()>()).unwrap(); + reactor.run(outgoing_future).unwrap(); }); Ok(IrcServer { state: Arc::new(ServerState::new(rx_incoming.wait()?, tx_outgoing, config)), + view: rx_view.wait()?, }) } + + /// Gets the log view from the internal transport. Only used for unit testing. + #[cfg(test)] + fn log_view(&self) -> &LogView { + self.view.as_ref().unwrap() + } } #[cfg(test)] mod test { use super::{IrcServer, Server}; + use std::thread; + use std::time::Duration; use std::collections::HashMap; use std::default::Default; - use client::conn::MockConnection; use client::data::Config; #[cfg(not(feature = "nochanlists"))] use client::data::User; use proto::command::Command::{PART, PRIVMSG}; + use futures::{Future, Stream}; pub fn test_config() -> Config { Config { @@ -544,53 +557,67 @@ mod test { server: Some(format!("irc.test.net")), channels: Some(vec![format!("#test"), format!("#test2")]), user_info: Some(format!("Testing.")), + use_mock_connection: Some(true), ..Default::default() } } pub fn get_server_value(server: IrcServer) -> String { - server.conn().written(server.config().encoding()).unwrap() + // We sleep here because of synchronization issues. + // We can't guarantee that everything will have been sent by the time of this call. + thread::sleep(Duration::from_millis(100)); + server.log_view().sent().unwrap().iter().fold(String::new(), |mut acc, msg| { + acc.push_str(&msg.to_string()); + acc + }) } #[test] - fn iterator() { + fn stream() { let exp = "PRIVMSG test :Hi!\r\nPRIVMSG test :This is a test!\r\n\ :test!test@test JOIN #test\r\n"; - let server = IrcServer::from_connection(test_config(), MockConnection::new(exp)); + let server = IrcServer::from_config(Config { + mock_initial_value: Some(exp.to_owned()), + ..test_config() + }).unwrap(); let mut messages = String::new(); - for message in server.iter() { - messages.push_str(&message.unwrap().to_string()); - } + server.stream().for_each(|message| { + messages.push_str(&message.to_string()); + Ok(()) + }).wait().unwrap(); assert_eq!(&messages[..], exp); } #[test] fn handle_message() { - let value = "PING :irc.test.net\r\n:irc.test.net 376 test :End of /MOTD command.\r\n"; - let server = IrcServer::from_connection(test_config(), MockConnection::new(value)); - for message in server.iter() { + let value = ":irc.test.net 376 test :End of /MOTD command.\r\n"; + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); assert_eq!( &get_server_value(server)[..], - "PONG :irc.test.net\r\nJOIN #test\r\nJOIN #test2\r\n" + "JOIN #test\r\nJOIN #test2\r\n" ); } #[test] fn handle_end_motd_with_nick_password() { let value = ":irc.test.net 376 test :End of /MOTD command.\r\n"; - let server = IrcServer::from_connection( - Config { - nick_password: Some(format!("password")), - channels: Some(vec![format!("#test"), format!("#test2")]), - ..Default::default() - }, - MockConnection::new(value), - ); - for message in server.iter() { + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + nick_password: Some(format!("password")), + channels: Some(vec![format!("#test"), format!("#test2")]), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); assert_eq!( &get_server_value(server)[..], "NICKSERV IDENTIFY password\r\nJOIN #test\r\n\ @@ -601,22 +628,21 @@ mod test { #[test] fn handle_end_motd_with_chan_keys() { let value = ":irc.test.net 376 test :End of /MOTD command\r\n"; - let server = IrcServer::from_connection( - Config { - nickname: Some(format!("test")), - channels: Some(vec![format!("#test"), format!("#test2")]), - channel_keys: { - let mut map = HashMap::new(); - map.insert(format!("#test2"), format!("password")); - Some(map) - }, - ..Default::default() + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + nickname: Some(format!("test")), + channels: Some(vec![format!("#test"), format!("#test2")]), + channel_keys: { + let mut map = HashMap::new(); + map.insert(format!("#test2"), format!("password")); + Some(map) }, - MockConnection::new(value), - ); - for message in server.iter() { + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); assert_eq!( &get_server_value(server)[..], "JOIN #test\r\nJOIN #test2 password\r\n" @@ -627,20 +653,19 @@ mod test { fn handle_end_motd_with_ghost() { let value = ":irc.pdgn.co 433 * test :Nickname is already in use.\r\n\ :irc.test.net 376 test2 :End of /MOTD command.\r\n"; - let server = IrcServer::from_connection( - Config { - nickname: Some(format!("test")), - alt_nicks: Some(vec![format!("test2")]), - nick_password: Some(format!("password")), - channels: Some(vec![format!("#test"), format!("#test2")]), - should_ghost: Some(true), - ..Default::default() - }, - MockConnection::new(value), - ); - for message in server.iter() { + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + nickname: Some(format!("test")), + alt_nicks: Some(vec![format!("test2")]), + nick_password: Some(format!("password")), + channels: Some(vec![format!("#test"), format!("#test2")]), + should_ghost: Some(true), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); assert_eq!( &get_server_value(server)[..], "NICK :test2\r\nNICKSERV GHOST test password\r\n\ @@ -652,21 +677,20 @@ mod test { fn handle_end_motd_with_ghost_seq() { let value = ":irc.pdgn.co 433 * test :Nickname is already in use.\r\n\ :irc.test.net 376 test2 :End of /MOTD command.\r\n"; - let server = IrcServer::from_connection( - Config { - nickname: Some(format!("test")), - alt_nicks: Some(vec![format!("test2")]), - nick_password: Some(format!("password")), - channels: Some(vec![format!("#test"), format!("#test2")]), - should_ghost: Some(true), - ghost_sequence: Some(vec![format!("RECOVER"), format!("RELEASE")]), - ..Default::default() - }, - MockConnection::new(value), - ); - for message in server.iter() { + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + nickname: Some(format!("test")), + alt_nicks: Some(vec![format!("test2")]), + nick_password: Some(format!("password")), + channels: Some(vec![format!("#test"), format!("#test2")]), + should_ghost: Some(true), + ghost_sequence: Some(vec![format!("RECOVER"), format!("RELEASE")]), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); assert_eq!( &get_server_value(server)[..], "NICK :test2\r\nNICKSERV RECOVER test password\ @@ -678,18 +702,17 @@ mod test { #[test] fn handle_end_motd_with_umodes() { let value = ":irc.test.net 376 test :End of /MOTD command.\r\n"; - let server = IrcServer::from_connection( - Config { - nickname: Some(format!("test")), - umodes: Some(format!("+B")), - channels: Some(vec![format!("#test"), format!("#test2")]), - ..Default::default() - }, - MockConnection::new(value), - ); - for message in server.iter() { + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + nickname: Some(format!("test")), + umodes: Some(format!("+B")), + channels: Some(vec![format!("#test"), format!("#test2")]), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); assert_eq!( &get_server_value(server)[..], "MODE test +B\r\nJOIN #test\r\nJOIN #test2\r\n" @@ -698,11 +721,15 @@ mod test { #[test] fn nickname_in_use() { - let value = ":irc.pdgn.co 433 * test :Nickname is already in use."; - let server = IrcServer::from_connection(test_config(), MockConnection::new(value)); - for message in server.iter() { + let value = ":irc.pdgn.co 433 * test :Nickname is already in use.\r\n"; + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); assert_eq!(&get_server_value(server)[..], "NICK :test2\r\n"); } @@ -711,15 +738,19 @@ mod test { fn ran_out_of_nicknames() { let value = ":irc.pdgn.co 433 * test :Nickname is already in use.\r\n\ :irc.pdgn.co 433 * test2 :Nickname is already in use.\r\n"; - let server = IrcServer::from_connection(test_config(), MockConnection::new(value)); - for message in server.iter() { + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); } #[test] fn send() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); assert!( server .send(PRIVMSG(format!("#test"), format!("Hi there!"))) @@ -733,23 +764,27 @@ mod test { #[test] fn send_no_newline_injection() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); assert!( server - .send(PRIVMSG(format!("#test"), format!("Hi there!\nJOIN #bad"))) + .send(PRIVMSG(format!("#test"), format!("Hi there!\r\nJOIN #bad"))) .is_ok() ); - assert_eq!(&get_server_value(server)[..], "PRIVMSG #test :Hi there!\n"); + assert_eq!(&get_server_value(server)[..], "PRIVMSG #test :Hi there!\r\n"); } #[test] #[cfg(not(feature = "nochanlists"))] fn channel_tracking_names() { let value = ":irc.test.net 353 test = #test :test ~owner &admin\r\n"; - let server = IrcServer::from_connection(test_config(), MockConnection::new(value)); - for message in server.iter() { + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); assert_eq!(server.list_channels().unwrap(), vec!["#test".to_owned()]) } @@ -757,10 +792,14 @@ mod test { #[cfg(not(feature = "nochanlists"))] fn channel_tracking_names_part() { let value = ":irc.test.net 353 test = #test :test ~owner &admin\r\n"; - let server = IrcServer::from_connection(test_config(), MockConnection::new(value)); - for message in server.iter() { + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); assert!(server.send(PART(format!("#test"), None)).is_ok()); assert!(server.list_channels().unwrap().is_empty()) } @@ -769,10 +808,14 @@ mod test { #[cfg(not(feature = "nochanlists"))] fn user_tracking_names() { let value = ":irc.test.net 353 test = #test :test ~owner &admin\r\n"; - let server = IrcServer::from_connection(test_config(), MockConnection::new(value)); - for message in server.iter() { + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); assert_eq!( server.list_users("#test").unwrap(), vec![User::new("test"), User::new("~owner"), User::new("&admin")] @@ -784,10 +827,14 @@ mod test { fn user_tracking_names_join() { let value = ":irc.test.net 353 test = #test :test ~owner &admin\r\n\ :test2!test@test JOIN #test\r\n"; - let server = IrcServer::from_connection(test_config(), MockConnection::new(value)); - for message in server.iter() { + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); assert_eq!( server.list_users("#test").unwrap(), vec![ @@ -804,10 +851,14 @@ mod test { fn user_tracking_names_part() { let value = ":irc.test.net 353 test = #test :test ~owner &admin\r\n\ :owner!test@test PART #test\r\n"; - let server = IrcServer::from_connection(test_config(), MockConnection::new(value)); - for message in server.iter() { + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); assert_eq!( server.list_users("#test").unwrap(), vec![User::new("test"), User::new("&admin")] @@ -819,10 +870,14 @@ mod test { fn user_tracking_names_mode() { let value = ":irc.test.net 353 test = #test :+test ~owner &admin\r\n\ :test!test@test MODE #test +o test\r\n"; - let server = IrcServer::from_connection(test_config(), MockConnection::new(value)); - for message in server.iter() { + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); assert_eq!( server.list_users("#test").unwrap(), vec![User::new("@test"), User::new("~owner"), User::new("&admin")] @@ -844,10 +899,14 @@ mod test { #[cfg(feature = "nochanlists")] fn no_user_tracking() { let value = ":irc.test.net 353 test = #test :test ~owner &admin"; - let server = IrcServer::from_connection(test_config(), MockConnection::new(value)); - for message in server.iter() { + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); assert!(server.list_users("#test").is_none()) } @@ -855,10 +914,14 @@ mod test { #[cfg(feature = "ctcp")] fn finger_response() { let value = ":test!test@test PRIVMSG test :\u{001}FINGER\u{001}\r\n"; - let server = IrcServer::from_connection(test_config(), MockConnection::new(value)); - for message in server.iter() { + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); assert_eq!( &get_server_value(server)[..], "NOTICE test :\u{001}FINGER :test (test)\u{001}\ @@ -870,10 +933,14 @@ mod test { #[cfg(feature = "ctcp")] fn version_response() { let value = ":test!test@test PRIVMSG test :\u{001}VERSION\u{001}\r\n"; - let server = IrcServer::from_connection(test_config(), MockConnection::new(value)); - for message in server.iter() { + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); assert_eq!( &get_server_value(server)[..], "NOTICE test :\u{001}VERSION irc:git:Rust\u{001}\ @@ -885,10 +952,14 @@ mod test { #[cfg(feature = "ctcp")] fn source_response() { let value = ":test!test@test PRIVMSG test :\u{001}SOURCE\u{001}\r\n"; - let server = IrcServer::from_connection(test_config(), MockConnection::new(value)); - for message in server.iter() { + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); assert_eq!( &get_server_value(server)[..], "NOTICE test :\u{001}SOURCE https://github.com/aatxe/irc\u{001}\r\n\ @@ -900,10 +971,14 @@ mod test { #[cfg(feature = "ctcp")] fn ctcp_ping_response() { let value = ":test!test@test PRIVMSG test :\u{001}PING test\u{001}\r\n"; - let server = IrcServer::from_connection(test_config(), MockConnection::new(value)); - for message in server.iter() { + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); assert_eq!( &get_server_value(server)[..], "NOTICE test :\u{001}PING test\u{001}\r\n" @@ -914,10 +989,14 @@ mod test { #[cfg(feature = "ctcp")] fn time_response() { let value = ":test!test@test PRIVMSG test :\u{001}TIME\u{001}\r\n"; - let server = IrcServer::from_connection(test_config(), MockConnection::new(value)); - for message in server.iter() { + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); let val = get_server_value(server); assert!(val.starts_with("NOTICE test :\u{001}TIME :")); assert!(val.ends_with("\u{001}\r\n")); @@ -927,10 +1006,14 @@ mod test { #[cfg(feature = "ctcp")] fn user_info_response() { let value = ":test!test@test PRIVMSG test :\u{001}USERINFO\u{001}\r\n"; - let server = IrcServer::from_connection(test_config(), MockConnection::new(value)); - for message in server.iter() { + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); assert_eq!( &get_server_value(server)[..], "NOTICE test :\u{001}USERINFO :Testing.\u{001}\ @@ -942,10 +1025,14 @@ mod test { #[cfg(feature = "ctcp")] fn ctcp_ping_no_timestamp() { let value = ":test!test@test PRIVMSG test :\u{001}PING\u{001}\r\n"; - let server = IrcServer::from_connection(test_config(), MockConnection::new(value)); - for message in server.iter() { + let server = IrcServer::from_config(Config { + mock_initial_value: Some(value.to_owned()), + ..test_config() + }).unwrap(); + server.stream().for_each(|message| { println!("{:?}", message); - } + Ok(()) + }).wait().unwrap(); assert_eq!(&get_server_value(server)[..], ""); } } diff --git a/src/client/server/utils.rs b/src/client/server/utils.rs index 7d7a806..34399bf 100644 --- a/src/client/server/utils.rs +++ b/src/client/server/utils.rs @@ -354,15 +354,13 @@ where #[cfg(test)] mod test { use super::ServerExt; - use std::default::Default; - use client::conn::MockConnection; use client::data::Config; use client::server::IrcServer; use client::server::test::{get_server_value, test_config}; #[test] fn identify() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.identify().unwrap(); assert_eq!( &get_server_value(server)[..], @@ -373,14 +371,11 @@ mod test { #[test] fn identify_with_password() { - let server = IrcServer::from_connection( - Config { - nickname: Some(format!("test")), - password: Some(format!("password")), - ..Default::default() - }, - MockConnection::empty(), - ); + let server = IrcServer::from_config(Config { + nickname: Some(format!("test")), + password: Some(format!("password")), + ..test_config() + }).unwrap(); server.identify().unwrap(); assert_eq!( &get_server_value(server)[..], @@ -391,14 +386,14 @@ mod test { #[test] fn send_pong() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_pong("irc.test.net").unwrap(); assert_eq!(&get_server_value(server)[..], "PONG :irc.test.net\r\n"); } #[test] fn send_join() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_join("#test,#test2,#test3").unwrap(); assert_eq!( &get_server_value(server)[..], @@ -408,21 +403,21 @@ mod test { #[test] fn send_part() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_part("#test").unwrap(); assert_eq!(&get_server_value(server)[..], "PART #test\r\n"); } #[test] fn send_oper() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_oper("test", "test").unwrap(); assert_eq!(&get_server_value(server)[..], "OPER test :test\r\n"); } #[test] fn send_privmsg() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_privmsg("#test", "Hi, everybody!").unwrap(); assert_eq!( &get_server_value(server)[..], @@ -432,7 +427,7 @@ mod test { #[test] fn send_notice() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_notice("#test", "Hi, everybody!").unwrap(); assert_eq!( &get_server_value(server)[..], @@ -442,14 +437,14 @@ mod test { #[test] fn send_topic_no_topic() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_topic("#test", "").unwrap(); assert_eq!(&get_server_value(server)[..], "TOPIC #test\r\n"); } #[test] fn send_topic() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_topic("#test", "Testing stuff.").unwrap(); assert_eq!( &get_server_value(server)[..], @@ -459,7 +454,7 @@ mod test { #[test] fn send_kill() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_kill("test", "Testing kills.").unwrap(); assert_eq!( &get_server_value(server)[..], @@ -469,14 +464,14 @@ mod test { #[test] fn send_kick_no_message() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_kick("#test", "test", "").unwrap(); assert_eq!(&get_server_value(server)[..], "KICK #test test\r\n"); } #[test] fn send_kick() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_kick("#test", "test", "Testing kicks.").unwrap(); assert_eq!( &get_server_value(server)[..], @@ -486,42 +481,42 @@ mod test { #[test] fn send_mode_no_modeparams() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_mode("#test", "+i", "").unwrap(); assert_eq!(&get_server_value(server)[..], "MODE #test +i\r\n"); } #[test] fn send_mode() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_mode("#test", "+o", "test").unwrap(); assert_eq!(&get_server_value(server)[..], "MODE #test +o test\r\n"); } #[test] fn send_samode_no_modeparams() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_samode("#test", "+i", "").unwrap(); assert_eq!(&get_server_value(server)[..], "SAMODE #test +i\r\n"); } #[test] fn send_samode() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_samode("#test", "+o", "test").unwrap(); assert_eq!(&get_server_value(server)[..], "SAMODE #test +o test\r\n"); } #[test] fn send_sanick() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_sanick("test", "test2").unwrap(); assert_eq!(&get_server_value(server)[..], "SANICK test test2\r\n"); } #[test] fn send_invite() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_invite("test", "#test").unwrap(); assert_eq!(&get_server_value(server)[..], "INVITE test #test\r\n"); } @@ -529,7 +524,7 @@ mod test { #[test] #[cfg(feature = "ctcp")] fn send_ctcp() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_ctcp("test", "MESSAGE").unwrap(); assert_eq!( &get_server_value(server)[..], @@ -540,7 +535,7 @@ mod test { #[test] #[cfg(feature = "ctcp")] fn send_action() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_action("test", "tests.").unwrap(); assert_eq!( &get_server_value(server)[..], @@ -551,7 +546,7 @@ mod test { #[test] #[cfg(feature = "ctcp")] fn send_finger() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_finger("test").unwrap(); assert_eq!( &get_server_value(server)[..], @@ -562,7 +557,7 @@ mod test { #[test] #[cfg(feature = "ctcp")] fn send_version() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_version("test").unwrap(); assert_eq!( &get_server_value(server)[..], @@ -573,7 +568,7 @@ mod test { #[test] #[cfg(feature = "ctcp")] fn send_source() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_source("test").unwrap(); assert_eq!( &get_server_value(server)[..], @@ -584,7 +579,7 @@ mod test { #[test] #[cfg(feature = "ctcp")] fn send_user_info() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_user_info("test").unwrap(); assert_eq!( &get_server_value(server)[..], @@ -595,7 +590,7 @@ mod test { #[test] #[cfg(feature = "ctcp")] fn send_ctcp_ping() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_ctcp_ping("test").unwrap(); let val = get_server_value(server); println!("{}", val); @@ -606,7 +601,7 @@ mod test { #[test] #[cfg(feature = "ctcp")] fn send_time() { - let server = IrcServer::from_connection(test_config(), MockConnection::empty()); + let server = IrcServer::from_config(test_config()).unwrap(); server.send_time("test").unwrap(); assert_eq!( &get_server_value(server)[..], diff --git a/src/client/transport.rs b/src/client/transport.rs index b9f4d41..99f5e17 100644 --- a/src/client/transport.rs +++ b/src/client/transport.rs @@ -1,5 +1,6 @@ //! An IRC transport that wraps an IRC-framed stream to provide automatic PING replies. use std::io; +use std::sync::{Arc, RwLock, RwLockReadGuard}; use std::time::Instant; use error; use client::data::Config; @@ -30,6 +31,11 @@ where last_ping: Instant::now(), } } + + /// Gets the inner stream underlying the `IrcTransport`. + pub fn into_inner(self) -> Framed { + self.inner + } } impl Stream for IrcTransport @@ -76,3 +82,93 @@ where Ok(self.inner.poll_complete()?) } } + +/// A view of the logs from a particular `Logged` transport. +#[derive(Clone, Debug)] +pub struct LogView { + sent: Arc>>, + received: Arc>>, +} + +impl LogView { + /// Gets a read guard for all the messages sent on the transport. + pub fn sent(&self) -> error::Result>> { + self.sent.read().map_err(|_| + error::ErrorKind::PoisonedLog.into() + ) + } + + /// Gets a read guard for all the messages received on the transport. + pub fn received(&self) -> error::Result>> { + self.received.read().map_err(|_| + error::ErrorKind::PoisonedLog.into() + ) + } +} + +/// A logged version of the `IrcTransport` that records all sent and received messages. +/// Note: this will introduce some performance overhead by cloning all messages. +pub struct Logged where T: AsyncRead + AsyncWrite { + inner: IrcTransport, + view: LogView, +} + +impl Logged where T: AsyncRead + AsyncWrite { + /// Wraps the given `IrcTransport` in logging. + pub fn wrap(inner: IrcTransport) -> Logged { + Logged { + inner: inner, + view: LogView { + sent: Arc::new(RwLock::new(vec![])), + received: Arc::new(RwLock::new(vec![])), + } + } + } + + /// Gets a view of the logging for this transport. + pub fn view(&self) -> LogView { + self.view.clone() + } +} + +impl Stream for Logged + where + T: AsyncRead + AsyncWrite, +{ + type Item = Message; + type Error = error::Error; + + fn poll(&mut self) -> Poll, Self::Error> { + match try_ready!(self.inner.poll()) { + Some(msg) => { + let recv: error::Result<_> = self.view.received.write().map_err(|_| + error::ErrorKind::PoisonedLog.into() + ); + recv?.push(msg.clone()); + Ok(Async::Ready(Some(msg))) + }, + None => Ok(Async::Ready(None)) + } + } +} + +impl Sink for Logged + where + T: AsyncRead + AsyncWrite, +{ + type SinkItem = Message; + type SinkError = error::Error; + + fn start_send(&mut self, item: Self::SinkItem) -> StartSend { + let res = self.inner.start_send(item.clone())?; + let sent: error::Result<_> = self.view.sent.write().map_err(|_| + error::ErrorKind::PoisonedLog.into() + ); + sent?.push(item); + Ok(res) + } + + fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { + Ok(self.inner.poll_complete()?) + } +} diff --git a/src/error.rs b/src/error.rs index 85e886a..6a5d256 100644 --- a/src/error.rs +++ b/src/error.rs @@ -35,5 +35,11 @@ error_chain! { description("An error occured on one of the IrcServer's internal channels.") display("An error occured on one of the IrcServer's internal channels.") } + + /// An error occured causing a mutex for a logged transport to be poisoned. + PoisonedLog { + description("An error occured causing a mutex for a logged transport to be poisoned.") + display("An error occured causing a mutex for a logged transport to be poisoned.") + } } }