use clap::{Parser, ValueEnum};
use glob::glob;
use serde::Deserialize;
use std::{collections::HashMap, fs::File, io::BufReader, time::Duration};
use test_interface::{TestId, TestResult, TestResultOutput, TestResults};
mod ast;
mod cli;
mod git;
mod test_interface;
#[derive(Parser, ValueEnum, Default, Debug, Clone)]
pub enum ResultType {
#[default]
Deflaker,
Nodeflake,
Cargo,
}
impl ToString for ResultType {
fn to_string(&self) -> String {
match self {
ResultType::Deflaker => "deflaker",
ResultType::Nodeflake => "nodeflake",
ResultType::Cargo => "vanilla",
}
.to_string()
}
}
#[derive(Deserialize, Debug)]
struct VanillaResult {
success: bool,
time: f32,
}
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
pub struct Cli {
#[arg(
short = 't',
long = "type",
help = "type of results to process",
value_enum,
default_value_t = ResultType::Deflaker,
)]
pub results_type: ResultType,
#[arg(short = 'p', long = "project", help = "the project to analyse")]
pub project: String,
}
fn main() {
let args = Cli::parse();
println!("Analysing");
let glob_pattern = &format!(
"./uploads/{}-{}-*",
args.project,
args.results_type.to_string()
);
dbg!(glob_pattern);
match args.results_type {
ResultType::Cargo => {
let mut times = vec![];
let mut success = 0;
let mut fail = 0;
for item in glob(glob_pattern.as_str()).expect("Failed to get files") {
if let Ok(path) = item {
let file = File::open(path).unwrap();
let reader = BufReader::new(file);
let result: VanillaResult =
serde_json::from_reader(reader).expect("Invalid results file");
times.push(result.time);
let counter = match result.success {
true => &mut success,
false => &mut fail,
};
*counter += 1;
}
}
let sum: f32 = times.iter().sum();
let average = sum / times.len() as f32;
println!("Average exec time: {}", average);
println!(
"{} runs. {} success, {} failed.",
success + fail,
success,
fail
);
}
ResultType::Deflaker | ResultType::Nodeflake => {
let mut test_results: HashMap<TestId, TestResult> = HashMap::new();
let mut test_timings = vec![];
let mut full_timings = vec![]; let mut num_tests = None;
let mut flaky = HashMap::new();
let mut deflaker_found = vec![];
for item in glob(glob_pattern.as_str()).expect("Failed to get files") {
if let Ok(path) = item {
let file = File::open(path).unwrap();
let reader = BufReader::new(file);
let results: TestResultOutput =
serde_json::from_reader(reader).expect("Invalid results file");
if num_tests.is_none() {
num_tests = Some(results.meta.tests);
} else {
if num_tests.unwrap() != results.meta.tests {
println!("WARN: number of tests does not match between runs");
}
}
test_timings.push(results.meta.execution_time);
if let Some(full_time) = results.meta.deflake_time {
full_timings.push(full_time);
}
results.tests.into_iter().for_each(|(id, res)| {
if res == TestResult::Flaky {
deflaker_found.push(id);
return;
}
match test_results.get(&id) {
Some(current_res) => {
if *current_res != res {
println!("Test {} has varying results", id);
match flaky.get(&id) {
Some(v) => flaky.insert(id, v + 1),
None => flaky.insert(id, 1),
};
}
}
None => {
test_results.insert(id, res);
}
};
})
}
}
println!("Processing {} results", test_timings.len());
let sum: u128 = test_timings.iter().sum();
let average = sum / test_timings.len() as u128;
println!(
"Average test exec time: {}",
Duration::from_millis(average as u64).as_secs_f64()
);
if full_timings.len() > 0 {
let sum: u128 = full_timings.iter().sum();
let average = sum / full_timings.len() as u128;
println!(
"Average exec time incl deflaker: {}",
Duration::from_millis(average as u64).as_secs_f64()
);
} else {
println!("Deflaker not enabled for runs");
}
println!("{} Flaky ", flaky.len());
for flake in &flaky {
println!(" - {} ({} times)", flake.0, flake.1);
}
println!("{} Deflaker found ", deflaker_found.len());
for test in &deflaker_found {
println!(" - {} ", test);
}
}
}
}