[ty] Exit with `2` if there's any IO error (#21508)

Co-authored-by: David Peter <mail@david-peter.de>
This commit is contained in:
Micha Reiser 2025-11-19 09:39:19 +01:00 committed by GitHub
parent c796a70ec9
commit 663f78e644
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 54 additions and 25 deletions

View File

@ -67,7 +67,7 @@ jobs:
cd .. cd ..
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@0aff03414da5d242e97a9f43fb502e085637a4a1" uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@e5c5f5b2d762af91b28490537fe0077334165693"
ecosystem-analyzer \ ecosystem-analyzer \
--repository ruff \ --repository ruff \

View File

@ -52,7 +52,7 @@ jobs:
cd .. cd ..
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@0aff03414da5d242e97a9f43fb502e085637a4a1" uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@e5c5f5b2d762af91b28490537fe0077334165693"
ecosystem-analyzer \ ecosystem-analyzer \
--verbose \ --verbose \

View File

@ -5,6 +5,7 @@ mod python_version;
mod version; mod version;
pub use args::Cli; pub use args::Cli;
use ty_project::metadata::settings::TerminalSettings;
use ty_static::EnvVars; use ty_static::EnvVars;
use std::fmt::Write; use std::fmt::Write;
@ -21,7 +22,9 @@ use clap::{CommandFactory, Parser};
use colored::Colorize; use colored::Colorize;
use crossbeam::channel as crossbeam_channel; use crossbeam::channel as crossbeam_channel;
use rayon::ThreadPoolBuilder; use rayon::ThreadPoolBuilder;
use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig, DisplayDiagnostics, Severity}; use ruff_db::diagnostic::{
Diagnostic, DiagnosticId, DisplayDiagnosticConfig, DisplayDiagnostics, Severity,
};
use ruff_db::files::File; use ruff_db::files::File;
use ruff_db::max_parallelism; use ruff_db::max_parallelism;
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf}; use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
@ -193,6 +196,12 @@ pub enum ExitStatus {
InternalError = 101, InternalError = 101,
} }
impl ExitStatus {
pub const fn is_internal_error(self) -> bool {
matches!(self, ExitStatus::InternalError)
}
}
impl Termination for ExitStatus { impl Termination for ExitStatus {
fn report(self) -> ExitCode { fn report(self) -> ExitCode {
ExitCode::from(self as u8) ExitCode::from(self as u8)
@ -334,11 +343,8 @@ impl MainLoop {
let diagnostics_count = result.len(); let diagnostics_count = result.len();
let mut stdout = self.printer.stream_for_details().lock(); let mut stdout = self.printer.stream_for_details().lock();
let max_severity = result let exit_status =
.iter() exit_status_from_diagnostics(&result, terminal_settings);
.map(Diagnostic::severity)
.max()
.unwrap_or(Severity::Info);
// Only render diagnostics if they're going to be displayed, since doing // Only render diagnostics if they're going to be displayed, since doing
// so is expensive. // so is expensive.
@ -359,25 +365,14 @@ impl MainLoop {
)?; )?;
} }
if max_severity.is_fatal() { if exit_status.is_internal_error() {
tracing::warn!( tracing::warn!(
"A fatal error occurred while checking some files. Not all project files were analyzed. See the diagnostics list above for details." "A fatal error occurred while checking some files. Not all project files were analyzed. See the diagnostics list above for details."
); );
} }
if self.watcher.is_none() { if self.watcher.is_none() {
return Ok(match max_severity { return Ok(exit_status);
Severity::Info => ExitStatus::Success,
Severity::Warning => {
if terminal_settings.error_on_warning {
ExitStatus::Failure
} else {
ExitStatus::Success
}
}
Severity::Error => ExitStatus::Failure,
Severity::Fatal => ExitStatus::InternalError,
});
} }
} }
} else { } else {
@ -410,6 +405,40 @@ impl MainLoop {
} }
} }
fn exit_status_from_diagnostics(
diagnostics: &[Diagnostic],
terminal_settings: &TerminalSettings,
) -> ExitStatus {
if diagnostics.is_empty() {
return ExitStatus::Success;
}
let mut max_severity = Severity::Info;
let mut io_error = false;
for diagnostic in diagnostics {
max_severity = max_severity.max(diagnostic.severity());
io_error = io_error || matches!(diagnostic.id(), DiagnosticId::Io);
}
if !max_severity.is_fatal() && io_error {
return ExitStatus::Error;
}
match max_severity {
Severity::Info => ExitStatus::Success,
Severity::Warning => {
if terminal_settings.error_on_warning {
ExitStatus::Failure
} else {
ExitStatus::Success
}
}
Severity::Error => ExitStatus::Failure,
Severity::Fatal => ExitStatus::InternalError,
}
}
/// A progress reporter for `ty check`. /// A progress reporter for `ty check`.
struct IndicatifReporter { struct IndicatifReporter {
collector: CollectReporter, collector: CollectReporter,

View File

@ -562,9 +562,9 @@ fn check_non_existing_path() -> anyhow::Result<()> {
assert_cmd_snapshot!( assert_cmd_snapshot!(
case.command().arg("project/main.py").arg("project/tests"), case.command().arg("project/main.py").arg("project/tests"),
@r###" @r"
success: false success: false
exit_code: 1 exit_code: 2
----- stdout ----- ----- stdout -----
error[io]: `<temp_dir>/project/main.py`: No such file or directory (os error 2) error[io]: `<temp_dir>/project/main.py`: No such file or directory (os error 2)
@ -574,7 +574,7 @@ fn check_non_existing_path() -> anyhow::Result<()> {
----- stderr ----- ----- stderr -----
WARN No python files found under the given path(s) WARN No python files found under the given path(s)
"### "
); );
Ok(()) Ok(())

View File

@ -20,7 +20,7 @@ cd ..
echo "Project selector: ${PRIMER_SELECTOR}" echo "Project selector: ${PRIMER_SELECTOR}"
# Allow the exit code to be 0 or 1, only fail for actual mypy_primer crashes/bugs # Allow the exit code to be 0 or 1, only fail for actual mypy_primer crashes/bugs
uvx \ uvx \
--from="git+https://github.com/hauntsaninja/mypy_primer@ab5d30e2d4ecdaf7d6cc89395c7130143d6d3c82" \ --from="git+https://github.com/hauntsaninja/mypy_primer@089ac1da83cf26aee9c98de412b7eb10e20b2212" \
mypy_primer \ mypy_primer \
--repo ruff \ --repo ruff \
--type-checker ty \ --type-checker ty \