2019-11-06 11:08:02 +01:00
|
|
|
import contextlib
|
2021-08-30 00:43:28 +02:00
|
|
|
import email.message
|
2013-03-27 06:26:52 +01:00
|
|
|
import os
|
|
|
|
import shutil
|
2014-01-15 16:57:13 +01:00
|
|
|
import sys
|
2013-05-01 01:02:10 +02:00
|
|
|
import tempfile
|
2019-09-07 00:45:20 +02:00
|
|
|
from functools import partial
|
2021-08-30 00:43:28 +02:00
|
|
|
from typing import Iterator, Tuple, cast
|
|
|
|
from unittest import mock
|
2013-03-27 06:26:52 +01:00
|
|
|
|
2017-06-13 14:17:00 +02:00
|
|
|
import pytest
|
2017-05-16 12:16:30 +02:00
|
|
|
from pip._vendor.packaging.markers import Marker
|
|
|
|
from pip._vendor.packaging.requirements import Requirement
|
2013-08-18 11:59:44 +02:00
|
|
|
|
2019-07-10 09:36:33 +02:00
|
|
|
from pip._internal.commands import create_command
|
2021-08-30 00:43:28 +02:00
|
|
|
from pip._internal.commands.install import InstallCommand
|
2017-08-31 17:48:18 +02:00
|
|
|
from pip._internal.exceptions import (
|
2019-07-22 06:45:27 +02:00
|
|
|
HashErrors,
|
|
|
|
InstallationError,
|
|
|
|
InvalidWheelFilename,
|
|
|
|
PreviousBuildDirError,
|
2017-05-16 12:16:30 +02:00
|
|
|
)
|
2021-08-30 00:43:28 +02:00
|
|
|
from pip._internal.index.package_finder import PackageFinder
|
2021-12-05 01:52:59 +01:00
|
|
|
from pip._internal.metadata import select_backend
|
2019-09-27 00:39:53 +02:00
|
|
|
from pip._internal.network.session import PipSession
|
2022-03-26 11:07:21 +01:00
|
|
|
from pip._internal.operations.build.build_tracker import get_build_tracker
|
2017-08-31 17:48:18 +02:00
|
|
|
from pip._internal.operations.prepare import RequirementPreparer
|
|
|
|
from pip._internal.req import InstallRequirement, RequirementSet
|
2018-08-21 16:32:25 +02:00
|
|
|
from pip._internal.req.constructors import (
|
2019-02-23 19:10:34 +01:00
|
|
|
_get_url_from_path,
|
|
|
|
_looks_like_path,
|
2019-07-22 06:45:27 +02:00
|
|
|
install_req_from_editable,
|
|
|
|
install_req_from_line,
|
2020-02-14 13:22:50 +01:00
|
|
|
install_req_from_parsed_requirement,
|
2019-09-07 00:45:20 +02:00
|
|
|
install_req_from_req_string,
|
2019-07-22 06:45:27 +02:00
|
|
|
parse_editable,
|
2018-08-21 16:32:25 +02:00
|
|
|
)
|
2020-02-13 17:16:32 +01:00
|
|
|
from pip._internal.req.req_file import (
|
|
|
|
ParsedLine,
|
|
|
|
get_line_parser,
|
|
|
|
handle_requirement_line,
|
|
|
|
)
|
2020-02-23 22:17:38 +01:00
|
|
|
from pip._internal.resolution.legacy.resolver import Resolver
|
2019-09-24 10:56:42 +02:00
|
|
|
from pip._internal.utils.urls import path_to_url
|
2021-08-30 00:43:28 +02:00
|
|
|
from tests.lib import TestData, make_test_finder, requirements_file
|
|
|
|
from tests.lib.path import Path
|
2013-03-27 06:26:52 +01:00
|
|
|
|
2013-05-01 01:02:10 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def get_processed_req_from_line(
|
|
|
|
line: str, fname: str = "file", lineno: int = 1
|
|
|
|
) -> InstallRequirement:
|
2019-10-26 17:45:57 +02:00
|
|
|
line_parser = get_line_parser(None)
|
|
|
|
args_str, opts = line_parser(line)
|
|
|
|
parsed_line = ParsedLine(
|
|
|
|
fname,
|
|
|
|
lineno,
|
|
|
|
args_str,
|
|
|
|
opts,
|
|
|
|
False,
|
|
|
|
)
|
2020-02-13 17:16:32 +01:00
|
|
|
parsed_req = handle_requirement_line(parsed_line)
|
2020-02-13 11:18:10 +01:00
|
|
|
assert parsed_req is not None
|
2020-02-14 13:22:50 +01:00
|
|
|
req = install_req_from_parsed_requirement(parsed_req)
|
2020-04-13 11:09:05 +02:00
|
|
|
req.user_supplied = True
|
2018-01-20 13:49:39 +01:00
|
|
|
return req
|
|
|
|
|
|
|
|
|
2020-12-24 22:23:07 +01:00
|
|
|
class TestRequirementSet:
|
2013-03-27 06:26:52 +01:00
|
|
|
"""RequirementSet tests"""
|
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def setup(self) -> None:
|
2013-03-27 06:26:52 +01:00
|
|
|
self.tempdir = tempfile.mkdtemp()
|
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def teardown(self) -> None:
|
2013-03-27 06:26:52 +01:00
|
|
|
shutil.rmtree(self.tempdir, ignore_errors=True)
|
|
|
|
|
2019-11-06 11:08:02 +01:00
|
|
|
@contextlib.contextmanager
|
2021-08-30 00:43:28 +02:00
|
|
|
def _basic_resolver(
|
|
|
|
self, finder: PackageFinder, require_hashes: bool = False
|
|
|
|
) -> Iterator[Resolver]:
|
2019-09-07 00:45:20 +02:00
|
|
|
make_install_req = partial(
|
|
|
|
install_req_from_req_string,
|
|
|
|
isolated=False,
|
|
|
|
use_pep517=None,
|
|
|
|
)
|
2020-08-05 10:39:21 +02:00
|
|
|
session = PipSession()
|
2019-11-06 11:08:02 +01:00
|
|
|
|
2022-03-26 11:04:35 +01:00
|
|
|
with get_build_tracker() as tracker:
|
2019-11-06 11:08:02 +01:00
|
|
|
preparer = RequirementPreparer(
|
2021-08-13 15:23:45 +02:00
|
|
|
build_dir=os.path.join(self.tempdir, "build"),
|
|
|
|
src_dir=os.path.join(self.tempdir, "src"),
|
2019-11-06 11:08:02 +01:00
|
|
|
download_dir=None,
|
|
|
|
build_isolation=True,
|
2022-03-26 11:50:49 +01:00
|
|
|
build_tracker=tracker,
|
2020-08-05 10:39:21 +02:00
|
|
|
session=session,
|
2021-08-13 15:23:45 +02:00
|
|
|
progress_bar="on",
|
2019-11-06 11:08:02 +01:00
|
|
|
finder=finder,
|
|
|
|
require_hashes=require_hashes,
|
2019-11-11 04:10:20 +01:00
|
|
|
use_user_site=False,
|
2020-08-02 17:58:19 +02:00
|
|
|
lazy_wheel=False,
|
2022-01-25 09:54:02 +01:00
|
|
|
verbosity=0,
|
2019-11-06 11:08:02 +01:00
|
|
|
)
|
|
|
|
yield Resolver(
|
|
|
|
preparer=preparer,
|
|
|
|
make_install_req=make_install_req,
|
|
|
|
finder=finder,
|
2020-02-28 10:42:13 +01:00
|
|
|
wheel_cache=None,
|
2021-08-13 15:23:45 +02:00
|
|
|
use_user_site=False,
|
|
|
|
upgrade_strategy="to-satisfy-only",
|
|
|
|
ignore_dependencies=False,
|
|
|
|
ignore_installed=False,
|
|
|
|
ignore_requires_python=False,
|
|
|
|
force_reinstall=False,
|
2019-11-06 11:08:02 +01:00
|
|
|
)
|
2017-05-31 20:45:15 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_no_reuse_existing_build_dir(self, data: TestData) -> None:
|
2013-03-27 06:26:52 +01:00
|
|
|
"""Test prepare_files raise exception with previous build dir"""
|
|
|
|
|
2021-08-13 15:23:45 +02:00
|
|
|
build_dir = os.path.join(self.tempdir, "build", "simple")
|
2013-03-27 06:26:52 +01:00
|
|
|
os.makedirs(build_dir)
|
2021-08-13 15:23:45 +02:00
|
|
|
with open(os.path.join(build_dir, "setup.py"), "w"):
|
2018-08-16 20:25:30 +02:00
|
|
|
pass
|
2017-07-05 20:41:45 +02:00
|
|
|
reqset = RequirementSet()
|
2021-08-13 15:23:45 +02:00
|
|
|
req = install_req_from_line("simple")
|
2020-04-13 11:09:05 +02:00
|
|
|
req.user_supplied = True
|
2013-03-27 06:26:52 +01:00
|
|
|
reqset.add_requirement(req)
|
2019-06-01 18:45:53 +02:00
|
|
|
finder = make_test_finder(find_links=[data.find_links])
|
2019-11-06 11:08:02 +01:00
|
|
|
with self._basic_resolver(finder) as resolver:
|
2021-08-29 03:05:37 +02:00
|
|
|
with pytest.raises(
|
2019-11-06 11:08:02 +01:00
|
|
|
PreviousBuildDirError,
|
2021-08-29 03:05:37 +02:00
|
|
|
match=(
|
|
|
|
r"pip can't proceed with [\s\S]*{req}[\s\S]*{build_dir_esc}".format(
|
|
|
|
build_dir_esc=build_dir.replace("\\", "\\\\"), req=req
|
|
|
|
)
|
2021-08-13 15:23:45 +02:00
|
|
|
),
|
2021-08-29 03:05:37 +02:00
|
|
|
):
|
|
|
|
resolver.resolve(reqset.all_requirements, True)
|
2013-03-27 06:26:52 +01:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_environment_marker_extras(self, data: TestData) -> None:
|
2014-12-07 23:51:46 +01:00
|
|
|
"""
|
|
|
|
Test that the environment marker extras are used with
|
|
|
|
non-wheel installs.
|
|
|
|
"""
|
2017-07-05 20:41:45 +02:00
|
|
|
reqset = RequirementSet()
|
2021-08-13 15:23:45 +02:00
|
|
|
req = install_req_from_editable(data.packages.joinpath("LocalEnvironMarker"))
|
2020-04-13 11:09:05 +02:00
|
|
|
req.user_supplied = True
|
2014-12-07 23:51:46 +01:00
|
|
|
reqset.add_requirement(req)
|
2019-06-01 18:45:53 +02:00
|
|
|
finder = make_test_finder(find_links=[data.find_links])
|
2019-11-06 11:08:02 +01:00
|
|
|
with self._basic_resolver(finder) as resolver:
|
2020-02-06 03:24:23 +01:00
|
|
|
reqset = resolver.resolve(reqset.all_requirements, True)
|
2021-08-20 17:38:58 +02:00
|
|
|
assert not reqset.has_requirement("simple")
|
2014-12-07 23:51:46 +01:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_missing_hash_with_require_hashes(self, data: TestData) -> None:
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
"""Setting --require-hashes explicitly should raise errors if hashes
|
|
|
|
are missing.
|
|
|
|
"""
|
2019-09-23 00:49:34 +02:00
|
|
|
reqset = RequirementSet()
|
2021-08-13 15:23:45 +02:00
|
|
|
reqset.add_requirement(get_processed_req_from_line("simple==1.0", lineno=1))
|
2018-01-20 13:49:39 +01:00
|
|
|
|
2019-06-01 18:45:53 +02:00
|
|
|
finder = make_test_finder(find_links=[data.find_links])
|
2018-01-20 13:49:39 +01:00
|
|
|
|
2019-11-06 11:08:02 +01:00
|
|
|
with self._basic_resolver(finder, require_hashes=True) as resolver:
|
2021-08-29 03:05:37 +02:00
|
|
|
with pytest.raises(
|
2019-11-06 11:08:02 +01:00
|
|
|
HashErrors,
|
2021-08-29 03:05:37 +02:00
|
|
|
match=(
|
|
|
|
r"Hashes are required in --require-hashes mode, but they are "
|
|
|
|
r"missing .*\n"
|
|
|
|
r" simple==1.0 --hash=sha256:393043e672415891885c9a2a0929b1"
|
|
|
|
r"af95fb866d6ca016b42d2e6ce53619b653$"
|
|
|
|
),
|
|
|
|
):
|
|
|
|
resolver.resolve(reqset.all_requirements, True)
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_missing_hash_with_require_hashes_in_reqs_file(
|
|
|
|
self, data: TestData, tmpdir: Path
|
|
|
|
) -> None:
|
2015-10-21 21:50:57 +02:00
|
|
|
"""--require-hashes in a requirements file should make its way to the
|
|
|
|
RequirementSet.
|
|
|
|
"""
|
2019-06-01 18:45:53 +02:00
|
|
|
finder = make_test_finder(find_links=[data.find_links])
|
2019-08-22 08:46:55 +02:00
|
|
|
session = finder._link_collector.session
|
2021-08-30 00:43:28 +02:00
|
|
|
command = cast(InstallCommand, create_command("install"))
|
2021-08-13 15:23:45 +02:00
|
|
|
with requirements_file("--require-hashes", tmpdir) as reqs_file:
|
|
|
|
options, args = command.parse_args(["-r", reqs_file])
|
2020-02-28 10:42:13 +01:00
|
|
|
command.get_requirements(args, options, finder, session)
|
2019-09-23 00:49:34 +02:00
|
|
|
assert options.require_hashes
|
2015-10-21 21:50:57 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_unsupported_hashes(self, data: TestData) -> None:
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
"""VCS and dir links should raise errors when --require-hashes is
|
|
|
|
on.
|
|
|
|
|
|
|
|
In addition, complaints about the type of requirement (VCS or dir)
|
|
|
|
should trump the presence or absence of a hash.
|
|
|
|
|
|
|
|
"""
|
2019-09-23 00:49:34 +02:00
|
|
|
reqset = RequirementSet()
|
2021-08-13 15:23:45 +02:00
|
|
|
reqset.add_requirement(
|
|
|
|
get_processed_req_from_line(
|
|
|
|
"git+git://github.com/pypa/pip-test-package --hash=sha256:123",
|
|
|
|
lineno=1,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
dir_path = data.packages.joinpath("FSPkg")
|
|
|
|
reqset.add_requirement(
|
|
|
|
get_processed_req_from_line(
|
|
|
|
f"file://{dir_path}",
|
|
|
|
lineno=2,
|
|
|
|
)
|
|
|
|
)
|
2019-06-01 18:45:53 +02:00
|
|
|
finder = make_test_finder(find_links=[data.find_links])
|
2019-11-06 11:08:02 +01:00
|
|
|
|
2015-12-01 12:38:39 +01:00
|
|
|
sep = os.path.sep
|
2021-08-13 15:23:45 +02:00
|
|
|
if sep == "\\":
|
|
|
|
sep = "\\\\" # This needs to be escaped for the regex
|
2019-11-06 11:08:02 +01:00
|
|
|
|
|
|
|
with self._basic_resolver(finder, require_hashes=True) as resolver:
|
2021-08-29 03:05:37 +02:00
|
|
|
with pytest.raises(
|
2019-11-06 11:08:02 +01:00
|
|
|
HashErrors,
|
2021-08-29 03:05:37 +02:00
|
|
|
match=(
|
|
|
|
r"Can't verify hashes for these requirements because we don't "
|
|
|
|
r"have a way to hash version control repositories:\n"
|
|
|
|
r" git\+git://github\.com/pypa/pip-test-package \(from -r "
|
|
|
|
r"file \(line 1\)\)\n"
|
|
|
|
r"Can't verify hashes for these file:// requirements because "
|
|
|
|
r"they point to directories:\n"
|
|
|
|
r" file://.*{sep}data{sep}packages{sep}FSPkg "
|
|
|
|
r"\(from -r file \(line 2\)\)".format(sep=sep)
|
|
|
|
),
|
|
|
|
):
|
|
|
|
resolver.resolve(reqset.all_requirements, True)
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_unpinned_hash_checking(self, data: TestData) -> None:
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
"""Make sure prepare_files() raises an error when a requirement is not
|
|
|
|
version-pinned in hash-checking mode.
|
|
|
|
"""
|
2017-07-05 20:41:45 +02:00
|
|
|
reqset = RequirementSet()
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
# Test that there must be exactly 1 specifier:
|
2021-08-13 15:23:45 +02:00
|
|
|
reqset.add_requirement(
|
|
|
|
get_processed_req_from_line(
|
|
|
|
"simple --hash=sha256:a90427ae31f5d1d0d7ec06ee97d9fcf2d0fc9a786985"
|
|
|
|
"250c1c83fd68df5911dd",
|
|
|
|
lineno=1,
|
|
|
|
)
|
|
|
|
)
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
# Test that the operator must be ==:
|
2021-08-13 15:23:45 +02:00
|
|
|
reqset.add_requirement(
|
|
|
|
get_processed_req_from_line(
|
|
|
|
"simple2>1.0 --hash=sha256:3ad45e1e9aa48b4462af0"
|
|
|
|
"123f6a7e44a9115db1ef945d4d92c123dfe21815a06",
|
|
|
|
lineno=2,
|
|
|
|
)
|
|
|
|
)
|
2019-06-01 18:45:53 +02:00
|
|
|
finder = make_test_finder(find_links=[data.find_links])
|
2019-11-06 11:08:02 +01:00
|
|
|
with self._basic_resolver(finder, require_hashes=True) as resolver:
|
2021-08-29 03:05:37 +02:00
|
|
|
with pytest.raises(
|
2019-11-06 11:08:02 +01:00
|
|
|
HashErrors,
|
|
|
|
# Make sure all failing requirements are listed:
|
2021-08-29 03:05:37 +02:00
|
|
|
match=(
|
|
|
|
r"versions pinned with ==. These do not:\n"
|
|
|
|
r" simple .* \(from -r file \(line 1\)\)\n"
|
|
|
|
r" simple2>1.0 .* \(from -r file \(line 2\)\)"
|
|
|
|
),
|
|
|
|
):
|
|
|
|
resolver.resolve(reqset.all_requirements, True)
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_hash_mismatch(self, data: TestData) -> None:
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
"""A hash mismatch should raise an error."""
|
2021-08-13 15:23:45 +02:00
|
|
|
file_url = path_to_url((data.packages / "simple-1.0.tar.gz").resolve())
|
2019-09-23 00:49:34 +02:00
|
|
|
reqset = RequirementSet()
|
2021-08-13 15:23:45 +02:00
|
|
|
reqset.add_requirement(
|
|
|
|
get_processed_req_from_line(
|
|
|
|
f"{file_url} --hash=sha256:badbad",
|
|
|
|
lineno=1,
|
|
|
|
)
|
|
|
|
)
|
2019-06-01 18:45:53 +02:00
|
|
|
finder = make_test_finder(find_links=[data.find_links])
|
2019-11-06 11:08:02 +01:00
|
|
|
with self._basic_resolver(finder, require_hashes=True) as resolver:
|
2021-08-29 03:05:37 +02:00
|
|
|
with pytest.raises(
|
2019-11-06 11:08:02 +01:00
|
|
|
HashErrors,
|
2021-08-29 03:05:37 +02:00
|
|
|
match=(
|
|
|
|
r"THESE PACKAGES DO NOT MATCH THE HASHES.*\n"
|
|
|
|
r" file:///.*/data/packages/simple-1\.0\.tar\.gz .*:\n"
|
|
|
|
r" Expected sha256 badbad\n"
|
|
|
|
r" Got 393043e672415891885c9a2a0929b1af95fb"
|
|
|
|
r"866d6ca016b42d2e6ce53619b653$"
|
|
|
|
),
|
|
|
|
):
|
|
|
|
resolver.resolve(reqset.all_requirements, True)
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_unhashed_deps_on_require_hashes(self, data: TestData) -> None:
|
2015-09-26 01:07:20 +02:00
|
|
|
"""Make sure unhashed, unpinned, or otherwise unrepeatable
|
|
|
|
dependencies get complained about when --require-hashes is on."""
|
2017-07-05 20:41:45 +02:00
|
|
|
reqset = RequirementSet()
|
2019-06-01 18:45:53 +02:00
|
|
|
finder = make_test_finder(find_links=[data.find_links])
|
2021-08-13 15:23:45 +02:00
|
|
|
reqset.add_requirement(
|
|
|
|
get_processed_req_from_line(
|
|
|
|
"TopoRequires2==0.0.1 " # requires TopoRequires
|
|
|
|
"--hash=sha256:eaf9a01242c9f2f42cf2bd82a6a848cd"
|
|
|
|
"e3591d14f7896bdbefcf48543720c970",
|
|
|
|
lineno=1,
|
|
|
|
)
|
|
|
|
)
|
2019-11-06 11:08:02 +01:00
|
|
|
|
|
|
|
with self._basic_resolver(finder, require_hashes=True) as resolver:
|
2021-08-29 03:05:37 +02:00
|
|
|
with pytest.raises(
|
2019-11-06 11:08:02 +01:00
|
|
|
HashErrors,
|
2021-08-29 03:05:37 +02:00
|
|
|
match=(
|
|
|
|
r"In --require-hashes mode, all requirements must have their "
|
|
|
|
r"versions pinned.*\n"
|
|
|
|
r" TopoRequires from .*$"
|
|
|
|
),
|
|
|
|
):
|
|
|
|
resolver.resolve(reqset.all_requirements, True)
|
2015-09-26 01:07:20 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_hashed_deps_on_require_hashes(self) -> None:
|
2015-09-26 01:07:20 +02:00
|
|
|
"""Make sure hashed dependencies get installed when --require-hashes
|
|
|
|
is on.
|
2015-10-07 23:31:40 +02:00
|
|
|
|
|
|
|
(We actually just check that no "not all dependencies are hashed!"
|
|
|
|
error gets raised while preparing; there is no reason to expect
|
|
|
|
installation to then fail, as the code paths are the same as ever.)
|
|
|
|
|
2015-09-26 01:07:20 +02:00
|
|
|
"""
|
2017-07-05 20:41:45 +02:00
|
|
|
reqset = RequirementSet()
|
2021-08-13 15:23:45 +02:00
|
|
|
reqset.add_requirement(
|
|
|
|
get_processed_req_from_line(
|
|
|
|
"TopoRequires2==0.0.1 " # requires TopoRequires
|
|
|
|
"--hash=sha256:eaf9a01242c9f2f42cf2bd82a6a848cd"
|
|
|
|
"e3591d14f7896bdbefcf48543720c970",
|
|
|
|
lineno=1,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
reqset.add_requirement(
|
|
|
|
get_processed_req_from_line(
|
|
|
|
"TopoRequires==0.0.1 "
|
|
|
|
"--hash=sha256:d6dd1e22e60df512fdcf3640ced3039b3b02a56ab2cee81ebcb"
|
|
|
|
"3d0a6d4e8bfa6",
|
|
|
|
lineno=2,
|
|
|
|
)
|
|
|
|
)
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
|
2013-05-28 23:58:08 +02:00
|
|
|
|
2020-12-24 22:23:07 +01:00
|
|
|
class TestInstallRequirement:
|
2021-08-30 00:43:28 +02:00
|
|
|
def setup(self) -> None:
|
2016-03-10 03:55:20 +01:00
|
|
|
self.tempdir = tempfile.mkdtemp()
|
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def teardown(self) -> None:
|
2016-03-10 03:55:20 +01:00
|
|
|
shutil.rmtree(self.tempdir, ignore_errors=True)
|
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_url_with_query(self) -> None:
|
2013-11-15 01:35:24 +01:00
|
|
|
"""InstallRequirement should strip the fragment, but not the query."""
|
2021-08-13 15:23:45 +02:00
|
|
|
url = "http://foo.com/?p=bar.git;a=snapshot;h=v0.1;sf=tgz"
|
|
|
|
fragment = "#egg=bar"
|
2018-08-21 17:07:40 +02:00
|
|
|
req = install_req_from_line(url + fragment)
|
2021-08-30 00:43:28 +02:00
|
|
|
assert req.link is not None
|
2014-12-28 16:50:16 +01:00
|
|
|
assert req.link.url == url + fragment, req.link
|
2013-11-15 01:35:24 +01:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_pep440_wheel_link_requirement(self) -> None:
|
2021-08-13 15:23:45 +02:00
|
|
|
url = "https://whatever.com/test-0.4-py2.py3-bogus-any.whl"
|
|
|
|
line = "test @ https://whatever.com/test-0.4-py2.py3-bogus-any.whl"
|
2019-01-25 13:24:14 +01:00
|
|
|
req = install_req_from_line(line)
|
2021-08-13 15:23:45 +02:00
|
|
|
parts = str(req.req).split("@", 1)
|
2019-01-25 13:24:14 +01:00
|
|
|
assert len(parts) == 2
|
2021-08-13 15:23:45 +02:00
|
|
|
assert parts[0].strip() == "test"
|
2019-01-25 13:24:14 +01:00
|
|
|
assert parts[1].strip() == url
|
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_pep440_url_link_requirement(self) -> None:
|
2021-08-13 15:23:45 +02:00
|
|
|
url = "git+http://foo.com@ref#egg=foo"
|
|
|
|
line = "foo @ git+http://foo.com@ref#egg=foo"
|
2019-01-25 13:24:14 +01:00
|
|
|
req = install_req_from_line(line)
|
2021-08-13 15:23:45 +02:00
|
|
|
parts = str(req.req).split("@", 1)
|
2019-01-25 13:24:14 +01:00
|
|
|
assert len(parts) == 2
|
2021-08-13 15:23:45 +02:00
|
|
|
assert parts[0].strip() == "foo"
|
2019-01-25 13:24:14 +01:00
|
|
|
assert parts[1].strip() == url
|
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_url_with_authentication_link_requirement(self) -> None:
|
2021-08-13 15:23:45 +02:00
|
|
|
url = "https://what@whatever.com/test-0.4-py2.py3-bogus-any.whl"
|
|
|
|
line = "https://what@whatever.com/test-0.4-py2.py3-bogus-any.whl"
|
2019-01-25 13:24:14 +01:00
|
|
|
req = install_req_from_line(line)
|
|
|
|
assert req.link is not None
|
|
|
|
assert req.link.is_wheel
|
|
|
|
assert req.link.scheme == "https"
|
|
|
|
assert req.link.url == url
|
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_unsupported_wheel_link_requirement_raises(self) -> None:
|
2017-07-05 20:41:45 +02:00
|
|
|
reqset = RequirementSet()
|
2018-08-21 17:07:40 +02:00
|
|
|
req = install_req_from_line(
|
2021-08-13 15:23:45 +02:00
|
|
|
"https://whatever.com/peppercorn-0.4-py2.py3-bogus-any.whl",
|
2016-03-10 03:55:20 +01:00
|
|
|
)
|
|
|
|
assert req.link is not None
|
|
|
|
assert req.link.is_wheel
|
|
|
|
assert req.link.scheme == "https"
|
|
|
|
|
|
|
|
with pytest.raises(InstallationError):
|
|
|
|
reqset.add_requirement(req)
|
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_unsupported_wheel_local_file_requirement_raises(
|
|
|
|
self, data: TestData
|
|
|
|
) -> None:
|
2017-07-05 20:41:45 +02:00
|
|
|
reqset = RequirementSet()
|
2018-08-21 17:07:40 +02:00
|
|
|
req = install_req_from_line(
|
2021-08-13 15:23:45 +02:00
|
|
|
data.packages.joinpath("simple.dist-0.1-py1-none-invalid.whl"),
|
2016-03-10 03:55:20 +01:00
|
|
|
)
|
|
|
|
assert req.link is not None
|
|
|
|
assert req.link.is_wheel
|
|
|
|
assert req.link.scheme == "file"
|
|
|
|
|
|
|
|
with pytest.raises(InstallationError):
|
|
|
|
reqset.add_requirement(req)
|
2013-11-15 01:35:24 +01:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_str(self) -> None:
|
2021-08-13 15:23:45 +02:00
|
|
|
req = install_req_from_line("simple==0.1")
|
|
|
|
assert str(req) == "simple==0.1"
|
2015-03-06 17:56:24 +01:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_repr(self) -> None:
|
2021-08-13 15:23:45 +02:00
|
|
|
req = install_req_from_line("simple==0.1")
|
|
|
|
assert repr(req) == ("<InstallRequirement object: simple==0.1 editable=False>")
|
2015-03-06 17:56:24 +01:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_invalid_wheel_requirement_raises(self) -> None:
|
2013-11-15 01:35:24 +01:00
|
|
|
with pytest.raises(InvalidWheelFilename):
|
2021-08-13 15:23:45 +02:00
|
|
|
install_req_from_line("invalid.whl")
|
2013-05-28 23:58:08 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_wheel_requirement_sets_req_attribute(self) -> None:
|
2021-08-13 15:23:45 +02:00
|
|
|
req = install_req_from_line("simple-0.1-py2.py3-none-any.whl")
|
2015-11-12 00:51:46 +01:00
|
|
|
assert isinstance(req.req, Requirement)
|
2021-08-13 15:23:45 +02:00
|
|
|
assert str(req.req) == "simple==0.1"
|
2014-05-26 06:00:39 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_url_preserved_line_req(self) -> None:
|
2014-08-04 06:01:12 +02:00
|
|
|
"""Confirm the url is preserved in a non-editable requirement"""
|
2021-08-13 15:23:45 +02:00
|
|
|
url = "git+http://foo.com@ref#egg=foo"
|
2018-08-21 17:07:40 +02:00
|
|
|
req = install_req_from_line(url)
|
2021-08-30 00:43:28 +02:00
|
|
|
assert req.link is not None
|
2014-12-28 16:50:16 +01:00
|
|
|
assert req.link.url == url
|
2014-08-04 06:01:12 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_url_preserved_editable_req(self) -> None:
|
2014-08-04 06:01:12 +02:00
|
|
|
"""Confirm the url is preserved in a editable requirement"""
|
2021-08-13 15:23:45 +02:00
|
|
|
url = "git+http://foo.com@ref#egg=foo"
|
2018-08-21 16:32:25 +02:00
|
|
|
req = install_req_from_editable(url)
|
2021-08-30 00:43:28 +02:00
|
|
|
assert req.link is not None
|
2014-12-28 16:50:16 +01:00
|
|
|
assert req.link.url == url
|
2014-08-04 06:01:12 +02:00
|
|
|
|
2021-08-13 15:23:45 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"path",
|
|
|
|
(
|
|
|
|
"/path/to/foo.egg-info".replace("/", os.path.sep),
|
|
|
|
# Tests issue fixed by https://github.com/pypa/pip/pull/2530
|
|
|
|
"/path/to/foo.egg-info/".replace("/", os.path.sep),
|
|
|
|
),
|
|
|
|
)
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_get_dist(self, path: str) -> None:
|
2021-08-13 15:23:45 +02:00
|
|
|
req = install_req_from_line("foo")
|
2019-09-26 22:52:25 +02:00
|
|
|
req.metadata_directory = path
|
2015-03-12 20:52:07 +01:00
|
|
|
dist = req.get_dist()
|
2021-12-05 01:52:59 +01:00
|
|
|
assert isinstance(dist, select_backend().Distribution)
|
2021-09-28 11:32:40 +02:00
|
|
|
assert dist.raw_name == dist.canonical_name == "foo"
|
2021-08-13 15:23:45 +02:00
|
|
|
assert dist.location == "/path/to".replace("/", os.path.sep)
|
2015-03-12 20:52:07 +01:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_markers(self) -> None:
|
2014-01-15 16:57:13 +01:00
|
|
|
for line in (
|
2016-06-10 21:27:07 +02:00
|
|
|
# recommended syntax
|
2014-01-15 16:57:13 +01:00
|
|
|
'mock3; python_version >= "3"',
|
|
|
|
# with more spaces
|
|
|
|
'mock3 ; python_version >= "3" ',
|
|
|
|
# without spaces
|
|
|
|
'mock3;python_version >= "3"',
|
|
|
|
):
|
2018-08-21 17:07:40 +02:00
|
|
|
req = install_req_from_line(line)
|
2021-08-30 00:43:28 +02:00
|
|
|
assert req.req is not None
|
2021-08-13 15:23:45 +02:00
|
|
|
assert req.req.name == "mock3"
|
|
|
|
assert str(req.req.specifier) == ""
|
2016-11-02 13:28:17 +01:00
|
|
|
assert str(req.markers) == 'python_version >= "3"'
|
2014-01-15 16:57:13 +01:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_markers_semicolon(self) -> None:
|
2014-01-15 16:57:13 +01:00
|
|
|
# check that the markers can contain a semicolon
|
2018-08-21 17:07:40 +02:00
|
|
|
req = install_req_from_line('semicolon; os_name == "a; b"')
|
2021-08-30 00:43:28 +02:00
|
|
|
assert req.req is not None
|
2021-08-13 15:23:45 +02:00
|
|
|
assert req.req.name == "semicolon"
|
|
|
|
assert str(req.req.specifier) == ""
|
2016-11-02 13:28:17 +01:00
|
|
|
assert str(req.markers) == 'os_name == "a; b"'
|
2014-01-15 16:57:13 +01:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_markers_url(self) -> None:
|
2014-01-15 16:57:13 +01:00
|
|
|
# test "URL; markers" syntax
|
2021-08-13 15:23:45 +02:00
|
|
|
url = "http://foo.com/?p=bar.git;a=snapshot;h=v0.1;sf=tgz"
|
2020-12-23 20:25:12 +01:00
|
|
|
line = f'{url}; python_version >= "3"'
|
2018-08-21 17:07:40 +02:00
|
|
|
req = install_req_from_line(line)
|
2021-08-30 00:43:28 +02:00
|
|
|
assert req.link is not None
|
|
|
|
assert req.link.url == url, req.link.url
|
2016-11-02 13:28:17 +01:00
|
|
|
assert str(req.markers) == 'python_version >= "3"'
|
2014-01-15 16:57:13 +01:00
|
|
|
|
|
|
|
# without space, markers are part of the URL
|
2021-08-13 15:23:45 +02:00
|
|
|
url = "http://foo.com/?p=bar.git;a=snapshot;h=v0.1;sf=tgz"
|
2020-12-23 20:25:12 +01:00
|
|
|
line = f'{url};python_version >= "3"'
|
2018-08-21 17:07:40 +02:00
|
|
|
req = install_req_from_line(line)
|
2021-08-30 00:43:28 +02:00
|
|
|
assert req.link is not None
|
|
|
|
assert req.link.url == line, req.link.url
|
2014-01-15 16:57:13 +01:00
|
|
|
assert req.markers is None
|
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_markers_match_from_line(self) -> None:
|
2014-01-15 16:57:13 +01:00
|
|
|
# match
|
|
|
|
for markers in (
|
|
|
|
'python_version >= "1.0"',
|
2021-08-13 15:23:45 +02:00
|
|
|
f"sys_platform == {sys.platform!r}",
|
2014-01-15 16:57:13 +01:00
|
|
|
):
|
2021-08-13 15:23:45 +02:00
|
|
|
line = "name; " + markers
|
2018-08-21 17:07:40 +02:00
|
|
|
req = install_req_from_line(line)
|
2016-11-02 13:28:17 +01:00
|
|
|
assert str(req.markers) == str(Marker(markers))
|
2014-01-15 16:57:13 +01:00
|
|
|
assert req.match_markers()
|
|
|
|
|
|
|
|
# don't match
|
|
|
|
for markers in (
|
|
|
|
'python_version >= "5.0"',
|
2021-08-13 15:23:45 +02:00
|
|
|
f"sys_platform != {sys.platform!r}",
|
2014-01-15 16:57:13 +01:00
|
|
|
):
|
2021-08-13 15:23:45 +02:00
|
|
|
line = "name; " + markers
|
2018-08-21 17:07:40 +02:00
|
|
|
req = install_req_from_line(line)
|
2016-11-02 13:28:17 +01:00
|
|
|
assert str(req.markers) == str(Marker(markers))
|
|
|
|
assert not req.match_markers()
|
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_markers_match(self) -> None:
|
2016-11-02 13:28:17 +01:00
|
|
|
# match
|
|
|
|
for markers in (
|
|
|
|
'python_version >= "1.0"',
|
2021-08-13 15:23:45 +02:00
|
|
|
f"sys_platform == {sys.platform!r}",
|
2016-11-02 13:28:17 +01:00
|
|
|
):
|
2021-08-13 15:23:45 +02:00
|
|
|
line = "name; " + markers
|
|
|
|
req = install_req_from_line(line, comes_from="")
|
2016-11-02 13:28:17 +01:00
|
|
|
assert str(req.markers) == str(Marker(markers))
|
|
|
|
assert req.match_markers()
|
|
|
|
|
|
|
|
# don't match
|
|
|
|
for markers in (
|
|
|
|
'python_version >= "5.0"',
|
2021-08-13 15:23:45 +02:00
|
|
|
f"sys_platform != {sys.platform!r}",
|
2016-11-02 13:28:17 +01:00
|
|
|
):
|
2021-08-13 15:23:45 +02:00
|
|
|
line = "name; " + markers
|
|
|
|
req = install_req_from_line(line, comes_from="")
|
2016-11-02 13:28:17 +01:00
|
|
|
assert str(req.markers) == str(Marker(markers))
|
2014-01-15 16:57:13 +01:00
|
|
|
assert not req.match_markers()
|
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_extras_for_line_path_requirement(self) -> None:
|
2021-08-13 15:23:45 +02:00
|
|
|
line = "SomeProject[ex1,ex2]"
|
|
|
|
filename = "filename"
|
|
|
|
comes_from = f"-r {filename} (line 1)"
|
2018-08-21 17:07:40 +02:00
|
|
|
req = install_req_from_line(line, comes_from=comes_from)
|
2015-05-31 20:43:39 +02:00
|
|
|
assert len(req.extras) == 2
|
2021-08-13 15:23:45 +02:00
|
|
|
assert req.extras == {"ex1", "ex2"}
|
2015-05-31 20:43:39 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_extras_for_line_url_requirement(self) -> None:
|
2021-08-13 15:23:45 +02:00
|
|
|
line = "git+https://url#egg=SomeProject[ex1,ex2]"
|
|
|
|
filename = "filename"
|
|
|
|
comes_from = f"-r {filename} (line 1)"
|
2018-08-21 17:07:40 +02:00
|
|
|
req = install_req_from_line(line, comes_from=comes_from)
|
2015-05-31 20:43:39 +02:00
|
|
|
assert len(req.extras) == 2
|
2021-08-13 15:23:45 +02:00
|
|
|
assert req.extras == {"ex1", "ex2"}
|
2015-05-31 20:43:39 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_extras_for_editable_path_requirement(self) -> None:
|
2021-08-13 15:23:45 +02:00
|
|
|
url = ".[ex1,ex2]"
|
|
|
|
filename = "filename"
|
|
|
|
comes_from = f"-r {filename} (line 1)"
|
2018-08-21 16:32:25 +02:00
|
|
|
req = install_req_from_editable(url, comes_from=comes_from)
|
2015-05-31 20:43:39 +02:00
|
|
|
assert len(req.extras) == 2
|
2021-08-13 15:23:45 +02:00
|
|
|
assert req.extras == {"ex1", "ex2"}
|
2015-05-31 20:43:39 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_extras_for_editable_url_requirement(self) -> None:
|
2021-08-13 15:23:45 +02:00
|
|
|
url = "git+https://url#egg=SomeProject[ex1,ex2]"
|
|
|
|
filename = "filename"
|
|
|
|
comes_from = f"-r {filename} (line 1)"
|
2018-08-21 16:32:25 +02:00
|
|
|
req = install_req_from_editable(url, comes_from=comes_from)
|
2015-05-31 20:43:39 +02:00
|
|
|
assert len(req.extras) == 2
|
2021-08-13 15:23:45 +02:00
|
|
|
assert req.extras == {"ex1", "ex2"}
|
2015-05-31 20:43:39 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_unexisting_path(self) -> None:
|
2015-10-23 22:59:38 +02:00
|
|
|
with pytest.raises(InstallationError) as e:
|
2021-08-13 15:23:45 +02:00
|
|
|
install_req_from_line(os.path.join("this", "path", "does", "not", "exist"))
|
2015-10-23 22:59:38 +02:00
|
|
|
err_msg = e.value.args[0]
|
|
|
|
assert "Invalid requirement" in err_msg
|
2017-01-17 23:14:09 +01:00
|
|
|
assert "It looks like a path." in err_msg
|
2015-10-23 22:59:38 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_single_equal_sign(self) -> None:
|
2015-10-23 22:59:38 +02:00
|
|
|
with pytest.raises(InstallationError) as e:
|
2021-08-13 15:23:45 +02:00
|
|
|
install_req_from_line("toto=42")
|
2015-10-23 22:59:38 +02:00
|
|
|
err_msg = e.value.args[0]
|
|
|
|
assert "Invalid requirement" in err_msg
|
|
|
|
assert "= is not a valid operator. Did you mean == ?" in err_msg
|
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_unidentifiable_name(self) -> None:
|
2021-08-13 15:23:45 +02:00
|
|
|
test_name = "-"
|
2015-10-23 22:59:38 +02:00
|
|
|
with pytest.raises(InstallationError) as e:
|
2018-10-26 17:28:30 +02:00
|
|
|
install_req_from_line(test_name)
|
2015-10-23 22:59:38 +02:00
|
|
|
err_msg = e.value.args[0]
|
2020-12-23 20:25:12 +01:00
|
|
|
assert f"Invalid requirement: '{test_name}'" == err_msg
|
2015-10-23 22:59:38 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_requirement_file(self) -> None:
|
2021-08-13 15:23:45 +02:00
|
|
|
req_file_path = os.path.join(self.tempdir, "test.txt")
|
|
|
|
with open(req_file_path, "w") as req_file:
|
|
|
|
req_file.write("pip\nsetuptools")
|
2017-03-23 11:36:02 +01:00
|
|
|
with pytest.raises(InstallationError) as e:
|
2018-08-21 17:07:40 +02:00
|
|
|
install_req_from_line(req_file_path)
|
2017-03-23 11:36:02 +01:00
|
|
|
err_msg = e.value.args[0]
|
|
|
|
assert "Invalid requirement" in err_msg
|
2020-09-23 14:57:39 +02:00
|
|
|
assert "It looks like a path. The path does exist." in err_msg
|
2017-03-23 11:36:02 +01:00
|
|
|
assert "appears to be a requirements file." in err_msg
|
|
|
|
assert "If that is the case, use the '-r' flag to install" in err_msg
|
|
|
|
|
2013-05-28 23:58:08 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
@mock.patch("pip._internal.req.req_install.os.path.abspath")
|
|
|
|
@mock.patch("pip._internal.req.req_install.os.path.exists")
|
|
|
|
@mock.patch("pip._internal.req.req_install.os.path.isdir")
|
|
|
|
def test_parse_editable_local(
|
|
|
|
isdir_mock: mock.Mock, exists_mock: mock.Mock, abspath_mock: mock.Mock
|
|
|
|
) -> None:
|
2013-05-28 23:58:08 +02:00
|
|
|
exists_mock.return_value = isdir_mock.return_value = True
|
|
|
|
# mocks needed to support path operations on windows tests
|
2015-12-05 18:04:11 +01:00
|
|
|
abspath_mock.return_value = "/some/path"
|
2021-08-13 15:23:45 +02:00
|
|
|
assert parse_editable(".") == (None, "file:///some/path", set())
|
2015-12-05 18:04:11 +01:00
|
|
|
abspath_mock.return_value = "/some/path/foo"
|
2021-08-13 15:23:45 +02:00
|
|
|
assert parse_editable("foo") == (
|
|
|
|
None,
|
|
|
|
"file:///some/path/foo",
|
|
|
|
set(),
|
2014-01-28 15:17:51 +01:00
|
|
|
)
|
|
|
|
|
2013-05-28 23:58:08 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_parse_editable_explicit_vcs() -> None:
|
2021-08-13 15:23:45 +02:00
|
|
|
assert parse_editable("svn+https://foo#egg=foo") == (
|
|
|
|
"foo",
|
|
|
|
"svn+https://foo#egg=foo",
|
2020-05-22 00:28:47 +02:00
|
|
|
set(),
|
2014-01-28 15:17:51 +01:00
|
|
|
)
|
|
|
|
|
2013-05-28 23:58:08 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_parse_editable_vcs_extras() -> None:
|
2021-08-13 15:23:45 +02:00
|
|
|
assert parse_editable("svn+https://foo#egg=foo[extras]") == (
|
|
|
|
"foo[extras]",
|
|
|
|
"svn+https://foo#egg=foo[extras]",
|
2020-05-22 00:28:47 +02:00
|
|
|
set(),
|
2014-01-28 15:17:51 +01:00
|
|
|
)
|
|
|
|
|
2013-05-28 23:58:08 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
@mock.patch("pip._internal.req.req_install.os.path.abspath")
|
|
|
|
@mock.patch("pip._internal.req.req_install.os.path.exists")
|
|
|
|
@mock.patch("pip._internal.req.req_install.os.path.isdir")
|
|
|
|
def test_parse_editable_local_extras(
|
|
|
|
isdir_mock: mock.Mock, exists_mock: mock.Mock, abspath_mock: mock.Mock
|
|
|
|
) -> None:
|
2013-05-28 23:58:08 +02:00
|
|
|
exists_mock.return_value = isdir_mock.return_value = True
|
2015-12-05 18:04:11 +01:00
|
|
|
abspath_mock.return_value = "/some/path"
|
2021-08-13 15:23:45 +02:00
|
|
|
assert parse_editable(".[extras]") == (
|
|
|
|
None,
|
|
|
|
"file:///some/path",
|
|
|
|
{"extras"},
|
2014-01-28 15:17:51 +01:00
|
|
|
)
|
2015-12-05 18:04:11 +01:00
|
|
|
abspath_mock.return_value = "/some/path/foo"
|
2021-08-13 15:23:45 +02:00
|
|
|
assert parse_editable("foo[bar,baz]") == (
|
|
|
|
None,
|
|
|
|
"file:///some/path/foo",
|
|
|
|
{"bar", "baz"},
|
2014-01-28 15:17:51 +01:00
|
|
|
)
|
|
|
|
|
2013-05-28 23:58:08 +02:00
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_exclusive_environment_markers() -> None:
|
2015-04-07 22:37:38 +02:00
|
|
|
"""Make sure RequirementSet accepts several excluding env markers"""
|
2021-08-13 15:23:45 +02:00
|
|
|
eq36 = install_req_from_line("Django>=1.6.10,<1.7 ; python_version == '3.6'")
|
2020-04-13 11:09:05 +02:00
|
|
|
eq36.user_supplied = True
|
2021-08-13 15:23:45 +02:00
|
|
|
ne36 = install_req_from_line("Django>=1.6.10,<1.8 ; python_version != '3.6'")
|
2020-04-13 11:09:05 +02:00
|
|
|
ne36.user_supplied = True
|
2015-04-07 22:37:38 +02:00
|
|
|
|
2018-01-20 13:49:39 +01:00
|
|
|
req_set = RequirementSet()
|
2019-08-20 22:23:33 +02:00
|
|
|
req_set.add_requirement(eq36)
|
|
|
|
req_set.add_requirement(ne36)
|
2021-08-13 15:23:45 +02:00
|
|
|
assert req_set.has_requirement("Django")
|
2017-08-09 00:28:28 +02:00
|
|
|
|
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_mismatched_versions(caplog: pytest.LogCaptureFixture) -> None:
|
2019-11-03 15:05:44 +01:00
|
|
|
req = InstallRequirement(
|
2021-08-13 15:23:45 +02:00
|
|
|
req=Requirement("simplewheel==2.0"),
|
2019-11-03 15:05:44 +01:00
|
|
|
comes_from=None,
|
|
|
|
)
|
2020-04-11 18:04:26 +02:00
|
|
|
req.source_dir = "/tmp/somewhere" # make req believe it has been unpacked
|
2019-11-03 15:05:44 +01:00
|
|
|
# Monkeypatch!
|
2021-08-30 00:43:28 +02:00
|
|
|
metadata = email.message.Message()
|
|
|
|
metadata["name"] = "simplewheel"
|
|
|
|
metadata["version"] = "1.0"
|
|
|
|
req._metadata = metadata
|
|
|
|
|
2017-08-09 00:28:28 +02:00
|
|
|
req.assert_source_matches_version()
|
|
|
|
assert caplog.records[-1].message == (
|
2021-08-13 15:23:45 +02:00
|
|
|
"Requested simplewheel==2.0, but installing version 1.0"
|
2017-08-09 00:28:28 +02:00
|
|
|
)
|
2019-02-23 19:10:34 +01:00
|
|
|
|
|
|
|
|
2021-08-13 15:23:45 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"args, expected",
|
|
|
|
[
|
|
|
|
# Test UNIX-like paths
|
|
|
|
(("/path/to/installable"), True),
|
|
|
|
# Test relative paths
|
|
|
|
(("./path/to/installable"), True),
|
|
|
|
# Test current path
|
|
|
|
(("."), True),
|
|
|
|
# Test url paths
|
|
|
|
(("https://whatever.com/test-0.4-py2.py3-bogus-any.whl"), True),
|
|
|
|
# Test pep440 paths
|
|
|
|
(("test @ https://whatever.com/test-0.4-py2.py3-bogus-any.whl"), True),
|
|
|
|
# Test wheel
|
|
|
|
(("simple-0.1-py2.py3-none-any.whl"), False),
|
|
|
|
],
|
|
|
|
)
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_looks_like_path(args: str, expected: bool) -> None:
|
2019-02-23 19:10:34 +01:00
|
|
|
assert _looks_like_path(args) == expected
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.skipif(
|
2021-08-13 15:23:45 +02:00
|
|
|
not sys.platform.startswith("win"), reason="Test only available on Windows"
|
|
|
|
)
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"args, expected",
|
|
|
|
[
|
|
|
|
# Test relative paths
|
|
|
|
((".\\path\\to\\installable"), True),
|
|
|
|
(("relative\\path"), True),
|
|
|
|
# Test absolute paths
|
|
|
|
(("C:\\absolute\\path"), True),
|
|
|
|
],
|
2019-02-23 19:10:34 +01:00
|
|
|
)
|
2021-08-30 00:43:28 +02:00
|
|
|
def test_looks_like_path_win(args: str, expected: bool) -> None:
|
2019-02-23 19:10:34 +01:00
|
|
|
assert _looks_like_path(args) == expected
|
|
|
|
|
|
|
|
|
2021-08-13 15:23:45 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"args, mock_returns, expected",
|
|
|
|
[
|
|
|
|
# Test pep440 urls
|
|
|
|
(
|
|
|
|
(
|
|
|
|
"/path/to/foo @ git+http://foo.com@ref#egg=foo",
|
|
|
|
"foo @ git+http://foo.com@ref#egg=foo",
|
|
|
|
),
|
|
|
|
(False, False),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
# Test pep440 urls without spaces
|
|
|
|
(
|
|
|
|
(
|
|
|
|
"/path/to/foo@git+http://foo.com@ref#egg=foo",
|
|
|
|
"foo @ git+http://foo.com@ref#egg=foo",
|
|
|
|
),
|
|
|
|
(False, False),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
# Test pep440 wheel
|
|
|
|
(
|
|
|
|
(
|
|
|
|
"/path/to/test @ https://whatever.com/test-0.4-py2.py3-bogus-any.whl",
|
|
|
|
"test @ https://whatever.com/test-0.4-py2.py3-bogus-any.whl",
|
|
|
|
),
|
|
|
|
(False, False),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
# Test name is not a file
|
|
|
|
(("/path/to/simple==0.1", "simple==0.1"), (False, False), None),
|
|
|
|
],
|
|
|
|
)
|
2021-08-30 00:43:28 +02:00
|
|
|
@mock.patch("pip._internal.req.req_install.os.path.isdir")
|
|
|
|
@mock.patch("pip._internal.req.req_install.os.path.isfile")
|
|
|
|
def test_get_url_from_path(
|
|
|
|
isdir_mock: mock.Mock,
|
|
|
|
isfile_mock: mock.Mock,
|
|
|
|
args: Tuple[str, str],
|
|
|
|
mock_returns: Tuple[bool, bool],
|
|
|
|
expected: None,
|
|
|
|
) -> None:
|
2019-02-23 19:10:34 +01:00
|
|
|
isdir_mock.return_value = mock_returns[0]
|
|
|
|
isfile_mock.return_value = mock_returns[1]
|
|
|
|
assert _get_url_from_path(*args) is expected
|
|
|
|
|
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
@mock.patch("pip._internal.req.req_install.os.path.isdir")
|
|
|
|
@mock.patch("pip._internal.req.req_install.os.path.isfile")
|
|
|
|
def test_get_url_from_path__archive_file(
|
|
|
|
isdir_mock: mock.Mock, isfile_mock: mock.Mock
|
|
|
|
) -> None:
|
2019-02-23 19:10:34 +01:00
|
|
|
isdir_mock.return_value = False
|
|
|
|
isfile_mock.return_value = True
|
2021-08-13 15:23:45 +02:00
|
|
|
name = "simple-0.1-py2.py3-none-any.whl"
|
|
|
|
path = os.path.join("/path/to/" + name)
|
2019-02-23 19:10:34 +01:00
|
|
|
url = path_to_url(path)
|
|
|
|
assert _get_url_from_path(path, name) == url
|
|
|
|
|
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
@mock.patch("pip._internal.req.req_install.os.path.isdir")
|
|
|
|
@mock.patch("pip._internal.req.req_install.os.path.isfile")
|
|
|
|
def test_get_url_from_path__installable_dir(
|
|
|
|
isdir_mock: mock.Mock, isfile_mock: mock.Mock
|
|
|
|
) -> None:
|
2019-02-23 19:10:34 +01:00
|
|
|
isdir_mock.return_value = True
|
|
|
|
isfile_mock.return_value = True
|
2021-08-13 15:23:45 +02:00
|
|
|
name = "some/setuptools/project"
|
|
|
|
path = os.path.join("/path/to/" + name)
|
2019-02-23 19:10:34 +01:00
|
|
|
url = path_to_url(path)
|
|
|
|
assert _get_url_from_path(path, name) == url
|
|
|
|
|
|
|
|
|
2021-08-30 00:43:28 +02:00
|
|
|
@mock.patch("pip._internal.req.req_install.os.path.isdir")
|
|
|
|
def test_get_url_from_path__installable_error(isdir_mock: mock.Mock) -> None:
|
2019-02-23 19:10:34 +01:00
|
|
|
isdir_mock.return_value = True
|
2021-08-13 15:23:45 +02:00
|
|
|
name = "some/setuptools/project"
|
|
|
|
path = os.path.join("/path/to/" + name)
|
2019-02-23 19:10:34 +01:00
|
|
|
with pytest.raises(InstallationError) as e:
|
|
|
|
_get_url_from_path(path, name)
|
|
|
|
err_msg = e.value.args[0]
|
|
|
|
assert "Neither 'setup.py' nor 'pyproject.toml' found" in err_msg
|