|
1 use crate::{ |
|
2 messages::{HwProtocolMessage, HwServerMessage}, |
|
3 parser::{message, server_message}, |
|
4 types::ServerVar::*, |
|
5 types::*, |
|
6 types::{GameCfg, ServerVar, TeamInfo, VoteType}, |
|
7 }; |
|
8 |
|
9 use proptest::{ |
|
10 arbitrary::{any, Arbitrary}, |
|
11 proptest, |
|
12 strategy::{BoxedStrategy, Just, Strategy}, |
|
13 }; |
|
14 |
|
15 // Due to inability to define From between Options |
|
16 pub trait Into2<T>: Sized { |
|
17 fn into2(self) -> T; |
|
18 } |
|
19 impl<T> Into2<T> for T { |
|
20 fn into2(self) -> T { |
|
21 self |
|
22 } |
|
23 } |
|
24 impl Into2<Vec<String>> for Vec<Ascii> { |
|
25 fn into2(self) -> Vec<String> { |
|
26 self.into_iter().map(|x| x.0).collect() |
|
27 } |
|
28 } |
|
29 impl Into2<String> for Ascii { |
|
30 fn into2(self) -> String { |
|
31 self.0 |
|
32 } |
|
33 } |
|
34 impl Into2<Option<String>> for Option<Ascii> { |
|
35 fn into2(self) -> Option<String> { |
|
36 self.map(|x| x.0) |
|
37 } |
|
38 } |
|
39 |
|
40 #[macro_export] |
|
41 macro_rules! proto_msg_case { |
|
42 ($val: ident()) => { |
|
43 Just($val) |
|
44 }; |
|
45 ($val: ident($arg: ty)) => { |
|
46 any::<$arg>().prop_map(|v| $val(v.into2())) |
|
47 }; |
|
48 ($val: ident($arg1: ty, $arg2: ty)) => { |
|
49 any::<($arg1, $arg2)>().prop_map(|v| $val(v.0.into2(), v.1.into2())) |
|
50 }; |
|
51 ($val: ident($arg1: ty, $arg2: ty, $arg3: ty)) => { |
|
52 any::<($arg1, $arg2, $arg3)>().prop_map(|v| $val(v.0.into2(), v.1.into2(), v.2.into2())) |
|
53 }; |
|
54 } |
|
55 |
|
56 macro_rules! proto_msg_match { |
|
57 ($var: expr, def = $default: expr, $($num: expr => $constr: ident $res: tt),*) => ( |
|
58 match $var { |
|
59 $($num => (proto_msg_case!($constr $res)).boxed()),*, |
|
60 _ => Just($default).boxed() |
|
61 } |
|
62 ) |
|
63 } |
|
64 |
|
65 /// Wrapper type for generating non-empty strings |
|
66 #[derive(Debug)] |
|
67 pub struct Ascii(String); |
|
68 |
|
69 impl Arbitrary for Ascii { |
|
70 type Parameters = <String as Arbitrary>::Parameters; |
|
71 |
|
72 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { |
|
73 "[a-zA-Z0-9]+".prop_map(Ascii).boxed() |
|
74 } |
|
75 |
|
76 type Strategy = BoxedStrategy<Ascii>; |
|
77 } |
|
78 |
|
79 impl Arbitrary for GameCfg { |
|
80 type Parameters = (); |
|
81 |
|
82 fn arbitrary_with(_args: <Self as Arbitrary>::Parameters) -> <Self as Arbitrary>::Strategy { |
|
83 use crate::types::GameCfg::*; |
|
84 (0..10) |
|
85 .no_shrink() |
|
86 .prop_flat_map(|i| { |
|
87 proto_msg_match!(i, def = FeatureSize(0), |
|
88 0 => FeatureSize(u32), |
|
89 1 => MapType(Ascii), |
|
90 2 => MapGenerator(u32), |
|
91 3 => MazeSize(u32), |
|
92 4 => Seed(Ascii), |
|
93 5 => Template(u32), |
|
94 6 => Ammo(Ascii, Option<Ascii>), |
|
95 7 => Scheme(Ascii, Vec<Ascii>), |
|
96 8 => Script(Ascii), |
|
97 9 => Theme(Ascii), |
|
98 10 => DrawnMap(Ascii)) |
|
99 }) |
|
100 .boxed() |
|
101 } |
|
102 |
|
103 type Strategy = BoxedStrategy<GameCfg>; |
|
104 } |
|
105 |
|
106 impl Arbitrary for TeamInfo { |
|
107 type Parameters = (); |
|
108 |
|
109 fn arbitrary_with(_args: <Self as Arbitrary>::Parameters) -> <Self as Arbitrary>::Strategy { |
|
110 ( |
|
111 "[a-z]+", |
|
112 0u8..127u8, |
|
113 "[a-z]+", |
|
114 "[a-z]+", |
|
115 "[a-z]+", |
|
116 "[a-z]+", |
|
117 0u8..127u8, |
|
118 ) |
|
119 .prop_map(|(name, color, grave, fort, voice_pack, flag, difficulty)| { |
|
120 fn hog(n: u8) -> HedgehogInfo { |
|
121 HedgehogInfo { |
|
122 name: format!("hog{}", n), |
|
123 hat: format!("hat{}", n), |
|
124 } |
|
125 } |
|
126 let hedgehogs = [ |
|
127 hog(1), |
|
128 hog(2), |
|
129 hog(3), |
|
130 hog(4), |
|
131 hog(5), |
|
132 hog(6), |
|
133 hog(7), |
|
134 hog(8), |
|
135 ]; |
|
136 TeamInfo { |
|
137 owner: String::new(), |
|
138 name, |
|
139 color, |
|
140 grave, |
|
141 fort, |
|
142 voice_pack, |
|
143 flag, |
|
144 difficulty, |
|
145 hedgehogs, |
|
146 hedgehogs_number: 0, |
|
147 } |
|
148 }) |
|
149 .boxed() |
|
150 } |
|
151 |
|
152 type Strategy = BoxedStrategy<TeamInfo>; |
|
153 } |
|
154 |
|
155 impl Arbitrary for ServerVar { |
|
156 type Parameters = (); |
|
157 |
|
158 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { |
|
159 (0..=2) |
|
160 .no_shrink() |
|
161 .prop_flat_map(|i| { |
|
162 proto_msg_match!(i, def = ServerVar::LatestProto(0), |
|
163 0 => MOTDNew(Ascii), |
|
164 1 => MOTDOld(Ascii), |
|
165 2 => LatestProto(u16) |
|
166 ) |
|
167 }) |
|
168 .boxed() |
|
169 } |
|
170 |
|
171 type Strategy = BoxedStrategy<ServerVar>; |
|
172 } |
|
173 |
|
174 impl Arbitrary for VoteType { |
|
175 type Parameters = (); |
|
176 |
|
177 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { |
|
178 use VoteType::*; |
|
179 (0..=4) |
|
180 .no_shrink() |
|
181 .prop_flat_map(|i| { |
|
182 proto_msg_match!(i, def = VoteType::Pause, |
|
183 0 => Kick(Ascii), |
|
184 1 => Map(Option<Ascii>), |
|
185 2 => Pause(), |
|
186 3 => NewSeed(), |
|
187 4 => HedgehogsPerTeam(u8) |
|
188 ) |
|
189 }) |
|
190 .boxed() |
|
191 } |
|
192 |
|
193 type Strategy = BoxedStrategy<VoteType>; |
|
194 } |
|
195 |
|
196 pub fn gen_proto_msg() -> BoxedStrategy<HwProtocolMessage> where { |
|
197 use HwProtocolMessage::*; |
|
198 |
|
199 let res = (0..=58).no_shrink().prop_flat_map(|i| { |
|
200 proto_msg_match!(i, def = Ping, |
|
201 0 => Ping(), |
|
202 1 => Pong(), |
|
203 2 => Quit(Option<Ascii>), |
|
204 4 => Global(Ascii), |
|
205 5 => Watch(u32), |
|
206 6 => ToggleServerRegisteredOnly(), |
|
207 7 => SuperPower(), |
|
208 8 => Info(Ascii), |
|
209 9 => Nick(Ascii), |
|
210 10 => Proto(u16), |
|
211 11 => Password(Ascii, Ascii), |
|
212 12 => Checker(u16, Ascii, Ascii), |
|
213 13 => List(), |
|
214 14 => Chat(Ascii), |
|
215 15 => CreateRoom(Ascii, Option<Ascii>), |
|
216 16 => JoinRoom(Ascii, Option<Ascii>), |
|
217 17 => Follow(Ascii), |
|
218 18 => Rnd(Vec<Ascii>), |
|
219 19 => Kick(Ascii), |
|
220 20 => Ban(Ascii, Ascii, u32), |
|
221 21 => BanIp(Ascii, Ascii, u32), |
|
222 22 => BanNick(Ascii, Ascii, u32), |
|
223 23 => BanList(), |
|
224 24 => Unban(Ascii), |
|
225 25 => SetServerVar(ServerVar), |
|
226 26 => GetServerVar(), |
|
227 27 => RestartServer(), |
|
228 28 => Stats(), |
|
229 29 => Part(Option<Ascii>), |
|
230 30 => Cfg(GameCfg), |
|
231 31 => AddTeam(Box<TeamInfo>), |
|
232 32 => RemoveTeam(Ascii), |
|
233 33 => SetHedgehogsNumber(Ascii, u8), |
|
234 34 => SetTeamColor(Ascii, u8), |
|
235 35 => ToggleReady(), |
|
236 36 => StartGame(), |
|
237 37 => EngineMessage(Ascii), |
|
238 38 => RoundFinished(), |
|
239 39 => ToggleRestrictJoin(), |
|
240 40 => ToggleRestrictTeams(), |
|
241 41 => ToggleRegisteredOnly(), |
|
242 42 => RoomName(Ascii), |
|
243 43 => Delegate(Ascii), |
|
244 44 => TeamChat(Ascii), |
|
245 45 => MaxTeams(u8), |
|
246 46 => Fix(), |
|
247 47 => Unfix(), |
|
248 48 => Greeting(Option<Ascii>), |
|
249 49 => CallVote(Option<VoteType>), |
|
250 50 => Vote(bool), |
|
251 51 => ForceVote(bool), |
|
252 52 => Save(Ascii, Ascii), |
|
253 53 => Delete(Ascii), |
|
254 54 => SaveRoom(Ascii), |
|
255 55 => LoadRoom(Ascii), |
|
256 56 => CheckerReady(), |
|
257 57 => CheckedOk(Vec<Ascii>), |
|
258 58 => CheckedFail(Ascii) |
|
259 ) |
|
260 }); |
|
261 res.boxed() |
|
262 } |
|
263 |
|
264 pub fn gen_server_msg() -> BoxedStrategy<HwServerMessage> where { |
|
265 use HwServerMessage::*; |
|
266 |
|
267 let res = (0..=38).no_shrink().prop_flat_map(|i| { |
|
268 proto_msg_match!(i, def = Ping, |
|
269 0 => Connected(Ascii, u32), |
|
270 1 => Redirect(u16), |
|
271 2 => Ping(), |
|
272 3 => Pong(), |
|
273 4 => Bye(Ascii), |
|
274 5 => Nick(Ascii), |
|
275 6 => Proto(u16), |
|
276 7 => AskPassword(Ascii), |
|
277 8 => ServerAuth(Ascii), |
|
278 9 => LogonPassed(), |
|
279 10 => LobbyLeft(Ascii, Ascii), |
|
280 11 => LobbyJoined(Vec<Ascii>), |
|
281 // 12 => ChatMsg { Ascii, Ascii }, |
|
282 13 => ClientFlags(Ascii, Vec<Ascii>), |
|
283 14 => Rooms(Vec<Ascii>), |
|
284 15 => RoomAdd(Vec<Ascii>), |
|
285 16=> RoomJoined(Vec<Ascii>), |
|
286 17 => RoomLeft(Ascii, Ascii), |
|
287 18 => RoomRemove(Ascii), |
|
288 19 => RoomUpdated(Ascii, Vec<Ascii>), |
|
289 20 => Joining(Ascii), |
|
290 21 => TeamAdd(Vec<Ascii>), |
|
291 22 => TeamRemove(Ascii), |
|
292 23 => TeamAccepted(Ascii), |
|
293 24 => TeamColor(Ascii, u8), |
|
294 25 => HedgehogsNumber(Ascii, u8), |
|
295 26 => ConfigEntry(Ascii, Vec<Ascii>), |
|
296 27 => Kicked(), |
|
297 28 => RunGame(), |
|
298 29 => ForwardEngineMessage(Vec<Ascii>), |
|
299 30 => RoundFinished(), |
|
300 31 => ReplayStart(), |
|
301 32 => Info(Vec<Ascii>), |
|
302 33 => ServerMessage(Ascii), |
|
303 34 => ServerVars(Vec<Ascii>), |
|
304 35 => Notice(Ascii), |
|
305 36 => Warning(Ascii), |
|
306 37 => Error(Ascii), |
|
307 38 => Replay(Vec<Ascii>) |
|
308 ) |
|
309 }); |
|
310 res.boxed() |
|
311 } |
|
312 |
|
313 proptest! { |
|
314 #[test] |
|
315 fn is_parser_composition_idempotent(ref msg in gen_proto_msg()) { |
|
316 println!("!! Msg: {:?}, Bytes: {:?} !!", msg, msg.to_raw_protocol().as_bytes()); |
|
317 assert_eq!(message(msg.to_raw_protocol().as_bytes()), Ok((&b""[..], msg.clone()))) |
|
318 } |
|
319 |
|
320 #[test] |
|
321 fn is_server_message_parser_composition_idempotent(ref msg in gen_server_msg()) { |
|
322 println!("!! Msg: {:?}, Bytes: {:?} !!", msg, msg.to_raw_protocol().as_bytes()); |
|
323 assert_eq!(server_message(msg.to_raw_protocol().as_bytes()), Ok((&b""[..], msg.clone()))) |
|
324 } |
|
325 } |