From 17732246df7ea53cabdcbb0c70ef88633e5d1585 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 14 Mar 2024 13:13:47 -0700 Subject: [PATCH] Update packse to pull in additional local version tests (#2462) Precursor to #2430. --- crates/uv-cache/src/lib.rs | 13 + crates/uv-distribution/src/source/mod.rs | 8 +- crates/uv/tests/pip_compile_scenarios.rs | 22 +- crates/uv/tests/pip_install_scenarios.rs | 523 ++++++++++++++++++++++- scripts/scenarios/generate.py | 41 +- scripts/scenarios/requirements.in | 2 +- scripts/scenarios/requirements.txt | 12 +- 7 files changed, 587 insertions(+), 34 deletions(-) diff --git a/crates/uv-cache/src/lib.rs b/crates/uv-cache/src/lib.rs index c435dc5fc..6baa1a338 100644 --- a/crates/uv-cache/src/lib.rs +++ b/crates/uv-cache/src/lib.rs @@ -257,6 +257,19 @@ impl Cache { .write(true) .open(root.join(".git"))?; + // Add an empty .gitignore to the build bucket, to ensure that the cache's own .gitignore + // doesn't interfere with source distribution builds. Build backends (like hatchling) will + // traverse upwards to look for .gitignore files. + fs::create_dir_all(root.join(CacheBucket::BuiltWheels.to_str()))?; + match fs::OpenOptions::new().write(true).create_new(true).open( + root.join(CacheBucket::BuiltWheels.to_str()) + .join(".gitignore"), + ) { + Ok(_) => {} + Err(err) if err.kind() == io::ErrorKind::AlreadyExists => (), + Err(err) => return Err(err), + } + fs::canonicalize(root) } diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index b716be1f7..95d224577 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -816,7 +816,8 @@ impl<'a, T: BuildContext> SourceDistCachedBuilder<'a, T> { let span = info_span!("download_source_dist", filename = filename, source_dist = %source_dist); let temp_dir = - tempfile::tempdir_in(self.build_context.cache().root()).map_err(Error::CacheWrite)?; + tempfile::tempdir_in(self.build_context.cache().bucket(CacheBucket::BuiltWheels)) + .map_err(Error::CacheWrite)?; let reader = response .bytes_stream() .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)) @@ -896,8 +897,9 @@ impl<'a, T: BuildContext> SourceDistCachedBuilder<'a, T> { } else { debug!("Unpacking for build: {source_dist}"); - let temp_dir = tempfile::tempdir_in(self.build_context.cache().root()) - .map_err(Error::CacheWrite)?; + let temp_dir = + tempfile::tempdir_in(self.build_context.cache().bucket(CacheBucket::BuiltWheels)) + .map_err(Error::CacheWrite)?; // Unzip the archive into the temporary directory. let reader = fs_err::tokio::File::open(&path) diff --git a/crates/uv/tests/pip_compile_scenarios.rs b/crates/uv/tests/pip_compile_scenarios.rs index 146bd8f56..8d0387d6f 100644 --- a/crates/uv/tests/pip_compile_scenarios.rs +++ b/crates/uv/tests/pip_compile_scenarios.rs @@ -1,7 +1,7 @@ //! DO NOT EDIT //! //! Generated with ./scripts/scenarios/sync.sh -//! Scenarios from +//! Scenarios from //! #![cfg(all(feature = "python", feature = "pypi"))] @@ -27,9 +27,9 @@ fn command(context: &TestContext, python_versions: &[&str]) -> Command { .arg("compile") .arg("requirements.in") .arg("--index-url") - .arg("https://astral-sh.github.io/packse/0.3.7/simple-html/") + .arg("https://astral-sh.github.io/packse/0.3.9/simple-html/") .arg("--find-links") - .arg("https://raw.githubusercontent.com/zanieb/packse/0.3.7/vendor/links.html") + .arg("https://raw.githubusercontent.com/zanieb/packse/0.3.9/vendor/links.html") .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) @@ -66,7 +66,7 @@ fn incompatible_python_compatible_override() -> Result<()> { let context = TestContext::new("3.9"); let python_versions = &[]; - // In addition to the standard filters, swap out package names for more realistic messages + // In addition to the standard filters, swap out package names for shorter messages let mut filters = INSTA_FILTERS.to_vec(); filters.push((r"incompatible-python-compatible-override-", "package-")); @@ -115,7 +115,7 @@ fn compatible_python_incompatible_override() -> Result<()> { let context = TestContext::new("3.11"); let python_versions = &[]; - // In addition to the standard filters, swap out package names for more realistic messages + // In addition to the standard filters, swap out package names for shorter messages let mut filters = INSTA_FILTERS.to_vec(); filters.push((r"compatible-python-incompatible-override-", "package-")); @@ -162,7 +162,7 @@ fn incompatible_python_compatible_override_unavailable_no_wheels() -> Result<()> let context = TestContext::new("3.9"); let python_versions = &[]; - // In addition to the standard filters, swap out package names for more realistic messages + // In addition to the standard filters, swap out package names for shorter messages let mut filters = INSTA_FILTERS.to_vec(); filters.push(( r"incompatible-python-compatible-override-unavailable-no-wheels-", @@ -218,7 +218,7 @@ fn incompatible_python_compatible_override_available_no_wheels() -> Result<()> { let context = TestContext::new("3.9"); let python_versions = &["3.11"]; - // In addition to the standard filters, swap out package names for more realistic messages + // In addition to the standard filters, swap out package names for shorter messages let mut filters = INSTA_FILTERS.to_vec(); filters.push(( r"incompatible-python-compatible-override-available-no-wheels-", @@ -273,7 +273,7 @@ fn incompatible_python_compatible_override_no_compatible_wheels() -> Result<()> let context = TestContext::new("3.9"); let python_versions = &[]; - // In addition to the standard filters, swap out package names for more realistic messages + // In addition to the standard filters, swap out package names for shorter messages let mut filters = INSTA_FILTERS.to_vec(); filters.push(( r"incompatible-python-compatible-override-no-compatible-wheels-", @@ -331,7 +331,7 @@ fn incompatible_python_compatible_override_other_wheel() -> Result<()> { let context = TestContext::new("3.9"); let python_versions = &[]; - // In addition to the standard filters, swap out package names for more realistic messages + // In addition to the standard filters, swap out package names for shorter messages let mut filters = INSTA_FILTERS.to_vec(); filters.push(( r"incompatible-python-compatible-override-other-wheel-", @@ -391,7 +391,7 @@ fn python_patch_override_no_patch() -> Result<()> { let context = TestContext::new("3.8.18"); let python_versions = &[]; - // In addition to the standard filters, swap out package names for more realistic messages + // In addition to the standard filters, swap out package names for shorter messages let mut filters = INSTA_FILTERS.to_vec(); filters.push((r"python-patch-override-no-patch-", "package-")); @@ -438,7 +438,7 @@ fn python_patch_override_patch_compatible() -> Result<()> { let context = TestContext::new("3.8.18"); let python_versions = &[]; - // In addition to the standard filters, swap out package names for more realistic messages + // In addition to the standard filters, swap out package names for shorter messages let mut filters = INSTA_FILTERS.to_vec(); filters.push((r"python-patch-override-patch-compatible-", "package-")); diff --git a/crates/uv/tests/pip_install_scenarios.rs b/crates/uv/tests/pip_install_scenarios.rs index 7e552006f..8eb072293 100644 --- a/crates/uv/tests/pip_install_scenarios.rs +++ b/crates/uv/tests/pip_install_scenarios.rs @@ -1,7 +1,7 @@ //! DO NOT EDIT //! //! Generated with ./scripts/scenarios/sync.sh -//! Scenarios from +//! Scenarios from //! #![cfg(all(feature = "python", feature = "pypi"))] @@ -46,9 +46,9 @@ fn command(context: &TestContext) -> Command { .arg("pip") .arg("install") .arg("--index-url") - .arg("https://astral-sh.github.io/packse/0.3.7/simple-html/") + .arg("https://astral-sh.github.io/packse/0.3.9/simple-html/") .arg("--find-links") - .arg("https://raw.githubusercontent.com/zanieb/packse/0.3.7/vendor/links.html") + .arg("https://raw.githubusercontent.com/zanieb/packse/0.3.9/vendor/links.html") .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) @@ -1289,7 +1289,7 @@ fn local_simple() { ╰─▶ Because there is no version of package-a==1.2.3 and you require package-a==1.2.3, we can conclude that the requirements are unsatisfiable. "###); - // The verison '1.2.3+foo' satisfies the constraint '==1.2.3'. + // The version '1.2.3+foo' satisfies the constraint '==1.2.3'. assert_not_installed(&context.venv, "local_simple_a", &context.temp_dir); } @@ -1330,10 +1330,11 @@ fn local_not_used_with_sdist() { + package-a==1.2.3 "###); - // The verison '1.2.3' with an sdist satisfies the constraint '==1.2.3'. - assert_not_installed( + // The version '1.2.3' with an sdist satisfies the constraint '==1.2.3'. + assert_installed( &context.venv, "local_not_used_with_sdist_a", + "1.2.3", &context.temp_dir, ); } @@ -1374,7 +1375,7 @@ fn local_used_without_sdist() { ╰─▶ Because package-a==1.2.3 is unusable because no wheels are available with a matching Python ABI and you require package-a==1.2.3, we can conclude that the requirements are unsatisfiable. "###); - // The verison '1.2.3+foo' satisfies the constraint '==1.2.3'. + // The version '1.2.3+foo' satisfies the constraint '==1.2.3'. assert_not_installed( &context.venv, "local_used_without_sdist_a", @@ -1470,13 +1471,235 @@ fn local_transitive() { And because you require package-a and you require package-b==2.0.0+foo, we can conclude that the requirements are unsatisfiable. "###); - // The verison '2.0.0+foo' satisfies both ==2.0.0 and ==2.0.0+foo. + // The version '2.0.0+foo' satisfies both ==2.0.0 and ==2.0.0+foo. assert_not_installed(&context.venv, "local_transitive_a", &context.temp_dir); assert_not_installed(&context.venv, "local_transitive_b", &context.temp_dir); } +/// A transitive constraint on a local version should not match an exclusive ordered +/// operator. +/// +/// ```text +/// local-transitive-greater-than +/// ├── environment +/// │ └── python3.8 +/// ├── 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+foo +/// ``` +#[test] +fn local_transitive_greater_than() { + let context = TestContext::new("3.8"); + + // In addition to the standard filters, swap out package names for shorter messages + let mut filters = INSTA_FILTERS.to_vec(); + 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: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Downloaded 2 packages in [TIME] + Installed 2 packages in [TIME] + + package-a==1.0.0 + + package-b==2.0.0+foo + "###); + + assert_installed( + &context.venv, + "local_transitive_greater_than_a", + "1.0.0", + &context.temp_dir, + ); + assert_installed( + &context.venv, + "local_transitive_greater_than_b", + "2.0.0+foo", + &context.temp_dir, + ); +} + +/// A transitive constraint on a local version should match an inclusive ordered +/// operator. +/// +/// ```text +/// local-transitive-greater-than-or-equal +/// ├── environment +/// │ └── python3.8 +/// ├── 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 +/// └── b +/// └── b-2.0.0+foo +/// ``` +#[test] +fn local_transitive_greater_than_or_equal() { + let context = TestContext::new("3.8"); + + // In addition to the standard filters, swap out package names for shorter messages + let mut filters = INSTA_FILTERS.to_vec(); + 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] + Downloaded 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. + assert_installed( + &context.venv, + "local_transitive_greater_than_or_equal_a", + "1.0.0", + &context.temp_dir, + ); + assert_installed( + &context.venv, + "local_transitive_greater_than_or_equal_b", + "2.0.0+foo", + &context.temp_dir, + ); +} + +/// A transitive constraint on a local version should not match an exclusive ordered +/// operator. +/// +/// ```text +/// local-transitive-less-than +/// ├── environment +/// │ └── python3.8 +/// ├── 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+foo +/// ``` +#[test] +fn local_transitive_less_than() { + let context = TestContext::new("3.8"); + + // In addition to the standard filters, swap out package names for shorter messages + let mut filters = INSTA_FILTERS.to_vec(); + 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 you require package-b==2.0.0+foo, we can conclude that the requirements are unsatisfiable. + "###); + + assert_not_installed( + &context.venv, + "local_transitive_less_than_a", + &context.temp_dir, + ); + assert_not_installed( + &context.venv, + "local_transitive_less_than_b", + &context.temp_dir, + ); +} + +/// A transitive constraint on a local version should match an inclusive ordered +/// operator. +/// +/// ```text +/// local-transitive-less-than-or-equal +/// ├── environment +/// │ └── python3.8 +/// ├── 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 +/// └── b +/// └── b-2.0.0+foo +/// ``` +#[test] +fn local_transitive_less_than_or_equal() { + let context = TestContext::new("3.8"); + + // In addition to the standard filters, swap out package names for shorter messages + let mut filters = INSTA_FILTERS.to_vec(); + 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: 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 you require package-b==2.0.0+foo, we can conclude that the requirements are unsatisfiable. + "###); + + // The version '2.0.0+foo' satisfies both <=2.0.0 and ==2.0.0+foo. + assert_not_installed( + &context.venv, + "local_transitive_less_than_or_equal_a", + &context.temp_dir, + ); + assert_not_installed( + &context.venv, + "local_transitive_less_than_or_equal_b", + &context.temp_dir, + ); +} + /// A transitive dependency has both a non-local and local version published, but -/// the non-local version is unuable. +/// the non-local version is unusable. /// /// ```text /// local-transitive-confounding @@ -1515,7 +1738,7 @@ fn local_transitive_confounding() { And because only package-a==1.0.0 is available and you require package-a, we can conclude that the requirements are unsatisfiable. "###); - // The verison '1.2.3+foo' satisfies the constraint '==1.2.3'. + // The version '1.2.3+foo' satisfies the constraint '==1.2.3'. assert_not_installed( &context.venv, "local_transitive_confounding_a", @@ -1523,6 +1746,286 @@ fn local_transitive_confounding() { ); } +/// A dependency depends on a conflicting local version of a direct dependency. +/// +/// ```text +/// local-transitive-conflicting +/// ├── environment +/// │ └── python3.8 +/// ├── 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+foo +/// └── b-2.0.0+bar +/// ``` +#[test] +fn local_transitive_conflicting() { + let context = TestContext::new("3.8"); + + // In addition to the standard filters, swap out package names for shorter messages + let mut filters = INSTA_FILTERS.to_vec(); + 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 you require package-b==2.0.0+foo, we can conclude that the requirements are unsatisfiable. + "###); + + assert_not_installed( + &context.venv, + "local_transitive_conflicting_a", + &context.temp_dir, + ); + assert_not_installed( + &context.venv, + "local_transitive_conflicting_b", + &context.temp_dir, + ); +} + +/// 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.8 +/// ├── 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+foo +/// │ │ └── satisfied by b-2.0.0+bar +/// │ └── a-2.0.0 +/// │ └── requires b==2.0.0+bar +/// │ └── satisfied by b-2.0.0+bar +/// └── b +/// ├── b-2.0.0+foo +/// └── b-2.0.0+bar +/// ``` +#[test] +fn local_transitive_backtrack() { + let context = TestContext::new("3.8"); + + // In addition to the standard filters, swap out package names for shorter messages + let mut filters = INSTA_FILTERS.to_vec(); + 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: 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 + and package-a==1.0.0 depends on package-b==2.0.0, we can conclude that package-a<2.0.0 depends on package-b==2.0.0. + And because package-a==2.0.0 depends on package-b==2.0.0+bar, we can conclude that all versions of package-a depend on one of: + package-b==2.0.0 + package-b==2.0.0+bar + + And because you require package-a and you require package-b==2.0.0+foo, we can conclude that the requirements are unsatisfiable. + "###); + + // Backtracking to '1.0.0' gives us compatible local versions of b. + assert_not_installed( + &context.venv, + "local_transitive_backtrack_a", + &context.temp_dir, + ); + assert_not_installed( + &context.venv, + "local_transitive_backtrack_b", + &context.temp_dir, + ); +} + +/// A local version should be excluded in exclusive ordered comparisons. +/// +/// ```text +/// local-greater-than +/// ├── environment +/// │ └── python3.8 +/// ├── 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.8"); + + // In addition to the standard filters, swap out package names for shorter messages + let mut filters = INSTA_FILTERS.to_vec(); + filters.push((r"local-greater-than-", "package-")); + + uv_snapshot!(filters, command(&context) + .arg("local-greater-than-a>1.2.3") + , @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 1 package in [TIME] + Downloaded 1 package in [TIME] + Installed 1 package in [TIME] + + package-a==1.2.3+foo + "###); + + assert_installed( + &context.venv, + "local_greater_than_a", + "1.2.3+foo", + &context.temp_dir, + ); +} + +/// A local version should be included in inclusive ordered comparisons. +/// +/// ```text +/// local-greater-than-or-equal +/// ├── environment +/// │ └── python3.8 +/// ├── root +/// │ └── requires a>=1.2.3 +/// │ └── satisfied by a-1.2.3+foo +/// └── a +/// └── a-1.2.3+foo +/// ``` +#[test] +fn local_greater_than_or_equal() { + let context = TestContext::new("3.8"); + + // In addition to the standard filters, swap out package names for shorter messages + let mut filters = INSTA_FILTERS.to_vec(); + 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] + Downloaded 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'. + assert_installed( + &context.venv, + "local_greater_than_or_equal_a", + "1.2.3+foo", + &context.temp_dir, + ); +} + +/// A local version should be excluded in exclusive ordered comparisons. +/// +/// ```text +/// local-less-than +/// ├── environment +/// │ └── python3.8 +/// ├── 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.8"); + + // In addition to the standard filters, swap out package names for shorter messages + let mut filters = INSTA_FILTERS.to_vec(); + 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 is available and you require package-a<1.2.3, we can conclude that the requirements are unsatisfiable. + "###); + + assert_not_installed(&context.venv, "local_less_than_a", &context.temp_dir); +} + +/// A local version should be included in inclusive ordered comparisons. +/// +/// ```text +/// local-less-than-or-equal +/// ├── environment +/// │ └── python3.8 +/// ├── root +/// │ └── requires a<=1.2.3 +/// │ └── satisfied by a-1.2.3+foo +/// └── a +/// └── a-1.2.3+foo +/// ``` +#[test] +fn local_less_than_or_equal() { + let context = TestContext::new("3.8"); + + // In addition to the standard filters, swap out package names for shorter messages + let mut filters = INSTA_FILTERS.to_vec(); + 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: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × No solution found when resolving dependencies: + ╰─▶ Because only package-a>1.2.3 is available and you require package-a<=1.2.3, we can conclude that the requirements are unsatisfiable. + "###); + + // The version '1.2.3+foo' satisfies the constraint '<=1.2.3'. + assert_not_installed( + &context.venv, + "local_less_than_or_equal_a", + &context.temp_dir, + ); +} + /// The user requires any version of package `a` which only has prerelease versions /// available. /// diff --git a/scripts/scenarios/generate.py b/scripts/scenarios/generate.py index e0769f62c..636f1cbab 100755 --- a/scripts/scenarios/generate.py +++ b/scripts/scenarios/generate.py @@ -137,14 +137,49 @@ def main(scenarios: list[Path], snapshot_update: bool = True): # We do not yet support local version identifiers for scenario in data["scenarios"]: expected = scenario["expected"] - if ( - scenario["name"].startswith("local-") - and scenario["name"] != "local-not-latest" + if scenario["name"] in ( + "local-less-than-or-equal", + "local-simple", + "local-transitive-confounding", + "local-transitive-backtrack", + "local-used-with-sdist", + "local-used-without-sdist", + "local-transitive", + "local-transitive-less-than-or-equal", ): expected["satisfiable"] = False expected[ "explanation" ] = "We do not have correct behavior for local version identifiers yet" + elif scenario["name"] == "local-greater-than": + expected["satisfiable"] = True + expected["packages"] = [ + { + "name": "local-greater-than-a", + "version": "1.2.3+foo", + "module_name": "local_greater_than_a", + } + ] + expected["explanation"] = ( + "We do not have correct behavior for local version identifiers yet" + ) + elif scenario["name"] == "local-transitive-greater-than": + expected["satisfiable"] = True + expected["packages"] = [ + { + "name": "local-transitive-greater-than-a", + "version": "1.0.0", + "module_name": "local_transitive_greater_than_a", + }, + { + "name": "local-transitive-greater-than-b", + "version": "2.0.0+foo", + "module_name": "local_transitive_greater_than_b", + } + ] + expected["explanation"] = ( + "We do not have correct behavior for local version identifiers yet" + ) # Split scenarios into `install` and `compile` cases install_scenarios = [] diff --git a/scripts/scenarios/requirements.in b/scripts/scenarios/requirements.in index 7727b24e0..fa36647ef 100644 --- a/scripts/scenarios/requirements.in +++ b/scripts/scenarios/requirements.in @@ -1,2 +1,2 @@ chevron-blue -packse>=0.3.6 +packse>=0.3.9 diff --git a/scripts/scenarios/requirements.txt b/scripts/scenarios/requirements.txt index ed02038c3..ef7cce1bc 100644 --- a/scripts/scenarios/requirements.txt +++ b/scripts/scenarios/requirements.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile scripts/scenarios/requirements.in -o scripts/scenarios/requirements.txt --refresh-package packse --upgrade +# uv pip compile scripts/scenarios/requirements.in -o scripts/scenarios/requirements.txt --refresh-package packse certifi==2024.2.2 # via requests charset-normalizer==3.3.2 @@ -14,7 +14,7 @@ hatchling==1.21.1 # via packse idna==3.6 # via requests -importlib-metadata==7.0.1 +importlib-metadata==7.0.2 # via twine jaraco-classes==3.3.1 # via keyring @@ -30,9 +30,9 @@ msgspec==0.18.6 # via packse nh3==0.2.15 # via readme-renderer -packaging==23.2 +packaging==24.0 # via hatchling -packse==0.3.7 +packse==0.3.9 pathspec==0.12.1 # via hatchling pkginfo==1.10.0 @@ -55,7 +55,7 @@ rfc3986==2.0.0 # via twine rich==13.7.1 # via twine -setuptools==69.1.1 +setuptools==69.2.0 # via packse trove-classifiers==2024.3.3 # via hatchling @@ -65,5 +65,5 @@ urllib3==2.2.1 # via # requests # twine -zipp==3.17.0 +zipp==3.18.0 # via importlib-metadata