1pub mod results_cache;
2
3use serde_derive::Deserialize;
4use std::collections::{BTreeMap, BTreeSet};
5use std::default::Default;
6use std::fmt::Display;
7use thiserror::Error;
8
9pub type Result<T> = std::result::Result<T, Error>;
10pub type RunScores = BTreeMap<String, Vec<u64>>;
11pub type InteropScore = BTreeMap<String, u64>;
12pub type ExpectedFailureScores = BTreeMap<String, Vec<(u64, u64)>>;
13
14#[derive(Error, Debug)]
15pub enum Error {
16 #[error(transparent)]
17 Git(#[from] git2::Error),
18 #[error(transparent)]
19 Serde(#[from] serde_json::Error),
20 #[error("{0}")]
21 String(String),
22}
23
24#[derive(Debug, Deserialize)]
25pub struct Results {
26 pub status: TestStatus,
27 #[serde(default)]
28 pub subtests: Vec<SubtestResult>,
29 #[serde(default)]
30 pub expected: Option<TestStatus>,
31}
32
33#[derive(Debug, Deserialize)]
34pub struct SubtestResult {
35 pub name: String,
36 pub status: SubtestStatus,
37 #[serde(default)]
38 pub expected: Option<SubtestStatus>,
39}
40
41#[derive(Deserialize, PartialEq, Eq, Clone, Debug, Copy, Hash)]
42#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
43pub enum TestStatus {
44 Pass,
45 Fail,
46 Ok,
47 Error,
48 Timeout,
49 Crash,
50 Assert,
51 PreconditionFailed,
52 Skip,
53}
54
55impl TryFrom<&str> for TestStatus {
56 type Error = Error;
57
58 fn try_from(value: &str) -> Result<TestStatus> {
59 match value {
60 "PASS" => Ok(TestStatus::Pass),
61 "FAIL" => Ok(TestStatus::Fail),
62 "OK" => Ok(TestStatus::Ok),
63 "ERROR" => Ok(TestStatus::Error),
64 "TIMEOUT" => Ok(TestStatus::Timeout),
65 "CRASH" => Ok(TestStatus::Crash),
66 "ASSERT" => Ok(TestStatus::Assert),
67 "PRECONDITION_FAILED" => Ok(TestStatus::PreconditionFailed),
68 "SKIP" => Ok(TestStatus::Skip),
69 x => Err(Error::String(format!("Unrecognised test status {}", x))),
70 }
71 }
72}
73
74impl Display for TestStatus {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 write!(
77 f,
78 "{}",
79 match self {
80 TestStatus::Pass => "PASS",
81 TestStatus::Fail => "FAIL",
82 TestStatus::Ok => "OK",
83 TestStatus::Error => "ERROR",
84 TestStatus::Timeout => "TIMEOUT",
85 TestStatus::Crash => "CRASH",
86 TestStatus::Assert => "ASSERT",
87 TestStatus::PreconditionFailed => "PRECONDITION_FAILED",
88 TestStatus::Skip => "SKIP",
89 }
90 )
91 }
92}
93
94#[derive(Deserialize, PartialEq, Eq, Clone, Debug, Copy, Hash)]
95#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
96pub enum SubtestStatus {
97 Pass,
98 Fail,
99 Error,
100 Timeout,
101 Assert,
102 PreconditionFailed,
103 Notrun,
104 Skip,
105}
106
107impl TryFrom<&str> for SubtestStatus {
108 type Error = Error;
109
110 fn try_from(value: &str) -> Result<SubtestStatus> {
111 match value {
112 "PASS" => Ok(SubtestStatus::Pass),
113 "FAIL" => Ok(SubtestStatus::Fail),
114 "ERROR" => Ok(SubtestStatus::Error),
115 "TIMEOUT" => Ok(SubtestStatus::Timeout),
116 "ASSERT" => Ok(SubtestStatus::Assert),
117 "PRECONDITION_FAILED" => Ok(SubtestStatus::PreconditionFailed),
118 "NOTRUN" => Ok(SubtestStatus::Notrun),
119 "SKIP" => Ok(SubtestStatus::Skip),
120 x => Err(Error::String(format!("Unrecognised subtest status {}", x))),
121 }
122 }
123}
124
125impl Display for SubtestStatus {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 write!(
128 f,
129 "{}",
130 match self {
131 SubtestStatus::Pass => "PASS",
132 SubtestStatus::Fail => "FAIL",
133 SubtestStatus::Error => "ERROR",
134 SubtestStatus::Timeout => "TIMEOUT",
135 SubtestStatus::Assert => "ASSERT",
136 SubtestStatus::PreconditionFailed => "PRECONDITION_FAILED",
137 SubtestStatus::Notrun => "NOTRUN",
138 SubtestStatus::Skip => "SKIP",
139 }
140 )
141 }
142}
143
144#[derive(Debug, Default)]
145struct TestScore {
146 passes: u64,
147 total: u64,
148}
149
150impl TestScore {
151 fn new(passes: u64, total: u64) -> TestScore {
152 TestScore { passes, total }
153 }
154}
155
156#[derive(Debug, Default)]
157struct RunScore {
158 category_scores: Vec<f64>,
159 category_expected_failures: Vec<f64>,
160 unexpected_not_ok: BTreeSet<String>,
161}
162
163impl RunScore {
164 fn new(size: usize) -> RunScore {
165 RunScore {
166 category_scores: vec![0.; size],
167 category_expected_failures: vec![0.; size],
168 ..Default::default()
169 }
170 }
171}
172
173fn score_run<'a>(
174 run: impl Iterator<Item = (&'a str, &'a Results)>,
175 num_categories: usize,
176 categories_by_test: &BTreeMap<&'a str, Vec<usize>>,
177 expected_not_ok: &BTreeSet<String>,
178 test_scores_by_category: &mut [BTreeMap<&'a str, Vec<TestScore>>],
179) -> RunScore {
180 let mut run_score = RunScore::new(num_categories);
181 for (test_id, test_results) in run {
182 if let Some(categories) = categories_by_test.get(test_id) {
183 if test_results.status != TestStatus::Ok && !expected_not_ok.contains(test_id) {
184 run_score.unexpected_not_ok.insert(test_id.into());
185 }
186
187 let (test_passes, expected_failures, test_total) = if !test_results.subtests.is_empty()
188 {
189 let (test_passes, expected_failures) = test_results
190 .subtests
191 .iter()
192 .map(|subtest| {
193 if (subtest.status) == SubtestStatus::Pass {
194 (1, 0)
195 } else {
196 (
197 0,
198 if (test_results.expected.is_some()
199 && test_results.expected != Some(TestStatus::Ok)
200 && test_results.expected != Some(TestStatus::Pass))
201 || (subtest.expected.is_some()
202 && subtest.expected != Some(SubtestStatus::Pass))
203 {
204 1
205 } else {
206 0
207 },
208 )
209 }
210 })
211 .fold((0, 0), |acc, elem| (acc.0 + elem.0, acc.1 + elem.1));
212 (
213 test_passes,
214 expected_failures,
215 test_results.subtests.len() as u32,
216 )
217 } else {
218 let (is_pass, expected_failure) = if test_results.status == TestStatus::Pass {
219 (1, 0)
220 } else {
221 (
222 0,
223 if test_results.expected.is_some()
224 && test_results.expected != Some(TestStatus::Ok)
225 && test_results.expected != Some(TestStatus::Pass)
226 {
227 1
228 } else {
229 0
230 },
231 )
232 };
233 (is_pass, expected_failure, 1)
234 };
235 for category_idx in categories {
236 let test_scores = &mut test_scores_by_category[*category_idx];
237 let pass_count = test_scores.entry(test_id).or_insert_with(Vec::new);
238 pass_count.push(TestScore::new(test_passes, test_total as u64));
239
240 run_score.category_scores[*category_idx] += test_passes as f64 / test_total as f64;
241 run_score.category_expected_failures[*category_idx] +=
242 expected_failures as f64 / test_total as f64;
243 }
244 }
245 }
246 run_score
247}
248
249fn interop_score<'a>(
250 test_scores: impl Iterator<Item = &'a Vec<TestScore>>,
251 num_runs: usize,
252) -> u64 {
253 let mut interop_score = 0;
254 let mut num_test_scores = 0;
255 for test_score in test_scores {
256 num_test_scores += 1;
257 if test_score.len() != num_runs {
258 continue;
259 }
260 let min_score = test_score
261 .iter()
262 .map(|score| (1000. * score.passes as f64 / score.total as f64).trunc() as u64)
263 .min()
264 .unwrap_or(0);
265 interop_score += min_score
266 }
267 (interop_score as f64 / num_test_scores as f64).trunc() as u64
268}
269
270pub fn score_runs<'a>(
281 runs: impl Iterator<Item = &'a BTreeMap<String, Results>>,
282 tests_by_category: &BTreeMap<String, BTreeSet<String>>,
283 expected_not_ok: &BTreeSet<String>,
284) -> (RunScores, InteropScore, ExpectedFailureScores) {
285 let mut unexpected_not_ok = BTreeSet::new();
286
287 let num_categories = tests_by_category.len();
289 let mut categories = Vec::with_capacity(num_categories);
290 let mut test_count_by_category = Vec::with_capacity(num_categories);
291 let mut test_scores_by_category = Vec::with_capacity(num_categories);
292
293 let mut categories_by_test = BTreeMap::new();
294
295 let mut scores_by_category = BTreeMap::new();
296 let mut interop_by_category = BTreeMap::new();
297 let mut expected_failures_by_category = BTreeMap::new();
298
299 for (cat_idx, (category, tests)) in tests_by_category.iter().enumerate() {
300 categories.push(category);
301 test_count_by_category.push(tests.len());
302 test_scores_by_category.push(BTreeMap::new());
303
304 for test_id in tests {
305 categories_by_test
306 .entry(test_id.as_ref())
307 .or_insert_with(Vec::new)
308 .push(cat_idx)
309 }
310 scores_by_category.insert(category.clone(), Vec::with_capacity(runs.size_hint().0));
311 expected_failures_by_category
312 .insert(category.clone(), Vec::with_capacity(runs.size_hint().0));
313 interop_by_category.insert(category.clone(), 0);
314 }
315
316 let mut run_count = 0;
317 for run in runs {
318 run_count += 1;
319 let run_score = score_run(
320 run.iter()
321 .map(|(test_id, results)| (test_id.as_ref(), results)),
322 num_categories,
323 &categories_by_test,
324 expected_not_ok,
325 &mut test_scores_by_category,
326 );
327 for (idx, name) in categories.iter().enumerate() {
328 scores_by_category
329 .get_mut(*name)
330 .expect("Missing category")
331 .push(
332 (1000. * run_score.category_scores[idx] / test_count_by_category[idx] as f64)
333 .trunc() as u64,
334 );
335 expected_failures_by_category
336 .get_mut(*name)
337 .expect("Missing category")
338 .push((
339 (1000. * run_score.category_expected_failures[idx]
340 / test_count_by_category[idx] as f64)
341 .trunc() as u64,
342 (1000.
343 * (run_score.category_scores[idx]
344 / (test_count_by_category[idx] as f64
345 - run_score.category_expected_failures[idx])))
346 .trunc() as u64,
347 ));
348 }
349 unexpected_not_ok.extend(run_score.unexpected_not_ok)
350 }
351 for (idx, name) in categories.iter().enumerate() {
352 let scores = &test_scores_by_category[idx];
353 interop_by_category.insert((*name).clone(), interop_score(scores.values(), run_count));
354 }
355 (
356 scores_by_category,
357 interop_by_category,
358 expected_failures_by_category,
359 )
360}