diff -r d5e6c8c92d87 -r ea459da15b30 rust/hedgewars-network-protocol/src/tests/test.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hedgewars-network-protocol/src/tests/test.rs Mon Jan 31 18:24:49 2022 +0300 @@ -0,0 +1,325 @@ +use crate::{ + messages::{HwProtocolMessage, HwServerMessage}, + parser::{message, server_message}, + types::ServerVar::*, + types::*, + types::{GameCfg, ServerVar, TeamInfo, VoteType}, +}; + +use proptest::{ + arbitrary::{any, Arbitrary}, + proptest, + strategy::{BoxedStrategy, Just, Strategy}, +}; + +// Due to inability to define From between Options +pub trait Into2: Sized { + fn into2(self) -> T; +} +impl Into2 for T { + fn into2(self) -> T { + self + } +} +impl Into2> for Vec { + fn into2(self) -> Vec { + self.into_iter().map(|x| x.0).collect() + } +} +impl Into2 for Ascii { + fn into2(self) -> String { + self.0 + } +} +impl Into2> for Option { + fn into2(self) -> Option { + self.map(|x| x.0) + } +} + +#[macro_export] +macro_rules! proto_msg_case { + ($val: ident()) => { + Just($val) + }; + ($val: ident($arg: ty)) => { + any::<$arg>().prop_map(|v| $val(v.into2())) + }; + ($val: ident($arg1: ty, $arg2: ty)) => { + any::<($arg1, $arg2)>().prop_map(|v| $val(v.0.into2(), v.1.into2())) + }; + ($val: ident($arg1: ty, $arg2: ty, $arg3: ty)) => { + any::<($arg1, $arg2, $arg3)>().prop_map(|v| $val(v.0.into2(), v.1.into2(), v.2.into2())) + }; +} + +macro_rules! proto_msg_match { +($var: expr, def = $default: expr, $($num: expr => $constr: ident $res: tt),*) => ( + match $var { + $($num => (proto_msg_case!($constr $res)).boxed()),*, + _ => Just($default).boxed() + } +) +} + +/// Wrapper type for generating non-empty strings +#[derive(Debug)] +pub struct Ascii(String); + +impl Arbitrary for Ascii { + type Parameters = ::Parameters; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + "[a-zA-Z0-9]+".prop_map(Ascii).boxed() + } + + type Strategy = BoxedStrategy; +} + +impl Arbitrary for GameCfg { + type Parameters = (); + + fn arbitrary_with(_args: ::Parameters) -> ::Strategy { + use crate::types::GameCfg::*; + (0..10) + .no_shrink() + .prop_flat_map(|i| { + proto_msg_match!(i, def = FeatureSize(0), + 0 => FeatureSize(u32), + 1 => MapType(Ascii), + 2 => MapGenerator(u32), + 3 => MazeSize(u32), + 4 => Seed(Ascii), + 5 => Template(u32), + 6 => Ammo(Ascii, Option), + 7 => Scheme(Ascii, Vec), + 8 => Script(Ascii), + 9 => Theme(Ascii), + 10 => DrawnMap(Ascii)) + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} + +impl Arbitrary for TeamInfo { + type Parameters = (); + + fn arbitrary_with(_args: ::Parameters) -> ::Strategy { + ( + "[a-z]+", + 0u8..127u8, + "[a-z]+", + "[a-z]+", + "[a-z]+", + "[a-z]+", + 0u8..127u8, + ) + .prop_map(|(name, color, grave, fort, voice_pack, flag, difficulty)| { + fn hog(n: u8) -> HedgehogInfo { + HedgehogInfo { + name: format!("hog{}", n), + hat: format!("hat{}", n), + } + } + let hedgehogs = [ + hog(1), + hog(2), + hog(3), + hog(4), + hog(5), + hog(6), + hog(7), + hog(8), + ]; + TeamInfo { + owner: String::new(), + name, + color, + grave, + fort, + voice_pack, + flag, + difficulty, + hedgehogs, + hedgehogs_number: 0, + } + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} + +impl Arbitrary for ServerVar { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + (0..=2) + .no_shrink() + .prop_flat_map(|i| { + proto_msg_match!(i, def = ServerVar::LatestProto(0), + 0 => MOTDNew(Ascii), + 1 => MOTDOld(Ascii), + 2 => LatestProto(u16) + ) + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} + +impl Arbitrary for VoteType { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + use VoteType::*; + (0..=4) + .no_shrink() + .prop_flat_map(|i| { + proto_msg_match!(i, def = VoteType::Pause, + 0 => Kick(Ascii), + 1 => Map(Option), + 2 => Pause(), + 3 => NewSeed(), + 4 => HedgehogsPerTeam(u8) + ) + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} + +pub fn gen_proto_msg() -> BoxedStrategy where { + use HwProtocolMessage::*; + + let res = (0..=58).no_shrink().prop_flat_map(|i| { + proto_msg_match!(i, def = Ping, + 0 => Ping(), + 1 => Pong(), + 2 => Quit(Option), + 4 => Global(Ascii), + 5 => Watch(u32), + 6 => ToggleServerRegisteredOnly(), + 7 => SuperPower(), + 8 => Info(Ascii), + 9 => Nick(Ascii), + 10 => Proto(u16), + 11 => Password(Ascii, Ascii), + 12 => Checker(u16, Ascii, Ascii), + 13 => List(), + 14 => Chat(Ascii), + 15 => CreateRoom(Ascii, Option), + 16 => JoinRoom(Ascii, Option), + 17 => Follow(Ascii), + 18 => Rnd(Vec), + 19 => Kick(Ascii), + 20 => Ban(Ascii, Ascii, u32), + 21 => BanIp(Ascii, Ascii, u32), + 22 => BanNick(Ascii, Ascii, u32), + 23 => BanList(), + 24 => Unban(Ascii), + 25 => SetServerVar(ServerVar), + 26 => GetServerVar(), + 27 => RestartServer(), + 28 => Stats(), + 29 => Part(Option), + 30 => Cfg(GameCfg), + 31 => AddTeam(Box), + 32 => RemoveTeam(Ascii), + 33 => SetHedgehogsNumber(Ascii, u8), + 34 => SetTeamColor(Ascii, u8), + 35 => ToggleReady(), + 36 => StartGame(), + 37 => EngineMessage(Ascii), + 38 => RoundFinished(), + 39 => ToggleRestrictJoin(), + 40 => ToggleRestrictTeams(), + 41 => ToggleRegisteredOnly(), + 42 => RoomName(Ascii), + 43 => Delegate(Ascii), + 44 => TeamChat(Ascii), + 45 => MaxTeams(u8), + 46 => Fix(), + 47 => Unfix(), + 48 => Greeting(Option), + 49 => CallVote(Option), + 50 => Vote(bool), + 51 => ForceVote(bool), + 52 => Save(Ascii, Ascii), + 53 => Delete(Ascii), + 54 => SaveRoom(Ascii), + 55 => LoadRoom(Ascii), + 56 => CheckerReady(), + 57 => CheckedOk(Vec), + 58 => CheckedFail(Ascii) + ) + }); + res.boxed() +} + +pub fn gen_server_msg() -> BoxedStrategy where { + use HwServerMessage::*; + + let res = (0..=38).no_shrink().prop_flat_map(|i| { + proto_msg_match!(i, def = Ping, + 0 => Connected(Ascii, u32), + 1 => Redirect(u16), + 2 => Ping(), + 3 => Pong(), + 4 => Bye(Ascii), + 5 => Nick(Ascii), + 6 => Proto(u16), + 7 => AskPassword(Ascii), + 8 => ServerAuth(Ascii), + 9 => LogonPassed(), + 10 => LobbyLeft(Ascii, Ascii), + 11 => LobbyJoined(Vec), + // 12 => ChatMsg { Ascii, Ascii }, + 13 => ClientFlags(Ascii, Vec), + 14 => Rooms(Vec), + 15 => RoomAdd(Vec), + 16=> RoomJoined(Vec), + 17 => RoomLeft(Ascii, Ascii), + 18 => RoomRemove(Ascii), + 19 => RoomUpdated(Ascii, Vec), + 20 => Joining(Ascii), + 21 => TeamAdd(Vec), + 22 => TeamRemove(Ascii), + 23 => TeamAccepted(Ascii), + 24 => TeamColor(Ascii, u8), + 25 => HedgehogsNumber(Ascii, u8), + 26 => ConfigEntry(Ascii, Vec), + 27 => Kicked(), + 28 => RunGame(), + 29 => ForwardEngineMessage(Vec), + 30 => RoundFinished(), + 31 => ReplayStart(), + 32 => Info(Vec), + 33 => ServerMessage(Ascii), + 34 => ServerVars(Vec), + 35 => Notice(Ascii), + 36 => Warning(Ascii), + 37 => Error(Ascii), + 38 => Replay(Vec) + ) + }); + res.boxed() +} + +proptest! { + #[test] + fn is_parser_composition_idempotent(ref msg in gen_proto_msg()) { + println!("!! Msg: {:?}, Bytes: {:?} !!", msg, msg.to_raw_protocol().as_bytes()); + assert_eq!(message(msg.to_raw_protocol().as_bytes()), Ok((&b""[..], msg.clone()))) + } + + #[test] + fn is_server_message_parser_composition_idempotent(ref msg in gen_server_msg()) { + println!("!! Msg: {:?}, Bytes: {:?} !!", msg, msg.to_raw_protocol().as_bytes()); + assert_eq!(server_message(msg.to_raw_protocol().as_bytes()), Ok((&b""[..], msg.clone()))) + } +}