diff --git a/crates/uv/tests/common/mod.rs b/crates/uv/tests/common/mod.rs index fabc4a7cf..68f053986 100644 --- a/crates/uv/tests/common/mod.rs +++ b/crates/uv/tests/common/mod.rs @@ -28,7 +28,7 @@ pub static EXCLUDE_NEWER: &str = "2024-03-25T00:00:00Z"; /// Using a find links url allows using `--index-url` instead of `--extra-index-url` in tests /// to prevent dependency confusion attacks against our test suite. pub const BUILD_VENDOR_LINKS_URL: &str = - "https://raw.githubusercontent.com/astral-sh/packse/0.3.15/vendor/links.html"; + "https://raw.githubusercontent.com/astral-sh/packse/0.3.17/vendor/links.html"; #[doc(hidden)] // Macro and test context only, don't use directly. pub const INSTA_FILTERS: &[(&str, &str)] = &[ diff --git a/crates/uv/tests/lock_scenarios.rs b/crates/uv/tests/lock_scenarios.rs new file mode 100644 index 000000000..9fea02356 --- /dev/null +++ b/crates/uv/tests/lock_scenarios.rs @@ -0,0 +1,804 @@ +//! DO NOT EDIT +//! +//! Generated with `./scripts/sync_scenarios.sh` +//! Scenarios from +//! +#![cfg(all(feature = "python", feature = "pypi"))] +#![allow(clippy::needless_raw_string_hashes)] + +use anyhow::Result; +use assert_fs::prelude::*; +use insta::assert_snapshot; + +use common::{uv_snapshot, TestContext}; + +mod common; + +/// An extremely basic test of universal resolution. In this case, the resolution +/// should contain two distinct versions of `a` depending on `sys_platform`. +/// +/// ```text +/// fork-basic +/// ├── environment +/// │ └── python3.8 +/// ├── root +/// │ ├── requires a>=2; sys_platform == "linux" +/// │ │ └── satisfied by a-2.0.0 +/// │ └── requires a<2; sys_platform == "darwin" +/// │ └── satisfied by a-1.0.0 +/// └── a +/// ├── a-1.0.0 +/// └── a-2.0.0 +/// ``` +#[test] +fn fork_basic() -> Result<()> { + let context = TestContext::new("3.8"); + + // In addition to the standard filters, swap out package names for shorter messages + let mut filters = context.filters(); + filters.push((r"fork-basic-", "package-")); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r###" + [project] + name = "project" + version = "0.1.0" + dependencies = [ + '''fork-basic-a>=2; sys_platform == "linux"''', + '''fork-basic-a<2; sys_platform == "darwin"''', + ] + "###, + )?; + + let mut cmd = context.lock_without_exclude_newer(); + cmd.arg("--index-url") + .arg("https://astral-sh.github.io/packse/0.3.17/simple-html/"); + uv_snapshot!(filters, cmd, @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: `uv lock` is experimental and may change without warning. + Resolved 3 packages in [TIME] + "### + ); + + let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock"))?; + insta::with_settings!({ + filters => filters, + }, { + assert_snapshot!( + lock, @r###" + version = 1 + + [[distribution]] + name = "package-a" + version = "1.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + marker = "sys_platform == 'darwin'" + sdist = { url = "https://astral-sh.github.io/packse/0.3.17/files/fork_basic_a-1.0.0.tar.gz#sha256=3e45d6136e4a52416f85b7f53f405493db8f9fea33210299e6a68895bf0acf2a", hash = "sha256:3e45d6136e4a52416f85b7f53f405493db8f9fea33210299e6a68895bf0acf2a" } + wheels = [{ url = "https://astral-sh.github.io/packse/0.3.17/files/fork_basic_a-1.0.0-py3-none-any.whl#sha256=b81a7553af25f15c9d49ed26af9c5b86eb2be107f3dd1bd97d7a4b0e8ca0329e", hash = "sha256:b81a7553af25f15c9d49ed26af9c5b86eb2be107f3dd1bd97d7a4b0e8ca0329e" }] + + [[distribution]] + name = "package-a" + version = "2.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + marker = "sys_platform == 'linux'" + sdist = { url = "https://astral-sh.github.io/packse/0.3.17/files/fork_basic_a-2.0.0.tar.gz#sha256=ceb7349a6dd7640be952c70dce8ee6a44e3442dfd9b248b96242e37623e1028e", hash = "sha256:ceb7349a6dd7640be952c70dce8ee6a44e3442dfd9b248b96242e37623e1028e" } + wheels = [{ url = "https://astral-sh.github.io/packse/0.3.17/files/fork_basic_a-2.0.0-py3-none-any.whl#sha256=9cab1de38d28e75ac5fe5c4dda9157555c60dd03ee26e6ad51b01ca18d8a0f01", hash = "sha256:9cab1de38d28e75ac5fe5c4dda9157555c60dd03ee26e6ad51b01ca18d8a0f01" }] + + [[distribution]] + name = "project" + version = "0.1.0" + source = "editable+file://[TEMP_DIR]/" + sdist = { url = "file://[TEMP_DIR]/" } + + [[distribution.dependencies]] + name = "package-a" + version = "1.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + + [[distribution.dependencies]] + name = "package-a" + version = "2.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + "### + ); + }); + + Ok(()) +} + +/// This is actually a non-forking test case that tests the tracking of marker +/// expressions in general. In this case, the dependency on `c` should have its +/// marker expressions automatically combined. In this case, it's `linux OR darwin`, +/// even though `linux OR darwin` doesn't actually appear verbatim as a marker +/// expression for any dependency on `c`. +/// +/// ```text +/// fork-marker-accrue +/// ├── environment +/// │ └── python3.8 +/// ├── root +/// │ ├── requires a==1.0.0; implementation_name == "cpython" +/// │ │ └── satisfied by a-1.0.0 +/// │ └── requires b==1.0.0; implementation_name == "pypy" +/// │ └── satisfied by b-1.0.0 +/// ├── a +/// │ └── a-1.0.0 +/// │ └── requires c==1.0.0; sys_platform == "linux" +/// │ └── satisfied by c-1.0.0 +/// ├── b +/// │ └── b-1.0.0 +/// │ └── requires c==1.0.0; sys_platform == "darwin" +/// │ └── satisfied by c-1.0.0 +/// └── c +/// └── c-1.0.0 +/// ``` +#[test] +fn fork_marker_accrue() -> Result<()> { + let context = TestContext::new("3.8"); + + // In addition to the standard filters, swap out package names for shorter messages + let mut filters = context.filters(); + filters.push((r"fork-marker-accrue-", "package-")); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r###" + [project] + name = "project" + version = "0.1.0" + dependencies = [ + '''fork-marker-accrue-a==1.0.0; implementation_name == "cpython"''', + '''fork-marker-accrue-b==1.0.0; implementation_name == "pypy"''', + ] + "###, + )?; + + let mut cmd = context.lock_without_exclude_newer(); + cmd.arg("--index-url") + .arg("https://astral-sh.github.io/packse/0.3.17/simple-html/"); + uv_snapshot!(filters, cmd, @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: `uv lock` is experimental and may change without warning. + Resolved 4 packages in [TIME] + "### + ); + + let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock"))?; + insta::with_settings!({ + filters => filters, + }, { + assert_snapshot!( + lock, @r###" + version = 1 + + [[distribution]] + name = "package-a" + version = "1.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + marker = "implementation_name == 'cpython'" + sdist = { url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_accrue_a-1.0.0.tar.gz#sha256=9096dbf9c8e8c2da4a1527be515f740f697ee833ec1492953883f36c8931bc37", hash = "sha256:9096dbf9c8e8c2da4a1527be515f740f697ee833ec1492953883f36c8931bc37" } + wheels = [{ url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_accrue_a-1.0.0-py3-none-any.whl#sha256=5fed1607b73cc7a5e9703206c24cc3fa730600a776bf40ae264ad364ad610e0a", hash = "sha256:5fed1607b73cc7a5e9703206c24cc3fa730600a776bf40ae264ad364ad610e0a" }] + + [[distribution.dependencies]] + name = "package-c" + version = "1.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + + [[distribution]] + name = "package-b" + version = "1.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + marker = "implementation_name == 'pypy'" + sdist = { url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_accrue_b-1.0.0.tar.gz#sha256=d92d0083d2d5da2f83180c08dfc79a03ec9606c00bc3153566f7b577c0e6b859", hash = "sha256:d92d0083d2d5da2f83180c08dfc79a03ec9606c00bc3153566f7b577c0e6b859" } + wheels = [{ url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_accrue_b-1.0.0-py3-none-any.whl#sha256=e5382e438f417f2de9427296a5960f9f9631ff1fa11c93d6b0b3b9d7fb60760f", hash = "sha256:e5382e438f417f2de9427296a5960f9f9631ff1fa11c93d6b0b3b9d7fb60760f" }] + + [[distribution.dependencies]] + name = "package-c" + version = "1.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + + [[distribution]] + name = "package-c" + version = "1.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + marker = "sys_platform == 'darwin' or sys_platform == 'linux'" + sdist = { url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_accrue_c-1.0.0.tar.gz#sha256=81068ae8b43deb3165cab17eb52aa5f99cda64f51c359b4659918d86995b9cad", hash = "sha256:81068ae8b43deb3165cab17eb52aa5f99cda64f51c359b4659918d86995b9cad" } + wheels = [{ url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_accrue_c-1.0.0-py3-none-any.whl#sha256=f5fe6d35f360ea802b3a7da030e9ed1dce776c30ed028ea7be04fafcb7ac55b6", hash = "sha256:f5fe6d35f360ea802b3a7da030e9ed1dce776c30ed028ea7be04fafcb7ac55b6" }] + + [[distribution]] + name = "project" + version = "0.1.0" + source = "editable+file://[TEMP_DIR]/" + sdist = { url = "file://[TEMP_DIR]/" } + + [[distribution.dependencies]] + name = "package-a" + version = "1.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + + [[distribution.dependencies]] + name = "package-b" + version = "1.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + "### + ); + }); + + Ok(()) +} + +/// This tests a case where the resolver forks because of non-overlapping marker +/// expressions on `b`. In the original universal resolver implementation, this +/// resulted in multiple versions of `a` being unconditionally included in the lock +/// file. So this acts as a regression test to ensure that only one version of `a` +/// is selected. +/// +/// ```text +/// fork-marker-selection +/// ├── environment +/// │ └── python3.8 +/// ├── root +/// │ ├── requires a +/// │ │ ├── satisfied by a-0.1.0 +/// │ │ └── satisfied by a-0.2.0 +/// │ ├── requires b>=2; sys_platform == "linux" +/// │ │ └── satisfied by b-2.0.0 +/// │ └── requires b<2; sys_platform == "darwin" +/// │ └── satisfied by b-1.0.0 +/// ├── a +/// │ ├── a-0.1.0 +/// │ └── a-0.2.0 +/// │ └── requires b>=2.0.0 +/// │ └── satisfied by b-2.0.0 +/// └── b +/// ├── b-1.0.0 +/// └── b-2.0.0 +/// ``` +#[test] +fn fork_marker_selection() -> Result<()> { + let context = TestContext::new("3.8"); + + // In addition to the standard filters, swap out package names for shorter messages + let mut filters = context.filters(); + filters.push((r"fork-marker-selection-", "package-")); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r###" + [project] + name = "project" + version = "0.1.0" + dependencies = [ + '''fork-marker-selection-a''', + '''fork-marker-selection-b>=2; sys_platform == "linux"''', + '''fork-marker-selection-b<2; sys_platform == "darwin"''', + ] + "###, + )?; + + let mut cmd = context.lock_without_exclude_newer(); + cmd.arg("--index-url") + .arg("https://astral-sh.github.io/packse/0.3.17/simple-html/"); + uv_snapshot!(filters, cmd, @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: `uv lock` is experimental and may change without warning. + Resolved 5 packages in [TIME] + "### + ); + + let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock"))?; + insta::with_settings!({ + filters => filters, + }, { + assert_snapshot!( + lock, @r###" + version = 1 + + [[distribution]] + name = "package-a" + version = "0.1.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + sdist = { url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_selection_a-0.1.0.tar.gz#sha256=03c464276ee75f5a1468da2a4090ee6b5fda0f26f548707c9ffcf06d3cf69282", hash = "sha256:03c464276ee75f5a1468da2a4090ee6b5fda0f26f548707c9ffcf06d3cf69282" } + wheels = [{ url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_selection_a-0.1.0-py3-none-any.whl#sha256=0e45ca7b3616810a583dc9754b52b91c69aeea4070d6fe0806c67081d0e95473", hash = "sha256:0e45ca7b3616810a583dc9754b52b91c69aeea4070d6fe0806c67081d0e95473" }] + + [[distribution]] + name = "package-a" + version = "0.2.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + sdist = { url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_selection_a-0.2.0.tar.gz#sha256=ef1d840fe2e86c6eecd4673606076d858b51a3712c1de097b7503fee0c96b97f", hash = "sha256:ef1d840fe2e86c6eecd4673606076d858b51a3712c1de097b7503fee0c96b97f" } + wheels = [{ url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_selection_a-0.2.0-py3-none-any.whl#sha256=78797f388900cece9866aa20917c6a40040dd65f906f8ef034a8cedb4dd75e6c", hash = "sha256:78797f388900cece9866aa20917c6a40040dd65f906f8ef034a8cedb4dd75e6c" }] + + [[distribution.dependencies]] + name = "package-b" + version = "2.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + + [[distribution]] + name = "package-b" + version = "1.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + marker = "sys_platform == 'darwin'" + sdist = { url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_selection_b-1.0.0.tar.gz#sha256=97f1098f4c89457ab2b16982990d487ac6ae2c664f8e22e822a086df71999dc1", hash = "sha256:97f1098f4c89457ab2b16982990d487ac6ae2c664f8e22e822a086df71999dc1" } + wheels = [{ url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_selection_b-1.0.0-py3-none-any.whl#sha256=aba998c3dfa70f4118a4587f636c96f5a2785081b733120cf81b6d762f67b1ca", hash = "sha256:aba998c3dfa70f4118a4587f636c96f5a2785081b733120cf81b6d762f67b1ca" }] + + [[distribution]] + name = "package-b" + version = "2.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + marker = "sys_platform == 'linux'" + sdist = { url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_selection_b-2.0.0.tar.gz#sha256=1f66e4ba827d2913827fa52cc9fd08491b16ab409fa31c40a2fe4e3cde91cb4a", hash = "sha256:1f66e4ba827d2913827fa52cc9fd08491b16ab409fa31c40a2fe4e3cde91cb4a" } + wheels = [{ url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_selection_b-2.0.0-py3-none-any.whl#sha256=ad1b23547813b9ac69b33d3fcf1896cd49a90cd8f957e954dbdd77b628d631cf", hash = "sha256:ad1b23547813b9ac69b33d3fcf1896cd49a90cd8f957e954dbdd77b628d631cf" }] + + [[distribution]] + name = "project" + version = "0.1.0" + source = "editable+file://[TEMP_DIR]/" + sdist = { url = "file://[TEMP_DIR]/" } + + [[distribution.dependencies]] + name = "package-a" + version = "0.1.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + + [[distribution.dependencies]] + name = "package-a" + version = "0.2.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + + [[distribution.dependencies]] + name = "package-b" + version = "1.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + + [[distribution.dependencies]] + name = "package-b" + version = "2.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + "### + ); + }); + + Ok(()) +} + +/// +/// ```text +/// fork-marker-track +/// ├── environment +/// │ └── python3.8 +/// ├── root +/// │ ├── requires a +/// │ │ ├── satisfied by a-1.3.1 +/// │ │ ├── satisfied by a-2.0.0 +/// │ │ ├── satisfied by a-3.1.0 +/// │ │ └── satisfied by a-4.3.0 +/// │ ├── requires b>=2.8; sys_platform == "linux" +/// │ │ └── satisfied by b-2.8 +/// │ └── requires b<2.8; sys_platform == "darwin" +/// │ └── satisfied by b-2.7 +/// ├── a +/// │ ├── a-1.3.1 +/// │ │ └── requires c; implementation_name == "iron" +/// │ │ └── satisfied by c-1.10 +/// │ ├── a-2.0.0 +/// │ │ ├── requires b>=2.8 +/// │ │ │ └── satisfied by b-2.8 +/// │ │ └── requires c; implementation_name == "cpython" +/// │ │ └── satisfied by c-1.10 +/// │ ├── a-3.1.0 +/// │ │ ├── requires b>=2.8 +/// │ │ │ └── satisfied by b-2.8 +/// │ │ └── requires c; implementation_name == "pypy" +/// │ │ └── satisfied by c-1.10 +/// │ └── a-4.3.0 +/// │ └── requires b>=2.8 +/// │ └── satisfied by b-2.8 +/// ├── b +/// │ ├── b-2.7 +/// │ └── b-2.8 +/// └── c +/// └── c-1.10 +/// ``` +#[test] +fn fork_marker_track() -> Result<()> { + let context = TestContext::new("3.8"); + + // In addition to the standard filters, swap out package names for shorter messages + let mut filters = context.filters(); + filters.push((r"fork-marker-track-", "package-")); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r###" + [project] + name = "project" + version = "0.1.0" + dependencies = [ + '''fork-marker-track-a''', + '''fork-marker-track-b>=2.8; sys_platform == "linux"''', + '''fork-marker-track-b<2.8; sys_platform == "darwin"''', + ] + "###, + )?; + + let mut cmd = context.lock_without_exclude_newer(); + cmd.arg("--index-url") + .arg("https://astral-sh.github.io/packse/0.3.17/simple-html/"); + uv_snapshot!(filters, cmd, @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: `uv lock` is experimental and may change without warning. + Resolved 6 packages in [TIME] + "### + ); + + let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock"))?; + insta::with_settings!({ + filters => filters, + }, { + assert_snapshot!( + lock, @r###" + version = 1 + + [[distribution]] + name = "package-a" + version = "1.3.1" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + sdist = { url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_track_a-1.3.1.tar.gz#sha256=b88e1c256f2f3b2f3d0cff5398fd6a1a17682f3b5fd736e08d44c313ed48ef37", hash = "sha256:b88e1c256f2f3b2f3d0cff5398fd6a1a17682f3b5fd736e08d44c313ed48ef37" } + wheels = [{ url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_track_a-1.3.1-py3-none-any.whl#sha256=8f2bd8bcd8f3fc2cfe64621d62a3a9404db665830f7a76db60307a80cf8e632f", hash = "sha256:8f2bd8bcd8f3fc2cfe64621d62a3a9404db665830f7a76db60307a80cf8e632f" }] + + [[distribution.dependencies]] + name = "package-c" + version = "1.10" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + + [[distribution]] + name = "package-a" + version = "4.3.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + sdist = { url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_track_a-4.3.0.tar.gz#sha256=46a0ab5d6b934f2b8c762893660483036a81ac1f8df9a6555e72a3b4859e1a75", hash = "sha256:46a0ab5d6b934f2b8c762893660483036a81ac1f8df9a6555e72a3b4859e1a75" } + wheels = [{ url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_track_a-4.3.0-py3-none-any.whl#sha256=73ad4b017bae8cb4743be03bc406f65594c92ec5038b0f56a4acb07873bfcaa5", hash = "sha256:73ad4b017bae8cb4743be03bc406f65594c92ec5038b0f56a4acb07873bfcaa5" }] + + [[distribution.dependencies]] + name = "package-b" + version = "2.8" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + + [[distribution]] + name = "package-b" + version = "2.7" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + marker = "sys_platform == 'darwin'" + sdist = { url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_track_b-2.7.tar.gz#sha256=25258fd52c9611c9e101138f9986ada5930f5bea08988d0356645c772a8162dd", hash = "sha256:25258fd52c9611c9e101138f9986ada5930f5bea08988d0356645c772a8162dd" } + wheels = [{ url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_track_b-2.7-py3-none-any.whl#sha256=be56f5850a343cb02dfc22e75eaa1009db675ac2f1275b78ba4089c6ea2f2808", hash = "sha256:be56f5850a343cb02dfc22e75eaa1009db675ac2f1275b78ba4089c6ea2f2808" }] + + [[distribution]] + name = "package-b" + version = "2.8" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + marker = "sys_platform == 'linux'" + sdist = { url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_track_b-2.8.tar.gz#sha256=7ec0f88f013fa0b75a4c88097799866617de4cae558b18ad0677f7cc65ad6628", hash = "sha256:7ec0f88f013fa0b75a4c88097799866617de4cae558b18ad0677f7cc65ad6628" } + wheels = [{ url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_track_b-2.8-py3-none-any.whl#sha256=d9969066117d846fe3a200df5bafc3b3279cc419f36f7275e6e55b2dbde2d5d1", hash = "sha256:d9969066117d846fe3a200df5bafc3b3279cc419f36f7275e6e55b2dbde2d5d1" }] + + [[distribution]] + name = "package-c" + version = "1.10" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + marker = "implementation_name == 'iron'" + sdist = { url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_track_c-1.10.tar.gz#sha256=6f4a62bec34fbda0e605dc9acb40af318b1d789816d81cbd0bc7c60595de5930", hash = "sha256:6f4a62bec34fbda0e605dc9acb40af318b1d789816d81cbd0bc7c60595de5930" } + wheels = [{ url = "https://astral-sh.github.io/packse/0.3.17/files/fork_marker_track_c-1.10-py3-none-any.whl#sha256=19791f8bd3bad9a76be5477e1753dc2a4e797d163bef90fdfd99462c271ed6ff", hash = "sha256:19791f8bd3bad9a76be5477e1753dc2a4e797d163bef90fdfd99462c271ed6ff" }] + + [[distribution]] + name = "project" + version = "0.1.0" + source = "editable+file://[TEMP_DIR]/" + sdist = { url = "file://[TEMP_DIR]/" } + + [[distribution.dependencies]] + name = "package-a" + version = "1.3.1" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + + [[distribution.dependencies]] + name = "package-a" + version = "4.3.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + + [[distribution.dependencies]] + name = "package-b" + version = "2.7" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + + [[distribution.dependencies]] + name = "package-b" + version = "2.8" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + "### + ); + }); + + Ok(()) +} + +/// This is the same setup as `non-local-fork-marker-transitive`, but the disjoint +/// dependency specifications on `c` use the same constraints and thus depend on the +/// same version of `c`. In this case, there is no conflict. +/// +/// ```text +/// fork-non-fork-marker-transitive +/// ├── environment +/// │ └── python3.8 +/// ├── root +/// │ ├── requires a==1.0.0 +/// │ │ └── satisfied by a-1.0.0 +/// │ └── requires b==1.0.0 +/// │ └── satisfied by b-1.0.0 +/// ├── a +/// │ └── a-1.0.0 +/// │ └── requires c>=2.0.0; sys_platform == "linux" +/// │ └── satisfied by c-2.0.0 +/// ├── b +/// │ └── b-1.0.0 +/// │ └── requires c>=2.0.0; sys_platform == "darwin" +/// │ └── satisfied by c-2.0.0 +/// └── c +/// ├── c-1.0.0 +/// └── c-2.0.0 +/// ``` +#[test] +fn fork_non_fork_marker_transitive() -> Result<()> { + let context = TestContext::new("3.8"); + + // In addition to the standard filters, swap out package names for shorter messages + let mut filters = context.filters(); + filters.push((r"fork-non-fork-marker-transitive-", "package-")); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r###" + [project] + name = "project" + version = "0.1.0" + dependencies = [ + '''fork-non-fork-marker-transitive-a==1.0.0''', + '''fork-non-fork-marker-transitive-b==1.0.0''', + ] + "###, + )?; + + let mut cmd = context.lock_without_exclude_newer(); + cmd.arg("--index-url") + .arg("https://astral-sh.github.io/packse/0.3.17/simple-html/"); + uv_snapshot!(filters, cmd, @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: `uv lock` is experimental and may change without warning. + Resolved 4 packages in [TIME] + "### + ); + + let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock"))?; + insta::with_settings!({ + filters => filters, + }, { + assert_snapshot!( + lock, @r###" + version = 1 + + [[distribution]] + name = "package-a" + version = "1.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + sdist = { url = "https://astral-sh.github.io/packse/0.3.17/files/fork_non_fork_marker_transitive_a-1.0.0.tar.gz#sha256=017f775164ac5e33682262bbd44922938737bb8d7258161abb65d8d22f7f0749", hash = "sha256:017f775164ac5e33682262bbd44922938737bb8d7258161abb65d8d22f7f0749" } + wheels = [{ url = "https://astral-sh.github.io/packse/0.3.17/files/fork_non_fork_marker_transitive_a-1.0.0-py3-none-any.whl#sha256=d0ffdf00cba31099cc02d1419f1d2a0c8add5efe7c916b5e12bc23c8f7fdfb4c", hash = "sha256:d0ffdf00cba31099cc02d1419f1d2a0c8add5efe7c916b5e12bc23c8f7fdfb4c" }] + + [[distribution.dependencies]] + name = "package-c" + version = "2.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + + [[distribution]] + name = "package-b" + version = "1.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + sdist = { url = "https://astral-sh.github.io/packse/0.3.17/files/fork_non_fork_marker_transitive_b-1.0.0.tar.gz#sha256=f930b038c81f712230deda8d3b7d2a9a9758b71e86313722747e0ecd44d86e4a", hash = "sha256:f930b038c81f712230deda8d3b7d2a9a9758b71e86313722747e0ecd44d86e4a" } + wheels = [{ url = "https://astral-sh.github.io/packse/0.3.17/files/fork_non_fork_marker_transitive_b-1.0.0-py3-none-any.whl#sha256=d50cf9f9bcff0c90e969d6eba899bbbcb3c09666217c2c9a8011cdef089070a4", hash = "sha256:d50cf9f9bcff0c90e969d6eba899bbbcb3c09666217c2c9a8011cdef089070a4" }] + + [[distribution.dependencies]] + name = "package-c" + version = "2.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + + [[distribution]] + name = "package-c" + version = "2.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + marker = "sys_platform == 'darwin' or sys_platform == 'linux'" + sdist = { url = "https://astral-sh.github.io/packse/0.3.17/files/fork_non_fork_marker_transitive_c-2.0.0.tar.gz#sha256=c989314fe5534401e9b2374e9b0461c9d44c237853d9122bc7d9aee006ee0c34", hash = "sha256:c989314fe5534401e9b2374e9b0461c9d44c237853d9122bc7d9aee006ee0c34" } + wheels = [{ url = "https://astral-sh.github.io/packse/0.3.17/files/fork_non_fork_marker_transitive_c-2.0.0-py3-none-any.whl#sha256=661def8c77b372df8146049485a75678ecee810518fb7cba024b609920bdef74", hash = "sha256:661def8c77b372df8146049485a75678ecee810518fb7cba024b609920bdef74" }] + + [[distribution]] + name = "project" + version = "0.1.0" + source = "editable+file://[TEMP_DIR]/" + sdist = { url = "file://[TEMP_DIR]/" } + + [[distribution.dependencies]] + name = "package-a" + version = "1.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + + [[distribution.dependencies]] + name = "package-b" + version = "1.0.0" + source = "registry+https://astral-sh.github.io/packse/0.3.17/simple-html/" + "### + ); + }); + + Ok(()) +} + +/// This is like `non-local-fork-marker-transitive`, but the marker expressions are +/// placed on sibling dependency specifications. However, the actual dependency on +/// `c` is indirect, and thus, there's no fork detected by the universal resolver. +/// This in turn results in an unresolvable conflict on `c`. +/// +/// ```text +/// fork-non-local-fork-marker-direct +/// ├── environment +/// │ └── python3.8 +/// ├── root +/// │ ├── requires a==1.0.0; sys_platform == "linux" +/// │ │ └── satisfied by a-1.0.0 +/// │ └── requires b==1.0.0; sys_platform == "darwin" +/// │ └── satisfied by b-1.0.0 +/// ├── a +/// │ └── a-1.0.0 +/// │ └── requires c<2.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 fork_non_local_fork_marker_direct() -> Result<()> { + let context = TestContext::new("3.8"); + + // In addition to the standard filters, swap out package names for shorter messages + let mut filters = context.filters(); + filters.push((r"fork-non-local-fork-marker-direct-", "package-")); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r###" + [project] + name = "project" + version = "0.1.0" + dependencies = [ + '''fork-non-local-fork-marker-direct-a==1.0.0; sys_platform == "linux"''', + '''fork-non-local-fork-marker-direct-b==1.0.0; sys_platform == "darwin"''', + ] + "###, + )?; + + let mut cmd = context.lock_without_exclude_newer(); + cmd.arg("--index-url") + .arg("https://astral-sh.github.io/packse/0.3.17/simple-html/"); + uv_snapshot!(filters, cmd, @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + warning: `uv lock` is experimental and may change without warning. + × No solution found when resolving dependencies: + ╰─▶ Because package-b{sys_platform == 'darwin'}==1.0.0 depends on package-c>=2.0.0 and package-a{sys_platform == 'linux'}==1.0.0 depends on package-c<2.0.0, we can conclude that package-a{sys_platform == 'linux'}==1.0.0 and package-b{sys_platform == 'darwin'}==1.0.0 are incompatible. + And because project==0.1.0 depends on package-b{sys_platform == 'darwin'}==1.0.0 and package-a{sys_platform == 'linux'}==1.0.0, we can conclude that project==0.1.0 cannot be used. + And because only project==0.1.0 is available and project depends on project, we can conclude that the requirements are unsatisfiable. + "### + ); + + Ok(()) +} + +/// This setup introduces dependencies on two distinct versions of `c`, where each +/// such dependency has a marker expression attached that would normally make them +/// disjoint. In a non-universal resolver, this is no problem. But in a forking +/// resolver that tries to create one universal resolution, this can lead to two +/// distinct versions of `c` in the resolution. This is in and of itself not a +/// problem, since that is an expected scenario for universal resolution. The +/// problem in this case is that because the dependency specifications for `c` occur +/// in two different points (i.e., they are not sibling dependency specifications) +/// in the dependency graph, the forking resolver does not "detect" it, and thus +/// never forks and thus this results in "no resolution." +/// +/// ```text +/// fork-non-local-fork-marker-transitive +/// ├── environment +/// │ └── python3.8 +/// ├── root +/// │ ├── requires a==1.0.0 +/// │ │ └── satisfied by a-1.0.0 +/// │ └── requires b==1.0.0 +/// │ └── satisfied by b-1.0.0 +/// ├── a +/// │ └── a-1.0.0 +/// │ └── requires c<2.0.0; sys_platform == "linux" +/// │ └── satisfied by c-1.0.0 +/// ├── b +/// │ └── b-1.0.0 +/// │ └── requires c>=2.0.0; sys_platform == "darwin" +/// │ └── satisfied by c-2.0.0 +/// └── c +/// ├── c-1.0.0 +/// └── c-2.0.0 +/// ``` +#[test] +fn fork_non_local_fork_marker_transitive() -> Result<()> { + let context = TestContext::new("3.8"); + + // In addition to the standard filters, swap out package names for shorter messages + let mut filters = context.filters(); + filters.push((r"fork-non-local-fork-marker-transitive-", "package-")); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r###" + [project] + name = "project" + version = "0.1.0" + dependencies = [ + '''fork-non-local-fork-marker-transitive-a==1.0.0''', + '''fork-non-local-fork-marker-transitive-b==1.0.0''', + ] + "###, + )?; + + let mut cmd = context.lock_without_exclude_newer(); + cmd.arg("--index-url") + .arg("https://astral-sh.github.io/packse/0.3.17/simple-html/"); + uv_snapshot!(filters, cmd, @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + warning: `uv lock` is experimental and may change without warning. + × No solution found when resolving dependencies: + ╰─▶ Because package-b==1.0.0 depends on package-c{sys_platform == 'darwin'}>=2.0.0 and only package-c{sys_platform == 'darwin'}<=2.0.0 is available, we can conclude that package-b==1.0.0 depends on package-c{sys_platform == 'darwin'}==2.0.0. + And because package-c{sys_platform == 'darwin'}==2.0.0 depends on package-c==2.0.0 and package-c{sys_platform == 'linux'}==1.0.0 depends on package-c==1.0.0, we can conclude that package-b==1.0.0 and package-c{sys_platform == 'linux'}==1.0.0 are incompatible. + And because only the following versions of package-c{sys_platform == 'linux'} are available: + package-c{sys_platform == 'linux'}==1.0.0 + package-c{sys_platform == 'linux'}>=2.0.0 + and package-a==1.0.0 depends on package-c{sys_platform == 'linux'}<2.0.0, we can conclude that package-a==1.0.0 and package-b==1.0.0 are incompatible. + And because project==0.1.0 depends on package-b==1.0.0 and package-a==1.0.0, we can conclude that project==0.1.0 cannot be used. + And because only project==0.1.0 is available and project depends on project, we can conclude that the requirements are unsatisfiable. + "### + ); + + Ok(()) +} diff --git a/crates/uv/tests/pip_compile_scenarios.rs b/crates/uv/tests/pip_compile_scenarios.rs index 5a9012467..298ba4b80 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/sync_scenarios.sh` -//! Scenarios from +//! Scenarios from //! #![cfg(all(feature = "python", feature = "pypi", unix))] @@ -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.15/simple-html/") + .arg("https://astral-sh.github.io/packse/0.3.17/simple-html/") .arg("--find-links") - .arg("https://raw.githubusercontent.com/astral-sh/packse/0.3.15/vendor/links.html") + .arg("https://raw.githubusercontent.com/astral-sh/packse/0.3.17/vendor/links.html") .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str()) diff --git a/crates/uv/tests/pip_install_scenarios.rs b/crates/uv/tests/pip_install_scenarios.rs index ee2d3a6dc..3d55f3d00 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/sync_scenarios.sh` -//! Scenarios from +//! Scenarios from //! #![cfg(all(feature = "python", feature = "pypi", unix))] @@ -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.15/simple-html/") + .arg("https://astral-sh.github.io/packse/0.3.17/simple-html/") .arg("--find-links") - .arg("https://raw.githubusercontent.com/astral-sh/packse/0.3.15/vendor/links.html") + .arg("https://raw.githubusercontent.com/astral-sh/packse/0.3.17/vendor/links.html") .arg("--cache-dir") .arg(context.cache_dir.path()) .env("VIRTUAL_ENV", context.venv.as_os_str())