From 4422c3a6de3b99d3ff2efc804e00db4e6b324d58 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Fri, 1 Nov 2024 08:35:58 -0500 Subject: [PATCH] Show full error chain on tool upgrade failures --- crates/uv/src/commands/tool/upgrade.rs | 38 ++++++++++++++++---------- crates/uv/tests/it/tool_upgrade.rs | 28 ++++++++++--------- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/crates/uv/src/commands/tool/upgrade.rs b/crates/uv/src/commands/tool/upgrade.rs index 2296c62f88b6..e575a341ef72 100644 --- a/crates/uv/src/commands/tool/upgrade.rs +++ b/crates/uv/src/commands/tool/upgrade.rs @@ -1,6 +1,7 @@ use std::{collections::BTreeSet, fmt::Write}; use anyhow::Result; +use itertools::Itertools; use owo_colors::OwoColorize; use tracing::debug; @@ -95,9 +96,7 @@ pub(crate) async fn upgrade( // Determine whether we applied any upgrades. let mut did_upgrade_environment = vec![]; - // Determine whether any tool upgrade failed. - let mut failed_upgrade = false; - + let mut errors = Vec::new(); for name in &names { debug!("Upgrading tool: `{name}`"); let result = upgrade_tool( @@ -125,22 +124,31 @@ pub(crate) async fn upgrade( debug!("Upgrading `{name}` was a no-op"); } Err(err) => { - // If we have a single tool, return the error directly. - if names.len() > 1 { - writeln!( - printer.stderr(), - "Failed to upgrade `{}`: {err}", - name.cyan(), - )?; - } else { - writeln!(printer.stderr(), "{err}")?; - } - failed_upgrade = true; + errors.push((name, err)); } } } - if failed_upgrade { + if !errors.is_empty() { + for (name, err) in errors + .into_iter() + .sorted_unstable_by(|(name_a, _), (name_b, _)| name_a.cmp(name_b)) + { + writeln!( + printer.stderr(), + "{}: Failed to upgrade {}", + "error".red().bold(), + name.green() + )?; + for err in err.chain() { + writeln!( + printer.stderr(), + " {}: {}", + "Caused by".red().bold(), + err.to_string().trim() + )?; + } + } return Ok(ExitStatus::Failure); } diff --git a/crates/uv/tests/it/tool_upgrade.rs b/crates/uv/tests/it/tool_upgrade.rs index db832cd2b622..91b08b18ee6a 100644 --- a/crates/uv/tests/it/tool_upgrade.rs +++ b/crates/uv/tests/it/tool_upgrade.rs @@ -6,7 +6,7 @@ use uv_static::EnvVars; use crate::common::{uv_snapshot, TestContext}; #[test] -fn test_tool_upgrade_name() { +fn tool_upgrade_name() { let context = TestContext::new("3.12") .with_filtered_counts() .with_filtered_exe_suffix(); @@ -56,7 +56,7 @@ fn test_tool_upgrade_name() { } #[test] -fn test_tool_upgrade_multiple_names() { +fn tool_upgrade_multiple_names() { let context = TestContext::new("3.12") .with_filtered_counts() .with_filtered_exe_suffix(); @@ -131,7 +131,7 @@ fn test_tool_upgrade_multiple_names() { } #[test] -fn test_tool_upgrade_all() { +fn tool_upgrade_all() { let context = TestContext::new("3.12") .with_filtered_counts() .with_filtered_exe_suffix(); @@ -205,7 +205,7 @@ fn test_tool_upgrade_all() { } #[test] -fn test_tool_upgrade_non_existing_package() { +fn tool_upgrade_non_existing_package() { let context = TestContext::new("3.12") .with_filtered_counts() .with_filtered_exe_suffix(); @@ -223,7 +223,8 @@ fn test_tool_upgrade_non_existing_package() { ----- stdout ----- ----- stderr ----- - `black` is not installed; run `uv tool install black` to install + error: Failed to upgrade black + Caused by: `black` is not installed; run `uv tool install black` to install "###); // Attempt to upgrade all. @@ -242,7 +243,7 @@ fn test_tool_upgrade_non_existing_package() { } #[test] -fn test_tool_upgrade_not_stop_if_upgrade_fails() -> anyhow::Result<()> { +fn tool_upgrade_not_stop_if_upgrade_fails() -> anyhow::Result<()> { let context = TestContext::new("3.12") .with_filtered_counts() .with_filtered_exe_suffix(); @@ -314,14 +315,15 @@ fn test_tool_upgrade_not_stop_if_upgrade_fails() -> anyhow::Result<()> { + babel==2.14.0 - pytz==2018.5 Installed 1 executable: pybabel - Failed to upgrade `python-dotenv`: `python-dotenv` is missing a valid receipt; run `uv tool install --force python-dotenv` to reinstall + error: Failed to upgrade python-dotenv + Caused by: `python-dotenv` is missing a valid receipt; run `uv tool install --force python-dotenv` to reinstall "###); Ok(()) } #[test] -fn test_tool_upgrade_settings() { +fn tool_upgrade_settings() { let context = TestContext::new("3.12") .with_filtered_counts() .with_filtered_exe_suffix(); @@ -386,7 +388,7 @@ fn test_tool_upgrade_settings() { } #[test] -fn test_tool_upgrade_respect_constraints() { +fn tool_upgrade_respect_constraints() { let context = TestContext::new("3.12") .with_filtered_counts() .with_filtered_exe_suffix(); @@ -437,7 +439,7 @@ fn test_tool_upgrade_respect_constraints() { } #[test] -fn test_tool_upgrade_constraint() { +fn tool_upgrade_constraint() { let context = TestContext::new("3.12") .with_filtered_counts() .with_filtered_exe_suffix(); @@ -530,7 +532,7 @@ fn test_tool_upgrade_constraint() { /// Upgrade a tool, but only by upgrading one of it's `--with` dependencies, and not the tool /// itself. #[test] -fn test_tool_upgrade_with() { +fn tool_upgrade_with() { let context = TestContext::new("3.12") .with_filtered_counts() .with_filtered_exe_suffix(); @@ -578,7 +580,7 @@ fn test_tool_upgrade_with() { } #[test] -fn test_tool_upgrade_python() { +fn tool_upgrade_python() { let context = TestContext::new_with_versions(&["3.11", "3.12"]) .with_filtered_counts() .with_filtered_exe_suffix(); @@ -639,7 +641,7 @@ fn test_tool_upgrade_python() { } #[test] -fn test_tool_upgrade_python_with_all() { +fn tool_upgrade_python_with_all() { let context = TestContext::new_with_versions(&["3.11", "3.12"]) .with_filtered_counts() .with_filtered_exe_suffix();