|
| 1 | +use super::{Comparison, CrateResult, RawTestResults}; |
| 2 | +use crate::crates::Crate; |
| 3 | +use crate::results::{ |
| 4 | + FailureReason, |
| 5 | + TestResult::{self, BuildFail}, |
| 6 | +}; |
| 7 | +use std::collections::BTreeSet; |
| 8 | +use std::collections::HashMap; |
| 9 | + |
| 10 | +pub enum ToolchainSelect { |
| 11 | + Start, |
| 12 | + End, |
| 13 | +} |
| 14 | + |
| 15 | +pub enum ReportConfig { |
| 16 | + Simple, |
| 17 | + Complete(ToolchainSelect), |
| 18 | +} |
| 19 | + |
| 20 | +#[cfg_attr(test, derive(Debug, PartialEq))] |
| 21 | +#[derive(Clone)] |
| 22 | +pub enum ReportCrates { |
| 23 | + Plain(Vec<CrateResult>), |
| 24 | + Complete { |
| 25 | + tree: HashMap<Crate, Vec<CrateResult>>, |
| 26 | + results: HashMap<TestResult, Vec<CrateResult>>, |
| 27 | + }, |
| 28 | +} |
| 29 | + |
| 30 | +#[cfg_attr(test, derive(Debug, PartialEq))] |
| 31 | +pub struct TestResults { |
| 32 | + pub categories: HashMap<Comparison, ReportCrates>, |
| 33 | + pub info: HashMap<Comparison, u32>, |
| 34 | +} |
| 35 | + |
| 36 | +fn analyze_detailed(toolchain: usize, crates: Vec<CrateResult>) -> ReportCrates { |
| 37 | + let mut tree = HashMap::new(); |
| 38 | + let mut results = HashMap::new(); |
| 39 | + |
| 40 | + let mut root = Vec::new(); |
| 41 | + for krate in crates { |
| 42 | + if let BuildFail(FailureReason::DependsOn(ref deps)) = |
| 43 | + (&krate.runs[toolchain]).as_ref().unwrap().res |
| 44 | + { |
| 45 | + for dep in deps { |
| 46 | + tree.entry(dep.clone()) |
| 47 | + .or_insert_with(Vec::new) |
| 48 | + .push(krate.clone()) |
| 49 | + } |
| 50 | + } else { |
| 51 | + root.push(krate); |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + for krate in root { |
| 56 | + // record results only for root crates |
| 57 | + if let BuildFail(FailureReason::CompilerError(codes)) = |
| 58 | + krate.runs[toolchain].clone().unwrap().res |
| 59 | + { |
| 60 | + for code in codes { |
| 61 | + results |
| 62 | + .entry(BuildFail(FailureReason::CompilerError(btreeset![code]))) |
| 63 | + .or_insert_with(Vec::new) |
| 64 | + .push(krate.clone()) |
| 65 | + } |
| 66 | + } else { |
| 67 | + results |
| 68 | + .entry(krate.runs[toolchain].as_ref().unwrap().res.clone()) |
| 69 | + .or_insert_with(Vec::new) |
| 70 | + .push(krate) |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + ReportCrates::Complete { tree, results } |
| 75 | +} |
| 76 | + |
| 77 | +pub fn analyze_report(test: RawTestResults) -> TestResults { |
| 78 | + let mut comparison = HashMap::new(); |
| 79 | + for krate in test.crates { |
| 80 | + comparison |
| 81 | + .entry(krate.res) |
| 82 | + .or_insert_with(Vec::new) |
| 83 | + .push(krate); |
| 84 | + } |
| 85 | + |
| 86 | + let info = comparison |
| 87 | + .iter() |
| 88 | + .map(|(&key, vec)| (key, vec.len() as u32)) |
| 89 | + .collect::<HashMap<_, _>>(); |
| 90 | + |
| 91 | + let mut categories = HashMap::new(); |
| 92 | + for (cat, crates) in comparison { |
| 93 | + if let ReportConfig::Complete(toolchain) = cat.report_config() { |
| 94 | + // variants in an enum are numbered following an |
| 95 | + // increasing sequence starting from 0 |
| 96 | + categories.insert(cat, analyze_detailed(toolchain as usize, crates)); |
| 97 | + } else { |
| 98 | + categories.insert(cat, ReportCrates::Plain(crates)); |
| 99 | + } |
| 100 | + } |
| 101 | + |
| 102 | + TestResults { categories, info } |
| 103 | +} |
| 104 | + |
| 105 | +#[cfg(test)] |
| 106 | +mod tests { |
| 107 | + use super::*; |
| 108 | + use crate::config::Config; |
| 109 | + use crate::crates::{Crate, RegistryCrate}; |
| 110 | + use crate::experiments::{CapLints, Experiment, Mode, Status}; |
| 111 | + use crate::report::{generate_report, Comparison}; |
| 112 | + use crate::results::{DummyDB, FailureReason::*}; |
| 113 | + use crate::toolchain::{MAIN_TOOLCHAIN, TEST_TOOLCHAIN}; |
| 114 | + use failure::Fallible; |
| 115 | + |
| 116 | + #[test] |
| 117 | + fn test_report_analysis() -> Fallible<()> { |
| 118 | + macro_rules! reg { |
| 119 | + ($name:expr) => { |
| 120 | + Crate::Registry(RegistryCrate { |
| 121 | + name: $name.into(), |
| 122 | + version: "0.0.1".into(), |
| 123 | + }) |
| 124 | + }; |
| 125 | + } |
| 126 | + |
| 127 | + macro_rules! record_crates { |
| 128 | + ($db:expr, $ex:expr, $($name:expr => ($tc1:expr, $tc2:expr)),*) => { |
| 129 | + { |
| 130 | + let mut crates = Vec::new(); |
| 131 | + $( |
| 132 | + let krate = reg!($name); |
| 133 | + $db.add_dummy_result( |
| 134 | + &$ex, |
| 135 | + krate.clone(), |
| 136 | + MAIN_TOOLCHAIN.clone(), |
| 137 | + $tc1, |
| 138 | + ); |
| 139 | + $db.add_dummy_result( |
| 140 | + &$ex, |
| 141 | + krate.clone(), |
| 142 | + TEST_TOOLCHAIN.clone(), |
| 143 | + $tc2, |
| 144 | + ); |
| 145 | + crates.push(krate); |
| 146 | + )* |
| 147 | + crates |
| 148 | + } |
| 149 | + }; |
| 150 | + } |
| 151 | + |
| 152 | + let config = Config::default(); |
| 153 | + let mut db = DummyDB::default(); |
| 154 | + let ex = Experiment { |
| 155 | + name: "foo".to_string(), |
| 156 | + toolchains: [MAIN_TOOLCHAIN.clone(), TEST_TOOLCHAIN.clone()], |
| 157 | + mode: Mode::BuildAndTest, |
| 158 | + cap_lints: CapLints::Forbid, |
| 159 | + priority: 0, |
| 160 | + created_at: ::chrono::Utc::now(), |
| 161 | + started_at: None, |
| 162 | + completed_at: None, |
| 163 | + github_issue: None, |
| 164 | + status: Status::GeneratingReport, |
| 165 | + assigned_to: None, |
| 166 | + report_url: None, |
| 167 | + ignore_blacklist: false, |
| 168 | + requirement: None, |
| 169 | + }; |
| 170 | + |
| 171 | + let crates = record_crates! {db, ex, |
| 172 | + "test-pass" => (TestResult::TestPass, TestResult::TestPass), |
| 173 | + "ce-1" => (TestResult::TestPass, TestResult::BuildFail(CompilerError(btreeset!["001".parse()?, "002".parse()?]))), |
| 174 | + "ce-2" => (TestResult::TestPass, TestResult::BuildFail(CompilerError(btreeset!["002".parse()?]))), |
| 175 | + "unknown" => (TestResult::TestPass, TestResult::BuildFail(Unknown)), |
| 176 | + "dep-1" => (TestResult::TestPass, TestResult::BuildFail(DependsOn(btreeset![reg!("ce-1"), reg!("unknown")]))), |
| 177 | + "dep-2" => (TestResult::TestPass, TestResult::BuildFail(DependsOn(btreeset![reg!("ce-1"), reg!("ce-2")]))), |
| 178 | + "fix-1" => (TestResult::BuildFail(DependsOn(btreeset![reg!("ce-1"), reg!("ce-2")])), TestResult::TestPass), |
| 179 | + "fix-2" => (TestResult::BuildFail(Unknown), TestResult::TestPass) |
| 180 | + }; |
| 181 | + |
| 182 | + let raw = generate_report(&db, &config, &ex, &crates)?; |
| 183 | + let mut crates = raw |
| 184 | + .crates |
| 185 | + .clone() |
| 186 | + .into_iter() |
| 187 | + .map(|krate| { |
| 188 | + if let Crate::Registry(ref registry_krate) = krate.krate { |
| 189 | + (registry_krate.name.clone(), krate) |
| 190 | + } else { |
| 191 | + panic!("invalid crate type") |
| 192 | + } |
| 193 | + }) |
| 194 | + .collect::<HashMap<_, _>>(); |
| 195 | + let analyzed = analyze_report(raw); |
| 196 | + |
| 197 | + let mut info = HashMap::new(); |
| 198 | + info.insert(Comparison::Regressed, 5); |
| 199 | + info.insert(Comparison::Fixed, 2); |
| 200 | + info.insert(Comparison::SameTestPass, 1); |
| 201 | + |
| 202 | + macro_rules! create_results { |
| 203 | + ($src:expr, $($key:expr => ($($krate:expr),*)),*) => { |
| 204 | + { |
| 205 | + let mut map = HashMap::new(); |
| 206 | + $( |
| 207 | + let mut crates = Vec::new(); |
| 208 | + $( |
| 209 | + crates.push($src.get($krate).unwrap().clone()); |
| 210 | + )* |
| 211 | + map.insert($key, crates); |
| 212 | + )* |
| 213 | + map |
| 214 | + } |
| 215 | + } |
| 216 | + } |
| 217 | + |
| 218 | + let regr_tree = create_results! {crates, |
| 219 | + reg!("ce-1") => ("dep-1", "dep-2"), |
| 220 | + reg!("ce-2") => ("dep-2"), |
| 221 | + reg!("unknown") => ("dep-1") |
| 222 | + }; |
| 223 | + |
| 224 | + let regr_root = create_results! {crates, |
| 225 | + TestResult::BuildFail(CompilerError(btreeset!["001".parse()?])) => ("ce-1"), |
| 226 | + TestResult::BuildFail(CompilerError(btreeset!["002".parse()?])) => ("ce-1", "ce-2"), |
| 227 | + TestResult::BuildFail(Unknown) => ("unknown") |
| 228 | + }; |
| 229 | + |
| 230 | + let regressed = ReportCrates::Complete { |
| 231 | + tree: regr_tree, |
| 232 | + results: regr_root, |
| 233 | + }; |
| 234 | + |
| 235 | + let fix_tree = create_results! {crates, |
| 236 | + reg!("ce-1") => ("fix-1"), |
| 237 | + reg!("ce-2") => ("fix-1") |
| 238 | + }; |
| 239 | + |
| 240 | + let fix_root = create_results! {crates, |
| 241 | + TestResult::BuildFail(Unknown) => ("fix-2") |
| 242 | + }; |
| 243 | + |
| 244 | + let fixed = ReportCrates::Complete { |
| 245 | + tree: fix_tree, |
| 246 | + results: fix_root, |
| 247 | + }; |
| 248 | + |
| 249 | + let test_pass = ReportCrates::Plain(vec![crates.remove("test-pass").unwrap()]); |
| 250 | + |
| 251 | + let mut categories = HashMap::new(); |
| 252 | + categories.insert(Comparison::Regressed, regressed); |
| 253 | + categories.insert(Comparison::Fixed, fixed); |
| 254 | + categories.insert(Comparison::SameTestPass, test_pass); |
| 255 | + |
| 256 | + let expected = TestResults { categories, info }; |
| 257 | + assert_eq!(expected, analyzed); |
| 258 | + |
| 259 | + Ok(()) |
| 260 | + } |
| 261 | +} |
0 commit comments