diff --git a/crates/uv-test/src/lib.rs b/crates/uv-test/src/lib.rs index c5732ec4ff9b8..acbf6b1bd01f1 100755 --- a/crates/uv-test/src/lib.rs +++ b/crates/uv-test/src/lib.rs @@ -582,6 +582,16 @@ impl TestContext { self } + /// Add a (not context aware) filter for the current uv version `v..` + #[must_use] + pub fn with_filtered_current_version(mut self) -> Self { + self.filters.push(( + regex::escape(&format!("v{}", env!("CARGO_PKG_VERSION"))), + "v[CURRENT_VERSION]".to_string(), + )); + self + } + /// Adds filters for non-deterministic `CycloneDX` data #[must_use] pub fn with_cyclonedx_filters(mut self) -> Self { diff --git a/crates/uv/src/commands/self_update.rs b/crates/uv/src/commands/self_update.rs index 6d4fa9a571bb2..2a1256727c674 100644 --- a/crates/uv/src/commands/self_update.rs +++ b/crates/uv/src/commands/self_update.rs @@ -24,7 +24,7 @@ pub(crate) async fn self_update( ) -> Result { if client_builder.is_offline() { writeln!( - printer.stderr(), + printer.stderr_important(), "{}", format_args!( "{}{} Self-update is not possible because network connectivity is disabled (i.e., with `--offline`)", @@ -47,7 +47,7 @@ pub(crate) async fn self_update( let Ok(updater) = updater.load_receipt() else { debug!("No receipt found; assuming uv was installed via a package manager"); writeln!( - printer.stderr(), + printer.stderr_important(), "{}", format_args!( concat!( @@ -79,7 +79,7 @@ pub(crate) async fn self_update( let receipt_prefix = updater.install_prefix_root()?; writeln!( - printer.stderr(), + printer.stderr_important(), "{}", format_args!( concat!( @@ -152,7 +152,7 @@ pub(crate) async fn self_update( if dry_run { writeln!( - printer.stderr(), + printer.stderr_important(), "Would update uv from {} to {}", format!("v{}", env!("CARGO_PKG_VERSION")).bold().white(), format!("v{}", resolved.version).bold().white(), @@ -188,7 +188,7 @@ pub(crate) async fn self_update( } }; writeln!( - printer.stderr(), + printer.stderr_important(), "Would update uv from {} to {}", format!("v{}", env!("CARGO_PKG_VERSION")).bold().white(), version.bold().white(), @@ -309,7 +309,7 @@ async fn run_updater( }; writeln!( - printer.stderr(), + printer.stderr_important(), "{}", format_args!( "{}{} {direction} uv {}! {}", @@ -340,7 +340,7 @@ async fn run_updater( return if let AxoupdateError::Reqwest(err) = err { if err.status() == Some(http::StatusCode::FORBIDDEN) && !has_token { writeln!( - printer.stderr(), + printer.stderr_important(), "{}", format_args!( "{}{} GitHub API rate limit exceeded. Please provide a GitHub token via the {} option.", diff --git a/crates/uv/src/printer.rs b/crates/uv/src/printer.rs index 234a569c7872c..4848a59375833 100644 --- a/crates/uv/src/printer.rs +++ b/crates/uv/src/printer.rs @@ -52,6 +52,17 @@ impl Printer { } } + /// Return the [`Stderr`] for this printer. + pub(crate) fn stderr_important(self) -> Stderr { + match self { + Self::Silent => Stderr::Disabled, + Self::Quiet => Stderr::Enabled, + Self::Default => Stderr::Enabled, + Self::Verbose => Stderr::Enabled, + Self::NoProgress => Stderr::Enabled, + } + } + /// Return the [`Stderr`] for this printer. pub(crate) fn stderr(self) -> Stderr { match self { diff --git a/crates/uv/tests/it/self_update.rs b/crates/uv/tests/it/self_update.rs index 02a58691afc92..4316704e5a3e0 100644 --- a/crates/uv/tests/it/self_update.rs +++ b/crates/uv/tests/it/self_update.rs @@ -1,4 +1,4 @@ -use std::process::Command; +use std::{path::PathBuf, process::Command}; use anyhow::Result; use assert_fs::prelude::*; @@ -6,14 +6,13 @@ use axoupdater::{ ReleaseSourceType, test::helpers::{RuntestArgs, perform_runtest}, }; -use regex::escape; use serde_json::json; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; use uv_static::EnvVars; -use uv_test::{get_bin, uv_snapshot}; +use uv_test::{TestContext, get_bin, uv_snapshot}; #[test] fn check_self_update() { @@ -50,7 +49,7 @@ fn check_self_update() { } #[test] -fn test_self_update_offline_error() { +fn self_update_offline_error() { let context = uv_test::test_context!("3.12"); uv_snapshot!(context.self_update().arg("--offline"), @@ -64,13 +63,41 @@ fn test_self_update_offline_error() { "); } -#[tokio::test] -async fn test_self_update_uses_legacy_path_with_ghe_override() -> Result<()> { - let context = uv_test::test_context!("3.12").with_filter(( - escape(&format!("v{}", env!("CARGO_PKG_VERSION"))), - "v[CURRENT_VERSION]", - )); +#[test] +fn self_update_offline_quiet() { + let context = uv_test::test_context!("3.12"); + + uv_snapshot!(context.self_update().arg("--offline").arg("--quiet"), + @r" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + error: Self-update is not possible because network connectivity is disabled (i.e., with `--offline`) + "); +} +#[test] +fn self_update_offline_extra_quiet() { + let context = uv_test::test_context!("3.12"); + + uv_snapshot!(context.self_update().arg("--offline").arg("--quiet").arg("--quiet"), + @r" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + "); +} + +/// Set up a fake receipt and a mock update metadata endpoint to allow +/// simulating an update with `--dry-run`. +async fn setup_mock_update( + context: &TestContext, + target_version: &str, +) -> Result<(PathBuf, MockServer)> { let receipt_dir = context.temp_dir.child("receipt"); receipt_dir.create_dir_all()?; @@ -100,7 +127,6 @@ async fn test_self_update_uses_legacy_path_with_ghe_override() -> Result<()> { }))?)?; let server = MockServer::start().await; - let target_version = "9.9.9"; let installer_name = if cfg!(windows) { "uv-installer.ps1" } else { @@ -124,6 +150,16 @@ async fn test_self_update_uses_legacy_path_with_ghe_override() -> Result<()> { .mount(&server) .await; + Ok((receipt_dir.to_path_buf(), server)) +} + +#[tokio::test] +async fn test_self_update_uses_legacy_path_with_ghe_override() -> Result<()> { + let context = uv_test::test_context!("3.12").with_filtered_current_version(); + + let target_version = "9.9.9"; + let (receipt_dir, server) = setup_mock_update(&context, target_version).await?; + uv_snapshot!(context.filters(), context.self_update() .arg(target_version) .arg("--dry-run") @@ -140,3 +176,101 @@ async fn test_self_update_uses_legacy_path_with_ghe_override() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn self_update_dry_run_quiet() -> Result<()> { + let context = uv_test::test_context!("3.12").with_filtered_current_version(); + + let target_version = "9.9.9"; + let (receipt_dir, server) = setup_mock_update(&context, target_version).await?; + + uv_snapshot!(context.filters(), context.self_update() + .arg(target_version) + .arg("--dry-run") + .arg("--quiet") + .env("AXOUPDATER_CONFIG_PATH", receipt_dir.as_os_str()) + .env(EnvVars::UV_INSTALLER_GHE_BASE_URL, server.uri()), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Would update uv from v[CURRENT_VERSION] to v9.9.9 + "); + + Ok(()) +} + +#[tokio::test] +async fn self_update_dry_run_extra_quiet() -> Result<()> { + let context = uv_test::test_context!("3.12"); + + let receipt_dir = context.temp_dir.child("receipt"); + receipt_dir.create_dir_all()?; + + let target_version = "9.9.9"; + let (receipt_dir, server) = setup_mock_update(&context, target_version).await?; + + uv_snapshot!(context.self_update() + .arg(target_version) + .arg("--dry-run") + .arg("--quiet") + .arg("--quiet") + .env("AXOUPDATER_CONFIG_PATH", receipt_dir.as_os_str()) + .env(EnvVars::UV_INSTALLER_GHE_BASE_URL, server.uri()), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + "); + + Ok(()) +} + +#[tokio::test] +async fn self_update_noop_dry_run() -> Result<()> { + let context = uv_test::test_context!("3.12").with_filtered_current_version(); + + let target_version = env!("CARGO_PKG_VERSION"); + let (receipt_dir, server) = setup_mock_update(&context, target_version).await?; + + uv_snapshot!(context.filters(), context.self_update() + .arg(target_version) + .arg("--dry-run") + .env("AXOUPDATER_CONFIG_PATH", receipt_dir.as_os_str()) + .env(EnvVars::UV_INSTALLER_GHE_BASE_URL, server.uri()), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + info: Checking for updates... + You're on the latest version of uv (v[CURRENT_VERSION]) + "); + + Ok(()) +} + +#[tokio::test] +async fn self_update_noop_dry_run_quiet() -> Result<()> { + let context = uv_test::test_context!("3.12").with_filtered_current_version(); + + let target_version = env!("CARGO_PKG_VERSION"); + let (receipt_dir, server) = setup_mock_update(&context, target_version).await?; + + uv_snapshot!(context.filters(), context.self_update() + .arg(target_version) + .arg("--dry-run") + .arg("--quiet") + .env("AXOUPDATER_CONFIG_PATH", receipt_dir.as_os_str()) + .env(EnvVars::UV_INSTALLER_GHE_BASE_URL, server.uri()), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + "); + + Ok(()) +}