1use crate::{
9 FlakyOrRerun, NonSuccessKind, NonSuccessReruns, Property, Report, ReportUuid, TestCase,
10 TestCaseStatus, TestRerun, TestSuite, XmlString,
11};
12use chrono::{DateTime, FixedOffset};
13use proptest::{
14 arbitrary::Arbitrary,
15 collection, option,
16 prelude::*,
17 strategy::{BoxedStrategy, Map, Strategy},
18};
19use std::time::Duration;
20
21impl Arbitrary for XmlString {
22 type Parameters = <String as Arbitrary>::Parameters;
23 type Strategy = Map<<String as Arbitrary>::Strategy, fn(String) -> XmlString>;
24
25 fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
26 String::arbitrary_with(args).prop_map(|s| {
27 XmlString::new(s.trim())
30 })
31 }
32}
33
34pub(crate) fn text_node_strategy() -> impl Strategy<Value = XmlString> {
35 any::<XmlString>().prop_filter("Non-empty string", |s| !s.is_empty())
36}
37
38pub(crate) fn test_name_strategy() -> impl Strategy<Value = XmlString> {
40 let ident = "[a-z][a-z0-9_]{0,15}";
42
43 collection::vec(ident, 1..=4).prop_map(|segments| XmlString::new(segments.join("::")))
45}
46
47pub(crate) fn xml_attr_name_strategy() -> impl Strategy<Value = XmlString> {
49 "[a-zA-Z_][a-zA-Z0-9_.-]{0,15}".prop_map(XmlString::new)
51}
52
53pub(crate) fn datetime_strategy() -> impl Strategy<Value = DateTime<FixedOffset>> {
55 (946684800i64..4102444800i64, -1440i32..1440i32).prop_map(|(secs, offset_minutes)| {
59 let offset_secs = offset_minutes * 60;
60 let offset =
61 FixedOffset::east_opt(offset_secs).unwrap_or(FixedOffset::east_opt(0).unwrap());
62 DateTime::from_timestamp(secs, 0)
63 .unwrap()
64 .with_timezone(&offset)
65 })
66}
67
68pub(crate) fn duration_strategy() -> impl Strategy<Value = Duration> {
70 (0u64..3_600_000u64).prop_map(Duration::from_millis)
72}
73
74pub(crate) fn xml_attr_index_map_strategy(
76) -> impl Strategy<Value = indexmap::IndexMap<XmlString, XmlString>> {
77 collection::hash_map(xml_attr_name_strategy(), any::<XmlString>(), 0..3)
78 .prop_map(|hm| hm.into_iter().collect())
79}
80
81impl Arbitrary for NonSuccessReruns {
82 type Parameters = ();
83 type Strategy = BoxedStrategy<Self>;
84
85 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
86 (
87 any::<FlakyOrRerun>(),
88 collection::vec(any::<TestRerun>(), 0..5),
89 )
90 .prop_map(|(kind, runs)| {
91 let kind = if runs.is_empty() {
94 FlakyOrRerun::Rerun
95 } else {
96 kind
97 };
98 NonSuccessReruns { kind, runs }
99 })
100 .boxed()
101 }
102}
103
104fn test_suite_children_strategy() -> impl Strategy<
105 Value = (
106 Vec<TestCase>,
107 Vec<Property>,
108 Option<XmlString>,
109 Option<XmlString>,
110 ),
111> {
112 prop_oneof![
113 1 => Just((Vec::new(), Vec::new(), None, None)),
117 7 => (
118 collection::vec(any::<TestCase>(), 0..10),
119 collection::vec(any::<Property>(), 0..5),
120 any::<Option<XmlString>>(),
121 any::<Option<XmlString>>(),
122 ),
123 ]
124}
125
126impl Arbitrary for TestSuite {
127 type Parameters = ();
128 type Strategy = BoxedStrategy<Self>;
129
130 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
131 (
132 test_name_strategy(),
133 option::of(datetime_strategy()),
134 option::of(duration_strategy()),
135 option::of(0..=1000usize),
136 test_suite_children_strategy(),
137 collection::hash_map(xml_attr_name_strategy(), any::<XmlString>(), 0..5),
138 )
139 .prop_map(
140 |(
141 name,
142 timestamp,
143 time,
144 disabled,
145 (test_cases, properties, system_out, system_err),
146 extra,
147 )| {
148 let tests = test_cases.len();
150 let mut failures = 0;
151 let mut errors = 0;
152 let mut skipped = 0;
153
154 for test_case in &test_cases {
155 match &test_case.status {
156 TestCaseStatus::Success { .. } => {}
157 TestCaseStatus::NonSuccess { kind, .. } => match kind {
158 NonSuccessKind::Failure => failures += 1,
159 NonSuccessKind::Error => errors += 1,
160 },
161 TestCaseStatus::Skipped { .. } => skipped += 1,
162 }
163 }
164
165 TestSuite {
166 name,
167 tests,
168 skipped,
169 disabled,
170 errors,
171 failures,
172 timestamp,
173 time,
174 test_cases,
175 properties,
176 system_out,
177 system_err,
178 extra: extra.into_iter().collect(),
179 }
180 },
181 )
182 .boxed()
183 }
184}
185
186impl Arbitrary for Report {
187 type Parameters = ();
188 type Strategy = BoxedStrategy<Self>;
189
190 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
191 (
192 test_name_strategy(),
193 any::<Option<ReportUuid>>(),
194 option::of(datetime_strategy()),
195 option::of(duration_strategy()),
196 collection::vec(any::<TestSuite>(), 0..5),
197 )
198 .prop_map(|(name, uuid, timestamp, time, test_suites)| {
199 let tests = test_suites.iter().map(|ts| ts.tests).sum();
201 let failures = test_suites.iter().map(|ts| ts.failures).sum();
202 let errors = test_suites.iter().map(|ts| ts.errors).sum();
203 let skipped = test_suites.iter().map(|ts| ts.skipped).sum();
204
205 let disabled = if test_suites.iter().all(|ts| ts.disabled.is_none()) {
208 None
209 } else {
210 Some(test_suites.iter().filter_map(|ts| ts.disabled).sum())
211 };
212
213 Report {
214 name,
215 uuid,
216 timestamp,
217 time,
218 tests,
219 failures,
220 errors,
221 skipped,
222 disabled,
223 test_suites,
224 }
225 })
226 .boxed()
227 }
228}