diff --git a/cli/src/validate.rs b/cli/src/validate.rs index 4f6cb4b3..30cc1926 100644 --- a/cli/src/validate.rs +++ b/cli/src/validate.rs @@ -10,7 +10,8 @@ use context::junit::{ parser::{JunitParseError, JunitParser}, validator::{ validate as validate_report, JunitReportValidation, JunitReportValidationFlatIssue, - JunitReportValidationIssueSubOptimal, JunitValidationLevel, + JunitReportValidationIssueSubOptimal, JunitValidationIssue, JunitValidationIssueType, + JunitValidationLevel, }, }; @@ -212,12 +213,12 @@ fn print_validation_errors(report_validations: &JunitFileToValidation) -> (usize match report_validation.1 { Ok(validation) => { num_test_suites = validation.test_suites().len(); - num_test_cases = validation.test_cases_flat().len(); + num_test_cases = validation.test_cases().len(); num_validation_errors = validation.num_invalid_issues(); num_validation_warnings = validation.num_suboptimal_issues(); - all_issues = validation.all_issues_owned(); + all_issues = validation.all_issues_flat(); } Err(e) => { report_parse_error = Some(e); @@ -294,22 +295,25 @@ fn print_codeowners_validation( print_validation_level(JunitValidationLevel::Valid) ); println!(" Path: {:?}", owners.path); - for report_validation in report_validations { - match report_validation.1 { - Ok(validation) => { - let issues = validation.all_issues_owned(); - if issues.iter().any( - |i| - i.error_message == JunitReportValidationIssueSubOptimal::TestCasesFileOrFilepathMissing.to_string() - ) { - println!( - " {} - CODEOWNERS found but test cases are missing filepaths. We will not be able to correlate flaky tests with owners.", - print_validation_level(JunitValidationLevel::SubOptimal) - ); - } - } - Err(_) => {} - } + + let has_test_cases_without_matching_codeowners_paths = report_validations + .iter() + .filter_map(|(_, report_validation)| report_validation.as_ref().ok()) + .flat_map(|report_validation| report_validation.all_issues()) + .any(|issue| { + matches!( + issue, + JunitValidationIssueType::Report(JunitValidationIssue::SubOptimal( + JunitReportValidationIssueSubOptimal::TestCasesFileOrFilepathMissing + )) + ) + }); + + if has_test_cases_without_matching_codeowners_paths { + println!( + " {} - CODEOWNERS found but test cases are missing filepaths. We will not be able to correlate flaky tests with owners.", + print_validation_level(JunitValidationLevel::SubOptimal) + ); } } None => println!( diff --git a/context-js/tests/parse_and_validate.test.ts b/context-js/tests/parse_and_validate.test.ts index a9ec1c47..456ba2d6 100644 --- a/context-js/tests/parse_and_validate.test.ts +++ b/context-js/tests/parse_and_validate.test.ts @@ -97,7 +97,7 @@ describe("context-js", () => { expect(junitReportValidation.num_suboptimal_issues()).toBe(1); expect( junitReportValidation - .all_issues_owned() + .all_issues_flat() .filter((issue) => issue.error_type === JunitValidationType.Report), ).toHaveLength(1); }); diff --git a/context-py/tests/test_parse_and_validate.py b/context-py/tests/test_parse_and_validate.py index 6423821f..5e5c251d 100644 --- a/context-py/tests/test_parse_and_validate.py +++ b/context-py/tests/test_parse_and_validate.py @@ -243,7 +243,7 @@ def test_junit_validate_suboptimal(): len( [ x - for x in junit_report_validation.all_issues_owned() + for x in junit_report_validation.all_issues_flat() if x.error_type == JunitValidationType.Report ] ) diff --git a/context/src/junit/validator.rs b/context/src/junit/validator.rs index fca7e1a8..0026f248 100644 --- a/context/src/junit/validator.rs +++ b/context/src/junit/validator.rs @@ -208,15 +208,52 @@ pub fn validate(report: &Report) -> JunitReportValidation { report_validation } -#[cfg_attr(feature = "pyo3", gen_stub_pyclass, pyclass(eq, get_all))] -#[cfg_attr(feature = "wasm", wasm_bindgen(getter_with_clone))] +#[cfg_attr(feature = "pyo3", gen_stub_pyclass, pyclass(eq))] +#[cfg_attr(feature = "wasm", wasm_bindgen)] #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct JunitReportValidation { - all_issues: Vec, + all_issues: Vec, level: JunitValidationLevel, test_suites: Vec, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum JunitValidationIssueType { + Report(JunitReportValidationIssue), + TestSuite(JunitTestSuiteValidationIssue), + TestCase(JunitTestCaseValidationIssue), +} + +impl ToString for JunitValidationIssueType { + fn to_string(&self) -> String { + match self { + JunitValidationIssueType::Report(i) => i.to_string(), + JunitValidationIssueType::TestSuite(i) => i.to_string(), + JunitValidationIssueType::TestCase(i) => i.to_string(), + } + } +} + +impl From<&JunitValidationIssueType> for JunitValidationType { + fn from(value: &JunitValidationIssueType) -> Self { + match value { + JunitValidationIssueType::Report(..) => JunitValidationType::Report, + JunitValidationIssueType::TestSuite(..) => JunitValidationType::TestSuite, + JunitValidationIssueType::TestCase(..) => JunitValidationType::TestCase, + } + } +} + +impl From<&JunitValidationIssueType> for JunitValidationLevel { + fn from(value: &JunitValidationIssueType) -> Self { + match value { + JunitValidationIssueType::Report(i) => JunitValidationLevel::from(i), + JunitValidationIssueType::TestSuite(i) => JunitValidationLevel::from(i), + JunitValidationIssueType::TestCase(i) => JunitValidationLevel::from(i), + } + } +} + #[cfg_attr(feature = "pyo3", gen_stub_pyclass, pyclass(get_all))] #[cfg_attr(feature = "wasm", wasm_bindgen(getter_with_clone))] #[derive(Debug, Clone, Default, PartialEq, Eq)] @@ -229,8 +266,15 @@ pub struct JunitReportValidationFlatIssue { #[cfg_attr(feature = "pyo3", gen_stub_pymethods, pymethods)] #[cfg_attr(feature = "wasm", wasm_bindgen)] impl JunitReportValidation { - pub fn all_issues_owned(&self) -> Vec { - self.all_issues.clone() + pub fn all_issues_flat(&self) -> Vec { + self.all_issues + .iter() + .map(|i| JunitReportValidationFlatIssue { + level: JunitValidationLevel::from(i), + error_type: JunitValidationType::from(i), + error_message: i.to_string(), + }) + .collect() } pub fn test_suites_owned(&self) -> Vec { @@ -255,20 +299,20 @@ impl JunitReportValidation { pub fn num_invalid_issues(&self) -> usize { self.all_issues .iter() - .filter(|issue| issue.level == JunitValidationLevel::Invalid) + .filter(|issue| JunitValidationLevel::from(*issue) == JunitValidationLevel::Invalid) .count() } pub fn num_suboptimal_issues(&self) -> usize { self.all_issues .iter() - .filter(|issue| issue.level == JunitValidationLevel::SubOptimal) + .filter(|issue| JunitValidationLevel::from(*issue) == JunitValidationLevel::SubOptimal) .count() } } impl JunitReportValidation { - pub fn all_issues(&self) -> &[JunitReportValidationFlatIssue] { + pub fn all_issues(&self) -> &[JunitValidationIssueType] { &self.all_issues } @@ -276,7 +320,7 @@ impl JunitReportValidation { &self.test_suites } - pub fn test_cases_flat(&self) -> Vec<&JunitTestCaseValidation> { + pub fn test_cases(&self) -> Vec<&JunitTestCaseValidation> { self.test_suites .iter() .flat_map(|test_suite| test_suite.test_cases()) @@ -285,15 +329,11 @@ impl JunitReportValidation { fn derive_all_issues(&mut self) { let mut report_level_issues: HashSet = HashSet::new(); - let mut other_issues: Vec = Vec::new(); + let mut other_issues: Vec = Vec::new(); for test_suite in &self.test_suites { for issue in &test_suite.issues { - other_issues.push(JunitReportValidationFlatIssue { - level: JunitValidationLevel::from(issue), - error_type: JunitValidationType::TestSuite, - error_message: issue.to_string(), - }); + other_issues.push(JunitValidationIssueType::TestSuite(issue.clone())); } for test_case in &test_suite.test_cases { @@ -330,11 +370,7 @@ impl JunitReportValidation { } { report_level_issues.insert(report_level_issue); } else { - other_issues.push(JunitReportValidationFlatIssue { - level: JunitValidationLevel::from(issue), - error_type: JunitValidationType::TestCase, - error_message: issue.to_string(), - }); + other_issues.push(JunitValidationIssueType::TestCase(issue.clone())); } } } @@ -349,18 +385,17 @@ impl JunitReportValidation { other_issues.extend( report_level_issues .iter() - .map(|report_level_issue| JunitReportValidationFlatIssue { - level: JunitValidationLevel::from(report_level_issue), - error_type: JunitValidationType::Report, - error_message: report_level_issue.to_string(), - }) - .collect::>(), + .map(|issue| JunitValidationIssueType::Report(issue.clone())), ); - other_issues.sort_by(|a, b| match (a.level, b.level) { - (JunitValidationLevel::Invalid, JunitValidationLevel::SubOptimal) => Ordering::Less, - (JunitValidationLevel::SubOptimal, JunitValidationLevel::Invalid) => Ordering::Greater, - _ => a.error_message.cmp(&b.error_message), + other_issues.sort_by(|a, b| { + match (JunitValidationLevel::from(a), JunitValidationLevel::from(b)) { + (JunitValidationLevel::Invalid, JunitValidationLevel::SubOptimal) => Ordering::Less, + (JunitValidationLevel::SubOptimal, JunitValidationLevel::Invalid) => { + Ordering::Greater + } + _ => a.to_string().cmp(&b.to_string()), + } }); self.all_issues = other_issues;