1c67d6573Sopenharmony_ciuse regex::internal::ExecBuilder; 2c67d6573Sopenharmony_ci 3c67d6573Sopenharmony_ci/// Given a regex, check if all of the backends produce the same 4c67d6573Sopenharmony_ci/// results on a number of different inputs. 5c67d6573Sopenharmony_ci/// 6c67d6573Sopenharmony_ci/// For now this just throws quickcheck at the problem, which 7c67d6573Sopenharmony_ci/// is not very good because it only really tests half of the 8c67d6573Sopenharmony_ci/// problem space. It is pretty unlikely that a random string 9c67d6573Sopenharmony_ci/// will match any given regex, so this will probably just 10c67d6573Sopenharmony_ci/// be checking that the different backends fail in the same 11c67d6573Sopenharmony_ci/// way. This is still worthwhile to test, but is definitely not 12c67d6573Sopenharmony_ci/// the whole story. 13c67d6573Sopenharmony_ci/// 14c67d6573Sopenharmony_ci/// TODO(ethan): In order to cover the other half of the problem 15c67d6573Sopenharmony_ci/// space, we should generate a random matching string by inspecting 16c67d6573Sopenharmony_ci/// the AST of the input regex. The right way to do this probably 17c67d6573Sopenharmony_ci/// involves adding a custom Arbitrary instance around a couple 18c67d6573Sopenharmony_ci/// of newtypes. That way we can respect the quickcheck size hinting 19c67d6573Sopenharmony_ci/// and shrinking and whatnot. 20c67d6573Sopenharmony_cipub fn backends_are_consistent(re: &str) -> Result<u64, String> { 21c67d6573Sopenharmony_ci let standard_backends = vec![ 22c67d6573Sopenharmony_ci ( 23c67d6573Sopenharmony_ci "bounded_backtracking_re", 24c67d6573Sopenharmony_ci ExecBuilder::new(re) 25c67d6573Sopenharmony_ci .bounded_backtracking() 26c67d6573Sopenharmony_ci .build() 27c67d6573Sopenharmony_ci .map(|exec| exec.into_regex()) 28c67d6573Sopenharmony_ci .map_err(|err| format!("{}", err))?, 29c67d6573Sopenharmony_ci ), 30c67d6573Sopenharmony_ci ( 31c67d6573Sopenharmony_ci "pikevm_re", 32c67d6573Sopenharmony_ci ExecBuilder::new(re) 33c67d6573Sopenharmony_ci .nfa() 34c67d6573Sopenharmony_ci .build() 35c67d6573Sopenharmony_ci .map(|exec| exec.into_regex()) 36c67d6573Sopenharmony_ci .map_err(|err| format!("{}", err))?, 37c67d6573Sopenharmony_ci ), 38c67d6573Sopenharmony_ci ( 39c67d6573Sopenharmony_ci "default_re", 40c67d6573Sopenharmony_ci ExecBuilder::new(re) 41c67d6573Sopenharmony_ci .build() 42c67d6573Sopenharmony_ci .map(|exec| exec.into_regex()) 43c67d6573Sopenharmony_ci .map_err(|err| format!("{}", err))?, 44c67d6573Sopenharmony_ci ), 45c67d6573Sopenharmony_ci ]; 46c67d6573Sopenharmony_ci 47c67d6573Sopenharmony_ci let utf8bytes_backends = vec![ 48c67d6573Sopenharmony_ci ( 49c67d6573Sopenharmony_ci "bounded_backtracking_utf8bytes_re", 50c67d6573Sopenharmony_ci ExecBuilder::new(re) 51c67d6573Sopenharmony_ci .bounded_backtracking() 52c67d6573Sopenharmony_ci .bytes(true) 53c67d6573Sopenharmony_ci .build() 54c67d6573Sopenharmony_ci .map(|exec| exec.into_regex()) 55c67d6573Sopenharmony_ci .map_err(|err| format!("{}", err))?, 56c67d6573Sopenharmony_ci ), 57c67d6573Sopenharmony_ci ( 58c67d6573Sopenharmony_ci "pikevm_utf8bytes_re", 59c67d6573Sopenharmony_ci ExecBuilder::new(re) 60c67d6573Sopenharmony_ci .nfa() 61c67d6573Sopenharmony_ci .bytes(true) 62c67d6573Sopenharmony_ci .build() 63c67d6573Sopenharmony_ci .map(|exec| exec.into_regex()) 64c67d6573Sopenharmony_ci .map_err(|err| format!("{}", err))?, 65c67d6573Sopenharmony_ci ), 66c67d6573Sopenharmony_ci ( 67c67d6573Sopenharmony_ci "default_utf8bytes_re", 68c67d6573Sopenharmony_ci ExecBuilder::new(re) 69c67d6573Sopenharmony_ci .bytes(true) 70c67d6573Sopenharmony_ci .build() 71c67d6573Sopenharmony_ci .map(|exec| exec.into_regex()) 72c67d6573Sopenharmony_ci .map_err(|err| format!("{}", err))?, 73c67d6573Sopenharmony_ci ), 74c67d6573Sopenharmony_ci ]; 75c67d6573Sopenharmony_ci 76c67d6573Sopenharmony_ci let bytes_backends = vec![ 77c67d6573Sopenharmony_ci ( 78c67d6573Sopenharmony_ci "bounded_backtracking_bytes_re", 79c67d6573Sopenharmony_ci ExecBuilder::new(re) 80c67d6573Sopenharmony_ci .bounded_backtracking() 81c67d6573Sopenharmony_ci .only_utf8(false) 82c67d6573Sopenharmony_ci .build() 83c67d6573Sopenharmony_ci .map(|exec| exec.into_byte_regex()) 84c67d6573Sopenharmony_ci .map_err(|err| format!("{}", err))?, 85c67d6573Sopenharmony_ci ), 86c67d6573Sopenharmony_ci ( 87c67d6573Sopenharmony_ci "pikevm_bytes_re", 88c67d6573Sopenharmony_ci ExecBuilder::new(re) 89c67d6573Sopenharmony_ci .nfa() 90c67d6573Sopenharmony_ci .only_utf8(false) 91c67d6573Sopenharmony_ci .build() 92c67d6573Sopenharmony_ci .map(|exec| exec.into_byte_regex()) 93c67d6573Sopenharmony_ci .map_err(|err| format!("{}", err))?, 94c67d6573Sopenharmony_ci ), 95c67d6573Sopenharmony_ci ( 96c67d6573Sopenharmony_ci "default_bytes_re", 97c67d6573Sopenharmony_ci ExecBuilder::new(re) 98c67d6573Sopenharmony_ci .only_utf8(false) 99c67d6573Sopenharmony_ci .build() 100c67d6573Sopenharmony_ci .map(|exec| exec.into_byte_regex()) 101c67d6573Sopenharmony_ci .map_err(|err| format!("{}", err))?, 102c67d6573Sopenharmony_ci ), 103c67d6573Sopenharmony_ci ]; 104c67d6573Sopenharmony_ci 105c67d6573Sopenharmony_ci Ok(string_checker::check_backends(&standard_backends)? 106c67d6573Sopenharmony_ci + string_checker::check_backends(&utf8bytes_backends)? 107c67d6573Sopenharmony_ci + bytes_checker::check_backends(&bytes_backends)?) 108c67d6573Sopenharmony_ci} 109c67d6573Sopenharmony_ci 110c67d6573Sopenharmony_ci// 111c67d6573Sopenharmony_ci// A consistency checker parameterized by the input type (&str or &[u8]). 112c67d6573Sopenharmony_ci// 113c67d6573Sopenharmony_ci 114c67d6573Sopenharmony_cimacro_rules! checker { 115c67d6573Sopenharmony_ci ($module_name:ident, $regex_type:path, $mk_input:expr) => { 116c67d6573Sopenharmony_ci mod $module_name { 117c67d6573Sopenharmony_ci use quickcheck; 118c67d6573Sopenharmony_ci use quickcheck::{Arbitrary, TestResult}; 119c67d6573Sopenharmony_ci 120c67d6573Sopenharmony_ci pub fn check_backends( 121c67d6573Sopenharmony_ci backends: &[(&str, $regex_type)], 122c67d6573Sopenharmony_ci ) -> Result<u64, String> { 123c67d6573Sopenharmony_ci let mut total_passed = 0; 124c67d6573Sopenharmony_ci for regex in backends[1..].iter() { 125c67d6573Sopenharmony_ci total_passed += quickcheck_regex_eq(&backends[0], regex)?; 126c67d6573Sopenharmony_ci } 127c67d6573Sopenharmony_ci 128c67d6573Sopenharmony_ci Ok(total_passed) 129c67d6573Sopenharmony_ci } 130c67d6573Sopenharmony_ci 131c67d6573Sopenharmony_ci fn quickcheck_regex_eq( 132c67d6573Sopenharmony_ci &(name1, ref re1): &(&str, $regex_type), 133c67d6573Sopenharmony_ci &(name2, ref re2): &(&str, $regex_type), 134c67d6573Sopenharmony_ci ) -> Result<u64, String> { 135c67d6573Sopenharmony_ci quickcheck::QuickCheck::new() 136c67d6573Sopenharmony_ci .quicktest(RegexEqualityTest::new( 137c67d6573Sopenharmony_ci re1.clone(), 138c67d6573Sopenharmony_ci re2.clone(), 139c67d6573Sopenharmony_ci )) 140c67d6573Sopenharmony_ci .map_err(|err| { 141c67d6573Sopenharmony_ci format!( 142c67d6573Sopenharmony_ci "{}(/{}/) and {}(/{}/) are inconsistent.\ 143c67d6573Sopenharmony_ci QuickCheck Err: {:?}", 144c67d6573Sopenharmony_ci name1, re1, name2, re2, err 145c67d6573Sopenharmony_ci ) 146c67d6573Sopenharmony_ci }) 147c67d6573Sopenharmony_ci } 148c67d6573Sopenharmony_ci 149c67d6573Sopenharmony_ci struct RegexEqualityTest { 150c67d6573Sopenharmony_ci re1: $regex_type, 151c67d6573Sopenharmony_ci re2: $regex_type, 152c67d6573Sopenharmony_ci } 153c67d6573Sopenharmony_ci impl RegexEqualityTest { 154c67d6573Sopenharmony_ci fn new(re1: $regex_type, re2: $regex_type) -> Self { 155c67d6573Sopenharmony_ci RegexEqualityTest { re1: re1, re2: re2 } 156c67d6573Sopenharmony_ci } 157c67d6573Sopenharmony_ci } 158c67d6573Sopenharmony_ci 159c67d6573Sopenharmony_ci impl quickcheck::Testable for RegexEqualityTest { 160c67d6573Sopenharmony_ci fn result(&self, gen: &mut quickcheck::Gen) -> TestResult { 161c67d6573Sopenharmony_ci let input = $mk_input(gen); 162c67d6573Sopenharmony_ci let input = &input; 163c67d6573Sopenharmony_ci 164c67d6573Sopenharmony_ci if self.re1.find(&input) != self.re2.find(input) { 165c67d6573Sopenharmony_ci return TestResult::error(format!( 166c67d6573Sopenharmony_ci "find mismatch input={:?}", 167c67d6573Sopenharmony_ci input 168c67d6573Sopenharmony_ci )); 169c67d6573Sopenharmony_ci } 170c67d6573Sopenharmony_ci 171c67d6573Sopenharmony_ci let cap1 = self.re1.captures(input); 172c67d6573Sopenharmony_ci let cap2 = self.re2.captures(input); 173c67d6573Sopenharmony_ci match (cap1, cap2) { 174c67d6573Sopenharmony_ci (None, None) => {} 175c67d6573Sopenharmony_ci (Some(cap1), Some(cap2)) => { 176c67d6573Sopenharmony_ci for (c1, c2) in cap1.iter().zip(cap2.iter()) { 177c67d6573Sopenharmony_ci if c1 != c2 { 178c67d6573Sopenharmony_ci return TestResult::error(format!( 179c67d6573Sopenharmony_ci "captures mismatch input={:?}", 180c67d6573Sopenharmony_ci input 181c67d6573Sopenharmony_ci )); 182c67d6573Sopenharmony_ci } 183c67d6573Sopenharmony_ci } 184c67d6573Sopenharmony_ci } 185c67d6573Sopenharmony_ci _ => { 186c67d6573Sopenharmony_ci return TestResult::error(format!( 187c67d6573Sopenharmony_ci "captures mismatch input={:?}", 188c67d6573Sopenharmony_ci input 189c67d6573Sopenharmony_ci )) 190c67d6573Sopenharmony_ci } 191c67d6573Sopenharmony_ci } 192c67d6573Sopenharmony_ci 193c67d6573Sopenharmony_ci let fi1 = self.re1.find_iter(input); 194c67d6573Sopenharmony_ci let fi2 = self.re2.find_iter(input); 195c67d6573Sopenharmony_ci for (m1, m2) in fi1.zip(fi2) { 196c67d6573Sopenharmony_ci if m1 != m2 { 197c67d6573Sopenharmony_ci return TestResult::error(format!( 198c67d6573Sopenharmony_ci "find_iter mismatch input={:?}", 199c67d6573Sopenharmony_ci input 200c67d6573Sopenharmony_ci )); 201c67d6573Sopenharmony_ci } 202c67d6573Sopenharmony_ci } 203c67d6573Sopenharmony_ci 204c67d6573Sopenharmony_ci let ci1 = self.re1.captures_iter(input); 205c67d6573Sopenharmony_ci let ci2 = self.re2.captures_iter(input); 206c67d6573Sopenharmony_ci for (cap1, cap2) in ci1.zip(ci2) { 207c67d6573Sopenharmony_ci for (c1, c2) in cap1.iter().zip(cap2.iter()) { 208c67d6573Sopenharmony_ci if c1 != c2 { 209c67d6573Sopenharmony_ci return TestResult::error(format!( 210c67d6573Sopenharmony_ci "captures_iter mismatch input={:?}", 211c67d6573Sopenharmony_ci input 212c67d6573Sopenharmony_ci )); 213c67d6573Sopenharmony_ci } 214c67d6573Sopenharmony_ci } 215c67d6573Sopenharmony_ci } 216c67d6573Sopenharmony_ci 217c67d6573Sopenharmony_ci let s1 = self.re1.split(input); 218c67d6573Sopenharmony_ci let s2 = self.re2.split(input); 219c67d6573Sopenharmony_ci for (chunk1, chunk2) in s1.zip(s2) { 220c67d6573Sopenharmony_ci if chunk1 != chunk2 { 221c67d6573Sopenharmony_ci return TestResult::error(format!( 222c67d6573Sopenharmony_ci "split mismatch input={:?}", 223c67d6573Sopenharmony_ci input 224c67d6573Sopenharmony_ci )); 225c67d6573Sopenharmony_ci } 226c67d6573Sopenharmony_ci } 227c67d6573Sopenharmony_ci 228c67d6573Sopenharmony_ci TestResult::from_bool(true) 229c67d6573Sopenharmony_ci } 230c67d6573Sopenharmony_ci } 231c67d6573Sopenharmony_ci } // mod 232c67d6573Sopenharmony_ci }; // rule case 233c67d6573Sopenharmony_ci} // macro_rules! 234c67d6573Sopenharmony_ci 235c67d6573Sopenharmony_cichecker!(string_checker, ::regex::Regex, |gen| String::arbitrary(gen)); 236c67d6573Sopenharmony_cichecker!(bytes_checker, ::regex::bytes::Regex, |gen| Vec::<u8>::arbitrary( 237c67d6573Sopenharmony_ci gen 238c67d6573Sopenharmony_ci)); 239