uv/tests: add a few tests for conflicting groups

This includes a test where we define a conflict between an extra and a
group.
This commit is contained in:
Andrew Gallant 2024-11-13 14:27:48 -05:00 committed by Andrew Gallant
parent 277e7f8dd0
commit c0440e93cf

View file

@ -3559,6 +3559,406 @@ fn lock_conflicting_extra_nested_across_workspace() -> Result<()> {
Ok(()) Ok(())
} }
/// This tests a "basic" case for specifying conflicting groups.
#[test]
fn lock_conflicting_group_basic() -> Result<()> {
let context = TestContext::new("3.12");
// First we test that resolving with two groups that have
// conflicting dependencies fails.
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
description = "Add your description here"
requires-python = ">=3.12"
[dependency-groups]
project1 = ["sortedcontainers==2.3.0"]
project2 = ["sortedcontainers==2.4.0"]
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
"#,
)?;
uv_snapshot!(context.filters(), context.lock(), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
Because project:project1 depends on sortedcontainers==2.3.0 and project:project2 depends on sortedcontainers==2.4.0, we can conclude that project:project1 and project:project2 are incompatible.
And because your project depends on project:project1 and project:project2, we can conclude that your project's requirements are unsatisfiable.
"###);
// And now with the same group configuration, we tell uv about
// the conflicting groups, which forces it to resolve each in
// their own fork.
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
description = "Add your description here"
requires-python = ">=3.12"
[tool.uv]
conflicts = [
[
{ group = "project1" },
{ group = "project2" },
],
]
[dependency-groups]
project1 = ["sortedcontainers==2.3.0"]
project2 = ["sortedcontainers==2.4.0"]
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
"#,
)?;
uv_snapshot!(context.filters(), context.lock(), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
"###);
let lock = context.read("uv.lock");
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
lock, @r###"
version = 1
requires-python = ">=3.12"
resolution-markers = [
]
conflicts = [[
{ package = "project", group = "project1" },
{ package = "project", group = "project2" },
]]
[options]
exclude-newer = "2024-03-25T00:00:00Z"
[[package]]
name = "project"
version = "0.1.0"
source = { editable = "." }
[package.dev-dependencies]
project1 = [
{ name = "sortedcontainers", version = "2.3.0", source = { registry = "https://pypi.org/simple" } },
]
project2 = [
{ name = "sortedcontainers", version = "2.4.0", source = { registry = "https://pypi.org/simple" } },
]
[package.metadata]
[package.metadata.requires-dev]
project1 = [{ name = "sortedcontainers", specifier = "==2.3.0" }]
project2 = [{ name = "sortedcontainers", specifier = "==2.4.0" }]
[[package]]
name = "sortedcontainers"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
]
sdist = { url = "https://files.pythonhosted.org/packages/14/10/6a9481890bae97da9edd6e737c9c3dec6aea3fc2fa53b0934037b35c89ea/sortedcontainers-2.3.0.tar.gz", hash = "sha256:59cc937650cf60d677c16775597c89a960658a09cf7c1a668f86e1e4464b10a1", size = 30509 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/4d/a7046ae1a1a4cc4e9bbed194c387086f06b25038be596543d026946330c9/sortedcontainers-2.3.0-py2.py3-none-any.whl", hash = "sha256:37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f", size = 29479 },
]
[[package]]
name = "sortedcontainers"
version = "2.4.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
]
sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575 },
]
"###
);
});
// Re-run with `--locked`.
uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
"###);
// Install from the lockfile.
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ project==0.1.0 (from file://[TEMP_DIR]/)
"###);
// Another install, but with one of the groups enabled.
uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group=project1"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ sortedcontainers==2.3.0
"###);
// Another install, but with the other group enabled.
uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group=project2"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
- sortedcontainers==2.3.0
+ sortedcontainers==2.4.0
"###);
// And finally, installing both groups should error.
uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group=project1").arg("--group=project2"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: group `project1`, group `project2` are incompatible with the declared conflicts: {`project:project1`, `project:project2`}
"###);
Ok(())
}
/// This tests a case where we declare an extra and a group as conflicting.
#[test]
fn lock_conflicting_mixed() -> Result<()> {
let context = TestContext::new("3.12");
// First we test that resolving with a conflicting extra
// and group fails.
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
description = "Add your description here"
requires-python = ">=3.12"
[dependency-groups]
project1 = ["sortedcontainers==2.3.0"]
[project.optional-dependencies]
project2 = ["sortedcontainers==2.4.0"]
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
"#,
)?;
uv_snapshot!(context.filters(), context.lock(), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
Because project:project1 depends on sortedcontainers==2.3.0 and project[project2] depends on sortedcontainers==2.4.0, we can conclude that project[project2] and project:project1 are incompatible.
And because your project depends on project:project1 and your project requires project[project2], we can conclude that your projects's requirements are unsatisfiable.
"###);
// And now with the same extra/group configuration, we tell uv
// about the conflicting groups, which forces it to resolve each in
// their own fork.
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
description = "Add your description here"
requires-python = ">=3.12"
[tool.uv]
conflicts = [
[
{ group = "project1" },
{ extra = "project2" },
],
]
[dependency-groups]
project1 = ["sortedcontainers==2.3.0"]
[project.optional-dependencies]
project2 = ["sortedcontainers==2.4.0"]
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
"#,
)?;
uv_snapshot!(context.filters(), context.lock(), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
"###);
let lock = context.read("uv.lock");
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
lock, @r###"
version = 1
requires-python = ">=3.12"
resolution-markers = [
]
conflicts = [[
{ package = "project", group = "project1" },
{ package = "project", extra = "project2" },
]]
[options]
exclude-newer = "2024-03-25T00:00:00Z"
[[package]]
name = "project"
version = "0.1.0"
source = { editable = "." }
[package.optional-dependencies]
project2 = [
{ name = "sortedcontainers", version = "2.4.0", source = { registry = "https://pypi.org/simple" } },
]
[package.dev-dependencies]
project1 = [
{ name = "sortedcontainers", version = "2.3.0", source = { registry = "https://pypi.org/simple" } },
]
[package.metadata]
requires-dist = [{ name = "sortedcontainers", marker = "extra == 'project2'", specifier = "==2.4.0" }]
[package.metadata.requires-dev]
project1 = [{ name = "sortedcontainers", specifier = "==2.3.0" }]
[[package]]
name = "sortedcontainers"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
]
sdist = { url = "https://files.pythonhosted.org/packages/14/10/6a9481890bae97da9edd6e737c9c3dec6aea3fc2fa53b0934037b35c89ea/sortedcontainers-2.3.0.tar.gz", hash = "sha256:59cc937650cf60d677c16775597c89a960658a09cf7c1a668f86e1e4464b10a1", size = 30509 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/4d/a7046ae1a1a4cc4e9bbed194c387086f06b25038be596543d026946330c9/sortedcontainers-2.3.0-py2.py3-none-any.whl", hash = "sha256:37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f", size = 29479 },
]
[[package]]
name = "sortedcontainers"
version = "2.4.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
]
sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575 },
]
"###
);
});
// Re-run with `--locked`.
uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
"###);
// Install from the lockfile.
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ project==0.1.0 (from file://[TEMP_DIR]/)
"###);
// Another install, but with the group enabled.
uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group=project1"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ sortedcontainers==2.3.0
"###);
// Another install, but with the extra enabled.
uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--extra=project2"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
- sortedcontainers==2.3.0
+ sortedcontainers==2.4.0
"###);
// And finally, installing both the group and the extra should fail.
uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group=project1").arg("--extra=project2"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: group `project1`, extra `project2` are incompatible with the declared conflicts: {`project:project1`, `project[project2]`}
"###);
Ok(())
}
/// Show updated dependencies on `lock --upgrade`. /// Show updated dependencies on `lock --upgrade`.
#[test] #[test]
fn lock_upgrade_log() -> Result<()> { fn lock_upgrade_log() -> Result<()> {