diff --git a/poetry.lock b/poetry.lock index cf6819628b1..6723830c407 100644 --- a/poetry.lock +++ b/poetry.lock @@ -186,46 +186,6 @@ category = "main" optional = false python-versions = ">=3.6,<4.0" -[[package]] -name = "cryptography" -version = "3.2.1" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" - -[package.dependencies] -cffi = ">=1.8,<1.11.3 || >1.11.3" -six = ">=1.4.1" - -[package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] -docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] -pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=3.6.0,!=3.9.0,!=3.9.1,!=3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] - -[[package]] -name = "cryptography" -version = "3.3.2" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" - -[package.dependencies] -cffi = ">=1.12" -enum34 = {version = "*", markers = "python_version < \"3\""} -ipaddress = {version = "*", markers = "python_version < \"3\""} -six = ">=1.4.1" - -[package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] -docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] -pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=3.6.0,!=3.9.0,!=3.9.1,!=3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] - [[package]] name = "cryptography" version = "3.4.8" @@ -396,25 +356,6 @@ zipp = {version = ">=0.4", markers = "python_version < \"3.8\""} [package.extras] docs = ["sphinx", "rst.linker", "jaraco.packaging"] -[[package]] -name = "ipaddress" -version = "1.0.23" -description = "IPv4/IPv6 manipulation library" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "jeepney" -version = "0.4.3" -description = "Low-level, pure Python DBus protocol wrapper." -category = "main" -optional = false -python-versions = ">=3.5" - -[package.extras] -dev = ["testpath"] - [[package]] name = "jeepney" version = "0.7.1" @@ -515,14 +456,6 @@ python-versions = "*" [package.dependencies] six = ">=1.0.0,<2.0.0" -[[package]] -name = "more-itertools" -version = "7.2.0" -description = "More routines for operating on iterables, beyond itertools" -category = "dev" -optional = false -python-versions = ">=3.4" - [[package]] name = "more-itertools" version = "8.10.0" @@ -624,18 +557,25 @@ dev = ["pre-commit", "tox"] [[package]] name = "poetry-core" -version = "1.0.5" +version = "1.0.6-alpha.0" description = "Poetry PEP 517 Build Backend" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "~2.7 || ^3.5" +develop = false [package.dependencies] -enum34 = {version = ">=1.1.10,<2.0.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\""} -functools32 = {version = ">=3.2.3-2,<4.0.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\""} -importlib-metadata = {version = ">=1.7.0,<2.0.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.5\" and python_version < \"3.8\""} -pathlib2 = {version = ">=2.3.5,<3.0.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\""} -typing = {version = ">=3.7.4.1,<4.0.0.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\""} +enum34 = {version = "^1.1.10", markers = "python_version >= \"2.7\" and python_version < \"2.8\""} +functools32 = {version = "^3.2.3-2", markers = "python_version >= \"2.7\" and python_version < \"2.8\""} +importlib-metadata = {version = "^1.7.0", markers = "python_version >= \"2.7\" and python_version < \"2.8\" or python_version >= \"3.5\" and python_version < \"3.8\""} +pathlib2 = {version = "^2.3.5", markers = "python_version >= \"2.7\" and python_version < \"2.8\""} +typing = {version = "^3.7.4.1", markers = "python_version >= \"2.7\" and python_version < \"2.8\""} + +[package.source] +type = "git" +url = "https://github.com/awilkins/poetry-core.git" +reference = "fix/support-guaranteed-hashes" +resolved_reference = "a08fcaf8f4f833c771786dc5a54f960085e61fad" [[package]] name = "pre-commit" @@ -858,18 +798,6 @@ cryptography = "*" [package.extras] dbus-python = ["dbus-python"] -[[package]] -name = "secretstorage" -version = "3.2.0" -description = "Python bindings to FreeDesktop.org Secret Service API" -category = "main" -optional = false -python-versions = ">=3.5" - -[package.dependencies] -cryptography = ">=2.0" -jeepney = ">=0.4.2" - [[package]] name = "secretstorage" version = "3.3.1" @@ -989,6 +917,9 @@ category = "main" optional = false python-versions = "*" +[package.dependencies] +typing = {version = ">=3.7.4", markers = "python_version < \"3.5\""} + [[package]] name = "urllib3" version = "1.25.11" @@ -1061,7 +992,7 @@ testing = ["pathlib2", "unittest2", "jaraco.itertools", "func-timeout"] [metadata] lock-version = "1.1" python-versions = "~2.7 || ^3.5" -content-hash = "24a401f74f301836bdb4c066edcb766d1c0702a96151add3145e7b389928a94f" +content-hash = "c96fb73a1891caddb166cd5f0d68aad1510f9c660f4641ff11ff93f38b1c0a0d" [metadata.files] atomicwrites = [ @@ -1226,42 +1157,6 @@ crashtest = [ {file = "crashtest-0.3.1.tar.gz", hash = "sha256:42ca7b6ce88b6c7433e2ce47ea884e91ec93104a4b754998be498a8e6c3d37dd"}, ] cryptography = [ - {file = "cryptography-3.2.1-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7"}, - {file = "cryptography-3.2.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d"}, - {file = "cryptography-3.2.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33"}, - {file = "cryptography-3.2.1-cp27-cp27m-win32.whl", hash = "sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297"}, - {file = "cryptography-3.2.1-cp27-cp27m-win_amd64.whl", hash = "sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4"}, - {file = "cryptography-3.2.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8"}, - {file = "cryptography-3.2.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e"}, - {file = "cryptography-3.2.1-cp35-abi3-macosx_10_10_x86_64.whl", hash = "sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b"}, - {file = "cryptography-3.2.1-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df"}, - {file = "cryptography-3.2.1-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77"}, - {file = "cryptography-3.2.1-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7"}, - {file = "cryptography-3.2.1-cp35-cp35m-win32.whl", hash = "sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b"}, - {file = "cryptography-3.2.1-cp35-cp35m-win_amd64.whl", hash = "sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7"}, - {file = "cryptography-3.2.1-cp36-abi3-win32.whl", hash = "sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f"}, - {file = "cryptography-3.2.1-cp36-abi3-win_amd64.whl", hash = "sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538"}, - {file = "cryptography-3.2.1-cp36-cp36m-win32.whl", hash = "sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b"}, - {file = "cryptography-3.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13"}, - {file = "cryptography-3.2.1-cp37-cp37m-win32.whl", hash = "sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e"}, - {file = "cryptography-3.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb"}, - {file = "cryptography-3.2.1-cp38-cp38-win32.whl", hash = "sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b"}, - {file = "cryptography-3.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851"}, - {file = "cryptography-3.2.1.tar.gz", hash = "sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3"}, - {file = "cryptography-3.3.2-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:541dd758ad49b45920dda3b5b48c968f8b2533d8981bcdb43002798d8f7a89ed"}, - {file = "cryptography-3.3.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:49570438e60f19243e7e0d504527dd5fe9b4b967b5a1ff21cc12b57602dd85d3"}, - {file = "cryptography-3.3.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a4ac9648d39ce71c2f63fe7dc6db144b9fa567ddfc48b9fde1b54483d26042"}, - {file = "cryptography-3.3.2-cp27-cp27m-win32.whl", hash = "sha256:aa4969f24d536ae2268c902b2c3d62ab464b5a66bcb247630d208a79a8098e9b"}, - {file = "cryptography-3.3.2-cp27-cp27m-win_amd64.whl", hash = "sha256:1bd0ccb0a1ed775cd7e2144fe46df9dc03eefd722bbcf587b3e0616ea4a81eff"}, - {file = "cryptography-3.3.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e18e6ab84dfb0ab997faf8cca25a86ff15dfea4027b986322026cc99e0a892da"}, - {file = "cryptography-3.3.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:c7390f9b2119b2b43160abb34f63277a638504ef8df99f11cb52c1fda66a2e6f"}, - {file = "cryptography-3.3.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:0d7b69674b738068fa6ffade5c962ecd14969690585aaca0a1b1fc9058938a72"}, - {file = "cryptography-3.3.2-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:922f9602d67c15ade470c11d616f2b2364950602e370c76f0c94c94ae672742e"}, - {file = "cryptography-3.3.2-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:a0f0b96c572fc9f25c3f4ddbf4688b9b38c69836713fb255f4a2715d93cbaf44"}, - {file = "cryptography-3.3.2-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:a777c096a49d80f9d2979695b835b0f9c9edab73b59e4ceb51f19724dda887ed"}, - {file = "cryptography-3.3.2-cp36-abi3-win32.whl", hash = "sha256:3c284fc1e504e88e51c428db9c9274f2da9f73fdf5d7e13a36b8ecb039af6e6c"}, - {file = "cryptography-3.3.2-cp36-abi3-win_amd64.whl", hash = "sha256:7951a966613c4211b6612b0352f5bf29989955ee592c4a885d8c7d0f830d0433"}, - {file = "cryptography-3.3.2.tar.gz", hash = "sha256:5a60d3780149e13b7a6ff7ad6526b38846354d11a15e21068e57073e29e19bed"}, {file = "cryptography-3.4.8-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14"}, {file = "cryptography-3.4.8-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7"}, {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0a7dcbcd3f1913f664aca35d47c1331fce738d44ec34b7be8b9d332151b0b01e"}, @@ -1335,13 +1230,7 @@ importlib-resources = [ {file = "importlib_resources-3.2.1-py2.py3-none-any.whl", hash = "sha256:e2860cf0c4bc999947228d18be154fa3779c5dde0b882bd2d7b3f4d25e698bd6"}, {file = "importlib_resources-3.2.1.tar.gz", hash = "sha256:a9fe213ab6452708ec1b3f4ec6f2881b8ab3645cb4e5efb7fea2bbf05a91db3b"}, ] -ipaddress = [ - {file = "ipaddress-1.0.23-py2.py3-none-any.whl", hash = "sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc"}, - {file = "ipaddress-1.0.23.tar.gz", hash = "sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2"}, -] jeepney = [ - {file = "jeepney-0.4.3-py3-none-any.whl", hash = "sha256:d6c6b49683446d2407d2fe3acb7a368a77ff063f9182fe427da15d622adc24cf"}, - {file = "jeepney-0.4.3.tar.gz", hash = "sha256:3479b861cc2b6407de5188695fa1a8d57e5072d7059322469b62628869b8e36e"}, {file = "jeepney-0.7.1-py3-none-any.whl", hash = "sha256:1b5a0ea5c0e7b166b2f5895b91a08c14de8915afda4407fb5022a195224958ac"}, {file = "jeepney-0.7.1.tar.gz", hash = "sha256:fa9e232dfa0c498bd0b8a3a73b8d8a31978304dcef0515adc859d4e096f96f4f"}, ] @@ -1365,8 +1254,6 @@ more-itertools = [ {file = "more-itertools-5.0.0.tar.gz", hash = "sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4"}, {file = "more_itertools-5.0.0-py2-none-any.whl", hash = "sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc"}, {file = "more_itertools-5.0.0-py3-none-any.whl", hash = "sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9"}, - {file = "more-itertools-7.2.0.tar.gz", hash = "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832"}, - {file = "more_itertools-7.2.0-py3-none-any.whl", hash = "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"}, {file = "more-itertools-8.10.0.tar.gz", hash = "sha256:1debcabeb1df793814859d64a81ad7cb10504c24349368ccf214c664c474f41f"}, {file = "more_itertools-8.10.0-py3-none-any.whl", hash = "sha256:56ddac45541718ba332db05f464bebfb0768110111affd27f66e0051f276fa43"}, ] @@ -1426,10 +1313,7 @@ pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] -poetry-core = [ - {file = "poetry-core-1.0.5.tar.gz", hash = "sha256:8cad9893ea70e344b2ce1a75d3834ba3fb1bac8123f89aad758e59e2a5d67805"}, - {file = "poetry_core-1.0.5-py2.py3-none-any.whl", hash = "sha256:affb0d841d897fbc214fa00765ada432582dcb93b8d0d3a5696712dd96f67dde"}, -] +poetry-core = [] pre-commit = [ {file = "pre_commit-2.15.0-py2.py3-none-any.whl", hash = "sha256:a4ed01000afcb484d9eb8d504272e642c4c4099bbad3a6b27e519bd6a3e928a6"}, {file = "pre_commit-2.15.0.tar.gz", hash = "sha256:3c25add78dbdfb6a28a651780d5c311ac40dd17f160eb3954a0c59da40a505a7"}, @@ -1521,8 +1405,6 @@ scandir = [ ] secretstorage = [ {file = "SecretStorage-2.3.1.tar.gz", hash = "sha256:3af65c87765323e6f64c83575b05393f9e003431959c9395d1791d51497f29b6"}, - {file = "SecretStorage-3.2.0-py3-none-any.whl", hash = "sha256:ed5279d788af258e4676fa26b6efb6d335a31f1f9f529b6f1e200f388fac33e1"}, - {file = "SecretStorage-3.2.0.tar.gz", hash = "sha256:46305c3847ee3f7252b284e0eee5590fa6341c891104a2fd2313f8798c615a82"}, {file = "SecretStorage-3.3.1-py3-none-any.whl", hash = "sha256:422d82c36172d88d6a0ed5afdec956514b189ddbfb72fefab0c8a1cee4eaf71f"}, {file = "SecretStorage-3.3.1.tar.gz", hash = "sha256:fd666c51a6bf200643495a04abb261f83229dcb6fd8472ec393df7ffc8b6f195"}, ] diff --git a/poetry/installation/executor.py b/poetry/installation/executor.py index ba117b3a7ad..79bf4e34a49 100644 --- a/poetry/installation/executor.py +++ b/poetry/installation/executor.py @@ -608,16 +608,30 @@ def _download_link(self, operation, link): archive = self._chef.prepare(archive) if package.files: - archive_hash = ( - "sha256:" - + FileDependency( - package.name, - Path(archive.path) if isinstance(archive, Link) else archive, - ).hash() - ) - if archive_hash not in {f["hash"] for f in package.files}: + hashes = {f["hash"] for f in package.files} + hash_types = {h.split(":")[0] for h in hashes} + archive_hashes = set() + for hash_type in hash_types: + archive_hashes.add( + "{}:{}".format( + hash_type, + FileDependency( + package.name, + Path(archive.path) + if isinstance(archive, Link) + else archive, + ).hash(hash_type), + ) + ) + + if archive_hashes.isdisjoint(hashes): raise RuntimeError( - "Invalid hash for {} using archive {}".format(package, archive.name) + "Invalid hashes ({}) for {} using archive {}. Expected one of {}.".format( + ", ".join(sorted(archive_hashes)), + package, + archive.name, + ", ".join(sorted(hashes)), + ) ) return archive diff --git a/pyproject.toml b/pyproject.toml index cfd0c83d983..5954bc8f68d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ [tool.poetry.dependencies] python = "~2.7 || ^3.5" -poetry-core = "~1.0.5" +poetry-core = { git = "https://github.com/awilkins/poetry-core.git", branch = "fix/support-guaranteed-hashes" } cleo = "^0.8.1" clikit = "^0.6.2" crashtest = { version = "^0.3.0", python = "^3.6" } diff --git a/tests/installation/test_executor.py b/tests/installation/test_executor.py index bb659321d0f..db889312e83 100644 --- a/tests/installation/test_executor.py +++ b/tests/installation/test_executor.py @@ -11,6 +11,8 @@ from poetry.config.config import Config from poetry.core.packages.package import Package +from poetry.core.packages.utils.link import Link +from poetry.installation.chef import Chef from poetry.installation.executor import Executor from poetry.installation.operations import Install from poetry.installation.operations import Uninstall @@ -251,3 +253,78 @@ def test_executor_should_delete_incomplete_downloads( executor._download(Install(Package("tomlkit", "0.5.3"))) assert not destination_fixture.exists() + + +def test_executor_should_check_every_possible_hash_types( + config, io, pool, mocker, fixture_dir, tmp_dir +): + mocker.patch.object( + Chef, "get_cached_archive_for_link", side_effect=lambda link: link, + ) + mocker.patch.object( + Executor, + "_download_archive", + return_value=fixture_dir("distributions").joinpath( + "demo-0.1.0-py2.py3-none-any.whl" + ), + ) + + env = MockEnv(path=Path(tmp_dir)) + executor = Executor(env, pool, config, io) + + package = Package("demo", "0.1.0") + package.files = [ + { + "file": "demo-0.1.0-py2.py3-none-any.whl", + "hash": "md5:15507846fd4299596661d0197bfb4f90", + } + ] + + archive = executor._download_link( + Install(package), Link("https://example.com/demo-0.1.0-py2.py3-none-any.whl") + ) + + assert archive == fixture_dir("distributions").joinpath( + "demo-0.1.0-py2.py3-none-any.whl" + ) + + +def test_executor_should_check_every_possible_hash_types_before_failing( + config, io, pool, mocker, fixture_dir, tmp_dir +): + mocker.patch.object( + Chef, "get_cached_archive_for_link", side_effect=lambda link: link, + ) + mocker.patch.object( + Executor, + "_download_archive", + return_value=fixture_dir("distributions").joinpath( + "demo-0.1.0-py2.py3-none-any.whl" + ), + ) + + env = MockEnv(path=Path(tmp_dir)) + executor = Executor(env, pool, config, io) + + package = Package("demo", "0.1.0") + package.files = [ + {"file": "demo-0.1.0-py2.py3-none-any.whl", "hash": "md5:123456"}, + {"file": "demo-0.1.0-py2.py3-none-any.whl", "hash": "sha256:123456"}, + ] + + with pytest.raises(RuntimeError) as e: + executor._download_link( + Install(package), + Link("https://example.com/demo-0.1.0-py2.py3-none-any.whl"), + ) + + expected_message = ( + "Invalid hashes " + "(" + "md5:15507846fd4299596661d0197bfb4f90, " + "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a" + ") " + "for demo (0.1.0) using archive demo-0.1.0-py2.py3-none-any.whl. " + "Expected one of md5:123456, sha256:123456." + ) + assert str(e.value) == expected_message