Ensure preview default Python installs are upgradeable (#14261)

Python `bin` installations installed with `uv python install --default
--preview` (no version specified) were not being installed as
upgradeable. Instead each link was pointed at the highest patch version
for a minor version. This change ensures that these preview default
installations are also treated as upgradeable.

The PR includes some updates to the related tests. First, it checks the
default install without specified version case. Second, since it's
adding more read link checks, it creates a new `read_link` helper method
to consolidate repeated logic and replace instances of
`#[cfg(unix/windows)` with `if cfg!(unix/windows)`.

Fixes #14247
This commit is contained in:
John Mumm 2025-06-27 19:26:28 +02:00 committed by GitHub
parent 5754f2f2db
commit 880c5e4949
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 144 additions and 67 deletions

View file

@ -451,7 +451,7 @@ pub(crate) async fn install(
.expect("We should have a bin directory with preview enabled")
.as_path();
let upgradeable = preview.is_enabled() && is_default_install
let upgradeable = (default || is_default_install)
|| requested_minor_versions.contains(&installation.key().version().python_version());
create_bin_links(

View file

@ -356,27 +356,20 @@ fn python_install_preview() {
bin_python.assert(predicate::path::is_symlink());
// The link should be to a path containing a minor version symlink directory
#[cfg(unix)]
{
if cfg!(unix) {
insta::with_settings!({
filters => context.filters(),
}, {
insta::assert_snapshot!(
&bin_python
.read_link()
.unwrap_or_else(|_| panic!("{} should be readable", bin_python.path().display()))
.as_os_str().to_string_lossy(),
@"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/bin/python3.13"
read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/bin/python3.13"
);
});
}
#[cfg(windows)]
{
} else if cfg!(windows) {
insta::with_settings!({
filters => context.filters(),
}, {
insta::assert_snapshot!(
launcher_path(&bin_python).display(), @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/python"
read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/python"
);
});
}
@ -505,27 +498,20 @@ fn python_install_preview() {
.child(format!("python3.11{}", std::env::consts::EXE_SUFFIX));
// The link should be to a path containing a minor version symlink directory
#[cfg(unix)]
{
if cfg!(unix) {
insta::with_settings!({
filters => context.filters(),
}, {
insta::assert_snapshot!(
&bin_python
.read_link()
.unwrap_or_else(|_| panic!("{} should be readable", bin_python.path().display()))
.as_os_str().to_string_lossy(),
@"[TEMP_DIR]/managed/cpython-3.11-[PLATFORM]/bin/python3.11"
read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.11-[PLATFORM]/bin/python3.11"
);
});
}
#[cfg(windows)]
{
} else if cfg!(windows) {
insta::with_settings!({
filters => context.filters(),
}, {
insta::assert_snapshot!(
launcher_path(&bin_python).display(), @"[TEMP_DIR]/managed/cpython-3.11-[PLATFORM]/python"
read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.11-[PLATFORM]/python"
);
});
}
@ -563,15 +549,15 @@ fn python_install_preview() {
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.8-[PLATFORM]/bin/python3.12"
canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.8-[PLATFORM]/bin/python3.12"
);
});
} else {
} else if cfg!(windows) {
insta::with_settings!({
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.8-[PLATFORM]/python"
canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.8-[PLATFORM]/python"
);
});
}
@ -600,27 +586,20 @@ fn python_install_preview_upgrade() {
"###);
// Installing with a patch version should cause the link to be to the patch installation.
#[cfg(unix)]
{
if cfg!(unix) {
insta::with_settings!({
filters => context.filters(),
}, {
insta::assert_snapshot!(
&bin_python
.read_link()
.unwrap_or_else(|_| panic!("{} should be readable", bin_python.display()))
.display(),
@"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/bin/python3.12"
read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/bin/python3.12"
);
});
}
#[cfg(windows)]
{
} else if cfg!(windows) {
insta::with_settings!({
filters => context.filters(),
}, {
insta::assert_snapshot!(
launcher_path(&bin_python).display(), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/python"
read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/python"
);
});
}
@ -641,15 +620,15 @@ fn python_install_preview_upgrade() {
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/bin/python3.12"
canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/bin/python3.12"
);
});
} else {
} else if cfg!(windows) {
insta::with_settings!({
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/python"
canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/python"
);
});
}
@ -670,15 +649,15 @@ fn python_install_preview_upgrade() {
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/bin/python3.12"
canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/bin/python3.12"
);
});
} else {
} else if cfg!(windows) {
insta::with_settings!({
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/python"
canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.5-[PLATFORM]/python"
);
});
}
@ -699,15 +678,15 @@ fn python_install_preview_upgrade() {
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.4-[PLATFORM]/bin/python3.12"
canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.4-[PLATFORM]/bin/python3.12"
);
});
} else {
} else if cfg!(windows) {
insta::with_settings!({
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.4-[PLATFORM]/python"
canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.4-[PLATFORM]/python"
);
});
}
@ -728,15 +707,15 @@ fn python_install_preview_upgrade() {
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.6-[PLATFORM]/bin/python3.12"
canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.6-[PLATFORM]/bin/python3.12"
);
});
} else {
} else if cfg!(windows) {
insta::with_settings!({
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.6-[PLATFORM]/python"
canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.6-[PLATFORM]/python"
);
});
}
@ -933,7 +912,7 @@ fn python_install_default() {
bin_python_default.assert(predicate::path::missing());
// Install the latest version, i.e., a "default install"
uv_snapshot!(context.filters(), context.python_install().arg("--preview"), @r"
uv_snapshot!(context.filters(), context.python_install().arg("--default").arg("--preview"), @r"
success: true
exit_code: 0
----- stdout -----
@ -948,6 +927,75 @@ fn python_install_default() {
bin_python_major.assert(predicate::path::exists());
bin_python_default.assert(predicate::path::exists());
// And 3.13 should be the default
if cfg!(unix) {
insta::with_settings!({
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/bin/python3.13"
);
insta::assert_snapshot!(
canonicalize_link_path(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13"
);
});
insta::with_settings!({
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link(&bin_python_minor_13), @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/bin/python3.13"
);
insta::assert_snapshot!(
canonicalize_link_path(&bin_python_minor_13), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13"
);
});
insta::with_settings!({
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/bin/python3.13"
);
insta::assert_snapshot!(
canonicalize_link_path(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13"
);
});
} else if cfg!(windows) {
insta::with_settings!({
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/python"
);
insta::assert_snapshot!(
canonicalize_link_path(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python"
);
});
insta::with_settings!({
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link(&bin_python_minor_13), @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/python"
);
insta::assert_snapshot!(
canonicalize_link_path(&bin_python_minor_13), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python"
);
});
insta::with_settings!({
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.13-[PLATFORM]/python"
);
insta::assert_snapshot!(
canonicalize_link_path(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python"
);
});
}
// Uninstall again
uv_snapshot!(context.filters(), context.python_uninstall().arg("3.13"), @r"
success: true
@ -1001,7 +1049,10 @@ fn python_install_default() {
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12"
read_link(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.12-[PLATFORM]/bin/python3.12"
);
insta::assert_snapshot!(
canonicalize_link_path(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12"
);
});
@ -1009,7 +1060,10 @@ fn python_install_default() {
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12"
read_link(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12-[PLATFORM]/bin/python3.12"
);
insta::assert_snapshot!(
canonicalize_link_path(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12"
);
});
@ -1017,7 +1071,10 @@ fn python_install_default() {
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12"
read_link(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.12-[PLATFORM]/bin/python3.12"
);
insta::assert_snapshot!(
canonicalize_link_path(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12"
);
});
} else {
@ -1025,7 +1082,10 @@ fn python_install_default() {
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python"
read_link(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.12-[PLATFORM]/python"
);
insta::assert_snapshot!(
canonicalize_link_path(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python"
);
});
@ -1033,7 +1093,10 @@ fn python_install_default() {
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python"
read_link(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12-[PLATFORM]/python"
);
insta::assert_snapshot!(
canonicalize_link_path(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python"
);
});
@ -1041,7 +1104,10 @@ fn python_install_default() {
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python"
read_link(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.12-[PLATFORM]/python"
);
insta::assert_snapshot!(
canonicalize_link_path(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python"
);
});
}
@ -1069,7 +1135,7 @@ fn python_install_default() {
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13"
canonicalize_link_path(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13"
);
});
@ -1077,7 +1143,7 @@ fn python_install_default() {
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python_minor_13), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13"
canonicalize_link_path(&bin_python_minor_13), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13"
);
});
@ -1085,7 +1151,7 @@ fn python_install_default() {
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12"
canonicalize_link_path(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12"
);
});
@ -1093,15 +1159,15 @@ fn python_install_default() {
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13"
canonicalize_link_path(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13"
);
});
} else {
} else if cfg!(windows) {
insta::with_settings!({
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python"
canonicalize_link_path(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python"
);
});
@ -1109,7 +1175,7 @@ fn python_install_default() {
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python_minor_13), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python"
canonicalize_link_path(&bin_python_minor_13), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python"
);
});
@ -1117,7 +1183,7 @@ fn python_install_default() {
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python"
canonicalize_link_path(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python"
);
});
@ -1125,7 +1191,7 @@ fn python_install_default() {
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python"
canonicalize_link_path(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python"
);
});
}
@ -1139,7 +1205,7 @@ fn launcher_path(path: &Path) -> PathBuf {
launcher.python_path
}
fn read_link_path(path: &Path) -> String {
fn canonicalize_link_path(path: &Path) -> String {
#[cfg(unix)]
let canonical_path = fs_err::canonicalize(path);
@ -1152,6 +1218,17 @@ fn read_link_path(path: &Path) -> String {
.to_string()
}
fn read_link(path: &Path) -> String {
#[cfg(unix)]
let linked_path =
fs_err::read_link(path).unwrap_or_else(|_| panic!("{} should be readable", path.display()));
#[cfg(windows)]
let linked_path = launcher_path(path);
linked_path.simplified_display().to_string()
}
#[test]
fn python_install_unknown() {
let context: TestContext = TestContext::new_with_versions(&[]).with_managed_python_dirs();
@ -1212,7 +1289,7 @@ fn python_install_preview_broken_link() {
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13"
canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13"
);
});
}