From 41931357c246f3bffe3fef9239a8266c9a879b5e Mon Sep 17 00:00:00 2001 From: Max W Chase Date: Sun, 17 Jan 2021 23:01:24 -0500 Subject: [PATCH] Add URL constraint tests for #8253 --- tests/functional/test_new_resolver.py | 510 +++++++++++++++++++ tests/functional/test_new_resolver_hashes.py | 270 ++++++++++ 2 files changed, 780 insertions(+) diff --git a/tests/functional/test_new_resolver.py b/tests/functional/test_new_resolver.py index 16f9f4f4216..a461a5f5d38 100644 --- a/tests/functional/test_new_resolver.py +++ b/tests/functional/test_new_resolver.py @@ -10,6 +10,7 @@ create_basic_sdist_for_package, create_basic_wheel_for_package, create_test_package_with_setup, + path_to_url, ) from tests.lib.wheel import make_wheel @@ -1278,3 +1279,512 @@ def test_new_resolver_no_fetch_no_satisfying(script): "myuberpkg", ) assert "Processing " not in result.stdout, str(result) + + +def test_new_resolver_does_not_install_unneeded_packages_with_url_constraint(script): + archive_path = create_basic_wheel_for_package( + script, + "installed", + "0.1.0", + ) + not_installed_path = create_basic_wheel_for_package( + script, + "not_installed", + "0.1.0", + ) + + constraints_file = script.scratch_path / "constraints.txt" + constraints_file.write_text("not_installed @ " + path_to_url(not_installed_path)) + + (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" + ) + + assert_installed(script, installed="0.1.0") + assert_not_installed(script, "not_installed") + + +def test_new_resolver_installs_packages_with_url_constraint(script): + installed_path = create_basic_wheel_for_package( + script, + "installed", + "0.1.0", + ) + + constraints_file = script.scratch_path / "constraints.txt" + constraints_file.write_text("installed @ " + path_to_url(installed_path)) + + script.pip( + "install", + "--no-cache-dir", "--no-index", + "-c", constraints_file, + "installed" + ) + + assert_installed(script, installed="0.1.0") + + +def test_new_resolver_reinstall_link_requirement_with_constraint(script): + installed_path = create_basic_wheel_for_package( + script, + "installed", + "0.1.0", + ) + + cr_file = script.scratch_path / "constraints.txt" + cr_file.write_text("installed @ " + path_to_url(installed_path)) + + script.pip( + "install", + "--no-cache-dir", "--no-index", + "-r", cr_file, + "installed" + ) + + script.pip( + "install", + "--no-cache-dir", "--no-index", + "-c", cr_file, + "-r", cr_file, + "installed" + ) + # 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") + + +def test_new_resolver_prefers_url_constraint(script): + installed_path = create_basic_wheel_for_package( + script, + "test_pkg", + "0.1.0", + ) + not_installed_path = create_basic_wheel_for_package( + script, + "test_pkg", + "0.2.0", + ) + + constraints_file = script.scratch_path / "constraints.txt" + constraints_file.write_text("test_pkg @ " + path_to_url(installed_path)) + + (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" + ) + + assert_installed(script, test_pkg="0.1.0") + + +def test_new_resolver_prefers_url_constraint_on_update(script): + installed_path = create_basic_wheel_for_package( + script, + "test_pkg", + "0.1.0", + ) + not_installed_path = create_basic_wheel_for_package( + script, + "test_pkg", + "0.2.0", + ) + + constraints_file = script.scratch_path / "constraints.txt" + constraints_file.write_text("test_pkg @ " + path_to_url(installed_path)) + + (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" + ) + + assert_installed(script, test_pkg="0.2.0") + + script.pip( + "install", + "--no-cache-dir", "--no-index", + "--find-links", script.scratch_path / "index", + "-c", constraints_file, + "test_pkg" + ) + + assert_installed(script, test_pkg="0.1.0") + + +def test_new_resolver_fails_with_url_constraint_and_impossible_requirement(script): + not_installed_path = create_basic_wheel_for_package( + script, + "test_pkg", + "0.2.0", + ) + + constraints_file = script.scratch_path / "constraints.txt" + constraints_file.write_text("test_pkg @ " + path_to_url(not_installed_path)) + + script.pip( + "install", + "--no-cache-dir", "--no-index", + "-c", constraints_file, + "test_pkg<0.1.0", + expect_error=True, + ) + + assert_not_installed(script, "test_pkg") + + +def test_new_resolver_fails_with_url_constraint_and_impossible_constraint(script): + not_installed_path = create_basic_wheel_for_package( + script, + "test_pkg", + "0.2.0", + ) + + constraints_file = script.scratch_path / "constraints.txt" + constraints_file.write_text( + "test_pkg<0.1.0\ntest_pkg @ " + path_to_url(not_installed_path) + ) + + script.pip( + "install", + "--no-cache-dir", "--no-index", + "-c", constraints_file, + "test_pkg", + expect_error=True, + ) + + assert_not_installed(script, "test_pkg") + + +def test_new_resolver_ignores_unneeded_conflicting_constraints(script): + version_1 = create_basic_wheel_for_package( + script, + "test_pkg", + "0.1.0", + ) + version_2 = create_basic_wheel_for_package( + script, + "test_pkg", + "0.2.0", + ) + create_basic_wheel_for_package( + script, + "installed", + "0.1.0", + ) + + constraints = [ + "test_pkg @ " + path_to_url(version_1), + "test_pkg @ " + path_to_url(version_2), + ] + + constraints_file = script.scratch_path / "constraints.txt" + constraints_file.write_text("\n".join(constraints)) + + script.pip( + "install", + "--no-cache-dir", "--no-index", + "-c", constraints_file, + "installed" + ) + + assert_not_installed(script, "test_pkg") + assert_installed(script, installed="0.1.0") + + +def test_new_resolver_fails_on_needed_conflicting_constraints(script): + version_1 = create_basic_wheel_for_package( + script, + "test_pkg", + "0.1.0", + ) + version_2 = create_basic_wheel_for_package( + script, + "test_pkg", + "0.2.0", + ) + + constraints = [ + "test_pkg @ " + path_to_url(version_1), + "test_pkg @ " + path_to_url(version_2), + ] + + constraints_file = script.scratch_path / "constraints.txt" + constraints_file.write_text("\n".join(constraints)) + + script.pip( + "install", + "--no-cache-dir", "--no-index", + "-c", constraints_file, + "test_pkg", + expect_error=True, + ) + + assert_not_installed(script, "test_pkg") + + +def test_new_resolver_fails_on_conflicting_constraint_and_requirement(script): + version_1 = create_basic_wheel_for_package( + script, + "test_pkg", + "0.1.0", + ) + version_2 = create_basic_wheel_for_package( + script, + "test_pkg", + "0.2.0", + ) + + constraints_file = script.scratch_path / "constraints.txt" + constraints_file.write_text("test_pkg @ " + path_to_url(version_1)) + + script.pip( + "install", + "--no-cache-dir", "--no-index", + "-c", constraints_file, + "test_pkg @ " + path_to_url(version_2), + expect_error=True, + ) + + assert_not_installed(script, "test_pkg") + + +def test_new_resolver_succeeds_on_matching_constraint_and_requirement(script): + version_1 = create_basic_wheel_for_package( + script, + "test_pkg", + "0.1.0", + ) + + constraints_file = script.scratch_path / "constraints.txt" + constraints_file.write_text("test_pkg @ " + path_to_url(version_1)) + + script.pip( + "install", + "--no-cache-dir", "--no-index", + "-c", constraints_file, + "test_pkg @ " + path_to_url(version_1), + ) + + assert_installed(script, test_pkg="0.1.0") + + +def test_new_resolver_applies_url_constraint_to_dep(script): + version_1 = create_basic_wheel_for_package( + script, + "dep", + "0.1.0", + ) + version_2 = create_basic_wheel_for_package( + script, + "dep", + "0.2.0", + ) + + base = create_basic_wheel_for_package(script, "base", "0.1.0", depends=["dep"]) + + (script.scratch_path / "index").mkdir() + base.rename(script.scratch_path / "index" / base.name) + 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)) + + script.pip( + "install", + "--no-cache-dir", "--no-index", + "-c", constraints_file, + "--find-links", script.scratch_path / "index", + "base", + ) + + assert_installed(script, dep="0.1.0") + + +def test_new_resolver_url_constraint_fails_dep_with_conflicting_url_req(script): + version_1 = create_basic_wheel_for_package( + script, + "dep", + "0.1.0", + ) + version_2 = create_basic_wheel_for_package( + script, + "dep", + "0.2.0", + ) + + base = create_basic_wheel_for_package(script, "base", "0.1.0", depends=["dep"]) + base_2 = create_basic_wheel_for_package( + script, "base", "0.2.0", depends=["dep @ " + path_to_url(version_2)] + ) + + (script.scratch_path / "index").mkdir() + base.rename(script.scratch_path / "index" / base.name) + base_2.rename(script.scratch_path / "index" / base_2.name) + + constraints_file = script.scratch_path / "constraints.txt" + constraints_file.write_text("dep @ " + path_to_url(version_1)) + + script.pip( + "install", + "--no-cache-dir", "--no-index", + "-c", constraints_file, + "--find-links", script.scratch_path / "index", + "base", + expect_error=True, + ) + + assert_installed(script, dep="0.1.0", base="0.1.0") + + +def test_new_resolver_url_constraint_selects_dep_with_matching_url_req(script): + version_1 = create_basic_wheel_for_package( + script, + "dep", + "0.1.0", + ) + version_2 = create_basic_wheel_for_package( + script, + "dep", + "0.2.0", + ) + + base = create_basic_wheel_for_package( + script, "base", "0.1.0", depends=["dep @ " + path_to_url(version_1)] + ) + base_2 = create_basic_wheel_for_package( + script, "base", "0.2.0", depends=["dep @ " + path_to_url(version_2)] + ) + + (script.scratch_path / "index").mkdir() + base.rename(script.scratch_path / "index" / base.name) + base_2.rename(script.scratch_path / "index" / base_2.name) + + constraints_file = script.scratch_path / "constraints.txt" + constraints_file.write_text("dep @ " + path_to_url(version_1)) + + script.pip( + "install", + "--no-cache-dir", "--no-index", + "-c", constraints_file, + "--find-links", script.scratch_path / "index", + "base", + ) + + assert_installed(script, dep="0.1.0", base="0.1.0") + + +def test_new_resolver_handles_compatible_wheel_tags_in_constraint_url(script): + initial_path = create_basic_wheel_for_package(script, "base", "0.1.0") + + constrained = script.scratch_path / "constrained" + constrained.mkdir() + + final_path = constrained / initial_path.name.replace( + "py2.py3-none-any", "fakepy1-fakeabi-fakeplat" + ) + + initial_path.rename(final_path) + + constraints_file = script.scratch_path / "constraints.txt" + constraints_file.write_text("base @ " + path_to_url(final_path)) + + result = script.pip( + "install", + "--implementation", "fakepy", + "--python-version", "1", + "--abi", "fakeabi", + "--platform", "fakeplat", + "--target", script.scratch_path / "target", + "--no-cache-dir", "--no-index", + "-c", constraints_file, + "base", + ) + + dist_info = script.scratch_path / "target" / "base-0.1.0.dist-info" + result.did_create(dist_info) + + +def test_new_resolver_handles_incompatible_wheel_tags_in_constraint_url(script): + initial_path = create_basic_wheel_for_package(script, "base", "0.1.0") + + constrained = script.scratch_path / "constrained" + constrained.mkdir() + + final_path = constrained / initial_path.name.replace( + "py2.py3-none-any", "fakepy1-fakeabi-fakeplat" + ) + + initial_path.rename(final_path) + + constraints_file = script.scratch_path / "constraints.txt" + constraints_file.write_text("base @ " + path_to_url(final_path)) + + script.pip( + "install", + "--no-cache-dir", "--no-index", + "-c", constraints_file, + "base", + expect_error=True, + ) + + assert_not_installed(script, "base") + + +def test_new_resolver_avoids_incompatible_wheel_tags_in_constraint_url(script): + initial_path = create_basic_wheel_for_package(script, "dep", "0.1.0") + + constrained = script.scratch_path / "constrained" + constrained.mkdir() + + final_path = constrained / initial_path.name.replace( + "py2.py3-none-any", "fakepy1-fakeabi-fakeplat" + ) + + initial_path.rename(final_path) + + constraints_file = script.scratch_path / "constraints.txt" + constraints_file.write_text("dep @ " + path_to_url(final_path)) + + 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"] + ) + + index_dep.rename(index / index_dep.name) + base.rename(index / base.name) + base_2.rename(index / base_2.name) + + script.pip( + "install", + "--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") diff --git a/tests/functional/test_new_resolver_hashes.py b/tests/functional/test_new_resolver_hashes.py index 854b66418ae..ff52ccd9906 100644 --- a/tests/functional/test_new_resolver_hashes.py +++ b/tests/functional/test_new_resolver_hashes.py @@ -1,7 +1,9 @@ import collections import hashlib +import json import pytest +from pip._vendor.packaging.utils import canonicalize_name from pip._internal.utils.urls import path_to_url from tests.lib import create_basic_sdist_for_package, create_basic_wheel_for_package @@ -11,6 +13,30 @@ ) +def assert_installed(script, **kwargs): + ret = script.pip('list', '--format=json') + installed = set( + (canonicalize_name(val['name']), val['version']) + for val in json.loads(ret.stdout) + ) + expected = set((canonicalize_name(k), v) for k, v in kwargs.items()) + assert expected <= installed, \ + "{!r} not all in {!r}".format(expected, installed) + + +def assert_not_installed(script, *args): + ret = script.pip("list", "--format=json") + installed = set( + canonicalize_name(val["name"]) + for val in json.loads(ret.stdout) + ) + # None of the given names should be listed as installed, i.e. their + # intersection should be empty. + expected = set(canonicalize_name(k) for k in args) + assert not (expected & installed), \ + "{!r} contained in {!r}".format(expected, installed) + + def _create_find_links(script): sdist_path = create_basic_sdist_for_package(script, "base", "0.1.0") wheel_path = create_basic_wheel_for_package(script, "base", "0.1.0") @@ -204,3 +230,247 @@ def test_new_resolver_hash_intersect_empty_from_constraint(script): "from some requirements." ) assert message in result.stderr, str(result) + + +def test_new_resolver_hash_requirement_and_url_constraint_can_succeed(script): + wheel_path = create_basic_wheel_for_package(script, "base", "0.1.0") + + wheel_hash = hashlib.sha256(wheel_path.read_bytes()).hexdigest() + + requirements_txt = script.scratch_path / "requirements.txt" + requirements_txt.write_text( + """ + base --hash=sha256:{wheel_hash} + """.format( + wheel_hash=wheel_hash, + ), + ) + + constraints_txt = script.scratch_path / "constraints.txt" + constraints_txt.write_text( + """ + base @ {wheel_url} + """.format( + wheel_url=path_to_url(wheel_path), + ), + ) + + script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--constraint", constraints_txt, + "--requirement", requirements_txt, + ) + + assert_installed(script, base="0.1.0") + + +def test_new_resolver_hash_requirement_and_url_constraint_can_fail(script): + wheel_path = create_basic_wheel_for_package(script, "base", "0.1.0") + other_path = create_basic_wheel_for_package(script, "other", "0.1.0") + + other_hash = hashlib.sha256(other_path.read_bytes()).hexdigest() + + requirements_txt = script.scratch_path / "requirements.txt" + requirements_txt.write_text( + """ + base --hash=sha256:{other_hash} + """.format( + other_hash=other_hash, + ), + ) + + constraints_txt = script.scratch_path / "constraints.txt" + constraints_txt.write_text( + """ + base @ {wheel_url} + """.format( + wheel_url=path_to_url(wheel_path), + ), + ) + + script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--constraint", constraints_txt, + "--requirement", requirements_txt, + expect_error=True, + ) + assert_not_installed(script, "base") + + +def test_new_resolver_hash_constraint_and_url_constraint_can_succeed(script): + wheel_path = create_basic_wheel_for_package(script, "base", "0.1.0") + + wheel_hash = hashlib.sha256(wheel_path.read_bytes()).hexdigest() + + requirements_txt = script.scratch_path / "requirements.txt" + requirements_txt.write_text("base") + + constraints_txt = script.scratch_path / "constraints.txt" + constraints_txt.write_text( + """ + base @ {wheel_url} --hash=sha256:{wheel_hash} + """.format( + wheel_url=path_to_url(wheel_path), + wheel_hash=wheel_hash, + ), + ) + + script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--constraint", constraints_txt, + "--requirement", requirements_txt, + ) + + assert_installed(script, base="0.1.0") + + +def test_new_resolver_hash_constraint_and_url_constraint_can_fail(script): + wheel_path = create_basic_wheel_for_package(script, "base", "0.1.0") + other_path = create_basic_wheel_for_package(script, "other", "0.1.0") + + other_hash = hashlib.sha256(other_path.read_bytes()).hexdigest() + + requirements_txt = script.scratch_path / "requirements.txt" + requirements_txt.write_text("base") + + constraints_txt = script.scratch_path / "constraints.txt" + constraints_txt.write_text( + """ + base @ {wheel_url} --hash=sha256:{other_hash} + """.format( + wheel_url=path_to_url(wheel_path), + other_hash=other_hash, + ), + ) + + script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--constraint", constraints_txt, + "--requirement", requirements_txt, + expect_error=True, + ) + assert_not_installed(script, "base", "other") + + +def test_new_resolver_hash_in_dependency_and_url_constraint_can_succeed(script): + wheel_path = create_basic_wheel_for_package(script, "base", "0.1.0") + + wheel_hash = hashlib.sha256(wheel_path.read_bytes()).hexdigest() + + dependent_path = create_basic_wheel_for_package( + script, "dependent", "0.1.0", + depends=["base --hash=sha256:{wheel_hash}".format(wheel_hash=wheel_hash)] + ) + + (script.scratch_path / "index").mkdir() + dependent_path.rename(script.scratch_path / "index" / dependent_path.name) + + requirements_txt = script.scratch_path / "requirements.txt" + requirements_txt.write_text("dependent") + + constraints_txt = script.scratch_path / "constraints.txt" + constraints_txt.write_text( + """ + base @ {wheel_url} + """.format( + wheel_url=path_to_url(wheel_path), + ), + ) + + script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--find-links", script.scratch_path / "index", + "--constraint", constraints_txt, + "--requirement", requirements_txt, + ) + assert_installed(script, base="0.1.0", dependent="0.1.0") + + +def test_new_resolver_hash_in_dependency_and_url_constraint_can_fail(script): + wheel_path = create_basic_wheel_for_package(script, "base", "0.1.0") + + wheel_hash = hashlib.sha256(wheel_path.read_bytes() + b"garbage").hexdigest() + + dependent_path = create_basic_wheel_for_package( + script, "dependent", "0.1.0", + depends=["base --hash=sha256:{wheel_hash}".format(wheel_hash=wheel_hash)] + ) + + (script.scratch_path / "index").mkdir() + dependent_path.rename(script.scratch_path / "index" / dependent_path.name) + + requirements_txt = script.scratch_path / "requirements.txt" + requirements_txt.write_text("dependent") + + constraints_txt = script.scratch_path / "constraints.txt" + constraints_txt.write_text( + """ + base @ {wheel_url} + """.format( + wheel_url=path_to_url(wheel_path), + ), + ) + + script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--find-links", script.scratch_path / "index", + "--constraint", constraints_txt, + "--requirement", requirements_txt, + expect_error=True, + ) + assert_not_installed(script, "base", "dependent") + + +def test_new_resolver_hash_in_dependency_and_url_constraint_can_require_lesser(script): + wheel_path1 = create_basic_wheel_for_package(script, "base", "0.1.0") + wheel_path2 = create_basic_wheel_for_package(script, "base", "0.2.0") + + wheel_hash1 = hashlib.sha256(wheel_path1.read_bytes()).hexdigest() + wheel_hash2 = hashlib.sha256(wheel_path2.read_bytes()).hexdigest() + + dependent_path1 = create_basic_wheel_for_package( + script, "dependent", "0.1.0", + depends=["base --hash=sha256:{wheel_hash}".format(wheel_hash=wheel_hash1)] + ) + dependent_path2 = create_basic_wheel_for_package( + script, "dependent", "0.2.0", + depends=["base --hash=sha256:{wheel_hash}".format(wheel_hash=wheel_hash2)] + ) + + (script.scratch_path / "index").mkdir() + dependent_path1.rename(script.scratch_path / "index" / dependent_path1.name) + dependent_path2.rename(script.scratch_path / "index" / dependent_path2.name) + + requirements_txt = script.scratch_path / "requirements.txt" + requirements_txt.write_text("dependent") + + constraints_txt = script.scratch_path / "constraints.txt" + constraints_txt.write_text( + """ + base @ {wheel_url} + """.format( + wheel_url=path_to_url(wheel_path1), + ), + ) + + script.pip( + "install", + "--no-cache-dir", + "--no-index", + "--find-links", script.scratch_path / "index", + "--constraint", constraints_txt, + "--requirement", requirements_txt, + ) + assert_installed(script, base="0.1.0", dependent="0.1.0")