Make formatter ecosystem check failure output better understandable (#6300)

**Summary** Prompted by
https://github.com/astral-sh/ruff/pull/6257#issuecomment-1661308410, it
tried to make the ecosystem script output on failure better
understandable. All log messages are now written to a file, which is
printed on error. Running locally progress is still shown.

Looking through the log output i saw that we currently log syntax errors
in input, which is confusing because they aren't actual errors, but we
don't check that these files don't change due to parser regressions or
improvements. I added `--files-with-errors` to catch that.

**Test Plan** CI
This commit is contained in:
konsti 2023-08-03 20:23:25 +02:00 committed by GitHub
parent b3f3529499
commit 51ff98f9e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 76 additions and 26 deletions

View file

@ -55,6 +55,7 @@ jobs:
- crates/ruff_python_index/** - crates/ruff_python_index/**
- crates/ruff_text_size/** - crates/ruff_text_size/**
- crates/ruff_python_parser/** - crates/ruff_python_parser/**
- crates/ruff_dev/**
cargo-fmt: cargo-fmt:
name: "cargo fmt" name: "cargo fmt"
@ -336,7 +337,7 @@ jobs:
- name: "Formatter progress" - name: "Formatter progress"
run: scripts/formatter_ecosystem_checks.sh run: scripts/formatter_ecosystem_checks.sh
- name: "Github step summary" - name: "Github step summary"
run: grep "similarity index" target/progress_projects_report.txt | sort > $GITHUB_STEP_SUMMARY run: grep "similarity index" target/progress_projects_log.txt | sort > $GITHUB_STEP_SUMMARY
# CPython is not black formatted, so we run only the stability check # CPython is not black formatted, so we run only the stability check
- name: "Clone CPython 3.10" - name: "Clone CPython 3.10"
run: git clone --branch 3.10 --depth 1 https://github.com/python/cpython.git crates/ruff/resources/test/cpython run: git clone --branch 3.10 --depth 1 https://github.com/python/cpython.git crates/ruff/resources/test/cpython

View file

@ -184,12 +184,19 @@ pub(crate) struct Args {
/// Write all errors to this file in addition to stdout. Only used in multi-project mode. /// Write all errors to this file in addition to stdout. Only used in multi-project mode.
#[arg(long)] #[arg(long)]
pub(crate) error_file: Option<PathBuf>, pub(crate) error_file: Option<PathBuf>,
/// Write all log messages (same as cli) to this file
#[arg(long)]
pub(crate) log_file: Option<PathBuf>,
/// Assert that there are exactly this many input files with errors. This catches regressions
/// (or improvements) in the parser.
#[arg(long)]
pub(crate) files_with_errors: Option<u32>,
#[clap(flatten)] #[clap(flatten)]
pub(crate) log_level_args: LogLevelArgs, pub(crate) log_level_args: LogLevelArgs,
} }
pub(crate) fn main(args: &Args) -> anyhow::Result<ExitCode> { pub(crate) fn main(args: &Args) -> anyhow::Result<ExitCode> {
setup_logging(&args.log_level_args); setup_logging(&args.log_level_args, args.log_file.as_deref())?;
let all_success = if args.multi_project { let all_success = if args.multi_project {
format_dev_multi_project(args)? format_dev_multi_project(args)?
@ -202,13 +209,24 @@ pub(crate) fn main(args: &Args) -> anyhow::Result<ExitCode> {
} }
info!( info!(
parent: None, parent: None,
"Found {} stability errors in {} files (similarity index {:.3}) in {:.2}s", "Done: {} stability errors, {} files, similarity index {:.3}), took {:.2}s, {} input files contained syntax errors ",
error_count, error_count,
result.file_count, result.file_count,
result.statistics.similarity_index(), result.statistics.similarity_index(),
result.duration.as_secs_f32(), result.duration.as_secs_f32(),
result.syntax_error_in_input,
); );
if let Some(files_with_errors) = args.files_with_errors {
if result.syntax_error_in_input != files_with_errors {
error!(
"Expected {files_with_errors} input files with errors, found {}",
result.syntax_error_in_input
);
return Ok(ExitCode::FAILURE);
}
}
error_count == 0 error_count == 0
}; };
if all_success { if all_success {
@ -218,7 +236,7 @@ pub(crate) fn main(args: &Args) -> anyhow::Result<ExitCode> {
} }
} }
fn setup_logging(log_level_args: &LogLevelArgs) { fn setup_logging(log_level_args: &LogLevelArgs, log_file: Option<&Path>) -> io::Result<()> {
// Custom translation since we need the tracing type for `EnvFilter` // Custom translation since we need the tracing type for `EnvFilter`
let log_level = match LogLevel::from(log_level_args) { let log_level = match LogLevel::from(log_level_args) {
LogLevel::Default => tracing::Level::INFO, LogLevel::Default => tracing::Level::INFO,
@ -236,17 +254,25 @@ fn setup_logging(log_level_args: &LogLevelArgs) {
let indicitif_compatible_writer_layer = tracing_subscriber::fmt::layer() let indicitif_compatible_writer_layer = tracing_subscriber::fmt::layer()
.with_writer(indicatif_layer.get_stderr_writer()) .with_writer(indicatif_layer.get_stderr_writer())
.with_target(false); .with_target(false);
let log_layer = log_file.map(File::create).transpose()?.map(|log_file| {
tracing_subscriber::fmt::layer()
.with_writer(log_file)
.with_ansi(false)
});
tracing_subscriber::registry() tracing_subscriber::registry()
.with(filter_layer) .with(filter_layer)
.with(indicitif_compatible_writer_layer) .with(indicitif_compatible_writer_layer)
.with(indicatif_layer) .with(indicatif_layer)
.with(log_layer)
.init(); .init();
Ok(())
} }
/// Checks a directory of projects /// Checks a directory of projects
fn format_dev_multi_project(args: &Args) -> anyhow::Result<bool> { fn format_dev_multi_project(args: &Args) -> anyhow::Result<bool> {
let mut total_errors = 0; let mut total_errors = 0;
let mut total_files = 0; let mut total_files = 0;
let mut total_syntax_error_in_input = 0;
let start = Instant::now(); let start = Instant::now();
let mut project_paths = Vec::new(); let mut project_paths = Vec::new();
@ -277,20 +303,23 @@ fn format_dev_multi_project(args: &Args) -> anyhow::Result<bool> {
}; };
for project_path in project_paths { for project_path in project_paths {
info!(parent: None, "Starting {}", project_path.display()); debug!(parent: None, "Starting {}", project_path.display());
match format_dev_project(&[project_path.clone()], args.stability_check, args.write) { match format_dev_project(&[project_path.clone()], args.stability_check, args.write) {
Ok(result) => { Ok(result) => {
total_errors += result.error_count(); total_errors += result.error_count();
total_files += result.file_count; total_files += result.file_count;
total_syntax_error_in_input += result.syntax_error_in_input;
info!( info!(
parent: None, parent: None,
"Finished {}: {} files, similarity index {:.3}, {:.2}s", "Finished {}: {} stability errors, {} files, similarity index {:.3}), took {:.2}s, {} input files contained syntax errors ",
project_path.display(), project_path.display(),
result.error_count(),
result.file_count, result.file_count,
result.statistics.similarity_index(), result.statistics.similarity_index(),
result.duration.as_secs_f32(), result.duration.as_secs_f32(),
result.syntax_error_in_input,
); );
if result.error_count() > 0 { if result.error_count() > 0 {
error!( error!(
@ -320,10 +349,20 @@ fn format_dev_multi_project(args: &Args) -> anyhow::Result<bool> {
info!( info!(
parent: None, parent: None,
"{total_errors} stability errors in {total_files} files in {}s", "Finished: {total_errors} stability errors, {total_files} files, tool {}s, {total_syntax_error_in_input} input files contained syntax errors ",
duration.as_secs_f32() duration.as_secs_f32(),
); );
if let Some(files_with_errors) = args.files_with_errors {
if total_syntax_error_in_input != files_with_errors {
error!(
"Expected {files_with_errors} input files with errors, found {}",
total_syntax_error_in_input
);
return Ok(false);
}
}
Ok(total_errors == 0) Ok(total_errors == 0)
} }
@ -368,12 +407,27 @@ fn format_dev_project(
let mut statistics = Statistics::default(); let mut statistics = Statistics::default();
let mut formatted_counter = 0; let mut formatted_counter = 0;
let mut syntax_error_in_input = 0;
let mut diagnostics = Vec::new(); let mut diagnostics = Vec::new();
for (result, file) in results { for (result, file) in results {
formatted_counter += 1; formatted_counter += 1;
match result { match result {
Ok(statistics_file) => statistics += statistics_file, Ok(statistics_file) => statistics += statistics_file,
Err(error) => diagnostics.push(Diagnostic { file, error }), Err(error) => {
match error {
CheckFileError::SyntaxErrorInInput(error) => {
// This is not our error
debug!(
parent: None,
"Syntax error in {}: {}",
file.display(),
error
);
syntax_error_in_input += 1;
}
_ => diagnostics.push(Diagnostic { file, error }),
}
}
} }
} }
@ -384,6 +438,7 @@ fn format_dev_project(
file_count: formatted_counter, file_count: formatted_counter,
diagnostics, diagnostics,
statistics, statistics,
syntax_error_in_input,
}) })
} }
@ -408,19 +463,7 @@ fn format_dir_entry(
// Handle panics (mostly in `debug_assert!`) // Handle panics (mostly in `debug_assert!`)
let result = let result =
match catch_unwind(|| format_dev_file(&file, stability_check, write, options.clone())) { match catch_unwind(|| format_dev_file(&file, stability_check, write, options.clone())) {
Ok(result) => match result { Ok(result) => result,
Err(CheckFileError::SyntaxErrorInInput(error)) => {
// We don't care about this error, only log it
info!(
parent: None,
"Syntax error in {}: {}",
file.display(),
error
);
Ok(Statistics::default())
}
_ => result,
},
Err(panic) => { Err(panic) => {
if let Some(message) = panic.downcast_ref::<String>() { if let Some(message) = panic.downcast_ref::<String>() {
Err(CheckFileError::Panic { Err(CheckFileError::Panic {
@ -472,6 +515,7 @@ struct CheckRepoResult {
file_count: usize, file_count: usize,
diagnostics: Vec<Diagnostic>, diagnostics: Vec<Diagnostic>,
statistics: Statistics, statistics: Statistics,
syntax_error_in_input: u32,
} }
impl CheckRepoResult { impl CheckRepoResult {

View file

@ -41,19 +41,24 @@ if [ ! -d "$dir/warehouse" ]; then
git clone --filter=tree:0 https://github.com/pypi/warehouse "$dir/warehouse" git clone --filter=tree:0 https://github.com/pypi/warehouse "$dir/warehouse"
git -C "$dir/warehouse" checkout fe6455c0a946e81f61d72edc1049f536d8bba903 git -C "$dir/warehouse" checkout fe6455c0a946e81f61d72edc1049f536d8bba903
fi fi
# django project # zulip, a django user
if [ ! -d "$dir/zulip" ]; then if [ ! -d "$dir/zulip" ]; then
git clone --filter=tree:0 https://github.com/zulip/zulip "$dir/zulip" git clone --filter=tree:0 https://github.com/zulip/zulip "$dir/zulip"
git -C "$dir/zulip" checkout 6cb080c4479546a7f5cb017fcddea56605910b48 git -C "$dir/zulip" checkout 6cb080c4479546a7f5cb017fcddea56605910b48
fi fi
# cpython itself
if [ ! -d "$dir/cpython" ]; then
git clone --filter=tree:0 https://github.com/python/cpython "$dir/cpython"
git -C "$dir/cpython" checkout 45de31db9cc9be945702f3a7ca35bbb9f98476af
fi
# Uncomment if you want to update the hashes # Uncomment if you want to update the hashes
# for i in "$dir"/*/; do git -C "$i" switch main && git -C "$i" pull && echo "# $(basename "$i") $(git -C "$i" rev-parse HEAD)"; done # for i in "$dir"/*/; do git -C "$i" switch main && git -C "$i" pull && echo "# $(basename "$i") $(git -C "$i" rev-parse HEAD)"; done
time cargo run --bin ruff_dev -- format-dev --stability-check --error-file "$target/progress_projects_errors.txt" \ time cargo run --bin ruff_dev -- format-dev --stability-check --error-file "$target/progress_projects_errors.txt" \
--multi-project "$dir" >"$target/progress_projects_report.txt" || ( --log-file "$target/progress_projects_log.txt" --files-with-errors 25 --multi-project "$dir" || (
echo "Ecosystem check failed" echo "Ecosystem check failed"
cat "$target/progress_projects_report.txt" cat "$target/progress_projects_log.txt"
exit 1 exit 1
) )
grep "similarity index" "$target/progress_projects_report.txt" | sort grep "similarity index" "$target/progress_projects_log.txt" | sort