2 |
2 |
3 use std::str; |
3 use std::str; |
4 use std::str::FromStr; |
4 use std::str::FromStr; |
5 use super::messages::HWProtocolMessage; |
5 use super::messages::HWProtocolMessage; |
6 use super::messages::HWProtocolMessage::*; |
6 use super::messages::HWProtocolMessage::*; |
|
7 |
|
8 use proptest::test_runner::{TestRunner, Reason}; |
|
9 use proptest::arbitrary::{any, any_with, Arbitrary, StrategyFor}; |
|
10 use proptest::strategy::{Strategy, BoxedStrategy, Just, Filter, ValueTree}; |
|
11 use proptest::string::RegexGeneratorValueTree; |
|
12 use std::ops::Range; |
7 |
13 |
8 named!(end_of_message, tag!("\n\n")); |
14 named!(end_of_message, tag!("\n\n")); |
9 named!(str_line<&[u8], &str>, map_res!(not_line_ending, str::from_utf8)); |
15 named!(str_line<&[u8], &str>, map_res!(not_line_ending, str::from_utf8)); |
10 named!( a_line<&[u8], String>, map!(str_line, String::from)); |
16 named!( a_line<&[u8], String>, map!(str_line, String::from)); |
11 named!( u8_line<&[u8], u8>, map_res!(str_line, FromStr::from_str)); |
17 named!( u8_line<&[u8], u8>, map_res!(str_line, FromStr::from_str)); |
48 | do_parse!(tag_no_case!("FIX") >> (Fix)) |
54 | do_parse!(tag_no_case!("FIX") >> (Fix)) |
49 | do_parse!(tag_no_case!("UNFIX") >> (Unfix)) |
55 | do_parse!(tag_no_case!("UNFIX") >> (Unfix)) |
50 | do_parse!(tag_no_case!("RESTART_SERVER") >> eol >> tag!("YES") >> (RestartServer)) |
56 | do_parse!(tag_no_case!("RESTART_SERVER") >> eol >> tag!("YES") >> (RestartServer)) |
51 | do_parse!(tag_no_case!("REGISTERED_ONLY") >> (ToggleServerRegisteredOnly)) |
57 | do_parse!(tag_no_case!("REGISTERED_ONLY") >> (ToggleServerRegisteredOnly)) |
52 | do_parse!(tag_no_case!("SUPER_POWER") >> (SuperPower)) |
58 | do_parse!(tag_no_case!("SUPER_POWER") >> (SuperPower)) |
53 | do_parse!(tag_no_case!("PART") >> eol >> m: opt_param >> (Quit(m))) |
59 | do_parse!(tag_no_case!("PART") >> m: opt_param >> (Part(m))) |
54 | do_parse!(tag_no_case!("QUIT") >> eol >> m: opt_param >> (Part(m))) |
60 | do_parse!(tag_no_case!("QUIT") >> m: opt_param >> (Quit(m))) |
55 | do_parse!(tag_no_case!("DELEGATE") >> eol >> n: a_line >> (Delegate(n))) |
61 | do_parse!(tag_no_case!("DELEGATE") >> eol >> n: a_line >> (Delegate(n))) |
56 | do_parse!(tag_no_case!("SAVEROOM") >> eol >> r: a_line >> (SaveRoom(r))) |
62 | do_parse!(tag_no_case!("SAVEROOM") >> eol >> r: a_line >> (SaveRoom(r))) |
57 | do_parse!(tag_no_case!("LOADROOM") >> eol >> r: a_line >> (LoadRoom(r))) |
63 | do_parse!(tag_no_case!("LOADROOM") >> eol >> r: a_line >> (LoadRoom(r))) |
58 | do_parse!(tag_no_case!("DELETE") >> eol >> r: a_line >> (Delete(r))) |
64 | do_parse!(tag_no_case!("DELETE") >> eol >> r: a_line >> (Delete(r))) |
59 | do_parse!(tag_no_case!("GLOBAL") >> eol >> m: a_line >> (Global(m))) |
65 | do_parse!(tag_no_case!("GLOBAL") >> eol >> m: a_line >> (Global(m))) |
119 ) |
125 ) |
120 ); |
126 ); |
121 |
127 |
122 named!(pub extract_messages<&[u8], Vec<HWProtocolMessage> >, many0!(complete!(message))); |
128 named!(pub extract_messages<&[u8], Vec<HWProtocolMessage> >, many0!(complete!(message))); |
123 |
129 |
|
130 // Due to inability to define From between Options |
|
131 trait Into2<T>: Sized { fn into2(self) -> T; } |
|
132 impl <T> Into2<T> for T { fn into2(self) -> T { self } } |
|
133 impl Into2<String> for Ascii { fn into2(self) -> String { self.0 } } |
|
134 impl Into2<Option<String>> for Option<Ascii>{ |
|
135 fn into2(self) -> Option<String> { self.map(|x| {x.0}) } |
|
136 } |
|
137 |
|
138 macro_rules! proto_msg_case { |
|
139 ($val: ident()) => |
|
140 (Just($val)); |
|
141 ($val: ident($arg: ty)) => |
|
142 (any::<$arg>().prop_map(|v| {$val(v.into2())})); |
|
143 ($val: ident($arg1: ty, $arg2: ty)) => |
|
144 (any::<($arg1, $arg2)>().prop_map(|v| {$val(v.0.into2(), v.1.into2())})); |
|
145 ($val: ident($arg1: ty, $arg2: ty, $arg3: ty)) => |
|
146 (any::<($arg1, $arg2, $arg3)>().prop_map(|v| {$val(v.0.into2(), v.1.into2(), v.2.into2())})); |
|
147 } |
|
148 |
|
149 macro_rules! proto_msg_match { |
|
150 ($var: expr, def = $default: ident, $($num: expr => $constr: ident $res: tt),*) => ( |
|
151 match $var { |
|
152 $($num => (proto_msg_case!($constr $res)).boxed()),*, |
|
153 _ => Just($default).boxed() |
|
154 } |
|
155 ) |
|
156 } |
|
157 |
|
158 #[derive(Debug)] |
|
159 struct Ascii(String); |
|
160 |
|
161 struct AsciiValueTree(RegexGeneratorValueTree<String>); |
|
162 |
|
163 impl ValueTree for AsciiValueTree { |
|
164 type Value = Ascii; |
|
165 |
|
166 fn current(&self) -> Self::Value { Ascii(self.0.current()) } |
|
167 fn simplify(&mut self) -> bool { self.0.simplify() } |
|
168 fn complicate(&mut self) -> bool { self.0.complicate() } |
|
169 } |
|
170 |
|
171 impl Arbitrary for Ascii { |
|
172 type Parameters = <String as Arbitrary>::Parameters; |
|
173 |
|
174 fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { |
|
175 any_with::<String>(args) |
|
176 .prop_filter("not ascii", |s| { |
|
177 s.len() > 0 && s.is_ascii() && |
|
178 s.find(|c| { |
|
179 ['\0', '\n', '\x20'].contains(&c) |
|
180 }).is_none()}) |
|
181 .prop_map(Ascii) |
|
182 .boxed() |
|
183 } |
|
184 |
|
185 type Strategy = BoxedStrategy<Ascii>; |
|
186 type ValueTree = Box<ValueTree<Value = Ascii>>; |
|
187 } |
|
188 |
|
189 fn gen_proto_msg() -> BoxedStrategy<HWProtocolMessage> where { |
|
190 let res = (0..58).no_shrink().prop_flat_map(|i| { |
|
191 proto_msg_match!(i, def = Malformed, |
|
192 0 => Ping(), |
|
193 1 => Pong(), |
|
194 2 => Quit(Option<Ascii>), |
|
195 //3 => Cmd |
|
196 4 => Global(Ascii), |
|
197 5 => Watch(Ascii), |
|
198 6 => ToggleServerRegisteredOnly(), |
|
199 7 => SuperPower(), |
|
200 8 => Info(Ascii), |
|
201 9 => Nick(Ascii), |
|
202 10 => Proto(u32), |
|
203 11 => Password(Ascii, Ascii), |
|
204 12 => Checker(u32, Ascii, Ascii), |
|
205 13 => List(), |
|
206 14 => Chat(Ascii), |
|
207 15 => CreateRoom(Ascii, Option<Ascii>), |
|
208 16 => Join(Ascii, Option<Ascii>), |
|
209 17 => Follow(Ascii), |
|
210 //18 => Rnd(Vec<String>), |
|
211 19 => Kick(Ascii), |
|
212 20 => Ban(Ascii, Ascii, u32), |
|
213 21 => BanIP(Ascii, Ascii, u32), |
|
214 22 => BanNick(Ascii, Ascii, u32), |
|
215 23 => BanList(), |
|
216 24 => Unban(Ascii), |
|
217 //25 => SetServerVar(ServerVar), |
|
218 26 => GetServerVar(), |
|
219 27 => RestartServer(), |
|
220 28 => Stats(), |
|
221 29 => Part(Option<Ascii>), |
|
222 //30 => Cfg(GameCfg), |
|
223 //31 => AddTeam(TeamInfo), |
|
224 32 => RemoveTeam(Ascii), |
|
225 //33 => SetHedgehogsNumber(String, u8), |
|
226 //34 => SetTeamColor(String, u8), |
|
227 35 => ToggleReady(), |
|
228 36 => StartGame(), |
|
229 37 => EngineMessage(Ascii), |
|
230 38 => RoundFinished(), |
|
231 39 => ToggleRestrictJoin(), |
|
232 40 => ToggleRestrictTeams(), |
|
233 41 => ToggleRegisteredOnly(), |
|
234 42 => RoomName(Ascii), |
|
235 43 => Delegate(Ascii), |
|
236 44 => TeamChat(Ascii), |
|
237 45 => MaxTeams(u8), |
|
238 46 => Fix(), |
|
239 47 => Unfix(), |
|
240 48 => Greeting(Ascii), |
|
241 //49 => CallVote(Option<(String, Option<String>)>), |
|
242 50 => Vote(String), |
|
243 51 => ForceVote(Ascii), |
|
244 //52 => Save(String, String), |
|
245 53 => Delete(Ascii), |
|
246 54 => SaveRoom(Ascii), |
|
247 55 => LoadRoom(Ascii), |
|
248 56 => Malformed(), |
|
249 57 => Empty() |
|
250 )}); |
|
251 res.boxed() |
|
252 } |
|
253 |
|
254 proptest! { |
|
255 #[test] |
|
256 fn is_parser_composition_idempotent(ref msg in gen_proto_msg()) { |
|
257 println!("!! Msg: {:?}, Bytes: {:?} !!", msg, msg.to_raw_protocol().as_bytes()); |
|
258 assert_eq!(message(msg.to_raw_protocol().as_bytes()), IResult::Done(&b""[..], msg.clone())) |
|
259 } |
|
260 } |
|
261 |
124 #[test] |
262 #[test] |
125 fn parse_test() { |
263 fn parse_test() { |
126 assert_eq!(message(b"PING\n\n"), IResult::Done(&b""[..], Ping)); |
264 assert_eq!(message(b"PING\n\n"), IResult::Done(&b""[..], Ping)); |
127 assert_eq!(message(b"START_GAME\n\n"), IResult::Done(&b""[..], StartGame)); |
265 assert_eq!(message(b"START_GAME\n\n"), IResult::Done(&b""[..], StartGame)); |
128 assert_eq!(message(b"NICK\nit's me\n\n"), IResult::Done(&b""[..], Nick("it's me".to_string()))); |
266 assert_eq!(message(b"NICK\nit's me\n\n"), IResult::Done(&b""[..], Nick("it's me".to_string()))); |
130 assert_eq!(message(b"QUIT\nbye-bye\n\n"), IResult::Done(&b""[..], Quit(Some("bye-bye".to_string())))); |
268 assert_eq!(message(b"QUIT\nbye-bye\n\n"), IResult::Done(&b""[..], Quit(Some("bye-bye".to_string())))); |
131 assert_eq!(message(b"QUIT\n\n"), IResult::Done(&b""[..], Quit(None))); |
269 assert_eq!(message(b"QUIT\n\n"), IResult::Done(&b""[..], Quit(None))); |
132 assert_eq!(message(b"CMD\nwatch\ndemo\n\n"), IResult::Done(&b""[..], Watch("demo".to_string()))); |
270 assert_eq!(message(b"CMD\nwatch\ndemo\n\n"), IResult::Done(&b""[..], Watch("demo".to_string()))); |
133 assert_eq!(message(b"BAN\nme\nbad\n77\n\n"), IResult::Done(&b""[..], Ban("me".to_string(), "bad".to_string(), 77))); |
271 assert_eq!(message(b"BAN\nme\nbad\n77\n\n"), IResult::Done(&b""[..], Ban("me".to_string(), "bad".to_string(), 77))); |
134 |
272 |
|
273 assert_eq!(message(b"CMD\nPART\n\n"), IResult::Done(&b""[..], Part(None))); |
|
274 assert_eq!(message(b"CMD\nPART\n_msg_\n\n"), IResult::Done(&b""[..], Part(Some("_msg_".to_string())))); |
|
275 |
135 assert_eq!(extract_messages(b"QUIT\n1\n2\n\n"), IResult::Done(&b""[..], vec![Malformed])); |
276 assert_eq!(extract_messages(b"QUIT\n1\n2\n\n"), IResult::Done(&b""[..], vec![Malformed])); |
136 |
277 |
137 assert_eq!(extract_messages(b"PING\n\nPING\n\nP"), IResult::Done(&b"P"[..], vec![Ping, Ping])); |
278 assert_eq!(extract_messages(b"PING\n\nPING\n\nP"), IResult::Done(&b"P"[..], vec![Ping, Ping])); |
138 assert_eq!(extract_messages(b"SING\n\nPING\n\n"), IResult::Done(&b""[..], vec![Malformed, Ping])); |
279 assert_eq!(extract_messages(b"SING\n\nPING\n\n"), IResult::Done(&b""[..], vec![Malformed, Ping])); |
139 assert_eq!(extract_messages(b"\n\n\n\nPING\n\n"), IResult::Done(&b""[..], vec![Empty, Empty, Ping])); |
280 assert_eq!(extract_messages(b"\n\n\n\nPING\n\n"), IResult::Done(&b""[..], vec![Empty, Empty, Ping])); |