1 /*
2  * Copyright © 2019-today Peter M. Stahl pemistahl@gmail.com
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #![allow(deprecated)]
18 
19 use grex::{Feature, RegExpBuilder};
20 use proptest::prelude::*;
21 use regex::{Error, Regex, RegexBuilder};
22 
23 proptest! {
24     #![proptest_config(ProptestConfig::with_cases(500))]
25 
26     #[test]
27     fn valid_regexes_with_default_settings(
28         test_cases in prop::collection::hash_set(".{1,10}", 1..=5)
29     ) {
30         let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();
31         let regexp = RegExpBuilder::from(&test_cases_vec).build();
32         prop_assert!(compile_regexp(&regexp).is_ok());
33     }
34 
35     #[test]
36     fn valid_regexes_with_escape_sequences(
37         test_cases in prop::collection::hash_set(".{1,10}", 1..=5)
38     ) {
39         let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();
40         let regexp = RegExpBuilder::from(&test_cases_vec)
41             .with_escaping_of_non_ascii_chars(false)
42             .build();
43         prop_assert!(compile_regexp(&regexp).is_ok());
44     }
45 
46     #[test]
47     fn valid_regexes_with_verbose_mode(
48         test_cases in prop::collection::hash_set(".{1,10}", 1..=5)
49     ) {
50         let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();
51         let regexp = RegExpBuilder::from(&test_cases_vec)
52             .with_verbose_mode()
53             .build();
54         prop_assert!(compile_regexp(&regexp).is_ok());
55     }
56 
57     #[test]
58     fn valid_regexes_with_escape_sequences_and_verbose_mode(
59         test_cases in prop::collection::hash_set(".{1,10}", 1..=5)
60     ) {
61         let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();
62         let regexp = RegExpBuilder::from(&test_cases_vec)
63             .with_escaping_of_non_ascii_chars(false)
64             .with_verbose_mode()
65             .build();
66         prop_assert!(compile_regexp(&regexp).is_ok());
67     }
68 
69     #[test]
70     fn valid_regexes_with_conversion_features(
71         test_cases in prop::collection::hash_set(".{1,10}", 1..=5),
72         conversion_features in prop::collection::hash_set(conversion_feature_strategy(), 1..=9),
73         minimum_repetitions in 1..100u32,
74         minimum_substring_length in 1..100u32
75     ) {
76         let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();
77         let regexp = RegExpBuilder::from(&test_cases_vec)
78             .with_conversion_of(&conversion_features.into_iter().collect::<Vec<_>>())
79             .with_minimum_repetitions(minimum_repetitions)
80             .with_minimum_substring_length(minimum_substring_length)
81             .build();
82         prop_assert!(compile_regexp(&regexp).is_ok());
83     }
84 
85     #[test]
86     fn valid_regexes_with_conversion_features_and_escape_sequences(
87         test_cases in prop::collection::hash_set(".{1,10}", 1..=5),
88         conversion_features in prop::collection::hash_set(conversion_feature_strategy(), 1..=9),
89         minimum_repetitions in 1..100u32,
90         minimum_substring_length in 1..100u32
91     ) {
92         let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();
93         let regexp = RegExpBuilder::from(&test_cases_vec)
94             .with_conversion_of(&conversion_features.into_iter().collect::<Vec<_>>())
95             .with_minimum_repetitions(minimum_repetitions)
96             .with_minimum_substring_length(minimum_substring_length)
97             .with_escaping_of_non_ascii_chars(false)
98             .build();
99         prop_assert!(compile_regexp(&regexp).is_ok());
100     }
101 
102     #[test]
103     fn valid_regexes_with_conversion_features_and_verbose_mode(
104         test_cases in prop::collection::hash_set(".{1,10}", 1..=5),
105         conversion_features in prop::collection::hash_set(conversion_feature_strategy(), 1..=9),
106         minimum_repetitions in 1..100u32,
107         minimum_substring_length in 1..100u32
108     ) {
109         let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();
110         let regexp = RegExpBuilder::from(&test_cases_vec)
111             .with_conversion_of(&conversion_features.into_iter().collect::<Vec<_>>())
112             .with_minimum_repetitions(minimum_repetitions)
113             .with_minimum_substring_length(minimum_substring_length)
114             .with_verbose_mode()
115             .build();
116         prop_assert!(compile_regexp(&regexp).is_ok());
117     }
118 
119     #[test]
120     fn matching_regexes_with_default_settings(
121         test_cases in prop::collection::hash_set(".{1,10}", 1..=5)
122     ) {
123         let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();
124         let regexp = RegExpBuilder::from(&test_cases_vec).build();
125         if let Ok(compiled_regexp) = compile_regexp(&regexp) {
126             prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(&test_case)));
127         }
128     }
129 
130     #[test]
131     fn matching_regexes_with_escape_sequences(
132         test_cases in prop::collection::hash_set(".{1,10}", 1..=5)
133     ) {
134         let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();
135         let regexp = RegExpBuilder::from(&test_cases_vec)
136             .with_escaping_of_non_ascii_chars(false)
137             .build();
138         if let Ok(compiled_regexp) = compile_regexp(&regexp) {
139             prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(&test_case)));
140         }
141     }
142 
143     #[test]
144     fn matching_regexes_with_verbose_mode(
145         test_cases in prop::collection::hash_set(".{1,10}", 1..=5)
146     ) {
147         let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();
148         let regexp = RegExpBuilder::from(&test_cases_vec)
149             .with_verbose_mode()
150             .build();
151         if let Ok(compiled_regexp) = compile_regexp(&regexp) {
152             prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(&test_case)));
153         }
154     }
155 
156     #[test]
157     fn matching_regexes_with_escape_sequences_and_verbose_mode(
158         test_cases in prop::collection::hash_set(".{1,10}", 1..=5)
159     ) {
160         let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();
161         let regexp = RegExpBuilder::from(&test_cases_vec)
162             .with_escaping_of_non_ascii_chars(false)
163             .with_verbose_mode()
164             .build();
165         if let Ok(compiled_regexp) = compile_regexp(&regexp) {
166             prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(&test_case)));
167         }
168     }
169 
170     #[test]
171     fn matching_regexes_with_conversion_features(
172         test_cases in prop::collection::hash_set(".{1,10}", 1..=5),
173         conversion_features in prop::collection::hash_set(conversion_feature_strategy(), 1..=9),
174         minimum_repetitions in 1..100u32,
175         minimum_substring_length in 1..100u32
176     ) {
177         let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();
178         let regexp = RegExpBuilder::from(&test_cases_vec)
179             .with_conversion_of(&conversion_features.into_iter().collect::<Vec<_>>())
180             .with_minimum_repetitions(minimum_repetitions)
181             .with_minimum_substring_length(minimum_substring_length)
182             .build();
183         if let Ok(compiled_regexp) = compile_regexp(&regexp) {
184             prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(&test_case)));
185         }
186     }
187 
188     #[test]
189     fn matching_regexes_with_conversion_features_and_escape_sequences(
190         test_cases in prop::collection::hash_set(".{1,10}", 1..=5),
191         conversion_features in prop::collection::hash_set(conversion_feature_strategy(), 1..=9),
192         minimum_repetitions in 1..100u32,
193         minimum_substring_length in 1..100u32
194     ) {
195         let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();
196         let regexp = RegExpBuilder::from(&test_cases_vec)
197             .with_conversion_of(&conversion_features.into_iter().collect::<Vec<_>>())
198             .with_minimum_repetitions(minimum_repetitions)
199             .with_minimum_substring_length(minimum_substring_length)
200             .with_escaping_of_non_ascii_chars(false)
201             .build();
202         if let Ok(compiled_regexp) = compile_regexp(&regexp) {
203             prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(&test_case)));
204         }
205     }
206 
207     #[test]
208     fn matching_regexes_with_conversion_features_and_verbose_mode(
209         test_cases in prop::collection::hash_set(".{1,10}", 1..=5),
210         conversion_features in prop::collection::hash_set(conversion_feature_strategy(), 1..=9),
211         minimum_repetitions in 1..100u32,
212         minimum_substring_length in 1..100u32
213     ) {
214         let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();
215         let regexp = RegExpBuilder::from(&test_cases_vec)
216             .with_conversion_of(&conversion_features.into_iter().collect::<Vec<_>>())
217             .with_minimum_repetitions(minimum_repetitions)
218             .with_minimum_substring_length(minimum_substring_length)
219             .with_verbose_mode()
220             .build();
221         if let Ok(compiled_regexp) = compile_regexp(&regexp) {
222             prop_assert!(test_cases.iter().all(|test_case| compiled_regexp.is_match(&test_case)));
223         }
224     }
225 
226     #[test]
227     fn matching_regexes_without_start_anchor(
228         test_cases in prop::collection::hash_set("[A-C]{1,10}", 1..=5)
229     ) {
230         let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();
231         let regexp = RegExpBuilder::from(&test_cases_vec).without_start_anchor().build();
232         if let Ok(compiled_regexp) = compile_regexp(&regexp) {
233             for test_case in test_cases_vec {
234                 let substrings = compiled_regexp.find_iter(&test_case).map(|m| m.as_str()).collect::<Vec<_>>();
235                 prop_assert_eq!(
236                     substrings.len(),
237                     1,
238                     "expression '{}' does not match test case '{}' entirely but {} of its substrings: {:?}",
239                     regexp,
240                     test_case,
241                     substrings.len(),
242                     substrings
243                 );
244             }
245         }
246     }
247 
248     #[test]
249     fn matching_regexes_without_end_anchor(
250         test_cases in prop::collection::hash_set("[A-C]{1,10}", 1..=5)
251     ) {
252         let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();
253         let regexp = RegExpBuilder::from(&test_cases_vec).without_end_anchor().build();
254         if let Ok(compiled_regexp) = compile_regexp(&regexp) {
255             for test_case in test_cases_vec {
256                 let substrings = compiled_regexp.find_iter(&test_case).map(|m| m.as_str()).collect::<Vec<_>>();
257                 prop_assert_eq!(
258                     substrings.len(),
259                     1,
260                     "expression '{}' does not match test case '{}' entirely but {} of its substrings: {:?}",
261                     regexp,
262                     test_case,
263                     substrings.len(),
264                     substrings
265                 );
266             }
267         }
268     }
269 
270     #[test]
271     fn matching_regexes_without_anchors(
272         test_cases in prop::collection::hash_set("[A-C]{1,10}", 1..=5)
273     ) {
274         let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();
275         let regexp = RegExpBuilder::from(&test_cases_vec).without_anchors().build();
276         if let Ok(compiled_regexp) = compile_regexp(&regexp) {
277             for test_case in test_cases_vec {
278                 let substrings = compiled_regexp.find_iter(&test_case).map(|m| m.as_str()).collect::<Vec<_>>();
279                 prop_assert_eq!(
280                     substrings.len(),
281                     1,
282                     "expression '{}' does not match test case '{}' entirely but {} of its substrings: {:?}",
283                     regexp,
284                     test_case,
285                     substrings.len(),
286                     substrings
287                 );
288             }
289         }
290     }
291 
292     #[test]
293     fn regexes_not_matching_other_strings_with_default_settings(
294         test_cases in prop::collection::hash_set(".{1,10}", 1..=5),
295         other_strings in prop::collection::hash_set(".{1,10}", 1..=5)
296     ) {
297         if test_cases.is_disjoint(&other_strings) {
298             let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();
299             let regexp = RegExpBuilder::from(&test_cases_vec).build();
300             if let Ok(compiled_regexp) = compile_regexp(&regexp) {
301                 prop_assert!(other_strings.iter().all(|other_string| !compiled_regexp.is_match(&other_string)));
302             }
303         }
304     }
305 
306     #[test]
307     fn regexes_not_matching_other_strings_with_escape_sequences(
308         test_cases in prop::collection::hash_set(".{1,10}", 1..=5),
309         other_strings in prop::collection::hash_set(".{1,10}", 1..=5)
310     ) {
311         if test_cases.is_disjoint(&other_strings) {
312             let test_cases_vec = test_cases.iter().cloned().collect::<Vec<_>>();
313             let regexp = RegExpBuilder::from(&test_cases_vec)
314                 .with_escaping_of_non_ascii_chars(false)
315                 .build();
316             if let Ok(compiled_regexp) = compile_regexp(&regexp) {
317                 prop_assert!(other_strings.iter().all(|other_string| !compiled_regexp.is_match(&other_string)));
318             }
319         }
320     }
321 }
322 
conversion_feature_strategy() -> impl Strategy<Value = Feature>323 fn conversion_feature_strategy() -> impl Strategy<Value = Feature> {
324     prop_oneof![
325         Just(Feature::Digit),
326         Just(Feature::NonDigit),
327         Just(Feature::Space),
328         Just(Feature::NonSpace),
329         Just(Feature::Word),
330         Just(Feature::NonWord),
331         Just(Feature::Repetition),
332         Just(Feature::CaseInsensitivity),
333         Just(Feature::CapturingGroup)
334     ]
335 }
336 
compile_regexp(regexp: &str) -> Result<Regex, Error>337 fn compile_regexp(regexp: &str) -> Result<Regex, Error> {
338     RegexBuilder::new(regexp).size_limit(20000000).build()
339 }
340