perl_test_generators/
module.rs1use proptest::prelude::*;
6
7fn module_segment() -> impl Strategy<Value = String> {
9 prop_oneof![
10 Just("Foo".to_string()),
11 Just("Bar".to_string()),
12 Just("Baz".to_string()),
13 Just("HTTP".to_string()),
14 Just("IO".to_string()),
15 (
16 prop::char::range('A', 'Z'),
17 prop::collection::vec(
18 prop_oneof![prop::char::range('A', 'Z'), prop::char::range('0', '9'), Just('_'),],
19 0..=7_usize,
20 ),
21 )
22 .prop_map(|(first, rest)| std::iter::once(first).chain(rest).collect::<String>()),
23 (
24 prop::char::range('A', 'Z'),
25 prop::collection::vec(prop::char::range('a', 'z'), 0..=7_usize),
26 )
27 .prop_map(|(first, rest)| std::iter::once(first).chain(rest).collect::<String>()),
28 ]
29}
30
31pub fn module_path() -> impl Strategy<Value = String> {
33 prop::collection::vec(module_segment(), 1..=5_usize).prop_map(|segs| segs.join("::"))
34}
35
36pub fn module_path_segments() -> impl Strategy<Value = Vec<String>> {
38 prop::collection::vec(module_segment(), 1..=5_usize)
39}
40
41#[cfg(test)]
42mod tests {
43 use super::*;
44
45 proptest! {
46 #[test]
47 fn module_path_no_empty_segments(path in module_path()) {
48 for seg in path.split("::") {
49 assert!(!seg.is_empty(), "empty segment in {}", path);
50 }
51 }
52
53 #[test]
54 fn module_path_starts_uppercase(path in module_path()) {
55 prop_assert!(!path.is_empty(), "module path must not be empty");
56 let first = path.chars().next().unwrap_or_default();
57 prop_assert!(first.is_ascii_uppercase(), "first char not uppercase: {}", path);
58 }
59
60 #[test]
61 fn segments_non_empty(segs in module_path_segments()) {
62 prop_assert!(!segs.is_empty());
63 for s in &segs {
64 prop_assert!(!s.is_empty());
65 }
66 }
67
68 #[test]
69 fn segments_use_module_identifier_charset(segs in module_path_segments()) {
70 for seg in &segs {
71 let mut chars = seg.chars();
72 let Some(first) = chars.next() else {
73 prop_assert!(false, "segment cannot be empty");
74 continue;
75 };
76 prop_assert!(first.is_ascii_uppercase(), "segment must start uppercase: {seg}");
77 for ch in chars {
78 prop_assert!(
79 ch.is_ascii_alphanumeric() || ch == '_',
80 "invalid module segment char '{ch}' in {seg}",
81 );
82 }
83 }
84 }
85
86 #[test]
87 fn module_path_segment_count_is_at_least_one(path in module_path()) {
88 let count = path.split("::").count();
89 prop_assert!(count >= 1, "module path must have at least one segment: {path}");
90 }
91
92 #[test]
93 fn module_path_segment_count_is_at_most_five(path in module_path()) {
94 let count = path.split("::").count();
95 prop_assert!(count <= 5, "module path must have at most 5 segments: {path}");
96 }
97
98 #[test]
99 fn module_path_is_valid_perl_use_statement(path in module_path()) {
100 let use_stmt = format!("use {};", path);
102 prop_assert!(use_stmt.starts_with("use "), "use statement malformed: {use_stmt}");
103 prop_assert!(use_stmt.ends_with(';'), "use statement must end with semicolon");
104 }
105
106 #[test]
107 fn module_path_segments_count_bounded(segs in module_path_segments()) {
108 prop_assert!(!segs.is_empty(), "must produce at least one segment");
109 prop_assert!(segs.len() <= 5, "must produce at most 5 segments");
110 }
111
112 #[test]
113 fn module_path_segments_are_ascii(segs in module_path_segments()) {
114 for seg in &segs {
115 prop_assert!(seg.is_ascii(), "module segments must be ASCII: {seg}");
116 }
117 }
118
119 #[test]
120 fn module_path_segments_match_joined_path(segs in module_path_segments()) {
121 let joined = segs.join("::");
123 let re_split: Vec<&str> = joined.split("::").collect();
124 prop_assert_eq!(segs.len(), re_split.len());
125 for (orig, recovered) in segs.iter().zip(re_split.iter()) {
126 prop_assert_eq!(orig.as_str(), *recovered);
127 }
128 }
129
130 #[test]
131 fn module_path_does_not_start_or_end_with_colon_colon(path in module_path()) {
132 prop_assert!(!path.starts_with("::"), "path must not start with '::': {path}");
133 prop_assert!(!path.ends_with("::"), "path must not end with '::': {path}");
134 }
135
136 #[test]
137 fn module_path_contains_no_triple_colons(path in module_path()) {
138 prop_assert!(!path.contains(":::"), "path must not contain ':::': {path}");
139 }
140 }
141}