[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 ..
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 \
--repository ruff \

View File

@ -52,7 +52,7 @@ jobs:
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 \
--verbose \

View File

@ -5,6 +5,7 @@ mod python_version;
mod version;
pub use args::Cli;
use ty_project::metadata::settings::TerminalSettings;
use ty_static::EnvVars;
use std::fmt::Write;
@ -21,7 +22,9 @@ use clap::{CommandFactory, Parser};
use colored::Colorize;
use crossbeam::channel as crossbeam_channel;
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::max_parallelism;
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
@ -193,6 +196,12 @@ pub enum ExitStatus {
InternalError = 101,
}
impl ExitStatus {
pub const fn is_internal_error(self) -> bool {
matches!(self, ExitStatus::InternalError)
}
}
impl Termination for ExitStatus {
fn report(self) -> ExitCode {
ExitCode::from(self as u8)
@ -334,11 +343,8 @@ impl MainLoop {
let diagnostics_count = result.len();
let mut stdout = self.printer.stream_for_details().lock();
let max_severity = result
.iter()
.map(Diagnostic::severity)
.max()
.unwrap_or(Severity::Info);
let exit_status =
exit_status_from_diagnostics(&result, terminal_settings);
// Only render diagnostics if they're going to be displayed, since doing
// so is expensive.
@ -359,25 +365,14 @@ impl MainLoop {
)?;
}
if max_severity.is_fatal() {
if exit_status.is_internal_error() {
tracing::warn!(
"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() {
return Ok(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,
});
return Ok(exit_status);
}
}
} 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`.
struct IndicatifReporter {
collector: CollectReporter,

View File

@ -562,9 +562,9 @@ fn check_non_existing_path() -> anyhow::Result<()> {
assert_cmd_snapshot!(
case.command().arg("project/main.py").arg("project/tests"),
@r###"
@r"
success: false
exit_code: 1
exit_code: 2
----- stdout -----
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 -----
WARN No python files found under the given path(s)
"###
"
);
Ok(())

View File

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