use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::net::tcp::{ReadHalf, WriteHalf}; use tokio::net::TcpStream; use crate::prelude::*; struct TestScope<'a> { reader: BufReader>, writer: WriteHalf<'a>, buffer: Vec, } impl<'a> TestScope<'a> { async fn send(&mut self, str: &(impl AsRef + ?Sized)) -> Result<()> { self.writer.write_all(str.as_ref().as_bytes()).await?; self.writer.flush().await?; Ok(()) } async fn expect(&mut self, str: &(impl AsRef + ?Sized)) -> Result<()> { let len = self.reader.read_until(b'\n', &mut self.buffer).await?; assert_eq!(std::str::from_utf8(&self.buffer[0..len])?, str.as_ref()); self.buffer.clear(); Ok(()) } async fn assert(&mut self, f: impl FnOnce(&str) -> Result<()>) -> Result<()> { let len = self.reader.read_until(b'\n', &mut self.buffer).await?; let res = f(std::str::from_utf8(&self.buffer[0..len])?); self.buffer.clear(); res } } async fn init_client(stream: &mut TcpStream) -> Result { let (reader, writer) = stream.split(); let reader = BufReader::new(reader); let buffer = vec![]; Ok(TestScope { reader, writer, buffer, }) } macro_rules! send { ($scope: expr, $($arg:tt),*) => {{ $scope.send(&format!($($arg,)*)).await?; }}; } macro_rules! expect { ($scope: expr, $($arg:tt),*) => {{ $scope.expect(&format!($($arg,)*)).await?; }}; } const SERVER_HOSTNAME: &'static str = "irc.localhost"; #[rustfmt::skip] async fn registration(scope: &mut TestScope<'_>, nickname: &str) -> Result<()> { expect!(scope, ":irc.localhost NOTICE * :Welcome to my server!\n"); send!(scope, "NICK {nickname}\n"); send!(scope, "USER UserName 0 * :Real Name\n"); expect!(scope, ":irc.localhost 001 {nickname} :Welcome to Kek Server\n"); expect!(scope, ":irc.localhost 002 {nickname} :Welcome to Kek Server\n"); expect!(scope, ":irc.localhost 003 {nickname} :Welcome to Kek Server\n"); expect!(scope, ":irc.localhost 004 {nickname} irc.localhost kek-0.1.alpha.3 r CFILPQbcefgijklmnopqrstvz\n"); expect!(scope, ":irc.localhost 005 {nickname} CHANTYPES=# :are supported by this server\n"); Ok(()) } #[rustfmt::skip] async fn join(scope: &mut TestScope<'_>, nickname: &str, chan: &str) -> Result<()> { send!(scope, "JOIN #{chan}\n"); expect!(scope, ":{nickname} JOIN #{chan}\n"); expect!(scope, ":irc.localhost 332 {nickname} #{chan} :New room\n"); expect!(scope, ":irc.localhost 353 {nickname} = #{chan} :{nickname}\n"); expect!(scope, ":irc.localhost 366 {nickname} #{chan} :End of /NAMES list\n"); Ok(()) } #[tokio::test] async fn test_registration() -> Result<()> { let mut stream = TcpStream::connect("127.0.0.1:6667").await?; let mut scope = init_client(&mut stream).await?; registration(&mut scope, "NickName1").await?; join(&mut scope, "NickName1", "chan1").await?; Ok(()) } #[rustfmt::skip] #[tokio::test] async fn test_two_connections_one_player() -> Result<()> { let mut stream1 = TcpStream::connect("127.0.0.1:6667").await?; let mut scope1 = init_client(&mut stream1).await?; let mut stream2 = TcpStream::connect("127.0.0.1:6667").await?; let mut scope2 = init_client(&mut stream2).await?; let nickname = "NickName2"; let chan = "chan2"; registration(&mut scope1, nickname).await?; registration(&mut scope2, nickname).await?; join(&mut scope1, nickname, chan).await?; // force join on second connection when the other one joins a room expect!(scope2, ":{nickname} JOIN #{chan}\n"); expect!(scope2, ":{SERVER_HOSTNAME} 332 {nickname} #{chan} :New room\n"); expect!(scope2, ":{SERVER_HOSTNAME} 353 {nickname} = #{chan} :{nickname}\n"); expect!(scope2, ":{SERVER_HOSTNAME} 366 {nickname} #{chan} :End of /NAMES list\n"); // force send PRIVMSG to other connections send!(scope1, "PRIVMSG #{chan} :Chmoki vsem v etam chati!\n"); expect!(scope2, ":{nickname} PRIVMSG #{chan} :Chmoki vsem v etam chati!\n"); send!(scope2, "PRIVMSG #{chan} :I tebe privetiki\n"); expect!(scope1, ":{nickname} PRIVMSG #{chan} :I tebe privetiki\n"); let mut stream3 = TcpStream::connect("127.0.0.1:6667").await?; let mut scope3 = init_client(&mut stream3).await?; registration(&mut scope3, nickname).await?; // force join on registration expect!(scope3, ":{nickname} JOIN #{chan}\n"); expect!(scope3, ":{SERVER_HOSTNAME} 332 {nickname} #{chan} :New room\n"); expect!(scope3, ":{SERVER_HOSTNAME} 353 {nickname} = #{chan} :{nickname}\n"); expect!(scope3, ":{SERVER_HOSTNAME} 366 {nickname} #{chan} :End of /NAMES list\n"); Ok(()) } #[rustfmt::skip] #[tokio::test] async fn test_two_players() -> Result<()> { let mut stream1 = TcpStream::connect("127.0.0.1:6667").await?; let mut scope1 = init_client(&mut stream1).await?; let mut stream2 = TcpStream::connect("127.0.0.1:6667").await?; let mut scope2 = init_client(&mut stream2).await?; let nickname1 = "NickName3"; let nickname2 = "NickName4"; let chan = "chan3"; registration(&mut scope1, "NickName3").await?; registration(&mut scope2, "NickName4").await?; join(&mut scope1, nickname1, "chan3").await?; send!(scope2, "JOIN #{chan}\n"); expect!(scope2, ":{nickname2} JOIN #{chan}\n"); expect!(scope2, ":irc.localhost 332 {nickname2} #{chan} :New room\n"); scope2.assert(|line| { if line == format!(":irc.localhost 353 {nickname2} = #{chan} :{nickname1} {nickname2}\n") { return Ok(()) } if line == format!(":irc.localhost 353 {nickname2} = #{chan} :{nickname2} {nickname1}\n") { return Ok(()) } panic!("incorrect chan member list received: {line}"); }).await?; expect!(scope2, ":irc.localhost 366 {nickname2} #{chan} :End of /NAMES list\n"); expect!(scope1, ":{nickname2} JOIN #{chan}\n"); send!(scope1, "PRIVMSG #chan3 :Chmoki vsem v etam chati!\n"); expect!(scope2, ":{nickname1} PRIVMSG #chan3 :Chmoki vsem v etam chati!\n"); send!(scope2, "PRIVMSG #chan3 :I tebe privetiki\n"); expect!(scope1, ":{nickname2} PRIVMSG #chan3 :I tebe privetiki\n"); Ok(()) }