1 use nom::*; |
1 use nom::*; |
2 |
2 |
3 use std::str; |
3 use std::{ |
4 use std::str::FromStr; |
4 str, str::FromStr, |
5 use super::messages::HWProtocolMessage; |
5 ops::Range |
6 use super::messages::HWProtocolMessage::*; |
6 }; |
7 |
7 use super::{ |
8 use proptest::test_runner::{TestRunner, Reason}; |
8 messages::{HWProtocolMessage, HWProtocolMessage::*}, |
9 use proptest::arbitrary::{any, any_with, Arbitrary, StrategyFor}; |
9 test::gen_proto_msg |
10 use proptest::strategy::{Strategy, BoxedStrategy, Just, Filter, ValueTree}; |
10 }; |
11 use proptest::string::RegexGeneratorValueTree; |
|
12 use std::ops::Range; |
|
13 |
11 |
14 named!(end_of_message, tag!("\n\n")); |
12 named!(end_of_message, tag!("\n\n")); |
15 named!(str_line<&[u8], &str>, map_res!(not_line_ending, str::from_utf8)); |
13 named!(str_line<&[u8], &str>, map_res!(not_line_ending, str::from_utf8)); |
16 named!( a_line<&[u8], String>, map!(str_line, String::from)); |
14 named!( a_line<&[u8], String>, map!(str_line, String::from)); |
17 named!( u8_line<&[u8], u8>, map_res!(str_line, FromStr::from_str)); |
15 named!( u8_line<&[u8], u8>, map_res!(str_line, FromStr::from_str)); |
34 |
32 |
35 named!(one_param_message<&[u8], HWProtocolMessage>, alt!( |
33 named!(one_param_message<&[u8], HWProtocolMessage>, alt!( |
36 do_parse!(tag!("NICK") >> eol >> n: a_line >> (Nick(n))) |
34 do_parse!(tag!("NICK") >> eol >> n: a_line >> (Nick(n))) |
37 | do_parse!(tag!("INFO") >> eol >> n: a_line >> (Info(n))) |
35 | do_parse!(tag!("INFO") >> eol >> n: a_line >> (Info(n))) |
38 | do_parse!(tag!("CHAT") >> eol >> m: a_line >> (Chat(m))) |
36 | do_parse!(tag!("CHAT") >> eol >> m: a_line >> (Chat(m))) |
|
37 | do_parse!(tag!("PART") >> msg: opt_param >> (Part(msg))) |
39 | do_parse!(tag!("FOLLOW") >> eol >> n: a_line >> (Follow(n))) |
38 | do_parse!(tag!("FOLLOW") >> eol >> n: a_line >> (Follow(n))) |
40 | do_parse!(tag!("KICK") >> eol >> n: a_line >> (Kick(n))) |
39 | do_parse!(tag!("KICK") >> eol >> n: a_line >> (Kick(n))) |
41 | do_parse!(tag!("UNBAN") >> eol >> n: a_line >> (Unban(n))) |
40 | do_parse!(tag!("UNBAN") >> eol >> n: a_line >> (Unban(n))) |
42 | do_parse!(tag!("EM") >> eol >> m: a_line >> (EngineMessage(m))) |
41 | do_parse!(tag!("EM") >> eol >> m: a_line >> (EngineMessage(m))) |
43 | do_parse!(tag!("TEAMCHAT") >> eol >> m: a_line >> (TeamChat(m))) |
42 | do_parse!(tag!("TEAMCHAT") >> eol >> m: a_line >> (TeamChat(m))) |
125 ) |
124 ) |
126 ); |
125 ); |
127 |
126 |
128 named!(pub extract_messages<&[u8], Vec<HWProtocolMessage> >, many0!(complete!(message))); |
127 named!(pub extract_messages<&[u8], Vec<HWProtocolMessage> >, many0!(complete!(message))); |
129 |
128 |
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! { |
129 proptest! { |
255 #[test] |
130 #[test] |
256 fn is_parser_composition_idempotent(ref msg in gen_proto_msg()) { |
131 fn is_parser_composition_idempotent(ref msg in gen_proto_msg()) { |
257 println!("!! Msg: {:?}, Bytes: {:?} !!", msg, msg.to_raw_protocol().as_bytes()); |
132 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())) |
133 assert_eq!(message(msg.to_raw_protocol().as_bytes()), IResult::Done(&b""[..], msg.clone())) |