Show uv self update success and failure messages with --quiet (#18645)

With -q, suppress informational messages but still show when an update
actually happens. With -qq, suppress all output.

Closes #18412

## Summary

Adds `stderr_important()` to `Printer` which is only suppressed with
`-qq` (silent), not `-q` (quiet). Uses this for the update success
message in `uv self update` so cron users can run with `-q` and only
get notified when an update actually occurs.

## Test Plan

A set of new tests.

---------

Co-authored-by: Tomasz (Tom) Kramkowski <tom@astral.sh>
This commit is contained in:
Varun Kumar
2026-03-25 06:33:14 -05:00
committed by GitHub
parent 262a50bb4c
commit 8b1a15eae6
4 changed files with 173 additions and 18 deletions
+10
View File
@@ -582,6 +582,16 @@ impl TestContext {
self
}
/// Add a (not context aware) filter for the current uv version `v<major>.<minor>.<patch>`
#[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 {
+7 -7
View File
@@ -24,7 +24,7 @@ pub(crate) async fn self_update(
) -> Result<ExitStatus> {
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.",
+11
View File
@@ -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 {
+145 -11
View File
@@ -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(())
}