From 20d7df958f385f5fcba1b45565e20b3d14c44a52 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 10 Jul 2026 17:17:02 +0100 Subject: [PATCH 1/3] test: extend regression test on self-referential extras --- news/14160.trivial.rst | 2 + tests/functional/test_install_extras.py | 314 ++++++++++++++++++++++++ 2 files changed, 316 insertions(+) create mode 100644 news/14160.trivial.rst diff --git a/news/14160.trivial.rst b/news/14160.trivial.rst new file mode 100644 index 0000000000..cf4272faa7 --- /dev/null +++ b/news/14160.trivial.rst @@ -0,0 +1,2 @@ +Expand self-referential extras regression tests to distinguish behaviour across +pip versions before 20.3, 20.3 through 21.1, and 21.2+. diff --git a/tests/functional/test_install_extras.py b/tests/functional/test_install_extras.py index 1b90341cc2..007f1d9597 100644 --- a/tests/functional/test_install_extras.py +++ b/tests/functional/test_install_extras.py @@ -292,6 +292,320 @@ def test_install_self_referential_extras( script.assert_installed(pkg="1", dep_a="1", dep_b="1") +def test_install_self_referential_extras_nested( + script: PipTestEnvironment, +) -> None: + """Convenience extras can nest through other self-referential extras.""" + create_basic_wheel_for_package(script, "pytest", "1") + create_basic_wheel_for_package(script, "sphinx", "1") + create_basic_wheel_for_package(script, "ruff", "1") + create_basic_wheel_for_package( + script, + "pkg", + "1", + extras={ + "test": ["pytest"], + "docs": ["sphinx"], + "format": ["ruff"], + "dev": ["pkg[test]", "pkg[format]"], + "all": ["pkg[dev]", "pkg[docs]"], + }, + ) + + script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "pkg[all]", + ) + script.assert_installed(pkg="1", pytest="1", sphinx="1", ruff="1") + + +def test_install_self_referential_extras_with_external_dep( + script: PipTestEnvironment, +) -> None: + """A self-referential extra can also pull in an unrelated package.""" + create_basic_wheel_for_package(script, "dep_a", "1") + create_basic_wheel_for_package(script, "other", "1") + create_basic_wheel_for_package( + script, + "pkg", + "1", + extras={ + "a": ["dep_a"], + "all": ["pkg[a]", "other"], + }, + ) + + script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "pkg[all]", + ) + script.assert_installed(pkg="1", dep_a="1", other="1") + + +def test_install_self_referential_extras_after_partial_install( + script: PipTestEnvironment, +) -> None: + """Installing more extras on an already-installed version adds missing deps.""" + create_basic_wheel_for_package(script, "dep_a", "1") + create_basic_wheel_for_package(script, "dep_b", "1") + create_basic_wheel_for_package( + script, + "pkg", + "1", + extras={ + "a": ["dep_a"], + "b": ["dep_b"], + "all": ["pkg[a, b]"], + }, + ) + + script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "pkg[a]", + ) + script.assert_installed(pkg="1", dep_a="1") + script.assert_not_installed("dep_b") + + script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "pkg[all]", + ) + script.assert_installed(pkg="1", dep_a="1", dep_b="1") + + +def test_install_self_referential_extras_upgrade_different_extras( + script: PipTestEnvironment, +) -> None: + """Upgrading can change which extras exist and which deps they pull in.""" + create_basic_wheel_for_package(script, "dep_a", "1") + create_basic_wheel_for_package(script, "dep_b", "1") + create_basic_wheel_for_package( + script, + "pkg", + "1", + extras={"a": ["dep_a"]}, + ) + + script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "pkg[a]", + ) + script.assert_installed(pkg="1", dep_a="1") + script.assert_not_installed("dep_b") + + create_basic_wheel_for_package( + script, + "pkg", + "2", + extras={ + "a": ["dep_a"], + "b": ["dep_b"], + "all": ["pkg[a]", "pkg[b]"], + }, + ) + + result = script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "pkg[all]==2", + expect_stderr=True, + ) + assert "does not provide the extra 'b'" not in result.stderr, str(result) + script.assert_installed(pkg="2", dep_a="1", dep_b="1") + + +def test_install_self_referential_extras_skips_older_versions_without_extras( + script: PipTestEnvironment, +) -> None: + """Self-ref extras must not probe older installed versions that lack those extras.""" + create_basic_wheel_for_package(script, "dep_a", "1") + create_basic_wheel_for_package(script, "dep_b", "1") + create_basic_wheel_for_package(script, "pkg", "1") + create_basic_wheel_for_package(script, "pkg", "2") + + script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "pkg==1", + ) + script.assert_installed(pkg="1") + + create_basic_wheel_for_package( + script, + "pkg", + "3", + extras={ + "a": ["dep_a"], + "b": ["dep_b"], + "all": ["pkg[a]", "pkg[b]"], + }, + ) + + result = script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "pkg[all]==3", + expect_stderr=True, + ) + assert "does not provide the extra" not in result.stderr, str(result) + script.assert_installed(pkg="3", dep_a="1", dep_b="1") + + +def test_install_self_referential_extras_upgrade_changes_dep_version( + script: PipTestEnvironment, +) -> None: + """Self-referential extras follow upgraded dependency pins.""" + create_basic_wheel_for_package(script, "dep", "1") + create_basic_wheel_for_package(script, "dep", "2") + create_basic_wheel_for_package( + script, + "pkg", + "1", + extras={"a": ["dep==1"]}, + ) + + script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "pkg[a]", + ) + script.assert_installed(pkg="1", dep="1") + + create_basic_wheel_for_package( + script, + "pkg", + "2", + extras={ + "a": ["dep==2"], + "all": ["pkg[a]"], + }, + ) + + script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "pkg[all]==2", + ) + script.assert_installed(pkg="2", dep="2") + + +def test_install_self_referential_extras_circular( + script: PipTestEnvironment, +) -> None: + """Circular self-referential extras resolve without looping forever.""" + create_basic_wheel_for_package(script, "dep_a", "1") + create_basic_wheel_for_package(script, "dep_b", "1") + create_basic_wheel_for_package( + script, + "pkg", + "1", + extras={ + "a": ["dep_a", "pkg[b]"], + "b": ["dep_b", "pkg[a]"], + }, + ) + + script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "pkg[a]", + ) + script.assert_installed(pkg="1", dep_a="1", dep_b="1") + + +def test_install_self_referential_extras_unknown_nested( + script: PipTestEnvironment, +) -> None: + """A nested unknown extra warns the same way as a direct unknown extra.""" + create_basic_wheel_for_package(script, "dep_a", "1") + create_basic_wheel_for_package( + script, + "pkg", + "1", + extras={ + "a": ["dep_a"], + "all": ["pkg[missing]"], + }, + ) + + result = script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "pkg[all]", + expect_stderr=True, + ) + assert "pkg 1 does not provide the extra 'missing'" in result.stderr + script.assert_installed(pkg="1") + script.assert_not_installed("dep_a") + + +def test_install_self_referential_extras_name_normalization( + script: PipTestEnvironment, +) -> None: + """Self-referential extras honor PEP 685 extra name normalization.""" + create_basic_wheel_for_package(script, "meh", "1") + create_basic_wheel_for_package( + script, + "pkg", + "1", + extras={ + "x_y": ["meh"], + "all": ["pkg[x-y]"], + }, + ) + + script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "pkg[all]", + ) + script.assert_installed(pkg="1", meh="1") + + def test_install_setuptools_extras_inconsistency( script: PipTestEnvironment, tmp_path: Path ) -> None: From 7a00df18842e86f18aedaffe022b328fe4a6cff9 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 10 Jul 2026 17:29:26 +0100 Subject: [PATCH 2/3] style: reduce line width to pass pre-commit --- tests/functional/test_install_extras.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/test_install_extras.py b/tests/functional/test_install_extras.py index 007f1d9597..ddcb77583c 100644 --- a/tests/functional/test_install_extras.py +++ b/tests/functional/test_install_extras.py @@ -440,7 +440,7 @@ def test_install_self_referential_extras_upgrade_different_extras( def test_install_self_referential_extras_skips_older_versions_without_extras( script: PipTestEnvironment, ) -> None: - """Self-ref extras must not probe older installed versions that lack those extras.""" + """Self-ref extras must not probe older installed versions lacking those extras.""" create_basic_wheel_for_package(script, "dep_a", "1") create_basic_wheel_for_package(script, "dep_b", "1") create_basic_wheel_for_package(script, "pkg", "1") From ac7d7fa8533d50e6d8eb3d470cc6cab7ae89e35e Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 10 Jul 2026 18:26:33 +0100 Subject: [PATCH 3/3] test: merge two similar cases into a parametrize one --- tests/functional/test_install_extras.py | 68 +++++++------------------ 1 file changed, 19 insertions(+), 49 deletions(-) diff --git a/tests/functional/test_install_extras.py b/tests/functional/test_install_extras.py index ddcb77583c..ffe536e5e5 100644 --- a/tests/functional/test_install_extras.py +++ b/tests/functional/test_install_extras.py @@ -389,28 +389,41 @@ def test_install_self_referential_extras_after_partial_install( script.assert_installed(pkg="1", dep_a="1", dep_b="1") +@pytest.mark.parametrize( + "initial_extras, initial_req", + [ + ({}, "pkg==1"), + ({"a": ["dep_a"]}, "pkg[a]==1"), + ], + ids=["had-no-extra", "had-different-extras"], +) def test_install_self_referential_extras_upgrade_different_extras( script: PipTestEnvironment, + initial_extras: dict[str, list[str]], + initial_req: str, ) -> None: - """Upgrading can change which extras exist and which deps they pull in.""" + """Upgrading package can change which extras exist and which deps they pull in.""" create_basic_wheel_for_package(script, "dep_a", "1") + create_basic_wheel_for_package(script, "dep_a", "2") create_basic_wheel_for_package(script, "dep_b", "1") create_basic_wheel_for_package( script, "pkg", "1", - extras={"a": ["dep_a"]}, + extras=initial_extras, ) + # Ensure dep_a ver 2 is present so pkg ver 2's dep_a==1 pin is a downgrade. + initial_install = [initial_req] if initial_extras else [initial_req, "dep_a==2"] script.pip( "install", "--no-cache-dir", "--no-index", "--find-links", script.scratch_path, - "pkg[a]", + *initial_install, ) - script.assert_installed(pkg="1", dep_a="1") + script.assert_installed(pkg="1", dep_a="2") script.assert_not_installed("dep_b") create_basic_wheel_for_package( @@ -418,7 +431,7 @@ def test_install_self_referential_extras_upgrade_different_extras( "pkg", "2", extras={ - "a": ["dep_a"], + "a": ["dep_a==1"], "b": ["dep_b"], "all": ["pkg[a]", "pkg[b]"], }, @@ -433,51 +446,8 @@ def test_install_self_referential_extras_upgrade_different_extras( "pkg[all]==2", expect_stderr=True, ) - assert "does not provide the extra 'b'" not in result.stderr, str(result) - script.assert_installed(pkg="2", dep_a="1", dep_b="1") - - -def test_install_self_referential_extras_skips_older_versions_without_extras( - script: PipTestEnvironment, -) -> None: - """Self-ref extras must not probe older installed versions lacking those extras.""" - create_basic_wheel_for_package(script, "dep_a", "1") - create_basic_wheel_for_package(script, "dep_b", "1") - create_basic_wheel_for_package(script, "pkg", "1") - create_basic_wheel_for_package(script, "pkg", "2") - - script.pip( - "install", - "--no-cache-dir", - "--no-index", - "--find-links", - script.scratch_path, - "pkg==1", - ) - script.assert_installed(pkg="1") - - create_basic_wheel_for_package( - script, - "pkg", - "3", - extras={ - "a": ["dep_a"], - "b": ["dep_b"], - "all": ["pkg[a]", "pkg[b]"], - }, - ) - - result = script.pip( - "install", - "--no-cache-dir", - "--no-index", - "--find-links", - script.scratch_path, - "pkg[all]==3", - expect_stderr=True, - ) assert "does not provide the extra" not in result.stderr, str(result) - script.assert_installed(pkg="3", dep_a="1", dep_b="1") + script.assert_installed(pkg="2", dep_a="1", dep_b="1") def test_install_self_referential_extras_upgrade_changes_dep_version(