diff options
Diffstat (limited to 'tests/functional/test_new_resolver.py')
-rw-r--r-- | tests/functional/test_new_resolver.py | 1360 |
1 files changed, 840 insertions, 520 deletions
diff --git a/tests/functional/test_new_resolver.py b/tests/functional/test_new_resolver.py index 1dca963e1..fc52ab9c8 100644 --- a/tests/functional/test_new_resolver.py +++ b/tests/functional/test_new_resolver.py @@ -2,43 +2,38 @@ import os import pathlib import sys import textwrap +from typing import TYPE_CHECKING, Callable, Dict, List, Tuple import pytest from tests.lib import ( + PipTestEnvironment, create_basic_sdist_for_package, create_basic_wheel_for_package, create_test_package_with_setup, - path_to_url, ) from tests.lib.direct_url import get_created_direct_url -from tests.lib.path import Path from tests.lib.wheel import make_wheel +if TYPE_CHECKING: + from typing import Protocol -# TODO: Remove me. -def assert_installed(script, **kwargs): - script.assert_installed(**kwargs) +MakeFakeWheel = Callable[[str, str, str], pathlib.Path] -# TODO: Remove me. -def assert_not_installed(script, *args): - script.assert_not_installed(*args) - - -def assert_editable(script, *args): +def assert_editable(script: PipTestEnvironment, *args: str) -> None: # This simply checks whether all of the listed packages have a # corresponding .egg-link file installed. # TODO: Implement a more rigorous way to test for editable installations. egg_links = {f"{arg}.egg-link" for arg in args} - assert egg_links <= set(os.listdir(script.site_packages_path)), \ - f"{args!r} not all found in {script.site_packages_path!r}" + assert egg_links <= set( + os.listdir(script.site_packages_path) + ), f"{args!r} not all found in {script.site_packages_path!r}" @pytest.fixture() -def make_fake_wheel(script): - - def _make_fake_wheel(name, version, wheel_tag): +def make_fake_wheel(script: PipTestEnvironment) -> MakeFakeWheel: + def _make_fake_wheel(name: str, version: str, wheel_tag: str) -> pathlib.Path: wheel_house = script.scratch_path.joinpath("wheelhouse") wheel_house.mkdir() wheel_builder = make_wheel( @@ -53,7 +48,7 @@ def make_fake_wheel(script): return _make_fake_wheel -def test_new_resolver_can_install(script): +def test_new_resolver_can_install(script: PipTestEnvironment) -> None: create_basic_wheel_for_package( script, "simple", @@ -61,14 +56,16 @@ def test_new_resolver_can_install(script): ) script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "simple" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "simple", ) - assert_installed(script, simple="0.1.0") + script.assert_installed(simple="0.1.0") -def test_new_resolver_can_install_with_version(script): +def test_new_resolver_can_install_with_version(script: PipTestEnvironment) -> None: create_basic_wheel_for_package( script, "simple", @@ -76,14 +73,16 @@ def test_new_resolver_can_install_with_version(script): ) script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "simple==0.1.0" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "simple==0.1.0", ) - assert_installed(script, simple="0.1.0") + script.assert_installed(simple="0.1.0") -def test_new_resolver_picks_latest_version(script): +def test_new_resolver_picks_latest_version(script: PipTestEnvironment) -> None: create_basic_wheel_for_package( script, "simple", @@ -96,14 +95,16 @@ def test_new_resolver_picks_latest_version(script): ) script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "simple" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "simple", ) - assert_installed(script, simple="0.2.0") + script.assert_installed(simple="0.2.0") -def test_new_resolver_picks_installed_version(script): +def test_new_resolver_picks_installed_version(script: PipTestEnvironment) -> None: create_basic_wheel_for_package( script, "simple", @@ -116,23 +117,29 @@ def test_new_resolver_picks_installed_version(script): ) script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "simple==0.1.0" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "simple==0.1.0", ) - assert_installed(script, simple="0.1.0") + script.assert_installed(simple="0.1.0") result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "simple" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "simple", ) assert "Collecting" not in result.stdout, "Should not fetch new version" - assert_installed(script, simple="0.1.0") + script.assert_installed(simple="0.1.0") -def test_new_resolver_picks_installed_version_if_no_match_found(script): +def test_new_resolver_picks_installed_version_if_no_match_found( + script: PipTestEnvironment, +) -> None: create_basic_wheel_for_package( script, "simple", @@ -145,22 +152,20 @@ def test_new_resolver_picks_installed_version_if_no_match_found(script): ) script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "simple==0.1.0" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "simple==0.1.0", ) - assert_installed(script, simple="0.1.0") + script.assert_installed(simple="0.1.0") - result = script.pip( - "install", - "--no-cache-dir", "--no-index", - "simple" - ) + result = script.pip("install", "--no-cache-dir", "--no-index", "simple") assert "Collecting" not in result.stdout, "Should not fetch new version" - assert_installed(script, simple="0.1.0") + script.assert_installed(simple="0.1.0") -def test_new_resolver_installs_dependencies(script): +def test_new_resolver_installs_dependencies(script: PipTestEnvironment) -> None: create_basic_wheel_for_package( script, "base", @@ -174,14 +179,16 @@ def test_new_resolver_installs_dependencies(script): ) script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "base" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "base", ) - assert_installed(script, base="0.1.0", dep="0.1.0") + script.assert_installed(base="0.1.0", dep="0.1.0") -def test_new_resolver_ignore_dependencies(script): +def test_new_resolver_ignore_dependencies(script: PipTestEnvironment) -> None: create_basic_wheel_for_package( script, "base", @@ -195,12 +202,15 @@ def test_new_resolver_ignore_dependencies(script): ) script.pip( "install", - "--no-cache-dir", "--no-index", "--no-deps", - "--find-links", script.scratch_path, - "base" + "--no-cache-dir", + "--no-index", + "--no-deps", + "--find-links", + script.scratch_path, + "base", ) - assert_installed(script, base="0.1.0") - assert_not_installed(script, "dep") + script.assert_installed(base="0.1.0") + script.assert_not_installed("dep") @pytest.mark.parametrize( @@ -210,7 +220,9 @@ def test_new_resolver_ignore_dependencies(script): "base[add] >= 0.1.0", ], ) -def test_new_resolver_installs_extras(tmpdir, script, root_dep): +def test_new_resolver_installs_extras( + tmpdir: pathlib.Path, script: PipTestEnvironment, root_dep: str +) -> None: req_file = tmpdir.joinpath("requirements.txt") req_file.write_text(root_dep) @@ -227,14 +239,17 @@ def test_new_resolver_installs_extras(tmpdir, script, root_dep): ) script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "-r", req_file, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "-r", + req_file, ) - assert_installed(script, base="0.1.0", dep="0.1.0") + script.assert_installed(base="0.1.0", dep="0.1.0") -def test_new_resolver_installs_extras_warn_missing(script): +def test_new_resolver_installs_extras_warn_missing(script: PipTestEnvironment) -> None: create_basic_wheel_for_package( script, "base", @@ -248,34 +263,40 @@ def test_new_resolver_installs_extras_warn_missing(script): ) result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "base[add,missing]", expect_stderr=True, ) assert "does not provide the extra" in result.stderr, str(result) assert "missing" in result.stderr, str(result) - assert_installed(script, base="0.1.0", dep="0.1.0") + script.assert_installed(base="0.1.0", dep="0.1.0") -def test_new_resolver_installed_message(script): +def test_new_resolver_installed_message(script: PipTestEnvironment) -> None: create_basic_wheel_for_package(script, "A", "1.0") result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "A", expect_stderr=False, ) assert "Successfully installed A-1.0" in result.stdout, str(result) -def test_new_resolver_no_dist_message(script): +def test_new_resolver_no_dist_message(script: PipTestEnvironment) -> None: create_basic_wheel_for_package(script, "A", "1.0") result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "B", expect_error=True, expect_stderr=True, @@ -286,12 +307,13 @@ def test_new_resolver_no_dist_message(script): # requirement xxx (from versions: none) # ERROR: No matching distribution found for xxx - assert "Could not find a version that satisfies the requirement B" \ - in result.stderr, str(result) + assert ( + "Could not find a version that satisfies the requirement B" in result.stderr + ), str(result) assert "No matching distribution found for B" in result.stderr, str(result) -def test_new_resolver_installs_editable(script): +def test_new_resolver_installs_editable(script: PipTestEnvironment) -> None: create_basic_wheel_for_package( script, "base", @@ -305,12 +327,15 @@ def test_new_resolver_installs_editable(script): ) script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "base", - "--editable", source_dir, + "--editable", + source_dir, ) - assert_installed(script, base="0.1.0", dep="0.1.0") + script.assert_installed(base="0.1.0", dep="0.1.0") assert_editable(script, "dep") @@ -320,18 +345,17 @@ def test_new_resolver_installs_editable(script): # Something impossible to satisfy. ("<2", False, "0.1.0"), ("<2", True, "0.2.0"), - # Something guaranteed to satisfy. (">=2", False, "0.2.0"), (">=2", True, "0.2.0"), ], ) def test_new_resolver_requires_python( - script, - requires_python, - ignore_requires_python, - dep_version, -): + script: PipTestEnvironment, + requires_python: str, + ignore_requires_python: bool, + dep_version: str, +) -> None: create_basic_wheel_for_package( script, "base", @@ -354,7 +378,8 @@ def test_new_resolver_requires_python( "install", "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--find-links", + os.fspath(script.scratch_path), ] if ignore_requires_python: args.append("--ignore-requires-python") @@ -362,10 +387,10 @@ def test_new_resolver_requires_python( script.pip(*args) - assert_installed(script, base="0.1.0", dep=dep_version) + script.assert_installed(base="0.1.0", dep=dep_version) -def test_new_resolver_requires_python_error(script): +def test_new_resolver_requires_python_error(script: PipTestEnvironment) -> None: create_basic_wheel_for_package( script, "base", @@ -374,8 +399,10 @@ def test_new_resolver_requires_python_error(script): ) result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "base", expect_error=True, ) @@ -387,7 +414,7 @@ def test_new_resolver_requires_python_error(script): assert message in result.stderr, str(result) -def test_new_resolver_installed(script): +def test_new_resolver_installed(script: PipTestEnvironment) -> None: create_basic_wheel_for_package( script, "base", @@ -402,27 +429,29 @@ def test_new_resolver_installed(script): result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "base", ) assert "Requirement already satisfied" not in result.stdout, str(result) result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "base~=0.1.0", ) - assert "Requirement already satisfied: base~=0.1.0" in result.stdout, \ - str(result) + assert "Requirement already satisfied: base~=0.1.0" in result.stdout, str(result) result.did_not_update( - script.site_packages / "base", - message="base 0.1.0 reinstalled" + script.site_packages / "base", message="base 0.1.0 reinstalled" ) -def test_new_resolver_ignore_installed(script): +def test_new_resolver_ignore_installed(script: PipTestEnvironment) -> None: create_basic_wheel_for_package( script, "base", @@ -432,26 +461,32 @@ def test_new_resolver_ignore_installed(script): result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "base", ) assert satisfied_output not in result.stdout, str(result) result = script.pip( "install", - "--no-cache-dir", "--no-index", "--ignore-installed", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--ignore-installed", + "--find-links", + script.scratch_path, "base", ) assert satisfied_output not in result.stdout, str(result) result.did_update( - script.site_packages / "base", - message="base 0.1.0 not reinstalled" + script.site_packages / "base", message="base 0.1.0 not reinstalled" ) -def test_new_resolver_only_builds_sdists_when_needed(script): +def test_new_resolver_only_builds_sdists_when_needed( + script: PipTestEnvironment, +) -> None: create_basic_wheel_for_package( script, "base", @@ -473,57 +508,65 @@ def test_new_resolver_only_builds_sdists_when_needed(script): # We only ever need to check dep 0.2.0 as it's the latest version script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "base" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "base", ) - assert_installed(script, base="0.1.0", dep="0.2.0") + script.assert_installed(base="0.1.0", dep="0.2.0") # We merge criteria here, as we have two "dep" requirements script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "base", "dep" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "base", + "dep", ) - assert_installed(script, base="0.1.0", dep="0.2.0") + script.assert_installed(base="0.1.0", dep="0.2.0") -def test_new_resolver_install_different_version(script): +def test_new_resolver_install_different_version(script: PipTestEnvironment) -> None: create_basic_wheel_for_package(script, "base", "0.1.0") create_basic_wheel_for_package(script, "base", "0.2.0") script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "base==0.1.0", ) # This should trigger an uninstallation of base. result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "base==0.2.0", ) assert "Uninstalling base-0.1.0" in result.stdout, str(result) assert "Successfully uninstalled base-0.1.0" in result.stdout, str(result) - result.did_update( - script.site_packages / "base", - message="base not upgraded" - ) - assert_installed(script, base="0.2.0") + result.did_update(script.site_packages / "base", message="base not upgraded") + script.assert_installed(base="0.2.0") -def test_new_resolver_force_reinstall(script): +def test_new_resolver_force_reinstall(script: PipTestEnvironment) -> None: create_basic_wheel_for_package(script, "base", "0.1.0") script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "base==0.1.0", ) @@ -531,19 +574,18 @@ def test_new_resolver_force_reinstall(script): # even though the installed version matches. result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "--force-reinstall", "base==0.1.0", ) assert "Uninstalling base-0.1.0" in result.stdout, str(result) assert "Successfully uninstalled base-0.1.0" in result.stdout, str(result) - result.did_update( - script.site_packages / "base", - message="base not reinstalled" - ) - assert_installed(script, base="0.1.0") + result.did_update(script.site_packages / "base", message="base not reinstalled") + script.assert_installed(base="0.1.0") @pytest.mark.parametrize( @@ -561,20 +603,22 @@ def test_new_resolver_force_reinstall(script): ids=["default", "exact-pre", "explicit-pre", "no-stable"], ) def test_new_resolver_handles_prerelease( - script, - available_versions, - pip_args, - expected_version, -): + script: PipTestEnvironment, + available_versions: List[str], + pip_args: List[str], + expected_version: str, +) -> None: for version in available_versions: create_basic_wheel_for_package(script, "pkg", version) script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - *pip_args + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + *pip_args, ) - assert_installed(script, pkg=expected_version) + script.assert_installed(pkg=expected_version) @pytest.mark.parametrize( @@ -584,20 +628,24 @@ def test_new_resolver_handles_prerelease( (["dep; os_name == 'nonexist_os'"], ["pkg"]), # This tests the marker is picked up from a root dependency. ([], ["pkg", "dep; os_name == 'nonexist_os'"]), - ] + ], ) -def test_new_reolver_skips_marker(script, pkg_deps, root_deps): +def test_new_resolver_skips_marker( + script: PipTestEnvironment, pkg_deps: List[str], root_deps: List[str] +) -> None: create_basic_wheel_for_package(script, "pkg", "1.0", depends=pkg_deps) create_basic_wheel_for_package(script, "dep", "1.0") script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - *root_deps + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + *root_deps, ) - assert_installed(script, pkg="1.0") - assert_not_installed(script, "dep") + script.assert_installed(pkg="1.0") + script.assert_not_installed("dep") @pytest.mark.parametrize( @@ -607,9 +655,11 @@ def test_new_reolver_skips_marker(script, pkg_deps, root_deps): # This also tests the pkg constraint don't get merged with the # requirement prematurely. (pypa/pip#8134) ["pkg<2.0"], - ] + ], ) -def test_new_resolver_constraints(script, constraints): +def test_new_resolver_constraints( + script: PipTestEnvironment, constraints: List[str] +) -> None: create_basic_wheel_for_package(script, "pkg", "1.0") create_basic_wheel_for_package(script, "pkg", "2.0") create_basic_wheel_for_package(script, "pkg", "3.0") @@ -617,28 +667,34 @@ def test_new_resolver_constraints(script, constraints): constraints_file.write_text("\n".join(constraints)) script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "-c", constraints_file, - "pkg" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "-c", + constraints_file, + "pkg", ) - assert_installed(script, pkg="1.0") - assert_not_installed(script, "constraint_only") + script.assert_installed(pkg="1.0") + script.assert_not_installed("constraint_only") -def test_new_resolver_constraint_no_specifier(script): +def test_new_resolver_constraint_no_specifier(script: PipTestEnvironment) -> None: "It's allowed (but useless...) for a constraint to have no specifier" create_basic_wheel_for_package(script, "pkg", "1.0") constraints_file = script.scratch_path / "constraints.txt" constraints_file.write_text("pkg") script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "-c", constraints_file, - "pkg" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "-c", + constraints_file, + "pkg", ) - assert_installed(script, pkg="1.0") + script.assert_installed(pkg="1.0") @pytest.mark.parametrize( @@ -658,15 +714,20 @@ def test_new_resolver_constraint_no_specifier(script): ), ], ) -def test_new_resolver_constraint_reject_invalid(script, constraint, error): +def test_new_resolver_constraint_reject_invalid( + script: PipTestEnvironment, constraint: str, error: str +) -> None: create_basic_wheel_for_package(script, "pkg", "1.0") constraints_file = script.scratch_path / "constraints.txt" constraints_file.write_text(constraint) result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "-c", constraints_file, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "-c", + constraints_file, "pkg", expect_error=True, expect_stderr=True, @@ -674,7 +735,7 @@ def test_new_resolver_constraint_reject_invalid(script, constraint, error): assert error in result.stderr, str(result) -def test_new_resolver_constraint_on_dependency(script): +def test_new_resolver_constraint_on_dependency(script: PipTestEnvironment) -> None: create_basic_wheel_for_package(script, "base", "1.0", depends=["dep"]) create_basic_wheel_for_package(script, "dep", "1.0") create_basic_wheel_for_package(script, "dep", "2.0") @@ -683,13 +744,16 @@ def test_new_resolver_constraint_on_dependency(script): constraints_file.write_text("dep==2.0") script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "-c", constraints_file, - "base" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "-c", + constraints_file, + "base", ) - assert_installed(script, base="1.0") - assert_installed(script, dep="2.0") + script.assert_installed(base="1.0") + script.assert_installed(dep="2.0") @pytest.mark.parametrize( @@ -700,13 +764,12 @@ def test_new_resolver_constraint_on_dependency(script): ], ) def test_new_resolver_constraint_on_path_empty( - script, - constraint_version, - expect_error, - message, -): - """A path requirement can be filtered by a constraint. - """ + script: PipTestEnvironment, + constraint_version: str, + expect_error: bool, + message: str, +) -> None: + """A path requirement can be filtered by a constraint.""" setup_py = script.scratch_path / "setup.py" text = "from setuptools import setup\nsetup(name='foo', version='2.0')" setup_py.write_text(text) @@ -716,8 +779,10 @@ def test_new_resolver_constraint_on_path_empty( result = script.pip( "install", - "--no-cache-dir", "--no-index", - "-c", constraints_txt, + "--no-cache-dir", + "--no-index", + "-c", + constraints_txt, str(script.scratch_path), expect_error=expect_error, ) @@ -728,37 +793,42 @@ def test_new_resolver_constraint_on_path_empty( assert message in result.stdout, str(result) -def test_new_resolver_constraint_only_marker_match(script): +def test_new_resolver_constraint_only_marker_match(script: PipTestEnvironment) -> None: create_basic_wheel_for_package(script, "pkg", "1.0") create_basic_wheel_for_package(script, "pkg", "2.0") create_basic_wheel_for_package(script, "pkg", "3.0") - constrants_content = textwrap.dedent( + constraints_content = textwrap.dedent( """ pkg==1.0; python_version == "{ver[0]}.{ver[1]}" # Always satisfies. pkg==2.0; python_version < "0" # Never satisfies. """ ).format(ver=sys.version_info) constraints_txt = script.scratch_path / "constraints.txt" - constraints_txt.write_text(constrants_content) + constraints_txt.write_text(constraints_content) script.pip( "install", - "--no-cache-dir", "--no-index", - "-c", constraints_txt, - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "-c", + constraints_txt, + "--find-links", + script.scratch_path, "pkg", ) - assert_installed(script, pkg="1.0") + script.assert_installed(pkg="1.0") -def test_new_resolver_upgrade_needs_option(script): +def test_new_resolver_upgrade_needs_option(script: PipTestEnvironment) -> None: # Install pkg 1.0.0 create_basic_wheel_for_package(script, "pkg", "1.0.0") script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "pkg", ) @@ -768,44 +838,47 @@ def test_new_resolver_upgrade_needs_option(script): # This should not upgrade because we don't specify --upgrade result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "pkg", ) assert "Requirement already satisfied" in result.stdout, str(result) - assert_installed(script, pkg="1.0.0") + script.assert_installed(pkg="1.0.0") # This should upgrade result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "--upgrade", "PKG", # Deliberately uppercase to check canonicalization ) assert "Uninstalling pkg-1.0.0" in result.stdout, str(result) assert "Successfully uninstalled pkg-1.0.0" in result.stdout, str(result) - result.did_update( - script.site_packages / "pkg", - message="pkg not upgraded" - ) - assert_installed(script, pkg="2.0.0") + result.did_update(script.site_packages / "pkg", message="pkg not upgraded") + script.assert_installed(pkg="2.0.0") -def test_new_resolver_upgrade_strategy(script): +def test_new_resolver_upgrade_strategy(script: PipTestEnvironment) -> None: create_basic_wheel_for_package(script, "base", "1.0.0", depends=["dep"]) create_basic_wheel_for_package(script, "dep", "1.0.0") script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "base", ) - assert_installed(script, base="1.0.0") - assert_installed(script, dep="1.0.0") + script.assert_installed(base="1.0.0") + script.assert_installed(dep="1.0.0") # Now release new versions create_basic_wheel_for_package(script, "base", "2.0.0", depends=["dep"]) @@ -813,29 +886,102 @@ def test_new_resolver_upgrade_strategy(script): script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "--upgrade", "base", ) # With upgrade strategy "only-if-needed" (the default), dep should not # be upgraded. - assert_installed(script, base="2.0.0") - assert_installed(script, dep="1.0.0") + script.assert_installed(base="2.0.0") + script.assert_installed(dep="1.0.0") create_basic_wheel_for_package(script, "base", "3.0.0", depends=["dep"]) script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "--upgrade", "--upgrade-strategy=eager", + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "--upgrade", + "--upgrade-strategy=eager", "base", ) # With upgrade strategy "eager", dep should be upgraded. - assert_installed(script, base="3.0.0") - assert_installed(script, dep="2.0.0") + script.assert_installed(base="3.0.0") + script.assert_installed(dep="2.0.0") + + +if TYPE_CHECKING: + + class PackageBuilder(Protocol): + def __call__( + self, + script: PipTestEnvironment, + name: str, + version: str, + requires: List[str], + extras: Dict[str, List[str]], + ) -> str: + ... + + +def _local_with_setup( + script: PipTestEnvironment, + name: str, + version: str, + requires: List[str], + extras: Dict[str, List[str]], +) -> str: + """Create the package as a local source directory to install from path.""" + path = create_test_package_with_setup( + script, + name=name, + version=version, + install_requires=requires, + extras_require=extras, + ) + return str(path) + + +def _direct_wheel( + script: PipTestEnvironment, + name: str, + version: str, + requires: List[str], + extras: Dict[str, List[str]], +) -> str: + """Create the package as a wheel to install from path directly.""" + path = create_basic_wheel_for_package( + script, + name=name, + version=version, + depends=requires, + extras=extras, + ) + return str(path) + + +def _wheel_from_index( + script: PipTestEnvironment, + name: str, + version: str, + requires: List[str], + extras: Dict[str, List[str]], +) -> str: + """Create the package as a wheel to install from index.""" + create_basic_wheel_for_package( + script, + name=name, + version=version, + depends=requires, + extras=extras, + ) + return name class TestExtraMerge: @@ -844,40 +990,6 @@ class TestExtraMerge: extras, one listed as required and the other as in extra. """ - def _local_with_setup(script, name, version, requires, extras): - """Create the package as a local source directory to install from path. - """ - return create_test_package_with_setup( - script, - name=name, - version=version, - install_requires=requires, - extras_require=extras, - ) - - def _direct_wheel(script, name, version, requires, extras): - """Create the package as a wheel to install from path directly. - """ - return create_basic_wheel_for_package( - script, - name=name, - version=version, - depends=requires, - extras=extras, - ) - - def _wheel_from_index(script, name, version, requires, extras): - """Create the package as a wheel to install from index. - """ - create_basic_wheel_for_package( - script, - name=name, - version=version, - depends=requires, - extras=extras, - ) - return name - @pytest.mark.parametrize( "pkg_builder", [ @@ -887,8 +999,8 @@ class TestExtraMerge: ], ) def test_new_resolver_extra_merge_in_package( - self, monkeypatch, script, pkg_builder, - ): + self, script: PipTestEnvironment, pkg_builder: "PackageBuilder" + ) -> None: create_basic_wheel_for_package(script, "depdev", "1.0.0") create_basic_wheel_for_package( script, @@ -906,14 +1018,16 @@ class TestExtraMerge: script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, requirement + "[dev]", ) - assert_installed(script, pkg="1.0.0", dep="1.0.0", depdev="1.0.0") + script.assert_installed(pkg="1.0.0", dep="1.0.0", depdev="1.0.0") -def test_new_resolver_build_directory_error_zazo_19(script): +def test_new_resolver_build_directory_error_zazo_19(script: PipTestEnvironment) -> None: """https://github.com/pradyunsg/zazo/issues/19#issuecomment-631615674 This will first resolve like this: @@ -935,7 +1049,10 @@ def test_new_resolver_build_directory_error_zazo_19(script): can delete this. Please delete it and try again. """ create_basic_wheel_for_package( - script, "pkg_a", "3.0.0", depends=["pkg-b<2"], + script, + "pkg_a", + "3.0.0", + depends=["pkg-b<2"], ) create_basic_wheel_for_package(script, "pkg_a", "2.0.0") create_basic_wheel_for_package(script, "pkg_a", "1.0.0") @@ -945,36 +1062,43 @@ def test_new_resolver_build_directory_error_zazo_19(script): script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "pkg-a", "pkg-b", + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "pkg-a", + "pkg-b", ) - assert_installed(script, pkg_a="3.0.0", pkg_b="1.0.0") + script.assert_installed(pkg_a="3.0.0", pkg_b="1.0.0") -def test_new_resolver_upgrade_same_version(script): +def test_new_resolver_upgrade_same_version(script: PipTestEnvironment) -> None: create_basic_wheel_for_package(script, "pkg", "2") create_basic_wheel_for_package(script, "pkg", "1") script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "pkg", ) - assert_installed(script, pkg="2") + script.assert_installed(pkg="2") script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "--upgrade", "pkg", ) - assert_installed(script, pkg="2") + script.assert_installed(pkg="2") -def test_new_resolver_local_and_req(script): +def test_new_resolver_local_and_req(script: PipTestEnvironment) -> None: source_dir = create_test_package_with_setup( script, name="pkg", @@ -982,13 +1106,17 @@ def test_new_resolver_local_and_req(script): ) script.pip( "install", - "--no-cache-dir", "--no-index", - source_dir, "pkg!=0.1.0", + "--no-cache-dir", + "--no-index", + source_dir, + "pkg!=0.1.0", expect_error=True, ) -def test_new_resolver_no_deps_checks_requires_python(script): +def test_new_resolver_no_deps_checks_requires_python( + script: PipTestEnvironment, +) -> None: create_basic_wheel_for_package( script, "base", @@ -1007,7 +1135,8 @@ def test_new_resolver_no_deps_checks_requires_python(script): "--no-cache-dir", "--no-index", "--no-deps", - "--find-links", script.scratch_path, + "--find-links", + script.scratch_path, "base", expect_error=True, ) @@ -1019,7 +1148,9 @@ def test_new_resolver_no_deps_checks_requires_python(script): assert message in result.stderr -def test_new_resolver_prefers_installed_in_upgrade_if_latest(script): +def test_new_resolver_prefers_installed_in_upgrade_if_latest( + script: PipTestEnvironment, +) -> None: create_basic_wheel_for_package(script, "pkg", "1") local_pkg = create_test_package_with_setup(script, name="pkg", version="2") @@ -1036,17 +1167,20 @@ def test_new_resolver_prefers_installed_in_upgrade_if_latest(script): "install", "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--find-links", + script.scratch_path, "--upgrade", "pkg", ) - assert_installed(script, pkg="2") + script.assert_installed(pkg="2") @pytest.mark.parametrize("N", [2, 10, 20]) -def test_new_resolver_presents_messages_when_backtracking_a_lot(script, N): +def test_new_resolver_presents_messages_when_backtracking_a_lot( + script: PipTestEnvironment, N: int +) -> None: # Generate a set of wheels that will definitely cause backtracking. - for index in range(1, N+1): + for index in range(1, N + 1): A_version = f"{index}.0.0" B_version = f"{index}.0.0" C_version = "{index_minus_one}.0.0".format(index_minus_one=index - 1) @@ -1058,7 +1192,7 @@ def test_new_resolver_presents_messages_when_backtracking_a_lot(script, N): print("A", A_version, "B", B_version, "C", C_version) create_basic_wheel_for_package(script, "A", A_version, depends=depends) - for index in range(1, N+1): + for index in range(1, N + 1): B_version = f"{index}.0.0" C_version = f"{index}.0.0" depends = ["C == " + C_version] @@ -1066,7 +1200,7 @@ def test_new_resolver_presents_messages_when_backtracking_a_lot(script, N): print("B", B_version, "C", C_version) create_basic_wheel_for_package(script, "B", B_version, depends=depends) - for index in range(1, N+1): + for index in range(1, N + 1): C_version = f"{index}.0.0" print("C", C_version) create_basic_wheel_for_package(script, "C", C_version) @@ -1076,11 +1210,12 @@ def test_new_resolver_presents_messages_when_backtracking_a_lot(script, N): "install", "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "A" + "--find-links", + script.scratch_path, + "A", ) - assert_installed(script, A="1.0.0", B="1.0.0", C="1.0.0") + script.assert_installed(A="1.0.0", B="1.0.0", C="1.0.0") # These numbers are hard-coded in the code. if N >= 1: assert "This could take a while." in result.stdout @@ -1095,13 +1230,12 @@ def test_new_resolver_presents_messages_when_backtracking_a_lot(script, N): [ "0.1.0+local.1", # Normalized form. "0.1.0+local_1", # Non-normalized form containing an underscore. - # Non-normalized form containing a dash. This is allowed, installation # works correctly, but assert_installed() fails because pkg_resources # cannot handle it correctly. Nobody is complaining about it right now, # we're probably dropping it for importlib.metadata soon(tm), so let's # ignore it for the time being. - pytest.param("0.1.0+local-1", marks=pytest.mark.xfail), + pytest.param("0.1.0+local-1", marks=pytest.mark.xfail(strict=False)), ], ids=["meta_dot", "meta_underscore", "meta_dash"], ) @@ -1114,10 +1248,10 @@ def test_new_resolver_presents_messages_when_backtracking_a_lot(script, N): ids=["file_dot", "file_underscore"], ) def test_new_resolver_check_wheel_version_normalized( - script, - metadata_version, - filename_version, -): + script: PipTestEnvironment, + metadata_version: str, + filename_version: str, +) -> None: filename = f"simple-{filename_version}-py2.py3-none-any.whl" wheel_builder = make_wheel(name="simple", version=metadata_version) @@ -1125,56 +1259,63 @@ def test_new_resolver_check_wheel_version_normalized( script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "simple" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "simple", ) - assert_installed(script, simple="0.1.0+local.1") + script.assert_installed(simple="0.1.0+local.1") -def test_new_resolver_does_reinstall_local_sdists(script): +def test_new_resolver_does_reinstall_local_sdists(script: PipTestEnvironment) -> None: archive_path = create_basic_sdist_for_package( script, "pkg", "1.0", ) script.pip( - "install", "--no-cache-dir", "--no-index", + "install", + "--no-cache-dir", + "--no-index", archive_path, ) - assert_installed(script, pkg="1.0") + script.assert_installed(pkg="1.0") result = script.pip( - "install", "--no-cache-dir", "--no-index", + "install", + "--no-cache-dir", + "--no-index", archive_path, expect_stderr=True, ) assert "Installing collected packages: pkg" in result.stdout, str(result) - assert "DEPRECATION" in result.stderr, str(result) - assert_installed(script, pkg="1.0") + script.assert_installed(pkg="1.0") -def test_new_resolver_does_reinstall_local_paths(script): - pkg = create_test_package_with_setup( - script, - name="pkg", - version="1.0" - ) +def test_new_resolver_does_reinstall_local_paths(script: PipTestEnvironment) -> None: + pkg = create_test_package_with_setup(script, name="pkg", version="1.0") script.pip( - "install", "--no-cache-dir", "--no-index", + "install", + "--no-cache-dir", + "--no-index", pkg, ) - assert_installed(script, pkg="1.0") + script.assert_installed(pkg="1.0") result = script.pip( - "install", "--no-cache-dir", "--no-index", + "install", + "--no-cache-dir", + "--no-index", pkg, ) assert "Installing collected packages: pkg" in result.stdout, str(result) - assert_installed(script, pkg="1.0") + script.assert_installed(pkg="1.0") -def test_new_resolver_does_not_reinstall_when_from_a_local_index(script): +def test_new_resolver_does_not_reinstall_when_from_a_local_index( + script: PipTestEnvironment, +) -> None: create_basic_sdist_for_package( script, "simple", @@ -1182,25 +1323,29 @@ def test_new_resolver_does_not_reinstall_when_from_a_local_index(script): ) script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "simple" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "simple", ) - assert_installed(script, simple="0.1.0") + script.assert_installed(simple="0.1.0") result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "simple" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "simple", ) # Should not reinstall! assert "Installing collected packages: simple" not in result.stdout, str(result) assert "Requirement already satisfied: simple" in result.stdout, str(result) - assert_installed(script, simple="0.1.0") + script.assert_installed(simple="0.1.0") -def test_new_resolver_skip_inconsistent_metadata(script): +def test_new_resolver_skip_inconsistent_metadata(script: PipTestEnvironment) -> None: create_basic_wheel_for_package(script, "A", "1") a_2 = create_basic_wheel_for_package(script, "A", "2") @@ -1208,17 +1353,19 @@ def test_new_resolver_skip_inconsistent_metadata(script): result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "--verbose", "A", allow_stderr_warning=True, ) assert ( - " inconsistent version: filename has '3', but metadata has '2'" - ) in result.stderr, str(result) - assert_installed(script, a="1") + " inconsistent version: expected '3', but metadata has '2'" + ) in result.stdout, str(result) + script.assert_installed(a="1") @pytest.mark.parametrize( @@ -1226,7 +1373,9 @@ def test_new_resolver_skip_inconsistent_metadata(script): [True, False], ids=["upgrade", "no-upgrade"], ) -def test_new_resolver_lazy_fetch_candidates(script, upgrade): +def test_new_resolver_lazy_fetch_candidates( + script: PipTestEnvironment, upgrade: bool +) -> None: create_basic_wheel_for_package(script, "myuberpkg", "1") create_basic_wheel_for_package(script, "myuberpkg", "2") create_basic_wheel_for_package(script, "myuberpkg", "3") @@ -1234,8 +1383,10 @@ def test_new_resolver_lazy_fetch_candidates(script, upgrade): # Install an old version first. script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "myuberpkg==1", ) @@ -1246,32 +1397,36 @@ def test_new_resolver_lazy_fetch_candidates(script, upgrade): pip_upgrade_args = [] result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "myuberpkg", - *pip_upgrade_args # Trailing comma fails on Python 2. + *pip_upgrade_args, # Trailing comma fails on Python 2. ) # pip should install the version preferred by the strategy... if upgrade: - assert_installed(script, myuberpkg="3") + script.assert_installed(myuberpkg="3") else: - assert_installed(script, myuberpkg="1") + script.assert_installed(myuberpkg="1") # But should reach there in the best route possible, without trying # candidates it does not need to. assert "myuberpkg-2" not in result.stdout, str(result) -def test_new_resolver_no_fetch_no_satisfying(script): +def test_new_resolver_no_fetch_no_satisfying(script: PipTestEnvironment) -> None: create_basic_wheel_for_package(script, "myuberpkg", "1") # Install the package. This should emit a "Processing" message for # fetching the distribution from the --find-links page. result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "myuberpkg", ) assert "Processing " in result.stdout, str(result) @@ -1280,15 +1435,19 @@ def test_new_resolver_no_fetch_no_satisfying(script): # message because the currently installed version is latest. result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "--upgrade", "myuberpkg", ) assert "Processing " not in result.stdout, str(result) -def test_new_resolver_does_not_install_unneeded_packages_with_url_constraint(script): +def test_new_resolver_does_not_install_unneeded_packages_with_url_constraint( + script: PipTestEnvironment, +) -> None: archive_path = create_basic_wheel_for_package( script, "installed", @@ -1301,24 +1460,29 @@ def test_new_resolver_does_not_install_unneeded_packages_with_url_constraint(scr ) constraints_file = script.scratch_path / "constraints.txt" - constraints_file.write_text("not_installed @ " + path_to_url(not_installed_path)) + constraints_file.write_text(f"not_installed @ {not_installed_path.as_uri()}") (script.scratch_path / "index").mkdir() archive_path.rename(script.scratch_path / "index" / archive_path.name) script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path / "index", - "-c", constraints_file, - "installed" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path / "index", + "-c", + constraints_file, + "installed", ) - assert_installed(script, installed="0.1.0") - assert_not_installed(script, "not_installed") + script.assert_installed(installed="0.1.0") + script.assert_not_installed("not_installed") -def test_new_resolver_installs_packages_with_url_constraint(script): +def test_new_resolver_installs_packages_with_url_constraint( + script: PipTestEnvironment, +) -> None: installed_path = create_basic_wheel_for_package( script, "installed", @@ -1326,19 +1490,18 @@ def test_new_resolver_installs_packages_with_url_constraint(script): ) constraints_file = script.scratch_path / "constraints.txt" - constraints_file.write_text("installed @ " + path_to_url(installed_path)) + constraints_file.write_text(f"installed @ {installed_path.as_uri()}") script.pip( - "install", - "--no-cache-dir", "--no-index", - "-c", constraints_file, - "installed" + "install", "--no-cache-dir", "--no-index", "-c", constraints_file, "installed" ) - assert_installed(script, installed="0.1.0") + script.assert_installed(installed="0.1.0") -def test_new_resolver_reinstall_link_requirement_with_constraint(script): +def test_new_resolver_reinstall_link_requirement_with_constraint( + script: PipTestEnvironment, +) -> None: installed_path = create_basic_wheel_for_package( script, "installed", @@ -1346,27 +1509,32 @@ def test_new_resolver_reinstall_link_requirement_with_constraint(script): ) cr_file = script.scratch_path / "constraints.txt" - cr_file.write_text("installed @ " + path_to_url(installed_path)) + cr_file.write_text(f"installed @ {installed_path.as_uri()}") script.pip( "install", - "--no-cache-dir", "--no-index", - "-r", cr_file, + "--no-cache-dir", + "--no-index", + "-r", + cr_file, ) script.pip( "install", - "--no-cache-dir", "--no-index", - "-c", cr_file, - "-r", cr_file, + "--no-cache-dir", + "--no-index", + "-c", + cr_file, + "-r", + cr_file, ) # TODO: strengthen assertion to "second invocation does no work" # I don't think this is true yet, but it should be in the future. - assert_installed(script, installed="0.1.0") + script.assert_installed(installed="0.1.0") -def test_new_resolver_prefers_url_constraint(script): +def test_new_resolver_prefers_url_constraint(script: PipTestEnvironment) -> None: installed_path = create_basic_wheel_for_package( script, "test_pkg", @@ -1379,23 +1547,28 @@ def test_new_resolver_prefers_url_constraint(script): ) constraints_file = script.scratch_path / "constraints.txt" - constraints_file.write_text("test_pkg @ " + path_to_url(installed_path)) + constraints_file.write_text(f"test_pkg @ {installed_path.as_uri()}") (script.scratch_path / "index").mkdir() not_installed_path.rename(script.scratch_path / "index" / not_installed_path.name) script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path / "index", - "-c", constraints_file, - "test_pkg" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path / "index", + "-c", + constraints_file, + "test_pkg", ) - assert_installed(script, test_pkg="0.1.0") + script.assert_installed(test_pkg="0.1.0") -def test_new_resolver_prefers_url_constraint_on_update(script): +def test_new_resolver_prefers_url_constraint_on_update( + script: PipTestEnvironment, +) -> None: installed_path = create_basic_wheel_for_package( script, "test_pkg", @@ -1408,35 +1581,41 @@ def test_new_resolver_prefers_url_constraint_on_update(script): ) constraints_file = script.scratch_path / "constraints.txt" - constraints_file.write_text("test_pkg @ " + path_to_url(installed_path)) + constraints_file.write_text(f"test_pkg @ {installed_path.as_uri()}") (script.scratch_path / "index").mkdir() not_installed_path.rename(script.scratch_path / "index" / not_installed_path.name) script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path / "index", - "test_pkg" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path / "index", + "test_pkg", ) - assert_installed(script, test_pkg="0.2.0") + script.assert_installed(test_pkg="0.2.0") script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path / "index", - "-c", constraints_file, - "test_pkg" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path / "index", + "-c", + constraints_file, + "test_pkg", ) - assert_installed(script, test_pkg="0.1.0") + script.assert_installed(test_pkg="0.1.0") @pytest.mark.parametrize("version_option", ["--constraint", "--requirement"]) def test_new_resolver_fails_with_url_constraint_and_incompatible_version( - script, version_option, -): + script: PipTestEnvironment, + version_option: str, +) -> None: not_installed_path = create_basic_wheel_for_package( script, "test_pkg", @@ -1449,17 +1628,21 @@ def test_new_resolver_fails_with_url_constraint_and_incompatible_version( ) url_constraint = script.scratch_path / "constraints.txt" - url_constraint.write_text("test_pkg @ " + path_to_url(not_installed_path)) + url_constraint.write_text(f"test_pkg @ {not_installed_path.as_uri()}") version_req = script.scratch_path / "requirements.txt" version_req.write_text("test_pkg<0.2.0") result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "--constraint", url_constraint, - version_option, version_req, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "--constraint", + url_constraint, + version_option, + version_req, "test_pkg", expect_error=True, ) @@ -1469,19 +1652,24 @@ def test_new_resolver_fails_with_url_constraint_and_incompatible_version( "because these package versions have conflicting dependencies." ) in result.stderr, str(result) - assert_not_installed(script, "test_pkg") + script.assert_not_installed("test_pkg") # Assert that pip works properly in the absence of the constraints file. script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - version_option, version_req, - "test_pkg" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + version_option, + version_req, + "test_pkg", ) -def test_new_resolver_ignores_unneeded_conflicting_constraints(script): +def test_new_resolver_ignores_unneeded_conflicting_constraints( + script: PipTestEnvironment, +) -> None: version_1 = create_basic_wheel_for_package( script, "test_pkg", @@ -1499,8 +1687,8 @@ def test_new_resolver_ignores_unneeded_conflicting_constraints(script): ) constraints = [ - "test_pkg @ " + path_to_url(version_1), - "test_pkg @ " + path_to_url(version_2), + f"test_pkg @ {version_1.as_uri()}", + f"test_pkg @ {version_2.as_uri()}", ] constraints_file = script.scratch_path / "constraints.txt" @@ -1508,17 +1696,22 @@ def test_new_resolver_ignores_unneeded_conflicting_constraints(script): script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "-c", constraints_file, - "installed" + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "-c", + constraints_file, + "installed", ) - assert_not_installed(script, "test_pkg") - assert_installed(script, installed="0.1.0") + script.assert_not_installed("test_pkg") + script.assert_installed(installed="0.1.0") -def test_new_resolver_fails_on_needed_conflicting_constraints(script): +def test_new_resolver_fails_on_needed_conflicting_constraints( + script: PipTestEnvironment, +) -> None: version_1 = create_basic_wheel_for_package( script, "test_pkg", @@ -1531,8 +1724,8 @@ def test_new_resolver_fails_on_needed_conflicting_constraints(script): ) constraints = [ - "test_pkg @ " + path_to_url(version_1), - "test_pkg @ " + path_to_url(version_2), + f"test_pkg @ {version_1.as_uri()}", + f"test_pkg @ {version_2.as_uri()}", ] constraints_file = script.scratch_path / "constraints.txt" @@ -1540,9 +1733,12 @@ def test_new_resolver_fails_on_needed_conflicting_constraints(script): result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "-c", constraints_file, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "-c", + constraints_file, "test_pkg", expect_error=True, ) @@ -1552,18 +1748,22 @@ def test_new_resolver_fails_on_needed_conflicting_constraints(script): "dependencies." ) in result.stderr, str(result) - assert_not_installed(script, "test_pkg") + script.assert_not_installed("test_pkg") # Assert that pip works properly in the absence of the constraints file. script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "test_pkg", ) -def test_new_resolver_fails_on_conflicting_constraint_and_requirement(script): +def test_new_resolver_fails_on_conflicting_constraint_and_requirement( + script: PipTestEnvironment, +) -> None: version_1 = create_basic_wheel_for_package( script, "test_pkg", @@ -1576,14 +1776,17 @@ def test_new_resolver_fails_on_conflicting_constraint_and_requirement(script): ) constraints_file = script.scratch_path / "constraints.txt" - constraints_file.write_text("test_pkg @ " + path_to_url(version_1)) + constraints_file.write_text(f"test_pkg @ {version_1.as_uri()}") result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "-c", constraints_file, - "test_pkg @ " + path_to_url(version_2), + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "-c", + constraints_file, + f"test_pkg @ {version_2.as_uri()}", expect_error=True, ) @@ -1592,24 +1795,26 @@ def test_new_resolver_fails_on_conflicting_constraint_and_requirement(script): "because these package versions have conflicting dependencies." ) in result.stderr, str(result) - assert_not_installed(script, "test_pkg") + script.assert_not_installed("test_pkg") # Assert that pip works properly in the absence of the constraints file. script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "test_pkg @ " + path_to_url(version_2), + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + f"test_pkg @ {version_2.as_uri()}", ) @pytest.mark.parametrize("editable", [False, True]) -def test_new_resolver_succeeds_on_matching_constraint_and_requirement(script, editable): +def test_new_resolver_succeeds_on_matching_constraint_and_requirement( + script: PipTestEnvironment, editable: bool +) -> None: if editable: source_dir = create_test_package_with_setup( - script, - name="test_pkg", - version="0.1.0" + script, name="test_pkg", version="0.1.0" ) else: source_dir = create_basic_wheel_for_package( @@ -1618,29 +1823,32 @@ def test_new_resolver_succeeds_on_matching_constraint_and_requirement(script, ed "0.1.0", ) - req_line = "test_pkg @ " + path_to_url(source_dir) + req_line = f"test_pkg @ {source_dir.as_uri()}" constraints_file = script.scratch_path / "constraints.txt" constraints_file.write_text(req_line) + last_args: Tuple[str, ...] if editable: - last_args = ("-e", source_dir) + last_args = ("-e", os.fspath(source_dir)) else: last_args = (req_line,) script.pip( "install", - "--no-cache-dir", "--no-index", - "-c", constraints_file, + "--no-cache-dir", + "--no-index", + "-c", + constraints_file, *last_args, ) - assert_installed(script, test_pkg="0.1.0") + script.assert_installed(test_pkg="0.1.0") if editable: assert_editable(script, "test-pkg") -def test_new_resolver_applies_url_constraint_to_dep(script): +def test_new_resolver_applies_url_constraint_to_dep(script: PipTestEnvironment) -> None: version_1 = create_basic_wheel_for_package( script, "dep", @@ -1659,22 +1867,25 @@ def test_new_resolver_applies_url_constraint_to_dep(script): version_2.rename(script.scratch_path / "index" / version_2.name) constraints_file = script.scratch_path / "constraints.txt" - constraints_file.write_text("dep @ " + path_to_url(version_1)) + constraints_file.write_text(f"dep @ {version_1.as_uri()}") script.pip( "install", - "--no-cache-dir", "--no-index", - "-c", constraints_file, - "--find-links", script.scratch_path / "index", + "--no-cache-dir", + "--no-index", + "-c", + constraints_file, + "--find-links", + script.scratch_path / "index", "base", ) - assert_installed(script, dep="0.1.0") + script.assert_installed(dep="0.1.0") def test_new_resolver_handles_compatible_wheel_tags_in_constraint_url( - script, make_fake_wheel -): + script: PipTestEnvironment, make_fake_wheel: MakeFakeWheel +) -> None: initial_path = make_fake_wheel("base", "0.1.0", "fakepy1-fakeabi-fakeplat") constrained = script.scratch_path / "constrained" @@ -1685,28 +1896,35 @@ def test_new_resolver_handles_compatible_wheel_tags_in_constraint_url( initial_path.rename(final_path) constraints_file = script.scratch_path / "constraints.txt" - constraints_file.write_text("base @ " + path_to_url(final_path)) + constraints_file.write_text(f"base @ {final_path.as_uri()}") result = script.pip( "install", - "--implementation", "fakepy", - '--only-binary=:all:', - "--python-version", "1", - "--abi", "fakeabi", - "--platform", "fakeplat", - "--target", script.scratch_path / "target", - "--no-cache-dir", "--no-index", - "-c", constraints_file, + "--implementation", + "fakepy", + "--only-binary=:all:", + "--python-version", + "1", + "--abi", + "fakeabi", + "--platform", + "fakeplat", + "--target", + script.scratch_path / "target", + "--no-cache-dir", + "--no-index", + "-c", + constraints_file, "base", ) - dist_info = Path("scratch", "target", "base-0.1.0.dist-info") + dist_info = pathlib.Path("scratch", "target", "base-0.1.0.dist-info") result.did_create(dist_info) def test_new_resolver_handles_incompatible_wheel_tags_in_constraint_url( - script, make_fake_wheel -): + script: PipTestEnvironment, make_fake_wheel: MakeFakeWheel +) -> None: initial_path = make_fake_wheel("base", "0.1.0", "fakepy1-fakeabi-fakeplat") constrained = script.scratch_path / "constrained" @@ -1717,12 +1935,14 @@ def test_new_resolver_handles_incompatible_wheel_tags_in_constraint_url( initial_path.rename(final_path) constraints_file = script.scratch_path / "constraints.txt" - constraints_file.write_text("base @ " + path_to_url(final_path)) + constraints_file.write_text(f"base @ {final_path.as_uri()}") result = script.pip( "install", - "--no-cache-dir", "--no-index", - "-c", constraints_file, + "--no-cache-dir", + "--no-index", + "-c", + constraints_file, "base", expect_error=True, ) @@ -1732,12 +1952,12 @@ def test_new_resolver_handles_incompatible_wheel_tags_in_constraint_url( "dependencies." ) in result.stderr, str(result) - assert_not_installed(script, "base") + script.assert_not_installed("base") def test_new_resolver_avoids_incompatible_wheel_tags_in_constraint_url( - script, make_fake_wheel -): + script: PipTestEnvironment, make_fake_wheel: MakeFakeWheel +) -> None: initial_path = make_fake_wheel("dep", "0.1.0", "fakepy1-fakeabi-fakeplat") constrained = script.scratch_path / "constrained" @@ -1748,19 +1968,15 @@ def test_new_resolver_avoids_incompatible_wheel_tags_in_constraint_url( initial_path.rename(final_path) constraints_file = script.scratch_path / "constraints.txt" - constraints_file.write_text("dep @ " + path_to_url(final_path)) + constraints_file.write_text(f"dep @ {final_path.as_uri()}") index = script.scratch_path / "index" index.mkdir() index_dep = create_basic_wheel_for_package(script, "dep", "0.2.0") - base = create_basic_wheel_for_package( - script, "base", "0.1.0" - ) - base_2 = create_basic_wheel_for_package( - script, "base", "0.2.0", depends=["dep"] - ) + base = create_basic_wheel_for_package(script, "base", "0.1.0") + base_2 = create_basic_wheel_for_package(script, "base", "0.2.0", depends=["dep"]) index_dep.rename(index / index_dep.name) base.rename(index / base.name) @@ -1768,14 +1984,17 @@ def test_new_resolver_avoids_incompatible_wheel_tags_in_constraint_url( script.pip( "install", - "--no-cache-dir", "--no-index", - "-c", constraints_file, - "--find-links", script.scratch_path / "index", + "--no-cache-dir", + "--no-index", + "-c", + constraints_file, + "--find-links", + script.scratch_path / "index", "base", ) - assert_installed(script, base="0.1.0") - assert_not_installed(script, "dep") + script.assert_installed(base="0.1.0") + script.assert_not_installed("dep") @pytest.mark.parametrize( @@ -1832,18 +2051,18 @@ def test_new_resolver_avoids_incompatible_wheel_tags_in_constraint_url( ], ) def test_new_resolver_direct_url_equivalent( - tmp_path, - script, - suffixes_equivalent, - depend_suffix, - request_suffix, -): + tmp_path: pathlib.Path, + script: PipTestEnvironment, + suffixes_equivalent: bool, + depend_suffix: str, + request_suffix: str, +) -> None: pkga = create_basic_wheel_for_package(script, name="pkga", version="1") pkgb = create_basic_wheel_for_package( script, name="pkgb", version="1", - depends=[f"pkga@{path_to_url(pkga)}{depend_suffix}"], + depends=[f"pkga@{pkga.as_uri()}{depend_suffix}"], ) # Make pkgb visible via --find-links, but not pkga. @@ -1856,19 +2075,24 @@ def test_new_resolver_direct_url_equivalent( # URL suffix as specified in pkgb. This should work! script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", str(find_links), - f"{path_to_url(pkga)}{request_suffix}", "pkgb", + "--no-cache-dir", + "--no-index", + "--find-links", + str(find_links), + f"{pkga.as_uri()}{request_suffix}", + "pkgb", expect_error=(not suffixes_equivalent), ) if suffixes_equivalent: - assert_installed(script, pkga="1", pkgb="1") + script.assert_installed(pkga="1", pkgb="1") else: - assert_not_installed(script, "pkga", "pkgb") + script.assert_not_installed("pkga", "pkgb") -def test_new_resolver_direct_url_with_extras(tmp_path, script): +def test_new_resolver_direct_url_with_extras( + tmp_path: pathlib.Path, script: PipTestEnvironment +) -> None: pkg1 = create_basic_wheel_for_package(script, name="pkg1", version="1") pkg2 = create_basic_wheel_for_package( script, @@ -1895,18 +2119,23 @@ def test_new_resolver_direct_url_with_extras(tmp_path, script): # URL pkg2 should be able to provide pkg2[ext] required by pkg3. result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", str(find_links), - pkg2, "pkg3", + "--no-cache-dir", + "--no-index", + "--find-links", + str(find_links), + pkg2, + "pkg3", ) - assert_installed(script, pkg1="1", pkg2="1", pkg3="1") + script.assert_installed(pkg1="1", pkg2="1", pkg3="1") assert not get_created_direct_url(result, "pkg1") assert get_created_direct_url(result, "pkg2") assert not get_created_direct_url(result, "pkg3") -def test_new_resolver_modifies_installed_incompatible(script): +def test_new_resolver_modifies_installed_incompatible( + script: PipTestEnvironment, +) -> None: create_basic_wheel_for_package(script, name="a", version="1") create_basic_wheel_for_package(script, name="a", version="2") create_basic_wheel_for_package(script, name="a", version="3") @@ -1918,8 +2147,10 @@ def test_new_resolver_modifies_installed_incompatible(script): script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "b==1", ) @@ -1929,20 +2160,24 @@ def test_new_resolver_modifies_installed_incompatible(script): # discard b-1 and backtrack, so b-2 is selected instead. script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, "d==1", ) - assert_installed(script, d="1", c="2", b="2", a="2") + script.assert_installed(d="1", c="2", b="2", a="2") -def test_new_resolver_transitively_depends_on_unnamed_local(script): +def test_new_resolver_transitively_depends_on_unnamed_local( + script: PipTestEnvironment, +) -> None: create_basic_wheel_for_package(script, name="certbot-docs", version="1") certbot = create_test_package_with_setup( script, name="certbot", version="99.99.0.dev0", - extras_require={"docs": ["certbot-docs"]} + extras_require={"docs": ["certbot-docs"]}, ) certbot_apache = create_test_package_with_setup( script, @@ -1953,44 +2188,45 @@ def test_new_resolver_transitively_depends_on_unnamed_local(script): script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - f"{certbot}[docs]", certbot_apache, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + f"{certbot}[docs]", + certbot_apache, ) - assert_installed( - script, + script.assert_installed( certbot="99.99.0.dev0", certbot_apache="99.99.0.dev0", certbot_docs="1", ) -def _to_uri(path): - # Something like file:///path/to/package - return pathlib.Path(path).as_uri() - - -def _to_localhost_uri(path): +def _to_localhost_uri(path: pathlib.Path) -> str: # Something like file://localhost/path/to/package - return pathlib.Path(path).as_uri().replace("///", "//localhost/") + return path.as_uri().replace("///", "//localhost/") @pytest.mark.parametrize( "format_dep", [ - pytest.param(_to_uri, id="emptyhost"), + pytest.param(pathlib.Path.as_uri, id="emptyhost"), pytest.param(_to_localhost_uri, id="localhost"), ], ) @pytest.mark.parametrize( "format_input", [ - pytest.param(lambda path: path, id="path"), - pytest.param(_to_uri, id="emptyhost"), + pytest.param(pathlib.Path, id="path"), + pytest.param(pathlib.Path.as_uri, id="emptyhost"), pytest.param(_to_localhost_uri, id="localhost"), ], ) -def test_new_resolver_file_url_normalize(script, format_dep, format_input): +def test_new_resolver_file_url_normalize( + script: PipTestEnvironment, + format_dep: Callable[[pathlib.Path], str], + format_input: Callable[[pathlib.Path], str], +) -> None: lib_a = create_test_package_with_setup( script, name="lib_a", @@ -2005,13 +2241,17 @@ def test_new_resolver_file_url_normalize(script, format_dep, format_input): script.pip( "install", - "--no-cache-dir", "--no-index", - format_input(lib_a), lib_b, + "--no-cache-dir", + "--no-index", + format_input(lib_a), + lib_b, ) script.assert_installed(lib_a="1", lib_b="1") -def test_new_resolver_dont_backtrack_on_extra_if_base_constrained(script): +def test_new_resolver_dont_backtrack_on_extra_if_base_constrained( + script: PipTestEnvironment, +) -> None: create_basic_wheel_for_package(script, "dep", "1.0") create_basic_wheel_for_package(script, "pkg", "1.0", extras={"ext": ["dep"]}) create_basic_wheel_for_package(script, "pkg", "2.0", extras={"ext": ["dep"]}) @@ -2020,10 +2260,90 @@ def test_new_resolver_dont_backtrack_on_extra_if_base_constrained(script): result = script.pip( "install", - "--no-cache-dir", "--no-index", - "--find-links", script.scratch_path, - "--constraint", constraints_file, + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "--constraint", + constraints_file, "pkg[ext]", ) assert "pkg-2.0" not in result.stdout, "Should not try 2.0 due to constraint" script.assert_installed(pkg="1.0", dep="1.0") + + +def test_new_resolver_respect_user_requested_if_extra_is_installed( + script: PipTestEnvironment, +) -> None: + create_basic_wheel_for_package(script, "pkg1", "1.0") + create_basic_wheel_for_package(script, "pkg2", "1.0", extras={"ext": ["pkg1"]}) + create_basic_wheel_for_package(script, "pkg2", "2.0", extras={"ext": ["pkg1"]}) + create_basic_wheel_for_package(script, "pkg3", "1.0", depends=["pkg2[ext]"]) + + # Install pkg3 with an older pkg2. + script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "pkg3", + "pkg2==1.0", + ) + script.assert_installed(pkg3="1.0", pkg2="1.0", pkg1="1.0") + + # Now upgrade both pkg3 and pkg2. pkg2 should be upgraded although pkg2[ext] + # is not requested by the user. + script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "--upgrade", + "pkg3", + "pkg2", + ) + script.assert_installed(pkg3="1.0", pkg2="2.0", pkg1="1.0") + + +def test_new_resolver_do_not_backtrack_on_build_failure( + script: PipTestEnvironment, +) -> None: + create_basic_sdist_for_package(script, "pkg1", "2.0", fails_egg_info=True) + create_basic_wheel_for_package(script, "pkg1", "1.0") + + result = script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "pkg1", + expect_error=True, + ) + + assert "egg_info" in result.stderr + + +def test_new_resolver_works_when_failing_package_builds_are_disallowed( + script: PipTestEnvironment, +) -> None: + create_basic_wheel_for_package(script, "pkg2", "1.0", depends=["pkg1"]) + create_basic_sdist_for_package(script, "pkg1", "2.0", fails_egg_info=True) + create_basic_wheel_for_package(script, "pkg1", "1.0") + constraints_file = script.scratch_path / "constraints.txt" + constraints_file.write_text("pkg1 != 2.0") + + script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--find-links", + script.scratch_path, + "-c", + constraints_file, + "pkg2", + ) + + script.assert_installed(pkg2="1.0", pkg1="1.0") |