uv/crates/uv/tests/it/pip_install_scenarios.rs

4370 lines
148 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! DO NOT EDIT
//!
//! Generated with `./scripts/sync_scenarios.sh`
//! Scenarios from <https://github.com/astral-sh/packse/tree/0.3.53/scenarios>
//!
#![cfg(all(feature = "python", feature = "pypi", unix))]
use std::process::Command;
use uv_static::EnvVars;
use crate::common::{TestContext, build_vendor_links_url, packse_index_url, uv_snapshot};
/// Create a `pip install` command with options shared across all scenarios.
fn command(context: &TestContext) -> Command {
let mut command = context.pip_install();
command
.arg("--index-url")
.arg(packse_index_url())
.arg("--find-links")
.arg(build_vendor_links_url());
command.env_remove(EnvVars::UV_EXCLUDE_NEWER);
command
}
/// The user requires an exact version of package `a` but only other versions exist
///
/// ```text
/// requires-exact-version-does-not-exist
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a==2.0.0
/// │ └── unsatisfied: no matching version
/// └── a
/// └── a-1.0.0
/// ```
#[test]
fn requires_exact_version_does_not_exist() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"requires-exact-version-does-not-exist-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("requires-exact-version-does-not-exist-a==2.0.0")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because there is no version of package-a==2.0.0 and you require package-a==2.0.0, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("requires_exact_version_does_not_exist_a");
}
/// The user requires a version of `a` greater than `1.0.0` but only smaller or equal versions exist
///
/// ```text
/// requires-greater-version-does-not-exist
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a>1.0.0
/// │ └── unsatisfied: no matching version
/// └── a
/// ├── a-0.1.0
/// └── a-1.0.0
/// ```
#[test]
fn requires_greater_version_does_not_exist() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"requires-greater-version-does-not-exist-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("requires-greater-version-does-not-exist-a>1.0.0")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a<=1.0.0 is available and you require package-a>1.0.0, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("requires_greater_version_does_not_exist_a");
}
/// The user requires a version of `a` less than `1.0.0` but only larger versions exist
///
/// ```text
/// requires-less-version-does-not-exist
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a<2.0.0
/// │ └── unsatisfied: no matching version
/// └── a
/// ├── a-2.0.0
/// ├── a-3.0.0
/// └── a-4.0.0
/// ```
#[test]
fn requires_less_version_does_not_exist() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"requires-less-version-does-not-exist-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("requires-less-version-does-not-exist-a<2.0.0")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a>=2.0.0 is available and you require package-a<2.0.0, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("requires_less_version_does_not_exist_a");
}
/// The user requires any version of package `a` which does not exist.
///
/// ```text
/// requires-package-does-not-exist
/// ├── environment
/// │ └── python3.12
/// └── root
/// └── requires a
/// └── unsatisfied: no versions for package
/// ```
#[test]
fn requires_package_does_not_exist() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"requires-package-does-not-exist-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("requires-package-does-not-exist-a")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because package-a was not found in the package registry and you require package-a, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("requires_package_does_not_exist_a");
}
/// The user requires package `a` but `a` requires package `b` which does not exist
///
/// ```text
/// transitive-requires-package-does-not-exist
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── satisfied by a-1.0.0
/// └── a
/// └── a-1.0.0
/// └── requires b
/// └── unsatisfied: no versions for package
/// ```
#[test]
fn transitive_requires_package_does_not_exist() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"transitive-requires-package-does-not-exist-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("transitive-requires-package-does-not-exist-a")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because package-b was not found in the package registry and package-a==1.0.0 depends on package-b, we can conclude that package-a==1.0.0 cannot be used.
And because only package-a==1.0.0 is available and you require package-a, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("transitive_requires_package_does_not_exist_a");
}
/// There is a non-contiguous range of compatible versions for the requested package `a`, but another dependency `c` excludes the range. This is the same as `dependency-excludes-range-of-compatible-versions` but some of the versions of `a` are incompatible for another reason e.g. dependency on non-existent package `d`.
///
/// ```text
/// dependency-excludes-non-contiguous-range-of-compatible-versions
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a
/// │ │ ├── satisfied by a-1.0.0
/// │ │ ├── satisfied by a-2.0.0
/// │ │ ├── satisfied by a-2.1.0
/// │ │ ├── satisfied by a-2.2.0
/// │ │ ├── satisfied by a-2.3.0
/// │ │ ├── satisfied by a-2.4.0
/// │ │ └── satisfied by a-3.0.0
/// │ ├── requires b<3.0.0,>=2.0.0
/// │ │ └── satisfied by b-2.0.0
/// │ └── requires c
/// │ ├── satisfied by c-1.0.0
/// │ └── satisfied by c-2.0.0
/// ├── a
/// │ ├── a-1.0.0
/// │ │ └── requires b==1.0.0
/// │ │ └── satisfied by b-1.0.0
/// │ ├── a-2.0.0
/// │ │ └── requires b==2.0.0
/// │ │ └── satisfied by b-2.0.0
/// │ ├── a-2.1.0
/// │ │ ├── requires b==2.0.0
/// │ │ │ └── satisfied by b-2.0.0
/// │ │ └── requires d
/// │ │ └── unsatisfied: no versions for package
/// │ ├── a-2.2.0
/// │ │ └── requires b==2.0.0
/// │ │ └── satisfied by b-2.0.0
/// │ ├── a-2.3.0
/// │ │ ├── requires b==2.0.0
/// │ │ │ └── satisfied by b-2.0.0
/// │ │ └── requires d
/// │ │ └── unsatisfied: no versions for package
/// │ ├── a-2.4.0
/// │ │ └── requires b==2.0.0
/// │ │ └── satisfied by b-2.0.0
/// │ └── a-3.0.0
/// │ └── requires b==3.0.0
/// │ └── satisfied by b-3.0.0
/// ├── b
/// │ ├── b-1.0.0
/// │ ├── b-2.0.0
/// │ └── b-3.0.0
/// └── c
/// ├── c-1.0.0
/// │ └── requires a<2.0.0
/// │ └── satisfied by a-1.0.0
/// └── c-2.0.0
/// └── requires a>=3.0.0
/// └── satisfied by a-3.0.0
/// ```
#[test]
fn dependency_excludes_non_contiguous_range_of_compatible_versions() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((
r"dependency-excludes-non-contiguous-range-of-compatible-versions-",
"package-",
));
uv_snapshot!(filters, command(&context)
.arg("dependency-excludes-non-contiguous-range-of-compatible-versions-a")
.arg("dependency-excludes-non-contiguous-range-of-compatible-versions-b<3.0.0,>=2.0.0")
.arg("dependency-excludes-non-contiguous-range-of-compatible-versions-c")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because package-a==1.0.0 depends on package-b==1.0.0 and only the following versions of package-a are available:
package-a==1.0.0
package-a>2.0.0
we can conclude that package-a<2.0.0 depends on package-b==1.0.0.
And because only package-a<=3.0.0 is available, we can conclude that package-a<2.0.0 depends on package-b==1.0.0. (1)
Because only the following versions of package-c are available:
package-c==1.0.0
package-c==2.0.0
and package-c==1.0.0 depends on package-a<2.0.0, we can conclude that package-c<2.0.0 depends on package-a<2.0.0.
And because package-c==2.0.0 depends on package-a>=3.0.0, we can conclude that all versions of package-c depend on one of:
package-a<2.0.0
package-a>=3.0.0
And because we know from (1) that package-a<2.0.0 depends on package-b==1.0.0, we can conclude that package-a!=3.0.0, package-b!=1.0.0, all versions of package-c are incompatible.
And because package-a==3.0.0 depends on package-b==3.0.0, we can conclude that all versions of package-c depend on one of:
package-b<=1.0.0
package-b>=3.0.0
And because you require package-b>=2.0.0,<3.0.0 and package-c, we can conclude that your requirements are unsatisfiable.
");
// Only the `2.x` versions of `a` are available since `a==1.0.0` and `a==3.0.0` require incompatible versions of `b`, but all available versions of `c` exclude that range of `a` so resolution fails.
context
.assert_not_installed("dependency_excludes_non_contiguous_range_of_compatible_versions_a");
context
.assert_not_installed("dependency_excludes_non_contiguous_range_of_compatible_versions_b");
context
.assert_not_installed("dependency_excludes_non_contiguous_range_of_compatible_versions_c");
}
/// There is a range of compatible versions for the requested package `a`, but another dependency `c` excludes that range.
///
/// ```text
/// dependency-excludes-range-of-compatible-versions
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a
/// │ │ ├── satisfied by a-1.0.0
/// │ │ ├── satisfied by a-2.0.0
/// │ │ ├── satisfied by a-2.1.0
/// │ │ ├── satisfied by a-2.2.0
/// │ │ ├── satisfied by a-2.3.0
/// │ │ └── satisfied by a-3.0.0
/// │ ├── requires b<3.0.0,>=2.0.0
/// │ │ └── satisfied by b-2.0.0
/// │ └── requires c
/// │ ├── satisfied by c-1.0.0
/// │ └── satisfied by c-2.0.0
/// ├── a
/// │ ├── a-1.0.0
/// │ │ └── requires b==1.0.0
/// │ │ └── satisfied by b-1.0.0
/// │ ├── a-2.0.0
/// │ │ └── requires b==2.0.0
/// │ │ └── satisfied by b-2.0.0
/// │ ├── a-2.1.0
/// │ │ └── requires b==2.0.0
/// │ │ └── satisfied by b-2.0.0
/// │ ├── a-2.2.0
/// │ │ └── requires b==2.0.0
/// │ │ └── satisfied by b-2.0.0
/// │ ├── a-2.3.0
/// │ │ └── requires b==2.0.0
/// │ │ └── satisfied by b-2.0.0
/// │ └── a-3.0.0
/// │ └── requires b==3.0.0
/// │ └── satisfied by b-3.0.0
/// ├── b
/// │ ├── b-1.0.0
/// │ ├── b-2.0.0
/// │ └── b-3.0.0
/// └── c
/// ├── c-1.0.0
/// │ └── requires a<2.0.0
/// │ └── satisfied by a-1.0.0
/// └── c-2.0.0
/// └── requires a>=3.0.0
/// └── satisfied by a-3.0.0
/// ```
#[test]
fn dependency_excludes_range_of_compatible_versions() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((
r"dependency-excludes-range-of-compatible-versions-",
"package-",
));
uv_snapshot!(filters, command(&context)
.arg("dependency-excludes-range-of-compatible-versions-a")
.arg("dependency-excludes-range-of-compatible-versions-b<3.0.0,>=2.0.0")
.arg("dependency-excludes-range-of-compatible-versions-c")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because package-a==1.0.0 depends on package-b==1.0.0 and only the following versions of package-a are available:
package-a==1.0.0
package-a>2.0.0
we can conclude that package-a<2.0.0 depends on package-b==1.0.0.
And because only package-a<=3.0.0 is available, we can conclude that package-a<2.0.0 depends on package-b==1.0.0. (1)
Because only the following versions of package-c are available:
package-c==1.0.0
package-c==2.0.0
and package-c==1.0.0 depends on package-a<2.0.0, we can conclude that package-c<2.0.0 depends on package-a<2.0.0.
And because package-c==2.0.0 depends on package-a>=3.0.0, we can conclude that all versions of package-c depend on one of:
package-a<2.0.0
package-a>=3.0.0
And because we know from (1) that package-a<2.0.0 depends on package-b==1.0.0, we can conclude that package-a!=3.0.0, package-b!=1.0.0, all versions of package-c are incompatible.
And because package-a==3.0.0 depends on package-b==3.0.0, we can conclude that all versions of package-c depend on one of:
package-b<=1.0.0
package-b>=3.0.0
And because you require package-b>=2.0.0,<3.0.0 and package-c, we can conclude that your requirements are unsatisfiable.
");
// Only the `2.x` versions of `a` are available since `a==1.0.0` and `a==3.0.0` require incompatible versions of `b`, but all available versions of `c` exclude that range of `a` so resolution fails.
context.assert_not_installed("dependency_excludes_range_of_compatible_versions_a");
context.assert_not_installed("dependency_excludes_range_of_compatible_versions_b");
context.assert_not_installed("dependency_excludes_range_of_compatible_versions_c");
}
/// Only one version of the requested package `a` is compatible, but the user has banned that version.
///
/// ```text
/// excluded-only-compatible-version
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a!=2.0.0
/// │ │ ├── satisfied by a-1.0.0
/// │ │ └── satisfied by a-3.0.0
/// │ └── requires b<3.0.0,>=2.0.0
/// │ └── satisfied by b-2.0.0
/// ├── a
/// │ ├── a-1.0.0
/// │ │ └── requires b==1.0.0
/// │ │ └── satisfied by b-1.0.0
/// │ ├── a-2.0.0
/// │ │ └── requires b==2.0.0
/// │ │ └── satisfied by b-2.0.0
/// │ └── a-3.0.0
/// │ └── requires b==3.0.0
/// │ └── satisfied by b-3.0.0
/// └── b
/// ├── b-1.0.0
/// ├── b-2.0.0
/// └── b-3.0.0
/// ```
#[test]
fn excluded_only_compatible_version() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"excluded-only-compatible-version-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("excluded-only-compatible-version-a!=2.0.0")
.arg("excluded-only-compatible-version-b<3.0.0,>=2.0.0")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only the following versions of package-a are available:
package-a==1.0.0
package-a==2.0.0
package-a==3.0.0
and package-a==1.0.0 depends on package-b==1.0.0, we can conclude that package-a<2.0.0 depends on package-b==1.0.0.
And because package-a==3.0.0 depends on package-b==3.0.0, we can conclude that all of:
package-a<2.0.0
package-a>2.0.0
depend on one of:
package-b==1.0.0
package-b==3.0.0
And because you require one of:
package-a<2.0.0
package-a>2.0.0
and package-b>=2.0.0,<3.0.0, we can conclude that your requirements are unsatisfiable.
");
// Only `a==1.2.0` is available since `a==1.0.0` and `a==3.0.0` require incompatible versions of `b`. The user has excluded that version of `a` so resolution fails.
context.assert_not_installed("excluded_only_compatible_version_a");
context.assert_not_installed("excluded_only_compatible_version_b");
}
/// Only one version of the requested package is available, but the user has banned that version.
///
/// ```text
/// excluded-only-version
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a!=1.0.0
/// │ └── unsatisfied: no matching version
/// └── a
/// └── a-1.0.0
/// ```
#[test]
fn excluded_only_version() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"excluded-only-version-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("excluded-only-version-a!=1.0.0")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a==1.0.0 is available and you require one of:
package-a<1.0.0
package-a>1.0.0
we can conclude that your requirements are unsatisfiable.
");
// Only `a==1.0.0` is available but the user excluded it.
context.assert_not_installed("excluded_only_version_a");
}
/// Multiple optional dependencies are requested for the package via an 'all' extra.
///
/// ```text
/// all-extras-required
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a[all]
/// │ ├── satisfied by a-1.0.0
/// │ ├── satisfied by a-1.0.0[all]
/// │ ├── satisfied by a-1.0.0[extra_b]
/// │ └── satisfied by a-1.0.0[extra_c]
/// ├── a
/// │ ├── a-1.0.0
/// │ ├── a-1.0.0[all]
/// │ │ ├── requires a[extra_b]
/// │ │ │ ├── satisfied by a-1.0.0
/// │ │ │ ├── satisfied by a-1.0.0[all]
/// │ │ │ ├── satisfied by a-1.0.0[extra_b]
/// │ │ │ └── satisfied by a-1.0.0[extra_c]
/// │ │ └── requires a[extra_c]
/// │ │ ├── satisfied by a-1.0.0
/// │ │ ├── satisfied by a-1.0.0[all]
/// │ │ ├── satisfied by a-1.0.0[extra_b]
/// │ │ └── satisfied by a-1.0.0[extra_c]
/// │ ├── a-1.0.0[extra_b]
/// │ │ └── requires b
/// │ │ └── satisfied by b-1.0.0
/// │ └── a-1.0.0[extra_c]
/// │ └── requires c
/// │ └── satisfied by c-1.0.0
/// ├── b
/// │ └── b-1.0.0
/// └── c
/// └── c-1.0.0
/// ```
#[test]
fn all_extras_required() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"all-extras-required-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("all-extras-required-a[all]")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
Prepared 3 packages in [TIME]
Installed 3 packages in [TIME]
+ package-a==1.0.0
+ package-b==1.0.0
+ package-c==1.0.0
");
context.assert_installed("all_extras_required_a", "1.0.0");
context.assert_installed("all_extras_required_b", "1.0.0");
context.assert_installed("all_extras_required_c", "1.0.0");
}
/// Optional dependencies are requested for the package, the extra is only available on an older version.
///
/// ```text
/// extra-does-not-exist-backtrack
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a[extra]
/// │ ├── satisfied by a-2.0.0
/// │ ├── satisfied by a-3.0.0
/// │ ├── satisfied by a-1.0.0
/// │ └── satisfied by a-1.0.0[extra]
/// ├── a
/// │ ├── a-2.0.0
/// │ ├── a-3.0.0
/// │ ├── a-1.0.0
/// │ └── a-1.0.0[extra]
/// │ └── requires b==1.0.0
/// │ └── satisfied by b-1.0.0
/// └── b
/// └── b-1.0.0
/// ```
#[test]
fn extra_does_not_exist_backtrack() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"extra-does-not-exist-backtrack-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("extra-does-not-exist-backtrack-a[extra]")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==3.0.0
warning: The package `package-a==3.0.0` does not have an extra named `extra`
");
// The resolver should not backtrack to `a==1.0.0` because missing extras are allowed during resolution. `b` should not be installed.
context.assert_installed("extra_does_not_exist_backtrack_a", "3.0.0");
}
/// One of two incompatible optional dependencies are requested for the package.
///
/// ```text
/// extra-incompatible-with-extra-not-requested
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a[extra_c]
/// │ ├── satisfied by a-1.0.0
/// │ ├── satisfied by a-1.0.0[extra_b]
/// │ └── satisfied by a-1.0.0[extra_c]
/// ├── a
/// │ ├── a-1.0.0
/// │ ├── a-1.0.0[extra_b]
/// │ │ └── requires b==1.0.0
/// │ │ └── satisfied by b-1.0.0
/// │ └── a-1.0.0[extra_c]
/// │ └── requires b==2.0.0
/// │ └── satisfied by b-2.0.0
/// └── b
/// ├── b-1.0.0
/// └── b-2.0.0
/// ```
#[test]
fn extra_incompatible_with_extra_not_requested() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"extra-incompatible-with-extra-not-requested-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("extra-incompatible-with-extra-not-requested-a[extra_c]")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ package-a==1.0.0
+ package-b==2.0.0
");
// Because the user does not request both extras, it is okay that one is incompatible with the other.
context.assert_installed("extra_incompatible_with_extra_not_requested_a", "1.0.0");
context.assert_installed("extra_incompatible_with_extra_not_requested_b", "2.0.0");
}
/// Multiple optional dependencies are requested for the package, but they have conflicting requirements with each other.
///
/// ```text
/// extra-incompatible-with-extra
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a[extra_b,extra_c]
/// │ ├── satisfied by a-1.0.0
/// │ ├── satisfied by a-1.0.0[extra_b]
/// │ └── satisfied by a-1.0.0[extra_c]
/// ├── a
/// │ ├── a-1.0.0
/// │ ├── a-1.0.0[extra_b]
/// │ │ └── requires b==1.0.0
/// │ │ └── satisfied by b-1.0.0
/// │ └── a-1.0.0[extra_c]
/// │ └── requires b==2.0.0
/// │ └── satisfied by b-2.0.0
/// └── b
/// ├── b-1.0.0
/// └── b-2.0.0
/// ```
#[test]
fn extra_incompatible_with_extra() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"extra-incompatible-with-extra-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("extra-incompatible-with-extra-a[extra_b,extra_c]")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a[extra-b]==1.0.0 is available and package-a[extra-b]==1.0.0 depends on package-b==1.0.0, we can conclude that all versions of package-a[extra-b] depend on package-b==1.0.0.
And because package-a[extra-c]==1.0.0 depends on package-b==2.0.0 and only package-a[extra-c]==1.0.0 is available, we can conclude that all versions of package-a[extra-b] and all versions of package-a[extra-c] are incompatible.
And because you require package-a[extra-b] and package-a[extra-c], we can conclude that your requirements are unsatisfiable.
");
// Because both `extra_b` and `extra_c` are requested and they require incompatible versions of `b`, `a` cannot be installed.
context.assert_not_installed("extra_incompatible_with_extra_a");
}
/// Optional dependencies are requested for the package, but the extra is not compatible with other requested versions.
///
/// ```text
/// extra-incompatible-with-root
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a[extra]
/// │ │ ├── satisfied by a-1.0.0
/// │ │ └── satisfied by a-1.0.0[extra]
/// │ └── requires b==2.0.0
/// │ └── satisfied by b-2.0.0
/// ├── a
/// │ ├── a-1.0.0
/// │ └── a-1.0.0[extra]
/// │ └── requires b==1.0.0
/// │ └── satisfied by b-1.0.0
/// └── b
/// ├── b-1.0.0
/// └── b-2.0.0
/// ```
#[test]
fn extra_incompatible_with_root() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"extra-incompatible-with-root-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("extra-incompatible-with-root-a[extra]")
.arg("extra-incompatible-with-root-b==2.0.0")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a[extra]==1.0.0 is available and package-a[extra]==1.0.0 depends on package-b==1.0.0, we can conclude that all versions of package-a[extra] depend on package-b==1.0.0.
And because you require package-a[extra] and package-b==2.0.0, we can conclude that your requirements are unsatisfiable.
");
// Because the user requested `b==2.0.0` but the requested extra requires `b==1.0.0`, the dependencies cannot be satisfied.
context.assert_not_installed("extra_incompatible_with_root_a");
context.assert_not_installed("extra_incompatible_with_root_b");
}
/// Optional dependencies are requested for the package.
///
/// ```text
/// extra-required
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a[extra]
/// │ ├── satisfied by a-1.0.0
/// │ └── satisfied by a-1.0.0[extra]
/// ├── a
/// │ ├── a-1.0.0
/// │ └── a-1.0.0[extra]
/// │ └── requires b
/// │ └── satisfied by b-1.0.0
/// └── b
/// └── b-1.0.0
/// ```
#[test]
fn extra_required() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"extra-required-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("extra-required-a[extra]")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ package-a==1.0.0
+ package-b==1.0.0
");
context.assert_installed("extra_required_a", "1.0.0");
context.assert_installed("extra_required_b", "1.0.0");
}
/// Optional dependencies are requested for the package, but the extra does not exist.
///
/// ```text
/// missing-extra
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a[extra]
/// │ └── satisfied by a-1.0.0
/// └── a
/// └── a-1.0.0
/// ```
#[test]
fn missing_extra() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"missing-extra-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("missing-extra-a[extra]")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.0.0
warning: The package `package-a==1.0.0` does not have an extra named `extra`
");
// Missing extras are ignored during resolution.
context.assert_installed("missing_extra_a", "1.0.0");
}
/// Multiple optional dependencies are requested for the package.
///
/// ```text
/// multiple-extras-required
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a[extra_b,extra_c]
/// │ ├── satisfied by a-1.0.0
/// │ ├── satisfied by a-1.0.0[extra_b]
/// │ └── satisfied by a-1.0.0[extra_c]
/// ├── a
/// │ ├── a-1.0.0
/// │ ├── a-1.0.0[extra_b]
/// │ │ └── requires b
/// │ │ └── satisfied by b-1.0.0
/// │ └── a-1.0.0[extra_c]
/// │ └── requires c
/// │ └── satisfied by c-1.0.0
/// ├── b
/// │ └── b-1.0.0
/// └── c
/// └── c-1.0.0
/// ```
#[test]
fn multiple_extras_required() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"multiple-extras-required-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("multiple-extras-required-a[extra_b,extra_c]")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
Prepared 3 packages in [TIME]
Installed 3 packages in [TIME]
+ package-a==1.0.0
+ package-b==1.0.0
+ package-c==1.0.0
");
context.assert_installed("multiple_extras_required_a", "1.0.0");
context.assert_installed("multiple_extras_required_b", "1.0.0");
context.assert_installed("multiple_extras_required_c", "1.0.0");
}
/// The user requires two incompatible, existing versions of package `a`
///
/// ```text
/// direct-incompatible-versions
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a==1.0.0
/// │ │ └── satisfied by a-1.0.0
/// │ └── requires a==2.0.0
/// │ └── satisfied by a-2.0.0
/// └── a
/// ├── a-1.0.0
/// └── a-2.0.0
/// ```
#[test]
fn direct_incompatible_versions() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"direct-incompatible-versions-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("direct-incompatible-versions-a==1.0.0")
.arg("direct-incompatible-versions-a==2.0.0")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because you require package-a==1.0.0 and package-a==2.0.0, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("direct_incompatible_versions_a");
context.assert_not_installed("direct_incompatible_versions_a");
}
/// The user requires `a`, which requires two incompatible, existing versions of package `b`
///
/// ```text
/// transitive-incompatible-versions
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a==1.0.0
/// │ └── satisfied by a-1.0.0
/// └── a
/// └── a-1.0.0
/// ├── requires b==2.0.0
/// └── unsatisfied: no versions for package
/// └── requires b==1.0.0
/// └── unsatisfied: no versions for package
/// ```
#[test]
fn transitive_incompatible_versions() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"transitive-incompatible-versions-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("transitive-incompatible-versions-a==1.0.0")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because package-a==1.0.0 depends on package-b==1.0.0 and package-b==2.0.0, we can conclude that package-a==1.0.0 cannot be used.
And because you require package-a==1.0.0, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("transitive_incompatible_versions_a");
}
/// The user requires packages `a` and `b` but `a` requires a different version of `b`
///
/// ```text
/// transitive-incompatible-with-root-version
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a
/// │ │ └── satisfied by a-1.0.0
/// │ └── requires b==1.0.0
/// │ └── satisfied by b-1.0.0
/// ├── a
/// │ └── a-1.0.0
/// │ └── requires b==2.0.0
/// │ └── satisfied by b-2.0.0
/// └── b
/// ├── b-1.0.0
/// └── b-2.0.0
/// ```
#[test]
fn transitive_incompatible_with_root_version() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"transitive-incompatible-with-root-version-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("transitive-incompatible-with-root-version-a")
.arg("transitive-incompatible-with-root-version-b==1.0.0")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 depends on package-b==2.0.0, we can conclude that all versions of package-a depend on package-b==2.0.0.
And because you require package-a and package-b==1.0.0, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("transitive_incompatible_with_root_version_a");
context.assert_not_installed("transitive_incompatible_with_root_version_b");
}
/// The user requires package `a` and `b`; `a` and `b` require different versions of `c`
///
/// ```text
/// transitive-incompatible-with-transitive
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a
/// │ │ └── satisfied by a-1.0.0
/// │ └── requires b
/// │ └── satisfied by b-1.0.0
/// ├── a
/// │ └── a-1.0.0
/// │ └── requires c==1.0.0
/// │ └── satisfied by c-1.0.0
/// ├── b
/// │ └── b-1.0.0
/// │ └── requires c==2.0.0
/// │ └── satisfied by c-2.0.0
/// └── c
/// ├── c-1.0.0
/// └── c-2.0.0
/// ```
#[test]
fn transitive_incompatible_with_transitive() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"transitive-incompatible-with-transitive-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("transitive-incompatible-with-transitive-a")
.arg("transitive-incompatible-with-transitive-b")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 depends on package-c==1.0.0, we can conclude that all versions of package-a depend on package-c==1.0.0.
And because package-b==1.0.0 depends on package-c==2.0.0 and only package-b==1.0.0 is available, we can conclude that all versions of package-a and all versions of package-b are incompatible.
And because you require package-a and package-b, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("transitive_incompatible_with_transitive_a");
context.assert_not_installed("transitive_incompatible_with_transitive_b");
}
/// A local version should be included in inclusive ordered comparisons.
///
/// ```text
/// local-greater-than-or-equal
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a>=1.2.3
/// │ ├── satisfied by a-1.2.3+bar
/// │ └── satisfied by a-1.2.3+foo
/// └── a
/// ├── a-1.2.3+bar
/// └── a-1.2.3+foo
/// ```
#[test]
fn local_greater_than_or_equal() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"local-greater-than-or-equal-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("local-greater-than-or-equal-a>=1.2.3")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.2.3+foo
");
// The version '1.2.3+foo' satisfies the constraint '>=1.2.3'.
context.assert_installed("local_greater_than_or_equal_a", "1.2.3+foo");
}
/// A local version should be excluded in exclusive ordered comparisons.
///
/// ```text
/// local-greater-than
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a>1.2.3
/// │ └── unsatisfied: no matching version
/// └── a
/// └── a-1.2.3+foo
/// ```
#[test]
fn local_greater_than() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"local-greater-than-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("local-greater-than-a>1.2.3")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a==1.2.3+foo is available and you require package-a>1.2.3, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("local_greater_than_a");
}
/// A local version should be included in inclusive ordered comparisons.
///
/// ```text
/// local-less-than-or-equal
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a<=1.2.3
/// │ ├── satisfied by a-1.2.3+bar
/// │ └── satisfied by a-1.2.3+foo
/// └── a
/// ├── a-1.2.3+bar
/// └── a-1.2.3+foo
/// ```
#[test]
fn local_less_than_or_equal() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"local-less-than-or-equal-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("local-less-than-or-equal-a<=1.2.3")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.2.3+foo
");
// The version '1.2.3+foo' satisfies the constraint '<=1.2.3'.
context.assert_installed("local_less_than_or_equal_a", "1.2.3+foo");
}
/// A local version should be excluded in exclusive ordered comparisons.
///
/// ```text
/// local-less-than
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a<1.2.3
/// │ └── unsatisfied: no matching version
/// └── a
/// └── a-1.2.3+foo
/// ```
#[test]
fn local_less_than() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"local-less-than-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("local-less-than-a<1.2.3")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a==1.2.3+foo is available and you require package-a<1.2.3, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("local_less_than_a");
}
/// Tests that we can select an older version with a local segment when newer versions are incompatible.
///
/// ```text
/// local-not-latest
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a>=1
/// │ ├── satisfied by a-1.2.3
/// │ ├── satisfied by a-1.2.2+foo
/// │ └── satisfied by a-1.2.1+foo
/// └── a
/// ├── a-1.2.3
/// ├── a-1.2.2+foo
/// └── a-1.2.1+foo
/// ```
#[test]
fn local_not_latest() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"local-not-latest-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("local-not-latest-a>=1")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.2.1+foo
");
context.assert_installed("local_not_latest_a", "1.2.1+foo");
}
/// If there is a 1.2.3 version with an sdist published and no compatible wheels, then the sdist will be used.
///
/// ```text
/// local-not-used-with-sdist
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a==1.2.3
/// │ ├── satisfied by a-1.2.3
/// │ └── satisfied by a-1.2.3+foo
/// └── a
/// ├── a-1.2.3
/// └── a-1.2.3+foo
/// ```
#[test]
fn local_not_used_with_sdist() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"local-not-used-with-sdist-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("local-not-used-with-sdist-a==1.2.3")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.2.3+foo
");
// The version '1.2.3' with an sdist satisfies the constraint '==1.2.3'.
context.assert_installed("local_not_used_with_sdist_a", "1.2.3+foo");
}
/// A simple version constraint should not exclude published versions with local segments.
///
/// ```text
/// local-simple
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a==1.2.3
/// │ ├── satisfied by a-1.2.3+bar
/// │ └── satisfied by a-1.2.3+foo
/// └── a
/// ├── a-1.2.3+bar
/// └── a-1.2.3+foo
/// ```
#[test]
fn local_simple() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"local-simple-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("local-simple-a==1.2.3")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.2.3+foo
");
// The version '1.2.3+foo' satisfies the constraint '==1.2.3'.
context.assert_installed("local_simple_a", "1.2.3+foo");
}
/// A dependency depends on a conflicting local version of a direct dependency, but we can backtrack to a compatible version.
///
/// ```text
/// local-transitive-backtrack
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a
/// │ │ ├── satisfied by a-1.0.0
/// │ │ └── satisfied by a-2.0.0
/// │ └── requires b==2.0.0+foo
/// │ └── satisfied by b-2.0.0+foo
/// ├── a
/// │ ├── a-1.0.0
/// │ │ └── requires b==2.0.0
/// │ │ ├── satisfied by b-2.0.0+bar
/// │ │ └── satisfied by b-2.0.0+foo
/// │ └── a-2.0.0
/// │ └── requires b==2.0.0+bar
/// │ └── satisfied by b-2.0.0+bar
/// └── b
/// ├── b-2.0.0+bar
/// └── b-2.0.0+foo
/// ```
#[test]
fn local_transitive_backtrack() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"local-transitive-backtrack-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("local-transitive-backtrack-a")
.arg("local-transitive-backtrack-b==2.0.0+foo")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ package-a==1.0.0
+ package-b==2.0.0+foo
");
// Backtracking to '1.0.0' gives us compatible local versions of b.
context.assert_installed("local_transitive_backtrack_a", "1.0.0");
context.assert_installed("local_transitive_backtrack_b", "2.0.0+foo");
}
/// A dependency depends on a conflicting local version of a direct dependency.
///
/// ```text
/// local-transitive-conflicting
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a
/// │ │ └── satisfied by a-1.0.0
/// │ └── requires b==2.0.0+foo
/// │ └── satisfied by b-2.0.0+foo
/// ├── a
/// │ └── a-1.0.0
/// │ └── requires b==2.0.0+bar
/// │ └── satisfied by b-2.0.0+bar
/// └── b
/// ├── b-2.0.0+bar
/// └── b-2.0.0+foo
/// ```
#[test]
fn local_transitive_conflicting() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"local-transitive-conflicting-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("local-transitive-conflicting-a")
.arg("local-transitive-conflicting-b==2.0.0+foo")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 depends on package-b==2.0.0+bar, we can conclude that all versions of package-a depend on package-b==2.0.0+bar.
And because you require package-a and package-b==2.0.0+foo, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("local_transitive_conflicting_a");
context.assert_not_installed("local_transitive_conflicting_b");
}
/// A transitive dependency has both a non-local and local version published, but the non-local version is unusable.
///
/// ```text
/// local-transitive-confounding
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── satisfied by a-1.0.0
/// ├── a
/// │ └── a-1.0.0
/// │ └── requires b==2.0.0
/// │ ├── satisfied by b-2.0.0
/// │ ├── satisfied by b-2.0.0+bar
/// │ └── satisfied by b-2.0.0+foo
/// └── b
/// ├── b-2.0.0
/// ├── b-2.0.0+bar
/// └── b-2.0.0+foo
/// ```
#[test]
fn local_transitive_confounding() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"local-transitive-confounding-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("local-transitive-confounding-a")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ package-a==1.0.0
+ package-b==2.0.0+foo
");
// The version '2.0.0+foo' satisfies the constraint '==2.0.0'.
context.assert_installed("local_transitive_confounding_a", "1.0.0");
context.assert_installed("local_transitive_confounding_b", "2.0.0+foo");
}
/// A transitive constraint on a local version should match an inclusive ordered operator.
///
/// ```text
/// local-transitive-greater-than-or-equal
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a
/// │ │ └── satisfied by a-1.0.0
/// │ └── requires b==2.0.0+foo
/// │ └── satisfied by b-2.0.0+foo
/// ├── a
/// │ └── a-1.0.0
/// │ └── requires b>=2.0.0
/// │ ├── satisfied by b-2.0.0+bar
/// │ └── satisfied by b-2.0.0+foo
/// └── b
/// ├── b-2.0.0+bar
/// └── b-2.0.0+foo
/// ```
#[test]
fn local_transitive_greater_than_or_equal() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"local-transitive-greater-than-or-equal-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("local-transitive-greater-than-or-equal-a")
.arg("local-transitive-greater-than-or-equal-b==2.0.0+foo")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ package-a==1.0.0
+ package-b==2.0.0+foo
");
// The version '2.0.0+foo' satisfies both >=2.0.0 and ==2.0.0+foo.
context.assert_installed("local_transitive_greater_than_or_equal_a", "1.0.0");
context.assert_installed("local_transitive_greater_than_or_equal_b", "2.0.0+foo");
}
/// A transitive constraint on a local version should not match an exclusive ordered operator.
///
/// ```text
/// local-transitive-greater-than
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a
/// │ │ └── satisfied by a-1.0.0
/// │ └── requires b==2.0.0+foo
/// │ └── satisfied by b-2.0.0+foo
/// ├── a
/// │ └── a-1.0.0
/// │ └── requires b>2.0.0
/// │ └── unsatisfied: no matching version
/// └── b
/// ├── b-2.0.0+bar
/// └── b-2.0.0+foo
/// ```
#[test]
fn local_transitive_greater_than() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"local-transitive-greater-than-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("local-transitive-greater-than-a")
.arg("local-transitive-greater-than-b==2.0.0+foo")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 depends on package-b>2.0.0, we can conclude that all versions of package-a depend on package-b>2.0.0.
And because you require package-a and package-b==2.0.0+foo, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("local_transitive_greater_than_a");
context.assert_not_installed("local_transitive_greater_than_b");
}
/// A transitive constraint on a local version should match an inclusive ordered operator.
///
/// ```text
/// local-transitive-less-than-or-equal
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a
/// │ │ └── satisfied by a-1.0.0
/// │ └── requires b==2.0.0+foo
/// │ └── satisfied by b-2.0.0+foo
/// ├── a
/// │ └── a-1.0.0
/// │ └── requires b<=2.0.0
/// │ ├── satisfied by b-2.0.0+bar
/// │ └── satisfied by b-2.0.0+foo
/// └── b
/// ├── b-2.0.0+bar
/// └── b-2.0.0+foo
/// ```
#[test]
fn local_transitive_less_than_or_equal() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"local-transitive-less-than-or-equal-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("local-transitive-less-than-or-equal-a")
.arg("local-transitive-less-than-or-equal-b==2.0.0+foo")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ package-a==1.0.0
+ package-b==2.0.0+foo
");
// The version '2.0.0+foo' satisfies both <=2.0.0 and ==2.0.0+foo.
context.assert_installed("local_transitive_less_than_or_equal_a", "1.0.0");
context.assert_installed("local_transitive_less_than_or_equal_b", "2.0.0+foo");
}
/// A transitive constraint on a local version should not match an exclusive ordered operator.
///
/// ```text
/// local-transitive-less-than
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a
/// │ │ └── satisfied by a-1.0.0
/// │ └── requires b==2.0.0+foo
/// │ └── satisfied by b-2.0.0+foo
/// ├── a
/// │ └── a-1.0.0
/// │ └── requires b<2.0.0
/// │ └── unsatisfied: no matching version
/// └── b
/// ├── b-2.0.0+bar
/// └── b-2.0.0+foo
/// ```
#[test]
fn local_transitive_less_than() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"local-transitive-less-than-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("local-transitive-less-than-a")
.arg("local-transitive-less-than-b==2.0.0+foo")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 depends on package-b<2.0.0, we can conclude that all versions of package-a depend on package-b<2.0.0.
And because you require package-a and package-b==2.0.0+foo, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("local_transitive_less_than_a");
context.assert_not_installed("local_transitive_less_than_b");
}
/// A simple version constraint should not exclude published versions with local segments.
///
/// ```text
/// local-transitive
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a
/// │ │ └── satisfied by a-1.0.0
/// │ └── requires b==2.0.0+foo
/// │ └── satisfied by b-2.0.0+foo
/// ├── a
/// │ └── a-1.0.0
/// │ └── requires b==2.0.0
/// │ ├── satisfied by b-2.0.0+foo
/// │ └── satisfied by b-2.0.0+bar
/// └── b
/// ├── b-2.0.0+foo
/// └── b-2.0.0+bar
/// ```
#[test]
fn local_transitive() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"local-transitive-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("local-transitive-a")
.arg("local-transitive-b==2.0.0+foo")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ package-a==1.0.0
+ package-b==2.0.0+foo
");
// The version '2.0.0+foo' satisfies both ==2.0.0 and ==2.0.0+foo.
context.assert_installed("local_transitive_a", "1.0.0");
context.assert_installed("local_transitive_b", "2.0.0+foo");
}
/// Even if there is a 1.2.3 version published, if it is unavailable for some reason (no sdist and no compatible wheels in this case), a 1.2.3 version with a local segment should be usable instead.
///
/// ```text
/// local-used-without-sdist
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a==1.2.3
/// │ ├── satisfied by a-1.2.3
/// │ └── satisfied by a-1.2.3+foo
/// └── a
/// ├── a-1.2.3
/// └── a-1.2.3+foo
/// ```
#[test]
fn local_used_without_sdist() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"local-used-without-sdist-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("local-used-without-sdist-a==1.2.3")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.2.3+foo
");
// The version '1.2.3+foo' satisfies the constraint '==1.2.3'.
context.assert_installed("local_used_without_sdist_a", "1.2.3+foo");
}
/// An equal version constraint should match a post-release version if the post-release version is available.
///
/// ```text
/// post-equal-available
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a==1.2.3.post0
/// │ └── satisfied by a-1.2.3.post0
/// └── a
/// ├── a-1.2.3
/// └── a-1.2.3.post0
/// ```
#[test]
fn post_equal_available() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"post-equal-available-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("post-equal-available-a==1.2.3.post0")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.2.3.post0
");
// The version '1.2.3.post0' satisfies the constraint '==1.2.3.post0'.
context.assert_installed("post_equal_available_a", "1.2.3.post0");
}
/// An equal version constraint should not match a post-release version if the post-release version is not available.
///
/// ```text
/// post-equal-not-available
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a==1.2.3.post0
/// │ └── unsatisfied: no matching version
/// └── a
/// ├── a-1.2.3
/// └── a-1.2.3.post1
/// ```
#[test]
fn post_equal_not_available() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"post-equal-not-available-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("post-equal-not-available-a==1.2.3.post0")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because there is no version of package-a==1.2.3.post0 and you require package-a==1.2.3.post0, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("post_equal_not_available_a");
}
/// A greater-than-or-equal version constraint should match a post-release version if the constraint is itself a post-release version.
///
/// ```text
/// post-greater-than-or-equal-post
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a>=1.2.3.post0
/// │ ├── satisfied by a-1.2.3.post0
/// │ └── satisfied by a-1.2.3.post1
/// └── a
/// ├── a-1.2.3.post0
/// └── a-1.2.3.post1
/// ```
#[test]
fn post_greater_than_or_equal_post() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"post-greater-than-or-equal-post-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("post-greater-than-or-equal-post-a>=1.2.3.post0")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.2.3.post1
");
// The version '1.2.3.post1' satisfies the constraint '>=1.2.3.post0'.
context.assert_installed("post_greater_than_or_equal_post_a", "1.2.3.post1");
}
/// A greater-than-or-equal version constraint should match a post-release version.
///
/// ```text
/// post-greater-than-or-equal
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a>=1.2.3
/// │ └── satisfied by a-1.2.3.post1
/// └── a
/// └── a-1.2.3.post1
/// ```
#[test]
fn post_greater_than_or_equal() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"post-greater-than-or-equal-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("post-greater-than-or-equal-a>=1.2.3")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.2.3.post1
");
// The version '1.2.3.post1' satisfies the constraint '>=1.2.3'.
context.assert_installed("post_greater_than_or_equal_a", "1.2.3.post1");
}
/// A greater-than version constraint should not match a post-release version if the post-release version is not available.
///
/// ```text
/// post-greater-than-post-not-available
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a>1.2.3.post2
/// │ └── unsatisfied: no matching version
/// └── a
/// ├── a-1.2.3
/// ├── a-1.2.3.post0
/// └── a-1.2.3.post1
/// ```
#[test]
fn post_greater_than_post_not_available() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"post-greater-than-post-not-available-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("post-greater-than-post-not-available-a>1.2.3.post2")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a<=1.2.3.post1 is available and you require package-a>=1.2.3.post3, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("post_greater_than_post_not_available_a");
}
/// A greater-than version constraint should match a post-release version if the constraint is itself a post-release version.
///
/// ```text
/// post-greater-than-post
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a>1.2.3.post0
/// │ └── satisfied by a-1.2.3.post1
/// └── a
/// ├── a-1.2.3.post0
/// └── a-1.2.3.post1
/// ```
#[test]
fn post_greater_than_post() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"post-greater-than-post-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("post-greater-than-post-a>1.2.3.post0")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.2.3.post1
");
// The version '1.2.3.post1' satisfies the constraint '>1.2.3.post0'.
context.assert_installed("post_greater_than_post_a", "1.2.3.post1");
}
/// A greater-than version constraint should not match a post-release version.
///
/// ```text
/// post-greater-than
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a>1.2.3
/// │ └── unsatisfied: no matching version
/// └── a
/// └── a-1.2.3.post1
/// ```
#[test]
fn post_greater_than() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"post-greater-than-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("post-greater-than-a>1.2.3")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a==1.2.3.post1 is available and you require package-a>1.2.3, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("post_greater_than_a");
}
/// A less-than-or-equal version constraint should not match a post-release version.
///
/// ```text
/// post-less-than-or-equal
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a<=1.2.3
/// │ └── unsatisfied: no matching version
/// └── a
/// └── a-1.2.3.post1
/// ```
#[test]
fn post_less_than_or_equal() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"post-less-than-or-equal-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("post-less-than-or-equal-a<=1.2.3")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a==1.2.3.post1 is available and you require package-a<=1.2.3, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("post_less_than_or_equal_a");
}
/// A less-than version constraint should not match a post-release version.
///
/// ```text
/// post-less-than
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a<1.2.3
/// │ └── unsatisfied: no matching version
/// └── a
/// └── a-1.2.3.post1
/// ```
#[test]
fn post_less_than() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"post-less-than-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("post-less-than-a<1.2.3")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a==1.2.3.post1 is available and you require package-a<1.2.3, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("post_less_than_a");
}
/// A greater-than version constraint should not match a post-release version with a local version identifier.
///
/// ```text
/// post-local-greater-than-post
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a>1.2.3.post1
/// │ └── unsatisfied: no matching version
/// └── a
/// ├── a-1.2.3.post1
/// └── a-1.2.3.post1+local
/// ```
#[test]
fn post_local_greater_than_post() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"post-local-greater-than-post-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("post-local-greater-than-post-a>1.2.3.post1")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a<=1.2.3.post1+local is available and you require package-a>=1.2.3.post2, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("post_local_greater_than_post_a");
}
/// A greater-than version constraint should not match a post-release version with a local version identifier.
///
/// ```text
/// post-local-greater-than
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a>1.2.3
/// │ └── unsatisfied: no matching version
/// └── a
/// ├── a-1.2.3.post1
/// └── a-1.2.3.post1+local
/// ```
#[test]
fn post_local_greater_than() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"post-local-greater-than-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("post-local-greater-than-a>1.2.3")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a<=1.2.3.post1+local is available and you require package-a>1.2.3, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("post_local_greater_than_a");
}
/// A simple version constraint should not match a post-release version.
///
/// ```text
/// post-simple
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a==1.2.3
/// │ └── unsatisfied: no matching version
/// └── a
/// └── a-1.2.3.post1
/// ```
#[test]
fn post_simple() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"post-simple-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("post-simple-a==1.2.3")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because there is no version of package-a==1.2.3 and you require package-a==1.2.3, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("post_simple_a");
}
/// The user requires `a` which has multiple prereleases available with different labels.
///
/// ```text
/// package-multiple-prereleases-kinds
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a>=1.0.0a1
/// │ ├── satisfied by a-1.0.0a1
/// │ ├── satisfied by a-1.0.0b1
/// │ └── satisfied by a-1.0.0rc1
/// └── a
/// ├── a-1.0.0a1
/// ├── a-1.0.0b1
/// └── a-1.0.0rc1
/// ```
#[test]
fn package_multiple_prereleases_kinds() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"package-multiple-prereleases-kinds-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("package-multiple-prereleases-kinds-a>=1.0.0a1")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.0.0rc1
");
// Release candidates should be the highest precedence prerelease kind.
context.assert_installed("package_multiple_prereleases_kinds_a", "1.0.0rc1");
}
/// The user requires `a` which has multiple alphas available.
///
/// ```text
/// package-multiple-prereleases-numbers
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a>=1.0.0a1
/// │ ├── satisfied by a-1.0.0a1
/// │ ├── satisfied by a-1.0.0a2
/// │ └── satisfied by a-1.0.0a3
/// └── a
/// ├── a-1.0.0a1
/// ├── a-1.0.0a2
/// └── a-1.0.0a3
/// ```
#[test]
fn package_multiple_prereleases_numbers() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"package-multiple-prereleases-numbers-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("package-multiple-prereleases-numbers-a>=1.0.0a1")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.0.0a3
");
// The latest alpha version should be selected.
context.assert_installed("package_multiple_prereleases_numbers_a", "1.0.0a3");
}
/// The user requires a non-prerelease version of `a` which only has prerelease versions available. There are pre-releases on the boundary of their range.
///
/// ```text
/// package-only-prereleases-boundary
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a<0.2.0
/// │ └── unsatisfied: no matching version
/// └── a
/// ├── a-0.1.0a1
/// ├── a-0.2.0a1
/// └── a-0.3.0a1
/// ```
#[test]
fn package_only_prereleases_boundary() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"package-only-prereleases-boundary-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("package-only-prereleases-boundary-a<0.2.0")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==0.1.0a1
");
// Since there are only prerelease versions of `a` available, a prerelease is allowed. Since the user did not explicitly request a pre-release, pre-releases at the boundary should not be selected.
context.assert_installed("package_only_prereleases_boundary_a", "0.1.0a1");
}
/// The user requires a version of package `a` which only matches prerelease versions but they did not include a prerelease specifier.
///
/// ```text
/// package-only-prereleases-in-range
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a>0.1.0
/// │ └── unsatisfied: no matching version
/// └── a
/// ├── a-0.1.0
/// └── a-1.0.0a1
/// ```
#[test]
fn package_only_prereleases_in_range() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"package-only-prereleases-in-range-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("package-only-prereleases-in-range-a>0.1.0")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a<0.1.0 is available and you require package-a>0.1.0, we can conclude that your requirements are unsatisfiable.
hint: Pre-releases are available for `package-a` in the requested range (e.g., 1.0.0a1), but pre-releases weren't enabled (try: `--prerelease=allow`)
");
// Since there are stable versions of `a` available, prerelease versions should not be selected without explicit opt-in.
context.assert_not_installed("package_only_prereleases_in_range_a");
}
/// The user requires any version of package `a` which only has prerelease versions available.
///
/// ```text
/// package-only-prereleases
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── unsatisfied: no matching version
/// └── a
/// └── a-1.0.0a1
/// ```
#[test]
fn package_only_prereleases() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"package-only-prereleases-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("package-only-prereleases-a")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.0.0a1
");
// Since there are only prerelease versions of `a` available, it should be installed even though the user did not include a prerelease specifier.
context.assert_installed("package_only_prereleases_a", "1.0.0a1");
}
/// The user requires a version of `a` with a prerelease specifier and both prerelease and stable releases are available.
///
/// ```text
/// package-prerelease-specified-mixed-available
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a>=0.1.0a1
/// │ ├── satisfied by a-0.1.0
/// │ ├── satisfied by a-0.2.0a1
/// │ ├── satisfied by a-0.3.0
/// │ └── satisfied by a-1.0.0a1
/// └── a
/// ├── a-0.1.0
/// ├── a-0.2.0a1
/// ├── a-0.3.0
/// └── a-1.0.0a1
/// ```
#[test]
fn package_prerelease_specified_mixed_available() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"package-prerelease-specified-mixed-available-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("package-prerelease-specified-mixed-available-a>=0.1.0a1")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.0.0a1
");
// Since the user provided a prerelease specifier, the latest prerelease version should be selected.
context.assert_installed("package_prerelease_specified_mixed_available_a", "1.0.0a1");
}
/// The user requires a version of `a` with a prerelease specifier and only stable releases are available.
///
/// ```text
/// package-prerelease-specified-only-final-available
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a>=0.1.0a1
/// │ ├── satisfied by a-0.1.0
/// │ ├── satisfied by a-0.2.0
/// │ └── satisfied by a-0.3.0
/// └── a
/// ├── a-0.1.0
/// ├── a-0.2.0
/// └── a-0.3.0
/// ```
#[test]
fn package_prerelease_specified_only_final_available() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((
r"package-prerelease-specified-only-final-available-",
"package-",
));
uv_snapshot!(filters, command(&context)
.arg("package-prerelease-specified-only-final-available-a>=0.1.0a1")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==0.3.0
");
// The latest stable version should be selected.
context.assert_installed(
"package_prerelease_specified_only_final_available_a",
"0.3.0",
);
}
/// The user requires a version of `a` with a prerelease specifier and only prerelease releases are available.
///
/// ```text
/// package-prerelease-specified-only-prerelease-available
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a>=0.1.0a1
/// │ ├── satisfied by a-0.1.0a1
/// │ ├── satisfied by a-0.2.0a1
/// │ └── satisfied by a-0.3.0a1
/// └── a
/// ├── a-0.1.0a1
/// ├── a-0.2.0a1
/// └── a-0.3.0a1
/// ```
#[test]
fn package_prerelease_specified_only_prerelease_available() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((
r"package-prerelease-specified-only-prerelease-available-",
"package-",
));
uv_snapshot!(filters, command(&context)
.arg("package-prerelease-specified-only-prerelease-available-a>=0.1.0a1")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==0.3.0a1
");
// The latest prerelease version should be selected.
context.assert_installed(
"package_prerelease_specified_only_prerelease_available_a",
"0.3.0a1",
);
}
/// The user requires a non-prerelease version of `a` but has enabled pre-releases. There are pre-releases on the boundary of their range.
///
/// ```text
/// package-prereleases-boundary
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a<0.2.0
/// │ └── satisfied by a-0.1.0
/// └── a
/// ├── a-0.1.0
/// ├── a-0.2.0a1
/// └── a-0.3.0
/// ```
#[test]
fn package_prereleases_boundary() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"package-prereleases-boundary-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("--prerelease=allow")
.arg("package-prereleases-boundary-a<0.2.0")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==0.1.0
");
// Since the user did not use a pre-release specifier, pre-releases at the boundary should not be selected even though pre-releases are allowed.
context.assert_installed("package_prereleases_boundary_a", "0.1.0");
}
/// The user requires a non-prerelease version of `a` but has enabled pre-releases. There are pre-releases on the boundary of their range.
///
/// ```text
/// package-prereleases-global-boundary
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a<0.2.0
/// │ └── satisfied by a-0.1.0
/// └── a
/// ├── a-0.1.0
/// ├── a-0.2.0a1
/// └── a-0.3.0
/// ```
#[test]
fn package_prereleases_global_boundary() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"package-prereleases-global-boundary-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("--prerelease=allow")
.arg("package-prereleases-global-boundary-a<0.2.0")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==0.1.0
");
// Since the user did not use a pre-release specifier, pre-releases at the boundary should not be selected even though pre-releases are allowed.
context.assert_installed("package_prereleases_global_boundary_a", "0.1.0");
}
/// The user requires a prerelease version of `a`. There are pre-releases on the boundary of their range.
///
/// ```text
/// package-prereleases-specifier-boundary
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a<0.2.0a2
/// │ ├── satisfied by a-0.1.0
/// │ └── satisfied by a-0.2.0a1
/// └── a
/// ├── a-0.1.0
/// ├── a-0.2.0
/// ├── a-0.2.0a1
/// ├── a-0.2.0a2
/// ├── a-0.2.0a3
/// └── a-0.3.0
/// ```
#[test]
fn package_prereleases_specifier_boundary() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"package-prereleases-specifier-boundary-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("package-prereleases-specifier-boundary-a<0.2.0a2")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==0.2.0a1
");
// Since the user used a pre-release specifier, pre-releases at the boundary should be selected.
context.assert_installed("package_prereleases_specifier_boundary_a", "0.2.0a1");
}
/// The user requires a version of package `a` which only matches prerelease versions. They did not include a prerelease specifier for the package, but they opted into prereleases globally.
///
/// ```text
/// requires-package-only-prereleases-in-range-global-opt-in
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a>0.1.0
/// │ └── unsatisfied: no matching version
/// └── a
/// ├── a-0.1.0
/// └── a-1.0.0a1
/// ```
#[test]
fn requires_package_only_prereleases_in_range_global_opt_in() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((
r"requires-package-only-prereleases-in-range-global-opt-in-",
"package-",
));
uv_snapshot!(filters, command(&context)
.arg("--prerelease=allow")
.arg("requires-package-only-prereleases-in-range-global-opt-in-a>0.1.0")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.0.0a1
");
context.assert_installed(
"requires_package_only_prereleases_in_range_global_opt_in_a",
"1.0.0a1",
);
}
/// The user requires any version of package `a` has a prerelease version available and an older non-prerelease version.
///
/// ```text
/// requires-package-prerelease-and-final-any
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── satisfied by a-0.1.0
/// └── a
/// ├── a-0.1.0
/// └── a-1.0.0a1
/// ```
#[test]
fn requires_package_prerelease_and_final_any() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"requires-package-prerelease-and-final-any-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("requires-package-prerelease-and-final-any-a")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==0.1.0
");
// Since the user did not provide a prerelease specifier, the older stable version should be selected.
context.assert_installed("requires_package_prerelease_and_final_any_a", "0.1.0");
}
/// The user requires package `a` which has a dependency on a package which only matches prerelease versions; the user has opted into allowing prereleases in `b` explicitly.
///
/// ```text
/// transitive-package-only-prereleases-in-range-opt-in
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a
/// │ │ └── satisfied by a-0.1.0
/// │ └── requires b>0.0.0a1
/// │ ├── satisfied by b-0.1.0
/// │ └── satisfied by b-1.0.0a1
/// ├── a
/// │ └── a-0.1.0
/// │ └── requires b>0.1
/// │ └── unsatisfied: no matching version
/// └── b
/// ├── b-0.1.0
/// └── b-1.0.0a1
/// ```
#[test]
fn transitive_package_only_prereleases_in_range_opt_in() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((
r"transitive-package-only-prereleases-in-range-opt-in-",
"package-",
));
uv_snapshot!(filters, command(&context)
.arg("transitive-package-only-prereleases-in-range-opt-in-a")
.arg("transitive-package-only-prereleases-in-range-opt-in-b>0.0.0a1")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ package-a==0.1.0
+ package-b==1.0.0a1
");
// Since the user included a dependency on `b` with a prerelease specifier, a prerelease version can be selected.
context.assert_installed(
"transitive_package_only_prereleases_in_range_opt_in_a",
"0.1.0",
);
context.assert_installed(
"transitive_package_only_prereleases_in_range_opt_in_b",
"1.0.0a1",
);
}
/// The user requires package `a` which has a dependency on a package which only matches prerelease versions but they did not include a prerelease specifier.
///
/// ```text
/// transitive-package-only-prereleases-in-range
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── satisfied by a-0.1.0
/// ├── a
/// │ └── a-0.1.0
/// │ └── requires b>0.1
/// │ └── unsatisfied: no matching version
/// └── b
/// ├── b-0.1.0
/// └── b-1.0.0a1
/// ```
#[test]
fn transitive_package_only_prereleases_in_range() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"transitive-package-only-prereleases-in-range-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("transitive-package-only-prereleases-in-range-a")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-b<0.1 is available and package-a==0.1.0 depends on package-b>0.1, we can conclude that package-a==0.1.0 cannot be used.
And because only package-a==0.1.0 is available and you require package-a, we can conclude that your requirements are unsatisfiable.
hint: Pre-releases are available for `package-b` in the requested range (e.g., 1.0.0a1), but pre-releases weren't enabled (try: `--prerelease=allow`)
");
// Since there are stable versions of `b` available, the prerelease version should not be selected without explicit opt-in. The available version is excluded by the range requested by the user.
context.assert_not_installed("transitive_package_only_prereleases_in_range_a");
}
/// The user requires any version of package `a` which requires `b` which only has prerelease versions available.
///
/// ```text
/// transitive-package-only-prereleases
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── satisfied by a-0.1.0
/// ├── a
/// │ └── a-0.1.0
/// │ └── requires b
/// │ └── unsatisfied: no matching version
/// └── b
/// └── b-1.0.0a1
/// ```
#[test]
fn transitive_package_only_prereleases() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"transitive-package-only-prereleases-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("transitive-package-only-prereleases-a")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ package-a==0.1.0
+ package-b==1.0.0a1
");
// Since there are only prerelease versions of `b` available, it should be selected even though the user did not opt-in to prereleases.
context.assert_installed("transitive_package_only_prereleases_a", "0.1.0");
context.assert_installed("transitive_package_only_prereleases_b", "1.0.0a1");
}
/// A transitive dependency has both a prerelease and a stable selector, but can only be satisfied by a prerelease. There are many prerelease versions and some are excluded.
///
/// ```text
/// transitive-prerelease-and-stable-dependency-many-versions-holes
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a
/// │ │ └── satisfied by a-1.0.0
/// │ └── requires b
/// │ └── satisfied by b-1.0.0
/// ├── a
/// │ └── a-1.0.0
/// │ └── requires c!=2.0.0a5,!=2.0.0a6,!=2.0.0a7,!=2.0.0b1,<2.0.0b5,>1.0.0
/// │ ├── satisfied by c-2.0.0a1
/// │ ├── satisfied by c-2.0.0a2
/// │ ├── satisfied by c-2.0.0a3
/// │ ├── satisfied by c-2.0.0a4
/// │ ├── satisfied by c-2.0.0a8
/// │ ├── satisfied by c-2.0.0a9
/// │ ├── satisfied by c-2.0.0b2
/// │ ├── satisfied by c-2.0.0b3
/// │ └── satisfied by c-2.0.0b4
/// ├── b
/// │ └── b-1.0.0
/// │ └── requires c<=3.0.0,>=1.0.0
/// │ └── satisfied by c-1.0.0
/// └── c
/// ├── c-1.0.0
/// ├── c-2.0.0a1
/// ├── c-2.0.0a2
/// ├── c-2.0.0a3
/// ├── c-2.0.0a4
/// ├── c-2.0.0a5
/// ├── c-2.0.0a6
/// ├── c-2.0.0a7
/// ├── c-2.0.0a8
/// ├── c-2.0.0a9
/// ├── c-2.0.0b1
/// ├── c-2.0.0b2
/// ├── c-2.0.0b3
/// ├── c-2.0.0b4
/// ├── c-2.0.0b5
/// ├── c-2.0.0b6
/// ├── c-2.0.0b7
/// ├── c-2.0.0b8
/// └── c-2.0.0b9
/// ```
#[test]
fn transitive_prerelease_and_stable_dependency_many_versions_holes() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((
r"transitive-prerelease-and-stable-dependency-many-versions-holes-",
"package-",
));
uv_snapshot!(filters, command(&context)
.arg("transitive-prerelease-and-stable-dependency-many-versions-holes-a")
.arg("transitive-prerelease-and-stable-dependency-many-versions-holes-b")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only the following versions of package-c are available:
package-c<1.0.0
package-c>=2.0.0a5,<=2.0.0a7
package-c==2.0.0b1
package-c>=2.0.0b5
and package-a==1.0.0 depends on one of:
package-c>1.0.0,<2.0.0a5
package-c>2.0.0a7,<2.0.0b1
package-c>2.0.0b1,<2.0.0b5
we can conclude that package-a==1.0.0 cannot be used.
And because only package-a==1.0.0 is available and you require package-a, we can conclude that your requirements are unsatisfiable.
hint: `package-c` was requested with a pre-release marker (e.g., all of:
package-c>1.0.0,<2.0.0a5
package-c>2.0.0a7,<2.0.0b1
package-c>2.0.0b1,<2.0.0b5
), but pre-releases weren't enabled (try: `--prerelease=allow`)
");
// Since the user did not explicitly opt-in to a prerelease, it cannot be selected.
context
.assert_not_installed("transitive_prerelease_and_stable_dependency_many_versions_holes_a");
context
.assert_not_installed("transitive_prerelease_and_stable_dependency_many_versions_holes_b");
}
/// A transitive dependency has both a prerelease and a stable selector, but can only be satisfied by a prerelease. There are many prerelease versions.
///
/// ```text
/// transitive-prerelease-and-stable-dependency-many-versions
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a
/// │ │ └── satisfied by a-1.0.0
/// │ └── requires b
/// │ └── satisfied by b-1.0.0
/// ├── a
/// │ └── a-1.0.0
/// │ └── requires c>=2.0.0b1
/// │ ├── satisfied by c-2.0.0b1
/// │ ├── satisfied by c-2.0.0b2
/// │ ├── satisfied by c-2.0.0b3
/// │ ├── satisfied by c-2.0.0b4
/// │ ├── satisfied by c-2.0.0b5
/// │ ├── satisfied by c-2.0.0b6
/// │ ├── satisfied by c-2.0.0b7
/// │ ├── satisfied by c-2.0.0b8
/// │ └── satisfied by c-2.0.0b9
/// ├── b
/// │ └── b-1.0.0
/// │ └── requires c<=3.0.0,>=1.0.0
/// │ └── satisfied by c-1.0.0
/// └── c
/// ├── c-1.0.0
/// ├── c-2.0.0a1
/// ├── c-2.0.0a2
/// ├── c-2.0.0a3
/// ├── c-2.0.0a4
/// ├── c-2.0.0a5
/// ├── c-2.0.0a6
/// ├── c-2.0.0a7
/// ├── c-2.0.0a8
/// ├── c-2.0.0a9
/// ├── c-2.0.0b1
/// ├── c-2.0.0b2
/// ├── c-2.0.0b3
/// ├── c-2.0.0b4
/// ├── c-2.0.0b5
/// ├── c-2.0.0b6
/// ├── c-2.0.0b7
/// ├── c-2.0.0b8
/// └── c-2.0.0b9
/// ```
#[test]
fn transitive_prerelease_and_stable_dependency_many_versions() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((
r"transitive-prerelease-and-stable-dependency-many-versions-",
"package-",
));
uv_snapshot!(filters, command(&context)
.arg("transitive-prerelease-and-stable-dependency-many-versions-a")
.arg("transitive-prerelease-and-stable-dependency-many-versions-b")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 depends on package-c>=2.0.0b1, we can conclude that all versions of package-a depend on package-c>=2.0.0b1.
And because only package-c<2.0.0b1 is available, we can conclude that all versions of package-a depend on package-c>3.0.0.
And because package-b==1.0.0 depends on package-c and only package-b==1.0.0 is available, we can conclude that all versions of package-a and all versions of package-b are incompatible.
And because you require package-a and package-b, we can conclude that your requirements are unsatisfiable.
hint: `package-c` was requested with a pre-release marker (e.g., package-c>=2.0.0b1), but pre-releases weren't enabled (try: `--prerelease=allow`)
");
// Since the user did not explicitly opt-in to a prerelease, it cannot be selected.
context.assert_not_installed("transitive_prerelease_and_stable_dependency_many_versions_a");
context.assert_not_installed("transitive_prerelease_and_stable_dependency_many_versions_b");
}
/// A transitive dependency has both a prerelease and a stable selector, but can only be satisfied by a prerelease. The user includes an opt-in to prereleases of the transitive dependency.
///
/// ```text
/// transitive-prerelease-and-stable-dependency-opt-in
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a
/// │ │ └── satisfied by a-1.0.0
/// │ ├── requires b
/// │ │ └── satisfied by b-1.0.0
/// │ └── requires c>=0.0.0a1
/// │ ├── satisfied by c-1.0.0
/// │ └── satisfied by c-2.0.0b1
/// ├── a
/// │ └── a-1.0.0
/// │ └── requires c==2.0.0b1
/// │ └── satisfied by c-2.0.0b1
/// ├── b
/// │ └── b-1.0.0
/// │ └── requires c<=3.0.0,>=1.0.0
/// │ └── satisfied by c-1.0.0
/// └── c
/// ├── c-1.0.0
/// └── c-2.0.0b1
/// ```
#[test]
fn transitive_prerelease_and_stable_dependency_opt_in() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((
r"transitive-prerelease-and-stable-dependency-opt-in-",
"package-",
));
uv_snapshot!(filters, command(&context)
.arg("transitive-prerelease-and-stable-dependency-opt-in-a")
.arg("transitive-prerelease-and-stable-dependency-opt-in-b")
.arg("transitive-prerelease-and-stable-dependency-opt-in-c>=0.0.0a1")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
Prepared 3 packages in [TIME]
Installed 3 packages in [TIME]
+ package-a==1.0.0
+ package-b==1.0.0
+ package-c==2.0.0b1
");
// Since the user explicitly opted-in to a prerelease for `c`, it can be installed.
context.assert_installed(
"transitive_prerelease_and_stable_dependency_opt_in_a",
"1.0.0",
);
context.assert_installed(
"transitive_prerelease_and_stable_dependency_opt_in_b",
"1.0.0",
);
context.assert_installed(
"transitive_prerelease_and_stable_dependency_opt_in_c",
"2.0.0b1",
);
}
/// A transitive dependency has both a prerelease and a stable selector, but can only be satisfied by a prerelease
///
/// ```text
/// transitive-prerelease-and-stable-dependency
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a
/// │ │ └── satisfied by a-1.0.0
/// │ └── requires b
/// │ └── satisfied by b-1.0.0
/// ├── a
/// │ └── a-1.0.0
/// │ └── requires c==2.0.0b1
/// │ └── satisfied by c-2.0.0b1
/// ├── b
/// │ └── b-1.0.0
/// │ └── requires c<=3.0.0,>=1.0.0
/// │ └── satisfied by c-1.0.0
/// └── c
/// ├── c-1.0.0
/// └── c-2.0.0b1
/// ```
#[test]
fn transitive_prerelease_and_stable_dependency() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"transitive-prerelease-and-stable-dependency-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("transitive-prerelease-and-stable-dependency-a")
.arg("transitive-prerelease-and-stable-dependency-b")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because there is no version of package-c==2.0.0b1 and package-a==1.0.0 depends on package-c==2.0.0b1, we can conclude that package-a==1.0.0 cannot be used.
And because only package-a==1.0.0 is available and you require package-a, we can conclude that your requirements are unsatisfiable.
hint: `package-c` was requested with a pre-release marker (e.g., package-c==2.0.0b1), but pre-releases weren't enabled (try: `--prerelease=allow`)
");
// Since the user did not explicitly opt-in to a prerelease, it cannot be selected.
context.assert_not_installed("transitive_prerelease_and_stable_dependency_a");
context.assert_not_installed("transitive_prerelease_and_stable_dependency_b");
}
/// The user requires a package where recent versions require a Python version greater than the current version, but an older version is compatible.
///
/// ```text
/// python-greater-than-current-backtrack
/// ├── environment
/// │ └── python3.9
/// ├── root
/// │ └── requires a
/// │ ├── satisfied by a-1.0.0
/// │ ├── satisfied by a-2.0.0
/// │ ├── satisfied by a-3.0.0
/// │ └── satisfied by a-4.0.0
/// └── a
/// ├── a-1.0.0
/// ├── a-2.0.0
/// │ └── requires python>=3.10 (incompatible with environment)
/// ├── a-3.0.0
/// │ └── requires python>=3.11 (incompatible with environment)
/// └── a-4.0.0
/// └── requires python>=3.12 (incompatible with environment)
/// ```
#[test]
fn python_greater_than_current_backtrack() {
let context = TestContext::new("3.9");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"python-greater-than-current-backtrack-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("python-greater-than-current-backtrack-a")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.0.0
");
context.assert_installed("python_greater_than_current_backtrack_a", "1.0.0");
}
/// The user requires a package where recent versions require a Python version greater than the current version, but an excluded older version is compatible.
///
/// ```text
/// python-greater-than-current-excluded
/// ├── environment
/// │ └── python3.9
/// ├── root
/// │ └── requires a>=2.0.0
/// │ ├── satisfied by a-2.0.0
/// │ ├── satisfied by a-3.0.0
/// │ └── satisfied by a-4.0.0
/// └── a
/// ├── a-1.0.0
/// ├── a-2.0.0
/// │ └── requires python>=3.10 (incompatible with environment)
/// ├── a-3.0.0
/// │ └── requires python>=3.11 (incompatible with environment)
/// └── a-4.0.0
/// └── requires python>=3.12 (incompatible with environment)
/// ```
#[test]
fn python_greater_than_current_excluded() {
let context = TestContext::new("3.9");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"python-greater-than-current-excluded-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("python-greater-than-current-excluded-a>=2.0.0")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because the current Python version (3.9.[X]) does not satisfy Python>=3.10 and package-a==2.0.0 depends on Python>=3.10, we can conclude that package-a==2.0.0 cannot be used.
And because only the following versions of package-a are available:
package-a<=2.0.0
package-a==3.0.0
package-a==4.0.0
we can conclude that package-a>=2.0.0,<3.0.0 cannot be used. (1)
Because the current Python version (3.9.[X]) does not satisfy Python>=3.11 and package-a==3.0.0 depends on Python>=3.11, we can conclude that package-a==3.0.0 cannot be used.
And because we know from (1) that package-a>=2.0.0,<3.0.0 cannot be used, we can conclude that package-a>=2.0.0,<4.0.0 cannot be used. (2)
Because the current Python version (3.9.[X]) does not satisfy Python>=3.12 and package-a==4.0.0 depends on Python>=3.12, we can conclude that package-a==4.0.0 cannot be used.
And because we know from (2) that package-a>=2.0.0,<4.0.0 cannot be used, we can conclude that package-a>=2.0.0 cannot be used.
And because you require package-a>=2.0.0, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("python_greater_than_current_excluded_a");
}
/// The user requires a package which has many versions which all require a Python version greater than the current version
///
/// ```text
/// python-greater-than-current-many
/// ├── environment
/// │ └── python3.9
/// ├── root
/// │ └── requires a==1.0.0
/// │ └── unsatisfied: no matching version
/// └── a
/// ├── a-2.0.0
/// │ └── requires python>=3.10 (incompatible with environment)
/// ├── a-2.1.0
/// │ └── requires python>=3.10 (incompatible with environment)
/// ├── a-2.2.0
/// │ └── requires python>=3.10 (incompatible with environment)
/// ├── a-2.3.0
/// │ └── requires python>=3.10 (incompatible with environment)
/// ├── a-2.4.0
/// │ └── requires python>=3.10 (incompatible with environment)
/// ├── a-2.5.0
/// │ └── requires python>=3.10 (incompatible with environment)
/// ├── a-3.0.0
/// │ └── requires python>=3.11 (incompatible with environment)
/// ├── a-3.1.0
/// │ └── requires python>=3.11 (incompatible with environment)
/// ├── a-3.2.0
/// │ └── requires python>=3.11 (incompatible with environment)
/// ├── a-3.3.0
/// │ └── requires python>=3.11 (incompatible with environment)
/// ├── a-3.4.0
/// │ └── requires python>=3.11 (incompatible with environment)
/// └── a-3.5.0
/// └── requires python>=3.11 (incompatible with environment)
/// ```
#[test]
fn python_greater_than_current_many() {
let context = TestContext::new("3.9");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"python-greater-than-current-many-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("python-greater-than-current-many-a==1.0.0")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because there is no version of package-a==1.0.0 and you require package-a==1.0.0, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("python_greater_than_current_many_a");
}
/// The user requires a package which requires a Python version with a patch version greater than the current patch version
///
/// ```text
/// python-greater-than-current-patch
/// ├── environment
/// │ └── python3.13.0
/// ├── root
/// │ └── requires a==1.0.0
/// │ └── satisfied by a-1.0.0
/// └── a
/// └── a-1.0.0
/// └── requires python>=3.13.2 (incompatible with environment)
/// ```
#[cfg(feature = "python-patch")]
#[test]
fn python_greater_than_current_patch() {
let context = TestContext::new("3.13.0");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"python-greater-than-current-patch-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("python-greater-than-current-patch-a==1.0.0")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because the current Python version (3.13) does not satisfy Python>=3.13.2 and package-a==1.0.0 depends on Python>=3.13.2, we can conclude that package-a==1.0.0 cannot be used.
And because you require package-a==1.0.0, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("python_greater_than_current_patch_a");
}
/// The user requires a package which requires a Python version greater than the current version
///
/// ```text
/// python-greater-than-current
/// ├── environment
/// │ └── python3.9
/// ├── root
/// │ └── requires a==1.0.0
/// │ └── satisfied by a-1.0.0
/// └── a
/// └── a-1.0.0
/// └── requires python>=3.10 (incompatible with environment)
/// ```
#[test]
fn python_greater_than_current() {
let context = TestContext::new("3.9");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"python-greater-than-current-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("python-greater-than-current-a==1.0.0")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because the current Python version (3.9.[X]) does not satisfy Python>=3.10 and package-a==1.0.0 depends on Python>=3.10, we can conclude that package-a==1.0.0 cannot be used.
And because you require package-a==1.0.0, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("python_greater_than_current_a");
}
/// The user requires a package which requires a Python version less than the current version
///
/// ```text
/// python-less-than-current
/// ├── environment
/// │ └── python3.9
/// ├── root
/// │ └── requires a==1.0.0
/// │ └── satisfied by a-1.0.0
/// └── a
/// └── a-1.0.0
/// └── requires python<=3.8 (incompatible with environment)
/// ```
#[test]
fn python_less_than_current() {
let context = TestContext::new("3.9");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"python-less-than-current-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("python-less-than-current-a==1.0.0")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.0.0
");
// We ignore the upper bound on Python requirements
}
/// The user requires a package which requires a Python version that does not exist
///
/// ```text
/// python-version-does-not-exist
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a==1.0.0
/// │ └── satisfied by a-1.0.0
/// └── a
/// └── a-1.0.0
/// └── requires python>=3.30 (incompatible with environment)
/// ```
#[test]
fn python_version_does_not_exist() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"python-version-does-not-exist-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("python-version-does-not-exist-a==1.0.0")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because the current Python version (3.12.[X]) does not satisfy Python>=3.30 and package-a==1.0.0 depends on Python>=3.30, we can conclude that package-a==1.0.0 cannot be used.
And because you require package-a==1.0.0, we can conclude that your requirements are unsatisfiable.
");
context.assert_not_installed("python_version_does_not_exist_a");
}
/// Both wheels and source distributions are available, and the user has disabled binaries.
///
/// ```text
/// no-binary
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── satisfied by a-1.0.0
/// └── a
/// └── a-1.0.0
/// ```
#[test]
fn no_binary() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"no-binary-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("--no-binary")
.arg("no-binary-a")
.arg("no-binary-a")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.0.0
");
// The source distribution should be used for install
}
/// Both wheels and source distributions are available, and the user has disabled builds.
///
/// ```text
/// no-build
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── satisfied by a-1.0.0
/// └── a
/// └── a-1.0.0
/// ```
#[test]
fn no_build() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"no-build-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("--only-binary")
.arg("no-build-a")
.arg("no-build-a")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.0.0
");
// The wheel should be used for install
}
/// No wheels with matching ABI tags are available, nor are any source distributions available
///
/// ```text
/// no-sdist-no-wheels-with-matching-abi
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── satisfied by a-1.0.0
/// └── a
/// └── a-1.0.0
/// ```
#[test]
fn no_sdist_no_wheels_with_matching_abi() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"no-sdist-no-wheels-with-matching-abi-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("--python-platform=x86_64-manylinux2014")
.arg("no-sdist-no-wheels-with-matching-abi-a")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no wheels with a matching Python ABI tag (e.g., `cp312`), we can conclude that all versions of package-a cannot be used.
And because you require package-a, we can conclude that your requirements are unsatisfiable.
hint: You require CPython 3.12 (`cp312`), but we only found wheels for `package-a` (v1.0.0) with the following Python ABI tag: `graalpy240_310_native`
");
context.assert_not_installed("no_sdist_no_wheels_with_matching_abi_a");
}
/// No wheels with matching platform tags are available, nor are any source distributions available
///
/// ```text
/// no-sdist-no-wheels-with-matching-platform
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── satisfied by a-1.0.0
/// └── a
/// └── a-1.0.0
/// ```
#[test]
fn no_sdist_no_wheels_with_matching_platform() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"no-sdist-no-wheels-with-matching-platform-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("--python-platform=x86_64-manylinux2014")
.arg("no-sdist-no-wheels-with-matching-platform-a")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no wheels with a matching platform tag (e.g., `manylinux_2_17_x86_64`), we can conclude that all versions of package-a cannot be used.
And because you require package-a, we can conclude that your requirements are unsatisfiable.
hint: Wheels are available for `package-a` (v1.0.0) on the following platform: `macosx_10_0_ppc64`
");
context.assert_not_installed("no_sdist_no_wheels_with_matching_platform_a");
}
/// No wheels with matching Python tags are available, nor are any source distributions available
///
/// ```text
/// no-sdist-no-wheels-with-matching-python
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── satisfied by a-1.0.0
/// └── a
/// └── a-1.0.0
/// ```
#[test]
fn no_sdist_no_wheels_with_matching_python() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"no-sdist-no-wheels-with-matching-python-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("--python-platform=x86_64-manylinux2014")
.arg("no-sdist-no-wheels-with-matching-python-a")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no wheels with a matching Python implementation tag (e.g., `cp312`), we can conclude that all versions of package-a cannot be used.
And because you require package-a, we can conclude that your requirements are unsatisfiable.
hint: You require CPython 3.12 (`cp312`), but we only found wheels for `package-a` (v1.0.0) with the following Python implementation tag: `graalpy310`
");
context.assert_not_installed("no_sdist_no_wheels_with_matching_python_a");
}
/// No wheels are available, only source distributions but the user has disabled builds.
///
/// ```text
/// no-wheels-no-build
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── satisfied by a-1.0.0
/// └── a
/// └── a-1.0.0
/// ```
#[test]
fn no_wheels_no_build() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"no-wheels-no-build-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("--only-binary")
.arg("no-wheels-no-build-a")
.arg("no-wheels-no-build-a")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no usable wheels, we can conclude that all versions of package-a cannot be used.
And because you require package-a, we can conclude that your requirements are unsatisfiable.
hint: Wheels are required for `package-a` because building from source is disabled for `package-a` (i.e., with `--no-build-package package-a`)
");
context.assert_not_installed("no_wheels_no_build_a");
}
/// No wheels with matching platform tags are available, just source distributions.
///
/// ```text
/// no-wheels-with-matching-platform
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── satisfied by a-1.0.0
/// └── a
/// └── a-1.0.0
/// ```
#[test]
fn no_wheels_with_matching_platform() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"no-wheels-with-matching-platform-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("no-wheels-with-matching-platform-a")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.0.0
");
}
/// No wheels are available, only source distributions.
///
/// ```text
/// no-wheels
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── satisfied by a-1.0.0
/// └── a
/// └── a-1.0.0
/// ```
#[test]
fn no_wheels() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"no-wheels-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("no-wheels-a")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.0.0
");
}
/// No source distributions are available, only wheels but the user has disabled using pre-built binaries.
///
/// ```text
/// only-wheels-no-binary
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── satisfied by a-1.0.0
/// └── a
/// └── a-1.0.0
/// ```
#[test]
fn only_wheels_no_binary() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"only-wheels-no-binary-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("--no-binary")
.arg("only-wheels-no-binary-a")
.arg("only-wheels-no-binary-a")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no source distribution, we can conclude that all versions of package-a cannot be used.
And because you require package-a, we can conclude that your requirements are unsatisfiable.
hint: A source distribution is required for `package-a` because using pre-built wheels is disabled for `package-a` (i.e., with `--no-binary-package package-a`)
");
context.assert_not_installed("only_wheels_no_binary_a");
}
/// No source distributions are available, only wheels.
///
/// ```text
/// only-wheels
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── satisfied by a-1.0.0
/// └── a
/// └── a-1.0.0
/// ```
#[test]
fn only_wheels() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"only-wheels-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("only-wheels-a")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.0.0
");
}
/// A wheel for a specific platform is available alongside the default.
///
/// ```text
/// specific-tag-and-default
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── satisfied by a-1.0.0
/// └── a
/// └── a-1.0.0
/// ```
#[test]
fn specific_tag_and_default() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"specific-tag-and-default-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("specific-tag-and-default-a")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==1.0.0
");
}
/// The user requires a version of package `a` which only matches yanked versions.
///
/// ```text
/// package-only-yanked-in-range
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a>0.1.0
/// │ └── unsatisfied: no matching version
/// └── a
/// ├── a-0.1.0
/// └── a-1.0.0 (yanked)
/// ```
#[test]
fn package_only_yanked_in_range() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"package-only-yanked-in-range-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("package-only-yanked-in-range-a>0.1.0")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only the following versions of package-a are available:
package-a<0.1.0
package-a==1.0.0
and package-a==1.0.0 was yanked (reason: Yanked for testing), we can conclude that package-a>0.1.0 cannot be used.
And because you require package-a>0.1.0, we can conclude that your requirements are unsatisfiable.
");
// Since there are other versions of `a` available, yanked versions should not be selected without explicit opt-in.
context.assert_not_installed("package_only_yanked_in_range_a");
}
/// The user requires any version of package `a` which only has yanked versions available.
///
/// ```text
/// package-only-yanked
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── unsatisfied: no matching version
/// └── a
/// └── a-1.0.0 (yanked)
/// ```
#[test]
fn package_only_yanked() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"package-only-yanked-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("package-only-yanked-a")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 was yanked (reason: Yanked for testing), we can conclude that all versions of package-a cannot be used.
And because you require package-a, we can conclude that your requirements are unsatisfiable.
");
// Yanked versions should not be installed, even if they are the only one available.
context.assert_not_installed("package_only_yanked_a");
}
/// The user requires any version of `a` and both yanked and unyanked releases are available.
///
/// ```text
/// package-yanked-specified-mixed-available
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a>=0.1.0
/// │ ├── satisfied by a-0.1.0
/// │ └── satisfied by a-0.3.0
/// └── a
/// ├── a-0.1.0
/// ├── a-0.2.0 (yanked)
/// ├── a-0.3.0
/// └── a-1.0.0 (yanked)
/// ```
#[test]
fn package_yanked_specified_mixed_available() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"package-yanked-specified-mixed-available-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("package-yanked-specified-mixed-available-a>=0.1.0")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==0.3.0
");
// The latest unyanked version should be selected.
context.assert_installed("package_yanked_specified_mixed_available_a", "0.3.0");
}
/// The user requires any version of package `a` has a yanked version available and an older unyanked version.
///
/// ```text
/// requires-package-yanked-and-unyanked-any
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── satisfied by a-0.1.0
/// └── a
/// ├── a-0.1.0
/// └── a-1.0.0 (yanked)
/// ```
#[test]
fn requires_package_yanked_and_unyanked_any() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"requires-package-yanked-and-unyanked-any-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("requires-package-yanked-and-unyanked-any-a")
, @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ package-a==0.1.0
");
// The unyanked version should be selected.
context.assert_installed("requires_package_yanked_and_unyanked_any_a", "0.1.0");
}
/// The user requires package `a` which has a dependency on a package which only matches yanked versions; the user has opted into allowing the yanked version of `b` explicitly.
///
/// ```text
/// transitive-package-only-yanked-in-range-opt-in
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a
/// │ │ └── satisfied by a-0.1.0
/// │ └── requires b==1.0.0
/// │ └── unsatisfied: no matching version
/// ├── a
/// │ └── a-0.1.0
/// │ └── requires b>0.1
/// │ └── unsatisfied: no matching version
/// └── b
/// ├── b-0.1.0
/// └── b-1.0.0 (yanked)
/// ```
#[test]
fn transitive_package_only_yanked_in_range_opt_in() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((
r"transitive-package-only-yanked-in-range-opt-in-",
"package-",
));
uv_snapshot!(filters, command(&context)
.arg("transitive-package-only-yanked-in-range-opt-in-a")
.arg("transitive-package-only-yanked-in-range-opt-in-b==1.0.0")
, @r#"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ package-a==0.1.0
+ package-b==1.0.0
warning: `package-b==1.0.0` is yanked (reason: "Yanked for testing")
"#);
// Since the user included a dependency on `b` with an exact specifier, the yanked version can be selected.
context.assert_installed("transitive_package_only_yanked_in_range_opt_in_a", "0.1.0");
context.assert_installed("transitive_package_only_yanked_in_range_opt_in_b", "1.0.0");
}
/// The user requires package `a` which has a dependency on a package which only matches yanked versions.
///
/// ```text
/// transitive-package-only-yanked-in-range
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── satisfied by a-0.1.0
/// ├── a
/// │ └── a-0.1.0
/// │ └── requires b>0.1
/// │ └── unsatisfied: no matching version
/// └── b
/// ├── b-0.1.0
/// └── b-1.0.0 (yanked)
/// ```
#[test]
fn transitive_package_only_yanked_in_range() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"transitive-package-only-yanked-in-range-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("transitive-package-only-yanked-in-range-a")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only the following versions of package-b are available:
package-b<0.1
package-b==1.0.0
and package-b==1.0.0 was yanked (reason: Yanked for testing), we can conclude that package-b>0.1 cannot be used.
And because package-a==0.1.0 depends on package-b>0.1, we can conclude that package-a==0.1.0 cannot be used.
And because only package-a==0.1.0 is available and you require package-a, we can conclude that your requirements are unsatisfiable.
");
// Yanked versions should not be installed, even if they are the only valid version in a range.
context.assert_not_installed("transitive_package_only_yanked_in_range_a");
}
/// The user requires any version of package `a` which requires `b` which only has yanked versions available.
///
/// ```text
/// transitive-package-only-yanked
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ └── requires a
/// │ └── satisfied by a-0.1.0
/// ├── a
/// │ └── a-0.1.0
/// │ └── requires b
/// │ └── unsatisfied: no matching version
/// └── b
/// └── b-1.0.0 (yanked)
/// ```
#[test]
fn transitive_package_only_yanked() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"transitive-package-only-yanked-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("transitive-package-only-yanked-a")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only package-b==1.0.0 is available and package-b==1.0.0 was yanked (reason: Yanked for testing), we can conclude that all versions of package-b cannot be used.
And because package-a==0.1.0 depends on package-b, we can conclude that package-a==0.1.0 cannot be used.
And because only package-a==0.1.0 is available and you require package-a, we can conclude that your requirements are unsatisfiable.
");
// Yanked versions should not be installed, even if they are the only one available.
context.assert_not_installed("transitive_package_only_yanked_a");
}
/// A transitive dependency has both a yanked and an unyanked version, but can only be satisfied by a yanked. The user includes an opt-in to the yanked version of the transitive dependency.
///
/// ```text
/// transitive-yanked-and-unyanked-dependency-opt-in
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a
/// │ │ └── satisfied by a-1.0.0
/// │ ├── requires b
/// │ │ └── satisfied by b-1.0.0
/// │ └── requires c==2.0.0
/// │ └── unsatisfied: no matching version
/// ├── a
/// │ └── a-1.0.0
/// │ └── requires c==2.0.0
/// │ └── unsatisfied: no matching version
/// ├── b
/// │ └── b-1.0.0
/// │ └── requires c<=3.0.0,>=1.0.0
/// │ └── satisfied by c-1.0.0
/// └── c
/// ├── c-1.0.0
/// └── c-2.0.0 (yanked)
/// ```
#[test]
fn transitive_yanked_and_unyanked_dependency_opt_in() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((
r"transitive-yanked-and-unyanked-dependency-opt-in-",
"package-",
));
uv_snapshot!(filters, command(&context)
.arg("transitive-yanked-and-unyanked-dependency-opt-in-a")
.arg("transitive-yanked-and-unyanked-dependency-opt-in-b")
.arg("transitive-yanked-and-unyanked-dependency-opt-in-c==2.0.0")
, @r#"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
Prepared 3 packages in [TIME]
Installed 3 packages in [TIME]
+ package-a==1.0.0
+ package-b==1.0.0
+ package-c==2.0.0
warning: `package-c==2.0.0` is yanked (reason: "Yanked for testing")
"#);
// Since the user explicitly selected the yanked version of `c`, it can be installed.
context.assert_installed(
"transitive_yanked_and_unyanked_dependency_opt_in_a",
"1.0.0",
);
context.assert_installed(
"transitive_yanked_and_unyanked_dependency_opt_in_b",
"1.0.0",
);
context.assert_installed(
"transitive_yanked_and_unyanked_dependency_opt_in_c",
"2.0.0",
);
}
/// A transitive dependency has both a yanked and an unyanked version, but can only be satisfied by a yanked version
///
/// ```text
/// transitive-yanked-and-unyanked-dependency
/// ├── environment
/// │ └── python3.12
/// ├── root
/// │ ├── requires a
/// │ │ └── satisfied by a-1.0.0
/// │ └── requires b
/// │ └── satisfied by b-1.0.0
/// ├── a
/// │ └── a-1.0.0
/// │ └── requires c==2.0.0
/// │ └── unsatisfied: no matching version
/// ├── b
/// │ └── b-1.0.0
/// │ └── requires c<=3.0.0,>=1.0.0
/// │ └── satisfied by c-1.0.0
/// └── c
/// ├── c-1.0.0
/// └── c-2.0.0 (yanked)
/// ```
#[test]
fn transitive_yanked_and_unyanked_dependency() {
let context = TestContext::new("3.12");
// In addition to the standard filters, swap out package names for shorter messages
let mut filters = context.filters();
filters.push((r"transitive-yanked-and-unyanked-dependency-", "package-"));
uv_snapshot!(filters, command(&context)
.arg("transitive-yanked-and-unyanked-dependency-a")
.arg("transitive-yanked-and-unyanked-dependency-b")
, @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because package-c==2.0.0 was yanked (reason: Yanked for testing) and package-a==1.0.0 depends on package-c==2.0.0, we can conclude that package-a==1.0.0 cannot be used.
And because only package-a==1.0.0 is available and you require package-a, we can conclude that your requirements are unsatisfiable.
");
// Since the user did not explicitly select the yanked version, it cannot be used.
context.assert_not_installed("transitive_yanked_and_unyanked_dependency_a");
context.assert_not_installed("transitive_yanked_and_unyanked_dependency_b");
}