-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[TRUNK-12978] v1 validate command #129
[TRUNK-12978] v1 validate command #129
Conversation
406 tests were run on |
cli/src/main.rs
Outdated
|
||
use colored::{ColoredString, Colorize}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can we group this with the rest of the imports for organization?
cli/src/main.rs
Outdated
log::info!( | ||
"Starting trunk-analytics-cli {} (git={}) rustc={}", | ||
env!("CARGO_PKG_VERSION"), | ||
env!("VERGEN_GIT_SHA"), | ||
env!("VERGEN_RUSTC_SEMVER") | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we make a function that does this and reuse it across commands?
cli/src/main.rs
Outdated
" File set ({:?}): {}", | ||
file_set.file_set_type, | ||
file_set.glob | ||
); | ||
for file in &file_set.files { | ||
log::info!(" {}", file.original_path_rel); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit, optional: may want to use \t
tab character for spacing
cli/src/scanner.rs
Outdated
let original_path = path | ||
let original_path_abs = path | ||
.to_str() | ||
.expect("failed to convert path to string") | ||
.to_string(); | ||
let original_path_rel = path | ||
.strip_prefix(repo_root) | ||
.unwrap_or(&path) | ||
.to_str() | ||
.expect("failed to convert path to string") | ||
.to_string(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know you're following the already existing pattern here, but I think we need to update this so it doesn't panic here. Instead of using expect
we can use map_err
+ ?
(try sigil)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! went with ok_or_else
since I was working with an Option
rather than a Result
cli/src/types.rs
Outdated
#[derive(Debug, Serialize, Deserialize, Clone)] | ||
pub struct WithFilePath<T> { | ||
pub file_path: String, | ||
pub wrapped: T, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you probably don't need a named struct, but rather just a simple tuple:
https://doc.rust-lang.org/rust-by-example/primitives/tuples.html
cli/src/main.rs
Outdated
let mut reports: Vec<WithFilePath<Report>> = Vec::new(); | ||
let mut parse_errors: Vec<WithFilePath<JunitParseError>> = Vec::new(); | ||
file_sets.iter().try_for_each(|file_set| { | ||
file_set.files.iter().try_for_each(|bundled_file| { | ||
let path = std::path::Path::new(&bundled_file.original_path); | ||
let file = std::fs::File::open(path)?; | ||
let file_buf_reader = BufReader::new(file); | ||
let mut junit_parser = JunitParser::new(); | ||
junit_parser.parse(file_buf_reader).context(format!( | ||
"Encountered unrecoverable error while parsing file: {}", | ||
bundled_file.original_path_rel | ||
))?; | ||
parse_errors.extend(junit_parser.errors().iter().map(|e| WithFilePath::< | ||
JunitParseError, | ||
> { | ||
file_path: bundled_file.original_path_rel.clone(), | ||
wrapped: *e, | ||
})); | ||
reports.extend(junit_parser.into_reports().iter().map( | ||
|report| WithFilePath::<Report> { | ||
file_path: bundled_file.original_path_rel.clone(), | ||
wrapped: report.clone(), | ||
}, | ||
)); | ||
Ok::<(), anyhow::Error>(()) | ||
})?; | ||
Ok::<(), anyhow::Error>(()) | ||
})?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this is more idiomatically a fold
operation. for_each
is typically for side-effects
cli/src/main.rs
Outdated
@@ -511,10 +537,222 @@ async fn run_test(test_args: TestArgs) -> anyhow::Result<i32> { | |||
Ok(exit_code) | |||
} | |||
|
|||
async fn run_validate(validate_args: ValidateArgs) -> anyhow::Result<i32> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file is getting unwieldy in length. Can we split out a file for this command and we'll split out the other commands into files later?
Also, I'd love for you to split out functions for each logical part of the command that you run so that it's easy to understand the flow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Totally
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alrighty, lmk if what I've got now is a bit easier on the eyes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
cli/src/validate.rs
Outdated
) -> (usize, usize) { | ||
log::info!(""); | ||
let mut num_invalid_reports: usize = 0; | ||
let mut num_optionally_invalid_reports: usize = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: rename to suboptimal for consistency with the underlying types/enums
(and rename elsewhere)
|
||
println!("{assert}"); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: would also be nice to have a test for suboptimal junits
cli/src/main.rs
Outdated
async fn run(cli: Cli) -> anyhow::Result<i32> { | ||
match cli.command { | ||
Commands::Upload(upload_args) => run_upload(upload_args, None, None, None, None).await, | ||
Commands::Test(test_args) => run_test(test_args).await, | ||
Commands::Validate(validate_args) => run_validate(validate_args).await, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Totally optional, but we can get rid of the run_validate
indirection here:
Commands::Validate(validate_args) => run_validate(validate_args).await, | |
Commands::Validate(validate_args) => { | |
let ValidateArgs { | |
junit_paths, | |
show_warnings, | |
} = validate_args; | |
print_cli_start_info(); | |
validate(junit_paths, show_warnings).await | |
}, |
IMO, this gets us closer to splitting concerns in a way that prevents repeated code and inconsistencies
cli/src/validate.rs
Outdated
Vec::<(Report, String)>::new(), // Vec<(Report, file path)> | ||
Vec::<(JunitParseError, String)>::new(), // Vec<(JunitParseError, file path)> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accidental comments?
cli/src/validate.rs
Outdated
|
||
fn parse_file_sets( | ||
file_sets: Vec<FileSet>, | ||
) -> anyhow::Result<(Vec<(Report, String)>, Vec<(JunitParseError, String)>)> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can create a type alias for these more complex types and compose them together nicely for readability
https://doc.rust-lang.org/reference/items/type-aliases.html
For example:
type ParsedReportAndFilePaths = Vec<(Report, String)>;
type JunitParseErrorsAndFilePaths = Vec<(JunitParseError, String)>;
which would update your return type to:
anyhow::Result<(ParsedReportAndFilePaths, JunitParseErrorsAndFilePaths)>
Btw, it occurs to me that we could use a BTreeMap
here though, no? Something like
type JunitFileToReportAndErrors = BTreeMap<String, (Vec<Report>, Vec<JunitParseError>)>;
cli/src/validate.rs
Outdated
Ok::<(Vec<(Report, String)>, Vec<(JunitParseError, String)>), anyhow::Error>( | ||
file_sets_parse_results, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you set a return type of the closure you won't have to annotate the type of Result
here:
e.g.
|mut file_sets_parse_results, file_set| -> anyhow::Result<(Vec<(Report, String)>, Vec<(JunitParseError, String)>)> {
// ...
Ok(file_sets_parse_results)
}
cli/src/validate.rs
Outdated
fn print_matched_files(file_sets: &[FileSet], file_counter: FileSetCounter) { | ||
log::info!(""); | ||
log::info!( | ||
"Validating the following {} files:", | ||
file_counter.get_count() | ||
); | ||
for file_set in file_sets { | ||
log::info!(" File set matching {}:", file_set.glob); | ||
for file in &file_set.files { | ||
log::info!("\t{}", file.original_path_rel); | ||
} | ||
} | ||
} | ||
|
||
fn print_parse_errors(parse_errors: Vec<(JunitParseError, String)>) { | ||
log::info!(""); | ||
log::warn!( | ||
"Encountered the following {} non-fatal errors while parsing files:", | ||
parse_errors.len().to_string().yellow() | ||
); | ||
|
||
let mut current_file_original_path = parse_errors[0].1.clone(); | ||
log::warn!(" File: {}", current_file_original_path); | ||
|
||
for error in parse_errors { | ||
if error.1 != current_file_original_path { | ||
current_file_original_path = error.1; | ||
log::warn!(" File: {}", current_file_original_path); | ||
} | ||
|
||
log::warn!("\t{}", error.0); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is so much nicer! Thank you 🙏
log::info!(""); | ||
log::info!( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using \n
for making newlines
TRUNK-12978
Adds a first iteration of a
validate
command, intended to allow users onboarding to flaky tests to verify they're producing valid JUnit xml files that we can accurately process, before they configure their CI jobs to start uploading to us.Users pass a list of
--junit-paths
to validate. For each file found,validate
will parse and validate using the parser and validator in this repo'scontext
crate.If any fatal parsing errors are encountered,
validate
will exit with a non-zero exit code.If a file contains
INVALID
-level validation errors,validate
considers this file to be 'invalid.' If a file contains noINVALID
-level validation errors, and zero or moreSUBOPTIMAL
-level validation errors,validate
considers this file to be 'valid.' If any 'invalid' files are encountered,validate
exits with a non-zero exit code. If all files found are 'valid,'validate
exits successfully and prints (eventually) a link to return to the onboarding flow of flaky tests.Example invocation where fatal parsing error is encountered:
Example invocation where some files are invalid:
Example invocation where all files are valid, some with
SUBOPTIMAL
validation errors:Example invocation where all files are valid, all with no
SUBOPTIMAL
validation errors:Follow-ups: