summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPradyun Gedam <pradyunsg@gmail.com>2018-10-23 19:20:46 +0530
committerPradyun Gedam <pradyunsg@gmail.com>2018-10-23 19:20:46 +0530
commite19aceff40a3f1d91885c0e35ca86fc658f71c83 (patch)
treecfc135d7206b1f35ff20e1549134d31e348ea81a
parent5167495eb729f2f01ae0c4961b40dc1ca52ff828 (diff)
parent51819cc3d86f8c5bef91ede74672b72d34d7c228 (diff)
downloadpip-e19aceff40a3f1d91885c0e35ca86fc658f71c83.tar.gz
Merge branch 'master' into appveyor-rename
-rw-r--r--.appveyor.yml5
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml3
-rw-r--r--docs/html/development/getting-started.rst4
-rw-r--r--news/5839.bugfix1
-rw-r--r--news/EFE4CACD-41B3-478D-926D-2069D76A6059.trivial0
-rw-r--r--src/pip/_internal/commands/check.py4
-rw-r--r--src/pip/_internal/commands/install.py6
-rw-r--r--src/pip/_internal/operations/check.py18
-rw-r--r--src/pip/_internal/operations/freeze.py107
-rw-r--r--src/pip/_internal/vcs/__init__.py13
-rw-r--r--tests/conftest.py163
-rw-r--r--tests/functional/test_check.py17
-rw-r--r--tests/functional/test_completion.py62
-rw-r--r--tests/functional/test_freeze.py1
-rw-r--r--tests/functional/test_install.py93
-rw-r--r--tests/functional/test_install_config.py4
-rw-r--r--tests/functional/test_install_reqs.py16
-rw-r--r--tests/functional/test_install_user.py104
-rw-r--r--tests/functional/test_install_wheel.py13
-rw-r--r--tests/functional/test_list.py6
-rw-r--r--tests/functional/test_uninstall.py13
-rw-r--r--tests/functional/test_uninstall_user.py24
-rw-r--r--tests/functional/test_wheel.py79
-rw-r--r--tests/lib/__init__.py51
-rw-r--r--tests/lib/venv.py198
-rw-r--r--tests/unit/test_locations.py3
-rw-r--r--tools/tests-common_wheels-requirements.txt2
-rw-r--r--tools/tests-requirements.txt2
-rwxr-xr-xtools/travis/install.sh3
-rwxr-xr-xtools/travis/run.sh4
-rw-r--r--tox.ini7
32 files changed, 516 insertions, 511 deletions
diff --git a/.appveyor.yml b/.appveyor.yml
index cfad2a52f..e4324b470 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -18,7 +18,8 @@ environment:
install:
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
- "python --version"
- - "pip install certifi tox"
+ - "pip install --upgrade certifi tox tox-venv"
+ - "pip freeze --all"
# Fix git SSL errors.
- "python -m certifi >cacert.txt"
- "set /p GIT_SSL_CAINFO=<cacert.txt"
@@ -61,7 +62,7 @@ test_script:
$env:TEMP = "T:\"
$env:TMP = "T:\"
if ($env:RUN_INTEGRATION_TESTS -eq "True") {
- tox -e py -- -m integration -n 3 --duration=5
+ tox -e py -- --use-venv -m integration -n 3 --duration=5
}
else {
tox -e py -- -m unit -n 3
diff --git a/.gitignore b/.gitignore
index 31e69bc00..dfa41c022 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,7 @@ htmlcov/
nosetests.xml
coverage.xml
*.cover
+tests/data/common_wheels/
# Misc
*~
diff --git a/.travis.yml b/.travis.yml
index 058209841..c2af3efed 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,7 @@
language: python
cache: pip
dist: trusty
+python: 3.6
stages:
- primary
@@ -12,8 +13,8 @@ jobs:
- stage: primary
env: TOXENV=docs
- env: TOXENV=lint-py2
+ python: 2.7
- env: TOXENV=lint-py3
- python: 3.6
- env: TOXENV=mypy
- env: TOXENV=packaging
# Latest CPython
diff --git a/docs/html/development/getting-started.rst b/docs/html/development/getting-started.rst
index 6a91b605d..96068c6d9 100644
--- a/docs/html/development/getting-started.rst
+++ b/docs/html/development/getting-started.rst
@@ -8,8 +8,8 @@ This document is meant to get you setup to work on pip and to act as a guide and
reference to the the development setup. If you face any issues during this
process, please `open an issue`_ about it on the issue tracker.
-Development tools
-=================
+Development Environment
+-----------------------
pip uses :pypi:`tox` for testing against multiple different Python environments
and ensuring reproducible environments for linting and building documentation.
diff --git a/news/5839.bugfix b/news/5839.bugfix
new file mode 100644
index 000000000..a9ce698d6
--- /dev/null
+++ b/news/5839.bugfix
@@ -0,0 +1 @@
+Fix crashes from unparseable requirements when checking installed packages.
diff --git a/news/EFE4CACD-41B3-478D-926D-2069D76A6059.trivial b/news/EFE4CACD-41B3-478D-926D-2069D76A6059.trivial
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/news/EFE4CACD-41B3-478D-926D-2069D76A6059.trivial
diff --git a/src/pip/_internal/commands/check.py b/src/pip/_internal/commands/check.py
index 1be3ec21f..801cecc0b 100644
--- a/src/pip/_internal/commands/check.py
+++ b/src/pip/_internal/commands/check.py
@@ -16,7 +16,7 @@ class CheckCommand(Command):
summary = 'Verify installed packages have compatible dependencies.'
def run(self, options, args):
- package_set = create_package_set_from_installed()
+ package_set, parsing_probs = create_package_set_from_installed()
missing, conflicting = check_package_set(package_set)
for project_name in missing:
@@ -35,7 +35,7 @@ class CheckCommand(Command):
project_name, version, req, dep_name, dep_version,
)
- if missing or conflicting:
+ if missing or conflicting or parsing_probs:
return 1
else:
logger.info("No broken requirements found.")
diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py
index 6fc178f9f..49a488730 100644
--- a/src/pip/_internal/commands/install.py
+++ b/src/pip/_internal/commands/install.py
@@ -472,7 +472,11 @@ class InstallCommand(RequirementCommand):
)
def _warn_about_conflicts(self, to_install):
- package_set, _dep_info = check_install_conflicts(to_install)
+ try:
+ package_set, _dep_info = check_install_conflicts(to_install)
+ except Exception:
+ logger.error("Error checking for conflicts.", exc_info=True)
+ return
missing, conflicting = _dep_info
# NOTE: There is some duplication here from pip check
diff --git a/src/pip/_internal/operations/check.py b/src/pip/_internal/operations/check.py
index 799257aea..0096fa429 100644
--- a/src/pip/_internal/operations/check.py
+++ b/src/pip/_internal/operations/check.py
@@ -1,14 +1,18 @@
"""Validation of dependencies of packages
"""
+import logging
from collections import namedtuple
from pip._vendor.packaging.utils import canonicalize_name
+from pip._vendor.pkg_resources import RequirementParseError
from pip._internal.operations.prepare import make_abstract_dist
from pip._internal.utils.misc import get_installed_distributions
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
+logger = logging.getLogger(__name__)
+
if MYPY_CHECK_RUNNING:
from pip._internal.req.req_install import InstallRequirement # noqa: F401
from typing import ( # noqa: F401
@@ -28,7 +32,7 @@ PackageDetails = namedtuple('PackageDetails', ['version', 'requires'])
def create_package_set_from_installed(**kwargs):
- # type: (**Any) -> PackageSet
+ # type: (**Any) -> Tuple[PackageSet, bool]
"""Converts a list of distributions into a PackageSet.
"""
# Default to using all packages installed on the system
@@ -36,10 +40,16 @@ def create_package_set_from_installed(**kwargs):
kwargs = {"local_only": False, "skip": ()}
package_set = {}
+ problems = False
for dist in get_installed_distributions(**kwargs):
name = canonicalize_name(dist.project_name)
- package_set[name] = PackageDetails(dist.version, dist.requires())
- return package_set
+ try:
+ package_set[name] = PackageDetails(dist.version, dist.requires())
+ except RequirementParseError as e:
+ # Don't crash on broken metadata
+ logging.warning("Error parsing requirements for %s: %s", name, e)
+ problems = True
+ return package_set, problems
def check_package_set(package_set, should_ignore=None):
@@ -95,7 +105,7 @@ def check_install_conflicts(to_install):
installing given requirements
"""
# Start from the current state
- package_set = create_package_set_from_installed()
+ package_set, _ = create_package_set_from_installed()
# Install packages
would_be_installed = _simulate_installation_of(to_install, package_set)
diff --git a/src/pip/_internal/operations/freeze.py b/src/pip/_internal/operations/freeze.py
index af484f2c1..d3cdefbfc 100644
--- a/src/pip/_internal/operations/freeze.py
+++ b/src/pip/_internal/operations/freeze.py
@@ -5,11 +5,11 @@ import logging
import os
import re
-from pip._vendor import pkg_resources, six
+from pip._vendor import six
from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.pkg_resources import RequirementParseError
-from pip._internal.exceptions import InstallationError
+from pip._internal.exceptions import BadCommand, InstallationError
from pip._internal.req.constructors import (
install_req_from_editable, install_req_from_line,
)
@@ -34,16 +34,6 @@ def freeze(
if skip_regex:
skip_match = re.compile(skip_regex).search
- dependency_links = []
-
- for dist in pkg_resources.working_set:
- if dist.has_metadata('dependency_links.txt'):
- dependency_links.extend(
- dist.get_metadata_lines('dependency_links.txt')
- )
- for link in find_links:
- if '#egg=' in link:
- dependency_links.append(link)
for link in find_links:
yield '-f %s' % link
installations = {}
@@ -51,10 +41,7 @@ def freeze(
skip=(),
user_only=user_only):
try:
- req = FrozenRequirement.from_dist(
- dist,
- dependency_links
- )
+ req = FrozenRequirement.from_dist(dist)
except RequirementParseError:
logger.warning(
"Could not parse requirement: %s",
@@ -156,56 +143,64 @@ def freeze(
yield str(installation).rstrip()
-class FrozenRequirement(object):
- def __init__(self, name, req, editable, comments=()):
- self.name = name
- self.req = req
- self.editable = editable
- self.comments = comments
+def get_requirement_info(dist):
+ """
+ Compute and return values (req, editable, comments) for use in
+ FrozenRequirement.from_dist().
+ """
+ if not dist_is_editable(dist):
+ return (None, False, [])
- @classmethod
- def _init_args_from_dist(cls, dist, dependency_links):
- """
- Compute and return arguments (req, editable, comments) to pass to
- FrozenRequirement.__init__().
-
- This method is for use in FrozenRequirement.from_dist().
- """
- location = os.path.normcase(os.path.abspath(dist.location))
- from pip._internal.vcs import vcs, get_src_requirement
- if not dist_is_editable(dist):
- req = dist.as_requirement()
- return (req, False, [])
+ location = os.path.normcase(os.path.abspath(dist.location))
- vc_type = vcs.get_backend_type(location)
+ from pip._internal.vcs import vcs
+ vc_type = vcs.get_backend_type(location)
- if not vc_type:
- req = dist.as_requirement()
- return (req, False, [])
+ if not vc_type:
+ return (None, False, [])
- try:
- req = get_src_requirement(vc_type, dist, location)
- except InstallationError as exc:
- logger.warning(
- "Error when trying to get requirement for VCS system %s, "
- "falling back to uneditable format", exc
- )
- else:
- if req is not None:
- return (req, True, [])
+ try:
+ req = vc_type().get_src_requirement(dist, location)
+ except BadCommand:
+ logger.warning(
+ 'cannot determine version of editable source in %s '
+ '(%s command not found in path)',
+ location,
+ vc_type.name,
+ )
+ return (None, True, [])
+ except InstallationError as exc:
logger.warning(
- 'Could not determine repository location of %s', location
+ "Error when trying to get requirement for VCS system %s, "
+ "falling back to uneditable format", exc
)
- comments = ['## !! Could not determine repository location']
- req = dist.as_requirement()
+ else:
+ if req is not None:
+ return (req, True, [])
+
+ logger.warning(
+ 'Could not determine repository location of %s', location
+ )
+ comments = ['## !! Could not determine repository location']
- return (req, False, comments)
+ return (None, False, comments)
+
+
+class FrozenRequirement(object):
+ def __init__(self, name, req, editable, comments=()):
+ self.name = name
+ self.req = req
+ self.editable = editable
+ self.comments = comments
@classmethod
- def from_dist(cls, dist, dependency_links):
- args = cls._init_args_from_dist(dist, dependency_links)
- return cls(dist.project_name, *args)
+ def from_dist(cls, dist):
+ req, editable, comments = get_requirement_info(dist)
+ if req is None:
+ req = dist.as_requirement()
+
+ return cls(dist.project_name, req, editable, comments=comments)
def __str__(self):
req = self.req
diff --git a/src/pip/_internal/vcs/__init__.py b/src/pip/_internal/vcs/__init__.py
index d34c8be0b..808c152f7 100644
--- a/src/pip/_internal/vcs/__init__.py
+++ b/src/pip/_internal/vcs/__init__.py
@@ -479,16 +479,3 @@ class VersionControl(object):
the Git override checks that Git is actually available.
"""
return cls.is_repository_directory(location)
-
-
-def get_src_requirement(vc_type, dist, location):
- try:
- return vc_type().get_src_requirement(dist, location)
- except BadCommand:
- logger.warning(
- 'cannot determine version of editable source in %s '
- '(%s command not found in path)',
- location,
- vc_type.name,
- )
- return dist.as_requirement()
diff --git a/tests/conftest.py b/tests/conftest.py
index 2656c3519..c86a91e3a 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,3 +1,4 @@
+import compileall
import io
import os
import shutil
@@ -6,9 +7,10 @@ import sys
import pytest
import six
+from setuptools.wheel import Wheel
import pip._internal
-from tests.lib import SRC_DIR, TestData
+from tests.lib import DATA_DIR, SRC_DIR, TestData
from tests.lib.path import Path
from tests.lib.scripttest import PipTestEnvironment
from tests.lib.venv import VirtualEnvironment
@@ -19,9 +21,11 @@ def pytest_addoption(parser):
"--keep-tmpdir", action="store_true",
default=False, help="keep temporary test directories"
)
+ parser.addoption("--use-venv", action="store_true",
+ help="use venv for virtual environment creation")
-def pytest_collection_modifyitems(items):
+def pytest_collection_modifyitems(config, items):
for item in items:
if not hasattr(item, 'module'): # e.g.: DoctestTextfile
continue
@@ -30,6 +34,16 @@ def pytest_collection_modifyitems(items):
if item.get_marker('network') is not None and "CI" in os.environ:
item.add_marker(pytest.mark.flaky(reruns=3))
+ if six.PY3:
+ if (item.get_marker('incompatible_with_test_venv') and
+ config.getoption("--use-venv")):
+ item.add_marker(pytest.mark.skip(
+ 'Incompatible with test venv'))
+ if (item.get_marker('incompatible_with_venv') and
+ sys.prefix != sys.base_prefix):
+ item.add_marker(pytest.mark.skip(
+ 'Incompatible with venv'))
+
module_path = os.path.relpath(
item.module.__file__,
os.path.commonprefix([__file__, item.module.__file__]),
@@ -56,6 +70,16 @@ def pytest_collection_modifyitems(items):
)
+@pytest.fixture(scope='session')
+def tmpdir_factory(request, tmpdir_factory):
+ """ Modified `tmpdir_factory` session fixture
+ that will automatically cleanup after itself.
+ """
+ yield tmpdir_factory
+ if not request.config.getoption("--keep-tmpdir"):
+ tmpdir_factory.getbasetemp().remove(ignore_errors=True)
+
+
@pytest.yield_fixture
def tmpdir(request, tmpdir):
"""
@@ -163,63 +187,74 @@ def pip_src(tmpdir_factory):
return pip_src
+def _common_wheel_editable_install(tmpdir_factory, common_wheels, package):
+ wheel_candidates = list(common_wheels.glob('%s-*.whl' % package))
+ assert len(wheel_candidates) == 1, wheel_candidates
+ install_dir = Path(str(tmpdir_factory.mktemp(package))) / 'install'
+ Wheel(wheel_candidates[0]).install_as_egg(install_dir)
+ (install_dir / 'EGG-INFO').rename(install_dir / '%s.egg-info' % package)
+ assert compileall.compile_dir(str(install_dir), quiet=1)
+ return install_dir
+
+
+@pytest.fixture(scope='session')
+def setuptools_install(tmpdir_factory, common_wheels):
+ return _common_wheel_editable_install(tmpdir_factory,
+ common_wheels,
+ 'setuptools')
+
+
+@pytest.fixture(scope='session')
+def wheel_install(tmpdir_factory, common_wheels):
+ return _common_wheel_editable_install(tmpdir_factory,
+ common_wheels,
+ 'wheel')
+
+
+def install_egg_link(venv, project_name, egg_info_dir):
+ with open(venv.site / 'easy-install.pth', 'a') as fp:
+ fp.write(str(egg_info_dir.abspath) + '\n')
+ with open(venv.site / (project_name + '.egg-link'), 'w') as fp:
+ fp.write(str(egg_info_dir) + '\n.')
+
+
@pytest.yield_fixture(scope='session')
-def virtualenv_template(tmpdir_factory, pip_src):
- tmpdir = Path(str(tmpdir_factory.mktemp('virtualenv')))
+def virtualenv_template(request, tmpdir_factory, pip_src,
+ setuptools_install, common_wheels):
+
+ if six.PY3 and request.config.getoption('--use-venv'):
+ venv_type = 'venv'
+ else:
+ venv_type = 'virtualenv'
+
# Create the virtual environment
- venv = VirtualEnvironment.create(
- tmpdir.join("venv_orig"),
- pip_source_dir=pip_src,
- relocatable=True,
- )
- # Fix `site.py`.
- site_py = venv.lib / 'site.py'
- with open(site_py) as fp:
- site_contents = fp.read()
- for pattern, replace in (
- (
- # Ensure `virtualenv.system_site_packages = True` (needed
- # for testing `--user`) does not result in adding the real
- # site-packages' directory to `sys.path`.
- (
- '\ndef virtual_addsitepackages(known_paths):\n'
- ),
- (
- '\ndef virtual_addsitepackages(known_paths):\n'
- ' return known_paths\n'
- ),
- ),
- (
- # Fix sites ordering: user site must be added before system site.
- (
- '\n paths_in_sys = addsitepackages(paths_in_sys)'
- '\n paths_in_sys = addusersitepackages(paths_in_sys)\n'
- ),
- (
- '\n paths_in_sys = addusersitepackages(paths_in_sys)'
- '\n paths_in_sys = addsitepackages(paths_in_sys)\n'
- ),
- ),
- ):
- assert pattern in site_contents
- site_contents = site_contents.replace(pattern, replace)
- with open(site_py, 'w') as fp:
- fp.write(site_contents)
- if sys.platform == 'win32':
- # Work around setuptools' easy_install.exe
- # not working properly after relocation.
- for exe in os.listdir(venv.bin):
- if exe.startswith('easy_install'):
- (venv.bin / exe).remove()
- with open(venv.bin / 'easy_install.bat', 'w') as fp:
- fp.write('python.exe -m easy_install %*\n')
+ tmpdir = Path(str(tmpdir_factory.mktemp('virtualenv')))
+ venv = VirtualEnvironment(tmpdir.join("venv_orig"), venv_type=venv_type)
+
+ # Install setuptools and pip.
+ install_egg_link(venv, 'setuptools', setuptools_install)
+ pip_editable = Path(str(tmpdir_factory.mktemp('pip'))) / 'pip'
+ pip_src.copytree(pip_editable)
+ assert compileall.compile_dir(str(pip_editable), quiet=1)
+ subprocess.check_call([venv.bin / 'python', 'setup.py', '-q', 'develop'],
+ cwd=pip_editable)
+
+ # Drop (non-relocatable) launchers.
+ for exe in os.listdir(venv.bin):
+ if not (
+ exe.startswith('python') or
+ exe.startswith('libpy') # Don't remove libpypy-c.so...
+ ):
+ (venv.bin / exe).remove()
+
+ # Enable user site packages.
+ venv.user_site_packages = True
# Rename original virtualenv directory to make sure
# it's not reused by mistake from one of the copies.
venv_template = tmpdir / "venv_template"
- os.rename(venv.location, venv_template)
- yield venv_template
- tmpdir.rmtree(noerrors=True)
+ venv.move(venv_template)
+ yield venv
@pytest.yield_fixture
@@ -231,10 +266,12 @@ def virtualenv(virtualenv_template, tmpdir, isolate):
``tests.lib.venv.VirtualEnvironment`` object.
"""
venv_location = tmpdir.join("workspace", "venv")
- shutil.copytree(virtualenv_template, venv_location, symlinks=True)
- venv = VirtualEnvironment(venv_location)
- yield venv
- venv_location.rmtree(noerrors=True)
+ yield VirtualEnvironment(venv_location, virtualenv_template)
+
+
+@pytest.fixture
+def with_wheel(virtualenv, wheel_install):
+ install_egg_link(virtualenv, 'wheel', wheel_install)
@pytest.fixture
@@ -250,7 +287,7 @@ def script(tmpdir, virtualenv):
tmpdir.join("workspace"),
# Tell the Test Environment where our virtualenv is located
- virtualenv=virtualenv.location,
+ virtualenv=virtualenv,
# Do not ignore hidden files, they need to be checked as well
ignore_hidden=False,
@@ -266,15 +303,9 @@ def script(tmpdir, virtualenv):
@pytest.fixture(scope="session")
-def common_wheels(tmpdir_factory):
+def common_wheels():
"""Provide a directory with latest setuptools and wheel wheels"""
- wheels_dir = tmpdir_factory.mktemp('common_wheels')
- subprocess.check_call([
- 'pip', 'download', 'wheel', 'setuptools',
- '-d', str(wheels_dir),
- ])
- yield wheels_dir
- wheels_dir.remove(ignore_errors=True)
+ return DATA_DIR.join('common_wheels')
@pytest.fixture
diff --git a/tests/functional/test_check.py b/tests/functional/test_check.py
index e0c65d0e7..45611e73e 100644
--- a/tests/functional/test_check.py
+++ b/tests/functional/test_check.py
@@ -222,3 +222,20 @@ def test_check_development_versions_are_also_considered(script):
)
assert matches_expected_lines(result.stdout, expected_lines)
assert result.returncode == 0
+
+
+def test_basic_check_broken_metadata(script):
+ # Create some corrupt metadata
+ dist_info_dir = script.site_packages_path / 'pkga-1.0.dist-info'
+ dist_info_dir.mkdir()
+ with open(dist_info_dir / 'METADATA', 'w') as f:
+ f.write('Metadata-Version: 2.1\n'
+ 'Name: pkga\n'
+ 'Version: 1.0\n'
+ 'Requires-Dist: pip; python_version == "3.4";extra == "test"\n'
+ )
+
+ result = script.pip('check', expect_error=True)
+
+ assert 'Error parsing requirements' in result.stderr
+ assert result.returncode == 1
diff --git a/tests/functional/test_completion.py b/tests/functional/test_completion.py
index 586a0a89b..bee64d319 100644
--- a/tests/functional/test_completion.py
+++ b/tests/functional/test_completion.py
@@ -3,29 +3,26 @@ import sys
import pytest
-
-def test_completion_for_bash(script):
- """
- Test getting completion for bash shell
- """
- bash_completion = """\
+COMPLETION_FOR_SUPPORTED_SHELLS_TESTS = (
+ ('bash', """\
_pip_completion()
{
COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \\
COMP_CWORD=$COMP_CWORD \\
PIP_AUTO_COMPLETE=1 $1 ) )
}
-complete -o default -F _pip_completion pip"""
-
- result = script.pip('completion', '--bash')
- assert bash_completion in result.stdout, 'bash completion is wrong'
-
-
-def test_completion_for_zsh(script):
- """
- Test getting completion for zsh shell
- """
- zsh_completion = """\
+complete -o default -F _pip_completion pip"""),
+ ('fish', """\
+function __fish_complete_pip
+ set -lx COMP_WORDS (commandline -o) ""
+ set -lx COMP_CWORD ( \\
+ math (contains -i -- (commandline -t) $COMP_WORDS)-1 \\
+ )
+ set -lx PIP_AUTO_COMPLETE 1
+ string split \\ -- (eval $COMP_WORDS[1])
+end
+complete -fa "(__fish_complete_pip)" -c pip"""),
+ ('zsh', """\
function _pip_completion {
local words cword
read -Ac words
@@ -34,29 +31,24 @@ function _pip_completion {
COMP_CWORD=$(( cword-1 )) \\
PIP_AUTO_COMPLETE=1 $words[1] ) )
}
-compctl -K _pip_completion pip"""
-
- result = script.pip('completion', '--zsh')
- assert zsh_completion in result.stdout, 'zsh completion is wrong'
+compctl -K _pip_completion pip"""),
+)
-def test_completion_for_fish(script):
+@pytest.mark.parametrize(
+ 'shell, completion',
+ COMPLETION_FOR_SUPPORTED_SHELLS_TESTS,
+ ids=[t[0] for t in COMPLETION_FOR_SUPPORTED_SHELLS_TESTS],
+)
+def test_completion_for_supported_shells(script, pip_src, shell, completion):
"""
- Test getting completion for fish shell
+ Test getting completion for bash shell
"""
- fish_completion = """\
-function __fish_complete_pip
- set -lx COMP_WORDS (commandline -o) ""
- set -lx COMP_CWORD ( \\
- math (contains -i -- (commandline -t) $COMP_WORDS)-1 \\
- )
- set -lx PIP_AUTO_COMPLETE 1
- string split \\ -- (eval $COMP_WORDS[1])
-end
-complete -fa "(__fish_complete_pip)" -c pip"""
+ # Re-install pip so we get the launchers.
+ script.pip_install_local('--no-build-isolation', pip_src)
- result = script.pip('completion', '--fish')
- assert fish_completion in result.stdout, 'fish completion is wrong'
+ result = script.pip('completion', '--' + shell, use_module=False)
+ assert completion in result.stdout, str(result.stdout)
def test_completion_for_unknown_shell(script):
diff --git a/tests/functional/test_freeze.py b/tests/functional/test_freeze.py
index d51029281..bf6f058b8 100644
--- a/tests/functional/test_freeze.py
+++ b/tests/functional/test_freeze.py
@@ -576,7 +576,6 @@ def test_freeze_user(script, virtualenv, data):
Testing freeze with --user, first we have to install some stuff.
"""
script.pip('download', 'setuptools', 'wheel', '-d', data.packages)
- virtualenv.system_site_packages = True
script.pip_install_local('--find-links', data.find_links,
'--user', 'simple==2.0')
script.pip_install_local('--find-links', data.find_links,
diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py
index b10b78fa4..ab5d91cb2 100644
--- a/tests/functional/test_install.py
+++ b/tests/functional/test_install.py
@@ -26,15 +26,14 @@ def test_pep518_uses_build_env(script, data, common_wheels, command, variant):
if variant == 'missing_setuptools':
script.pip("uninstall", "-y", "setuptools")
elif variant == 'bad_setuptools':
- setuptools_init_path = script.site_packages_path.join(
- "setuptools", "__init__.py")
- with open(setuptools_init_path, 'a') as f:
+ setuptools_mod = script.site_packages_path.join("setuptools.py")
+ with open(setuptools_mod, 'a') as f:
f.write('\nraise ImportError("toto")')
else:
raise ValueError(variant)
script.pip(
command, '--no-index', '-f', common_wheels, '-f', data.packages,
- data.src.join("pep518-3.0"), use_module=True
+ data.src.join("pep518-3.0"),
)
@@ -74,11 +73,8 @@ def test_pep518_allows_missing_requires(script, data, common_wheels):
assert result.files_created
-def test_pep518_with_user_pip(script, virtualenv, pip_src,
- data, common_wheels):
- virtualenv.system_site_packages = True
- script.pip("install", "--ignore-installed", "--user", pip_src,
- use_module=True)
+def test_pep518_with_user_pip(script, pip_src, data, common_wheels):
+ script.pip("install", "--ignore-installed", "--user", pip_src)
system_pip_dir = script.site_packages_path / 'pip'
system_pip_dir.rmtree()
system_pip_dir.mkdir()
@@ -86,7 +82,7 @@ def test_pep518_with_user_pip(script, virtualenv, pip_src,
fp.write('raise ImportError\n')
script.pip(
'wheel', '--no-index', '-f', common_wheels, '-f', data.packages,
- data.src.join("pep518-3.0"), use_module=True,
+ data.src.join("pep518-3.0"),
)
@@ -96,7 +92,6 @@ def test_pep518_with_extra_and_markers(script, data, common_wheels):
'-f', common_wheels,
'-f', data.find_links,
data.src.join("pep518_with_extra_and_markers-1.0"),
- use_module=True,
)
@@ -130,10 +125,12 @@ def test_pep518_forkbombs(script, data, common_wheels, command, package):
@pytest.mark.network
-def test_pip_second_command_line_interface_works(script, data):
+def test_pip_second_command_line_interface_works(script, data, pip_src):
"""
Check if ``pip<PYVERSION>`` commands behaves equally
"""
+ # Re-install pip so we get the launchers.
+ script.pip_install_local('--no-build-isolation', pip_src)
# On old versions of Python, urllib3/requests will raise a warning about
# the lack of an SSLContext.
kwargs = {}
@@ -226,10 +223,8 @@ def test_basic_install_editable_from_git(script, tmpdir):
_test_install_editable_from_git(script, tmpdir)
-@pytest.mark.network
def test_install_editable_from_git_autobuild_wheel(
- script, tmpdir, common_wheels):
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
+ script, tmpdir, with_wheel):
_test_install_editable_from_git(script, tmpdir)
@@ -742,14 +737,11 @@ def test_install_nonlocal_compatible_wheel_path(script, data):
assert result.returncode == ERROR
-def test_install_with_target_and_scripts_no_warning(script, common_wheels):
+def test_install_with_target_and_scripts_no_warning(script, with_wheel):
"""
Test that installing with --target does not trigger the "script not
in PATH" warning (issue #5201)
"""
- # We need to have wheel installed so that the project builds via a wheel,
- # which is the only execution path that has the script warning.
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
target_dir = script.scratch_path / 'target'
pkga_path = script.scratch_path / 'pkga'
pkga_path.mkdir()
@@ -1093,23 +1085,13 @@ def test_install_topological_sort(script, data):
assert order1 in res or order2 in res, res
-@pytest.mark.network
-def test_install_wheel_broken(script, data, common_wheels):
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
- res = script.pip(
- 'install', '--no-index', '-f', data.find_links, '-f', common_wheels,
- 'wheelbroken',
- expect_stderr=True)
+def test_install_wheel_broken(script, with_wheel):
+ res = script.pip_install_local('wheelbroken', expect_stderr=True)
assert "Successfully installed wheelbroken-0.1" in str(res), str(res)
-@pytest.mark.network
-def test_cleanup_after_failed_wheel(script, data, common_wheels):
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
- res = script.pip(
- 'install', '--no-index', '-f', data.find_links, '-f', common_wheels,
- 'wheelbrokenafter',
- expect_stderr=True)
+def test_cleanup_after_failed_wheel(script, with_wheel):
+ res = script.pip_install_local('wheelbrokenafter', expect_stderr=True)
# One of the effects of not cleaning up is broken scripts:
script_py = script.bin_path / "script.py"
assert script_py.exists, script_py
@@ -1119,8 +1101,7 @@ def test_cleanup_after_failed_wheel(script, data, common_wheels):
assert "Running setup.py clean for wheelbrokenafter" in str(res), str(res)
-@pytest.mark.network
-def test_install_builds_wheels(script, data, common_wheels):
+def test_install_builds_wheels(script, data, with_wheel):
# We need to use a subprocess to get the right value on Windows.
res = script.run('python', '-c', (
'from pip._internal.utils import appdirs; '
@@ -1130,10 +1111,9 @@ def test_install_builds_wheels(script, data, common_wheels):
# NB This incidentally tests a local tree + tarball inputs
# see test_install_editable_from_git_autobuild_wheel for editable
# vcs coverage.
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
to_install = data.packages.join('requires_wheelbroken_upper')
res = script.pip(
- 'install', '--no-index', '-f', data.find_links, '-f', common_wheels,
+ 'install', '--no-index', '-f', data.find_links,
to_install, expect_stderr=True)
expected = ("Successfully installed requires-wheelbroken-upper-0"
" upper-2.0 wheelbroken-0.1")
@@ -1162,14 +1142,10 @@ def test_install_builds_wheels(script, data, common_wheels):
]
-@pytest.mark.network
-def test_install_no_binary_disables_building_wheels(
- script, data, common_wheels):
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
+def test_install_no_binary_disables_building_wheels(script, data, with_wheel):
to_install = data.packages.join('requires_wheelbroken_upper')
res = script.pip(
'install', '--no-index', '--no-binary=upper', '-f', data.find_links,
- '-f', common_wheels,
to_install, expect_stderr=True)
expected = ("Successfully installed requires-wheelbroken-upper-0"
" upper-2.0 wheelbroken-0.1")
@@ -1188,12 +1164,10 @@ def test_install_no_binary_disables_building_wheels(
assert "Running setup.py install for upper" in str(res), str(res)
-@pytest.mark.network
-def test_install_no_binary_disables_cached_wheels(script, data, common_wheels):
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
+def test_install_no_binary_disables_cached_wheels(script, data, with_wheel):
# Seed the cache
script.pip(
- 'install', '--no-index', '-f', data.find_links, '-f', common_wheels,
+ 'install', '--no-index', '-f', data.find_links,
'upper')
script.pip('uninstall', 'upper', '-y')
res = script.pip(
@@ -1246,7 +1220,6 @@ def test_double_install(script):
Test double install passing with two same version requirements
"""
result = script.pip('install', 'pip', 'pip',
- use_module=True,
expect_error=False)
msg = "Double requirement given: pip (already in pip, name='pip')"
assert msg not in result.stderr
@@ -1262,7 +1235,7 @@ def test_double_install_fail(script):
assert msg in result.stderr
-def test_install_incompatible_python_requires(script, common_wheels):
+def test_install_incompatible_python_requires(script):
script.scratch_path.join("pkga").mkdir()
pkga_path = script.scratch_path / 'pkga'
pkga_path.join("setup.py").write(textwrap.dedent("""
@@ -1271,16 +1244,12 @@ def test_install_incompatible_python_requires(script, common_wheels):
python_requires='<1.0',
version='0.1')
"""))
- script.pip(
- 'install', 'setuptools>24.2', # This should not be needed
- '--no-index', '-f', common_wheels,
- )
result = script.pip('install', pkga_path, expect_error=True)
assert ("pkga requires Python '<1.0' "
"but the running Python is ") in result.stderr, str(result)
-def test_install_incompatible_python_requires_editable(script, common_wheels):
+def test_install_incompatible_python_requires_editable(script):
script.scratch_path.join("pkga").mkdir()
pkga_path = script.scratch_path / 'pkga'
pkga_path.join("setup.py").write(textwrap.dedent("""
@@ -1289,18 +1258,13 @@ def test_install_incompatible_python_requires_editable(script, common_wheels):
python_requires='<1.0',
version='0.1')
"""))
- script.pip(
- 'install', 'setuptools>24.2', # This should not be needed
- '--no-index', '-f', common_wheels,
- )
result = script.pip(
'install', '--editable=%s' % pkga_path, expect_error=True)
assert ("pkga requires Python '<1.0' "
"but the running Python is ") in result.stderr, str(result)
-@pytest.mark.network
-def test_install_incompatible_python_requires_wheel(script, common_wheels):
+def test_install_incompatible_python_requires_wheel(script, with_wheel):
script.scratch_path.join("pkga").mkdir()
pkga_path = script.scratch_path / 'pkga'
pkga_path.join("setup.py").write(textwrap.dedent("""
@@ -1309,11 +1273,6 @@ def test_install_incompatible_python_requires_wheel(script, common_wheels):
python_requires='<1.0',
version='0.1')
"""))
- script.pip(
- 'install', 'setuptools>24.2', # This should not be needed
- '--no-index', '-f', common_wheels,
- )
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
script.run(
'python', 'setup.py', 'bdist_wheel', '--universal', cwd=pkga_path)
result = script.pip('install', './pkga/dist/pkga-0.1-py2.py3-none-any.whl',
@@ -1322,7 +1281,7 @@ def test_install_incompatible_python_requires_wheel(script, common_wheels):
"but the running Python is ") in result.stderr
-def test_install_compatible_python_requires(script, common_wheels):
+def test_install_compatible_python_requires(script):
script.scratch_path.join("pkga").mkdir()
pkga_path = script.scratch_path / 'pkga'
pkga_path.join("setup.py").write(textwrap.dedent("""
@@ -1331,10 +1290,6 @@ def test_install_compatible_python_requires(script, common_wheels):
python_requires='>1.0',
version='0.1')
"""))
- script.pip(
- 'install', 'setuptools>24.2', # This should not be needed
- '--no-index', '-f', common_wheels,
- )
res = script.pip('install', pkga_path, expect_error=True)
assert "Successfully installed pkga-0.1" in res.stdout, res
diff --git a/tests/functional/test_install_config.py b/tests/functional/test_install_config.py
index bf2516685..e4ad7e9f2 100644
--- a/tests/functional/test_install_config.py
+++ b/tests/functional/test_install_config.py
@@ -199,10 +199,8 @@ def test_options_from_venv_config(script, virtualenv):
)
-@pytest.mark.network
def test_install_no_binary_via_config_disables_cached_wheels(
- script, data, common_wheels):
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
+ script, data, with_wheel):
config_file = tempfile.NamedTemporaryFile(mode='wt', delete=False)
try:
script.environ['PIP_CONFIG_FILE'] = config_file.name
diff --git a/tests/functional/test_install_reqs.py b/tests/functional/test_install_reqs.py
index 582e17ed2..ff000fcd0 100644
--- a/tests/functional/test_install_reqs.py
+++ b/tests/functional/test_install_reqs.py
@@ -223,12 +223,8 @@ def test_install_local_with_subdirectory(script):
result.assert_installed('version_subpkg.py', editable=False)
-@pytest.mark.network
def test_wheel_user_with_prefix_in_pydistutils_cfg(
- script, data, virtualenv, common_wheels):
- # Make sure wheel is available in the virtualenv
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
- virtualenv.system_site_packages = True
+ script, data, with_wheel):
if os.name == 'posix':
user_filename = ".pydistutils.cfg"
else:
@@ -242,7 +238,7 @@ def test_wheel_user_with_prefix_in_pydistutils_cfg(
result = script.pip(
'install', '--user', '--no-index',
- '-f', data.find_links, '-f', common_wheels,
+ '-f', data.find_links,
'requiresupper')
# Check that we are really installing a wheel
assert 'Running setup.py install for requiresupper' not in result.stdout
@@ -353,9 +349,8 @@ def test_constrained_to_url_install_same_url(script, data):
in result.stdout), str(result)
-@pytest.mark.network
def test_double_install_spurious_hash_mismatch(
- script, tmpdir, data, common_wheels):
+ script, tmpdir, data, with_wheel):
"""Make sure installing the same hashed sdist twice doesn't throw hash
mismatch errors.
@@ -366,13 +361,12 @@ def test_double_install_spurious_hash_mismatch(
"""
# Install wheel package, otherwise, it won't try to build wheels.
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
with requirements_file('simple==1.0 --hash=sha256:393043e672415891885c9a2a'
'0929b1af95fb866d6ca016b42d2e6ce53619b653',
tmpdir) as reqs_file:
# Install a package (and build its wheel):
result = script.pip_install_local(
- '--find-links', data.find_links, '-f', common_wheels,
+ '--find-links', data.find_links,
'-r', reqs_file.abspath, expect_error=False)
assert 'Successfully installed simple-1.0' in str(result)
@@ -382,7 +376,7 @@ def test_double_install_spurious_hash_mismatch(
# Then install it again. We should not hit a hash mismatch, and the
# package should install happily.
result = script.pip_install_local(
- '--find-links', data.find_links, '-f', common_wheels,
+ '--find-links', data.find_links,
'-r', reqs_file.abspath, expect_error=False)
assert 'Successfully installed simple-1.0' in str(result)
diff --git a/tests/functional/test_install_user.py b/tests/functional/test_install_user.py
index 09edcb965..17d09768b 100644
--- a/tests/functional/test_install_user.py
+++ b/tests/functional/test_install_user.py
@@ -1,46 +1,36 @@
"""
tests specific to "pip install --user"
"""
-import os
import textwrap
from os.path import curdir, isdir, isfile
import pytest
-from pip._internal.utils.compat import cache_from_source, uses_pycache
from tests.lib import pyversion
from tests.lib.local_repos import local_checkout
-def _patch_dist_in_site_packages(script):
- sitecustomize_path = script.lib_path.join("sitecustomize.py")
- sitecustomize_path.write(textwrap.dedent("""
+def _patch_dist_in_site_packages(virtualenv):
+ # Since the tests are run from a virtualenv, and to avoid the "Will not
+ # install to the usersite because it will lack sys.path precedence..."
+ # error: Monkey patch `pip._internal.req.req_install.dist_in_site_packages`
+ # so it's possible to install a conflicting distribution in the user site.
+ virtualenv.sitecustomize = textwrap.dedent("""
def dist_in_site_packages(dist):
return False
from pip._internal.req import req_install
req_install.dist_in_site_packages = dist_in_site_packages
- """))
-
- # Caught py32 with an outdated __pycache__ file after a sitecustomize
- # update (after python should have updated it) so will delete the cache
- # file to be sure
- # See: https://github.com/pypa/pip/pull/893#issuecomment-16426701
- if uses_pycache:
- cache_path = cache_from_source(sitecustomize_path)
- if os.path.isfile(cache_path):
- os.remove(cache_path)
+ """)
class Tests_UserSite:
@pytest.mark.network
- def test_reset_env_system_site_packages_usersite(self, script, virtualenv):
+ def test_reset_env_system_site_packages_usersite(self, script):
"""
- reset_env(system_site_packages=True) produces env where a --user
- install can be found using pkg_resources
+ Check user site works as expected.
"""
- virtualenv.system_site_packages = True
script.pip('install', '--user', 'INITools==0.2')
result = script.run(
'python', '-c',
@@ -52,12 +42,11 @@ class Tests_UserSite:
@pytest.mark.network
def test_install_subversion_usersite_editable_with_distribute(
- self, script, virtualenv, tmpdir):
+ self, script, tmpdir):
"""
Test installing current directory ('.') into usersite after installing
distribute
"""
- virtualenv.system_site_packages = True
result = script.pip(
'install', '--user', '-e',
'%s#egg=initools' %
@@ -68,15 +57,11 @@ class Tests_UserSite:
)
result.assert_installed('INITools', use_user_site=True)
- @pytest.mark.network
def test_install_from_current_directory_into_usersite(
- self, script, virtualenv, data, common_wheels):
+ self, script, data, with_wheel):
"""
Test installing current directory ('.') into usersite
"""
- virtualenv.system_site_packages = True
- script.pip("install", "wheel", '--no-index', '-f', common_wheels)
-
run_from = data.packages.join("FSPkg")
result = script.pip(
'install', '-vvv', '--user', curdir,
@@ -92,10 +77,15 @@ class Tests_UserSite:
)
assert dist_info_folder in result.files_created
- def test_install_user_venv_nositepkgs_fails(self, script, data):
+ @pytest.mark.incompatible_with_test_venv
+ def test_install_user_venv_nositepkgs_fails(self, virtualenv,
+ script, data):
"""
user install in virtualenv (with no system packages) fails with message
"""
+ # We can't use PYTHONNOUSERSITE, as it's not
+ # honoured by virtualenv's custom site.py.
+ virtualenv.user_site_packages = False
run_from = data.packages.join("FSPkg")
result = script.pip(
'install', '--user', curdir,
@@ -108,11 +98,10 @@ class Tests_UserSite:
)
@pytest.mark.network
- def test_install_user_conflict_in_usersite(self, script, virtualenv):
+ def test_install_user_conflict_in_usersite(self, script):
"""
Test user install with conflict in usersite updates usersite.
"""
- virtualenv.system_site_packages = True
script.pip('install', '--user', 'INITools==0.3', '--no-binary=:all:')
@@ -132,26 +121,12 @@ class Tests_UserSite:
assert not isfile(initools_v3_file), initools_v3_file
@pytest.mark.network
- def test_install_user_conflict_in_globalsite(self, script, virtualenv):
+ def test_install_user_conflict_in_globalsite(self, virtualenv, script):
"""
Test user install with conflict in global site ignores site and
installs to usersite
"""
- # the test framework only supports testing using virtualenvs
- # the sys.path ordering for virtualenvs with --system-site-packages is
- # this: virtualenv-site, user-site, global-site
- # this test will use 2 modifications to simulate the
- # user-site/global-site relationship
- # 1) a monkey patch which will make it appear INITools==0.2 is not in
- # the virtualenv site if we don't patch this, pip will return an
- # installation error: "Will not install to the usersite because it
- # will lack sys.path precedence..."
- # 2) adding usersite to PYTHONPATH, so usersite as sys.path precedence
- # over the virtualenv site
-
- virtualenv.system_site_packages = True
- script.environ["PYTHONPATH"] = script.base_path / script.user_site
- _patch_dist_in_site_packages(script)
+ _patch_dist_in_site_packages(virtualenv)
script.pip('install', 'INITools==0.2', '--no-binary=:all:')
@@ -176,26 +151,12 @@ class Tests_UserSite:
assert isdir(initools_folder)
@pytest.mark.network
- def test_upgrade_user_conflict_in_globalsite(self, script, virtualenv):
+ def test_upgrade_user_conflict_in_globalsite(self, virtualenv, script):
"""
Test user install/upgrade with conflict in global site ignores site and
installs to usersite
"""
- # the test framework only supports testing using virtualenvs
- # the sys.path ordering for virtualenvs with --system-site-packages is
- # this: virtualenv-site, user-site, global-site
- # this test will use 2 modifications to simulate the
- # user-site/global-site relationship
- # 1) a monkey patch which will make it appear INITools==0.2 is not in
- # the virtualenv site if we don't patch this, pip will return an
- # installation error: "Will not install to the usersite because it
- # will lack sys.path precedence..."
- # 2) adding usersite to PYTHONPATH, so usersite as sys.path precedence
- # over the virtualenv site
-
- virtualenv.system_site_packages = True
- script.environ["PYTHONPATH"] = script.base_path / script.user_site
- _patch_dist_in_site_packages(script)
+ _patch_dist_in_site_packages(virtualenv)
script.pip('install', 'INITools==0.2', '--no-binary=:all:')
result2 = script.pip(
@@ -220,26 +181,12 @@ class Tests_UserSite:
@pytest.mark.network
def test_install_user_conflict_in_globalsite_and_usersite(
- self, script, virtualenv):
+ self, virtualenv, script):
"""
Test user install with conflict in globalsite and usersite ignores
global site and updates usersite.
"""
- # the test framework only supports testing using virtualenvs.
- # the sys.path ordering for virtualenvs with --system-site-packages is
- # this: virtualenv-site, user-site, global-site.
- # this test will use 2 modifications to simulate the
- # user-site/global-site relationship
- # 1) a monkey patch which will make it appear INITools==0.2 is not in
- # the virtualenv site if we don't patch this, pip will return an
- # installation error: "Will not install to the usersite because it
- # will lack sys.path precedence..."
- # 2) adding usersite to PYTHONPATH, so usersite as sys.path precedence
- # over the virtualenv site
-
- virtualenv.system_site_packages = True
- script.environ["PYTHONPATH"] = script.base_path / script.user_site
- _patch_dist_in_site_packages(script)
+ _patch_dist_in_site_packages(virtualenv)
script.pip('install', 'INITools==0.2', '--no-binary=:all:')
script.pip('install', '--user', 'INITools==0.3', '--no-binary=:all:')
@@ -270,12 +217,11 @@ class Tests_UserSite:
@pytest.mark.network
def test_install_user_in_global_virtualenv_with_conflict_fails(
- self, script, virtualenv):
+ self, script):
"""
Test user install in --system-site-packages virtualenv with conflict in
site fails.
"""
- virtualenv.system_site_packages = True
script.pip('install', 'INITools==0.2')
diff --git a/tests/functional/test_install_wheel.py b/tests/functional/test_install_wheel.py
index 58b9d2bc1..9937d5a0a 100644
--- a/tests/functional/test_install_wheel.py
+++ b/tests/functional/test_install_wheel.py
@@ -114,12 +114,10 @@ def test_install_from_wheel_with_headers(script, data):
result.stdout)
-@pytest.mark.network
-def test_install_wheel_with_target(script, data, common_wheels):
+def test_install_wheel_with_target(script, data, with_wheel):
"""
Test installing a wheel using pip install --target
"""
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
target_dir = script.scratch_path / 'target'
result = script.pip(
'install', 'simple.dist==0.1', '-t', target_dir,
@@ -130,8 +128,7 @@ def test_install_wheel_with_target(script, data, common_wheels):
)
-@pytest.mark.network
-def test_install_wheel_with_target_and_data_files(script, data, common_wheels):
+def test_install_wheel_with_target_and_data_files(script, data, with_wheel):
"""
Test for issue #4092. It will be checked that a data_files specification in
setup.py is handled correctly when a wheel is installed with the --target
@@ -150,7 +147,6 @@ def test_install_wheel_with_target_and_data_files(script, data, common_wheels):
]
)
"""
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
target_dir = script.scratch_path / 'prjwithdatafile'
package = data.packages.join("prjwithdatafile-1.0-py2.py3-none-any.whl")
result = script.pip('install', package,
@@ -234,13 +230,10 @@ def test_wheel_record_lines_in_deterministic_order(script, data):
assert record_lines == sorted(record_lines)
-@pytest.mark.network
-def test_install_user_wheel(script, virtualenv, data, common_wheels):
+def test_install_user_wheel(script, data, with_wheel):
"""
Test user install from wheel (that has a script)
"""
- virtualenv.system_site_packages = True
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
result = script.pip(
'install', 'has.script==1.0', '--user', '--no-index',
'--find-links=' + data.find_links,
diff --git a/tests/functional/test_list.py b/tests/functional/test_list.py
index 1f12d91e0..c376049c5 100644
--- a/tests/functional/test_list.py
+++ b/tests/functional/test_list.py
@@ -99,12 +99,11 @@ def test_local_columns_flag(script, data):
@pytest.mark.network
-def test_user_flag(script, data, virtualenv):
+def test_user_flag(script, data):
"""
Test the behavior of --user flag in the list command
"""
- virtualenv.system_site_packages = True
script.pip('download', 'setuptools', 'wheel', '-d', data.packages)
script.pip('install', '-f', data.find_links, '--no-index', 'simple==1.0')
script.pip('install', '-f', data.find_links, '--no-index',
@@ -116,12 +115,11 @@ def test_user_flag(script, data, virtualenv):
@pytest.mark.network
-def test_user_columns_flag(script, data, virtualenv):
+def test_user_columns_flag(script, data):
"""
Test the behavior of --user --format=columns flags in the list command
"""
- virtualenv.system_site_packages = True
script.pip('download', 'setuptools', 'wheel', '-d', data.packages)
script.pip('install', '-f', data.find_links, '--no-index', 'simple==1.0')
script.pip('install', '-f', data.find_links, '--no-index',
diff --git a/tests/functional/test_uninstall.py b/tests/functional/test_uninstall.py
index 7fd47b17e..2748e2f7a 100644
--- a/tests/functional/test_uninstall.py
+++ b/tests/functional/test_uninstall.py
@@ -67,7 +67,7 @@ def test_basic_uninstall_with_scripts(script):
Uninstall an easy_installed package with scripts.
"""
- result = script.run('easy_install', 'PyLogo', expect_stderr=True)
+ result = script.easy_install('PyLogo', expect_stderr=True)
easy_install_pth = script.site_packages / 'easy-install.pth'
pylogo = sys.platform == 'win32' and 'pylogo' or 'PyLogo'
assert(pylogo in result.files_updated[easy_install_pth].bytes)
@@ -85,7 +85,8 @@ def test_uninstall_easy_install_after_import(script):
Uninstall an easy_installed package after it's been imported
"""
- result = script.run('easy_install', 'INITools==0.2', expect_stderr=True)
+ result = script.easy_install('--always-unzip', 'INITools==0.2',
+ expect_stderr=True)
# the import forces the generation of __pycache__ if the version of python
# supports it
script.run('python', '-c', "import initools")
@@ -108,8 +109,8 @@ def test_uninstall_trailing_newline(script):
lacks a trailing newline
"""
- script.run('easy_install', 'INITools==0.2', expect_stderr=True)
- script.run('easy_install', 'PyLogo', expect_stderr=True)
+ script.easy_install('INITools==0.2', expect_stderr=True)
+ script.easy_install('PyLogo', expect_stderr=True)
easy_install_pth = script.site_packages_path / 'easy-install.pth'
# trim trailing newline from easy-install.pth
@@ -269,9 +270,7 @@ def test_uninstall_easy_installed_console_scripts(script):
"""
Test uninstalling package with console_scripts that is easy_installed.
"""
- args = ['easy_install']
- args.append('discover')
- result = script.run(*args, **{"expect_stderr": True})
+ result = script.easy_install('discover', expect_error=True)
assert script.bin / 'discover' + script.exe in result.files_created, (
sorted(result.files_created.keys())
)
diff --git a/tests/functional/test_uninstall_user.py b/tests/functional/test_uninstall_user.py
index eaa213f77..6cddacdbc 100644
--- a/tests/functional/test_uninstall_user.py
+++ b/tests/functional/test_uninstall_user.py
@@ -12,35 +12,20 @@ from tests.lib import assert_all_changes, pyversion
class Tests_UninstallUserSite:
@pytest.mark.network
- def test_uninstall_from_usersite(self, script, virtualenv):
+ def test_uninstall_from_usersite(self, script):
"""
Test uninstall from usersite
"""
- virtualenv.system_site_packages = True
result1 = script.pip('install', '--user', 'INITools==0.3')
result2 = script.pip('uninstall', '-y', 'INITools')
assert_all_changes(result1, result2, [script.venv / 'build', 'cache'])
def test_uninstall_from_usersite_with_dist_in_global_site(
- self, script, virtualenv):
+ self, virtualenv, script):
"""
Test uninstall from usersite (with same dist in global site)
"""
- # the test framework only supports testing using virtualenvs.
- # the sys.path ordering for virtualenvs with --system-site-packages is
- # this: virtualenv-site, user-site, global-site.
- # this test will use 2 modifications to simulate the
- # user-site/global-site relationship
- # 1) a monkey patch which will make it appear piptestpackage is not in
- # the virtualenv site if we don't patch this, pip will return an
- # installation error: "Will not install to the usersite because it
- # will lack sys.path precedence..."
- # 2) adding usersite to PYTHONPATH, so usersite has sys.path precedence
- # over the virtualenv site
-
- virtualenv.system_site_packages = True
- script.environ["PYTHONPATH"] = script.base_path / script.user_site
- _patch_dist_in_site_packages(script)
+ _patch_dist_in_site_packages(virtualenv)
script.pip_install_local('pip-test-package==0.1', '--no-binary=:all:')
@@ -62,11 +47,10 @@ class Tests_UninstallUserSite:
)
assert isdir(egg_info_folder)
- def test_uninstall_editable_from_usersite(self, script, virtualenv, data):
+ def test_uninstall_editable_from_usersite(self, script, data):
"""
Test uninstall editable local user install
"""
- virtualenv.system_site_packages = True
script.user_site_path.makedirs()
# install
diff --git a/tests/functional/test_wheel.py b/tests/functional/test_wheel.py
index fee5c7b38..0e1e2e54a 100644
--- a/tests/functional/test_wheel.py
+++ b/tests/functional/test_wheel.py
@@ -9,34 +9,34 @@ from pip._internal.locations import write_delete_marker_file
from tests.lib import pyversion
-def test_wheel_exit_status_code_when_no_requirements(script, common_wheels):
+@pytest.fixture(autouse=True)
+def auto_with_wheel(with_wheel):
+ pass
+
+
+def test_wheel_exit_status_code_when_no_requirements(script):
"""
Test wheel exit status code when no requirements specified
"""
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
result = script.pip('wheel', expect_error=True)
assert "You must give at least one requirement to wheel" in result.stderr
assert result.returncode == ERROR
-def test_wheel_exit_status_code_when_blank_requirements_file(
- script, common_wheels):
+def test_wheel_exit_status_code_when_blank_requirements_file(script):
"""
Test wheel exit status code when blank requirements file specified
"""
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
script.scratch_path.join("blank.txt").write("\n")
script.pip('wheel', '-r', 'blank.txt')
-@pytest.mark.network
-def test_pip_wheel_success(script, data, common_wheels):
+def test_pip_wheel_success(script, data):
"""
Test 'pip wheel' success.
"""
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
result = script.pip(
- 'wheel', '--no-index', '-f', data.find_links, '-f', common_wheels,
+ 'wheel', '--no-index', '-f', data.find_links,
'simple==3.0',
)
wheel_file_name = 'simple-3.0-py%s-none-any.whl' % pyversion[0]
@@ -45,12 +45,10 @@ def test_pip_wheel_success(script, data, common_wheels):
assert "Successfully built simple" in result.stdout, result.stdout
-@pytest.mark.network
-def test_basic_pip_wheel_downloads_wheels(script, data, common_wheels):
+def test_basic_pip_wheel_downloads_wheels(script, data):
"""
Test 'pip wheel' downloads wheels
"""
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
result = script.pip(
'wheel', '--no-index', '-f', data.find_links, 'simple.dist',
)
@@ -60,27 +58,23 @@ def test_basic_pip_wheel_downloads_wheels(script, data, common_wheels):
assert "Saved" in result.stdout, result.stdout
-@pytest.mark.network
-def test_pip_wheel_builds_when_no_binary_set(script, data, common_wheels):
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
+def test_pip_wheel_builds_when_no_binary_set(script, data):
data.packages.join('simple-3.0-py2.py3-none-any.whl').touch()
# Check that the wheel package is ignored
res = script.pip(
'wheel', '--no-index', '--no-binary', ':all:',
- '-f', data.find_links, '-f', common_wheels,
+ '-f', data.find_links,
'simple==3.0')
assert "Running setup.py bdist_wheel for simple" in str(res), str(res)
-@pytest.mark.network
-def test_pip_wheel_builds_editable_deps(script, data, common_wheels):
+def test_pip_wheel_builds_editable_deps(script, data):
"""
Test 'pip wheel' finds and builds dependencies of editables
"""
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
editable_path = os.path.join(data.src, 'requires_simple')
result = script.pip(
- 'wheel', '--no-index', '-f', data.find_links, '-f', common_wheels,
+ 'wheel', '--no-index', '-f', data.find_links,
'-e', editable_path
)
wheel_file_name = 'simple-1.0-py%s-none-any.whl' % pyversion[0]
@@ -88,15 +82,13 @@ def test_pip_wheel_builds_editable_deps(script, data, common_wheels):
assert wheel_file_path in result.files_created, result.stdout
-@pytest.mark.network
-def test_pip_wheel_builds_editable(script, data, common_wheels):
+def test_pip_wheel_builds_editable(script, data):
"""
Test 'pip wheel' builds an editable package
"""
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
editable_path = os.path.join(data.src, 'simplewheel-1.0')
result = script.pip(
- 'wheel', '--no-index', '-f', data.find_links, '-f', common_wheels,
+ 'wheel', '--no-index', '-f', data.find_links,
'-e', editable_path
)
wheel_file_name = 'simplewheel-1.0-py%s-none-any.whl' % pyversion[0]
@@ -104,14 +96,12 @@ def test_pip_wheel_builds_editable(script, data, common_wheels):
assert wheel_file_path in result.files_created, result.stdout
-@pytest.mark.network
-def test_pip_wheel_fail(script, data, common_wheels):
+def test_pip_wheel_fail(script, data):
"""
Test 'pip wheel' failure.
"""
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
result = script.pip(
- 'wheel', '--no-index', '-f', data.find_links, '-f', common_wheels,
+ 'wheel', '--no-index', '-f', data.find_links,
'wheelbroken==0.1',
expect_error=True,
)
@@ -126,17 +116,14 @@ def test_pip_wheel_fail(script, data, common_wheels):
assert result.returncode != 0
-@pytest.mark.network
-def test_no_clean_option_blocks_cleaning_after_wheel(
- script, data, common_wheels):
+def test_no_clean_option_blocks_cleaning_after_wheel(script, data):
"""
Test --no-clean option blocks cleaning after wheel build
"""
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
build = script.venv_path / 'build'
result = script.pip(
'wheel', '--no-clean', '--no-index', '--build', build,
- '--find-links=%s' % data.find_links, '-f', common_wheels,
+ '--find-links=%s' % data.find_links,
'simple',
expect_temp=True,
)
@@ -144,16 +131,14 @@ def test_no_clean_option_blocks_cleaning_after_wheel(
assert exists(build), "build/simple should still exist %s" % str(result)
-@pytest.mark.network
-def test_pip_wheel_source_deps(script, data, common_wheels):
+def test_pip_wheel_source_deps(script, data):
"""
Test 'pip wheel' finds and builds source archive dependencies
of wheels
"""
# 'requires_source' is a wheel that depends on the 'source' project
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
result = script.pip(
- 'wheel', '--no-index', '-f', data.find_links, '-f', common_wheels,
+ 'wheel', '--no-index', '-f', data.find_links,
'requires_source',
)
wheel_file_name = 'source-1.0-py%s-none-any.whl' % pyversion[0]
@@ -162,16 +147,12 @@ def test_pip_wheel_source_deps(script, data, common_wheels):
assert "Successfully built source" in result.stdout, result.stdout
-@pytest.mark.network
-def test_pip_wheel_fail_cause_of_previous_build_dir(
- script, data, common_wheels):
+def test_pip_wheel_fail_cause_of_previous_build_dir(script, data):
"""
Test when 'pip wheel' tries to install a package that has a previous build
directory
"""
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
-
# Given that I have a previous build dir of the `simple` package
build = script.venv_path / 'build' / 'simple'
os.makedirs(build)
@@ -189,19 +170,15 @@ def test_pip_wheel_fail_cause_of_previous_build_dir(
assert result.returncode == PREVIOUS_BUILD_DIR_ERROR, result
-@pytest.mark.network
-def test_wheel_package_with_latin1_setup(script, data, common_wheels):
+def test_wheel_package_with_latin1_setup(script, data):
"""Create a wheel from a package with latin-1 encoded setup.py."""
- script.pip('install', 'wheel', '--no-index', '-f', common_wheels)
pkg_to_wheel = data.packages.join("SetupPyLatin1")
result = script.pip('wheel', pkg_to_wheel)
assert 'Successfully built SetupPyUTF8' in result.stdout
-@pytest.mark.network
def test_pip_wheel_with_pep518_build_reqs(script, data, common_wheels):
- script.pip_install_local('-f', common_wheels, 'wheel')
result = script.pip('wheel', '--no-index', '-f', data.find_links,
'-f', common_wheels, 'pep518==3.0',)
wheel_file_name = 'pep518-3.0-py%s-none-any.whl' % pyversion[0]
@@ -211,10 +188,8 @@ def test_pip_wheel_with_pep518_build_reqs(script, data, common_wheels):
assert "Installing build dependencies" in result.stdout, result.stdout
-@pytest.mark.network
-def test_pip_wheel_with_pep518_build_reqs_no_isolation(script, data,
- common_wheels):
- script.pip_install_local('-f', common_wheels, 'wheel', 'simplewheel==2.0')
+def test_pip_wheel_with_pep518_build_reqs_no_isolation(script, data):
+ script.pip_install_local('simplewheel==2.0')
result = script.pip(
'wheel', '--no-index', '-f', data.find_links,
'--no-build-isolation', 'pep518==3.0',
@@ -226,9 +201,7 @@ def test_pip_wheel_with_pep518_build_reqs_no_isolation(script, data,
assert "Installing build dependencies" not in result.stdout, result.stdout
-@pytest.mark.network
def test_pip_wheel_with_user_set_in_config(script, data, common_wheels):
- script.pip_install_local('-f', common_wheels, 'wheel')
config_file = script.scratch_path / 'pip.conf'
script.environ['PIP_CONFIG_FILE'] = str(config_file)
config_file.write("[install]\nuser = true")
diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py
index 5a00fb425..bd8903df6 100644
--- a/tests/lib/__init__.py
+++ b/tests/lib/__init__.py
@@ -10,9 +10,7 @@ import shutil
import subprocess
import pytest
-import scripttest
-import six
-import virtualenv
+from scripttest import FoundDir, TestFileEnvironment
from tests.lib.path import Path, curdir
@@ -40,15 +38,6 @@ def path_to_url(path):
return 'file://' + url
-# workaround for https://github.com/pypa/virtualenv/issues/306
-def virtualenv_lib_path(venv_home, venv_lib):
- if not hasattr(sys, "pypy_version_info"):
- return venv_lib
- version_fmt = '{0}' if six.PY3 else '{0}.{1}'
- version_dir = version_fmt.format(*sys.version_info)
- return os.path.join(venv_home, 'lib-python', version_dir)
-
-
def create_file(path, contents=None):
"""Create a file on the path, with the given contents
"""
@@ -259,7 +248,7 @@ class TestPipResult(object):
)
-class PipTestEnvironment(scripttest.TestFileEnvironment):
+class PipTestEnvironment(TestFileEnvironment):
"""
A specialized TestFileEnvironment for testing pip
"""
@@ -281,19 +270,11 @@ class PipTestEnvironment(scripttest.TestFileEnvironment):
base_path = Path(base_path)
# Store paths related to the virtual environment
- _virtualenv = kwargs.pop("virtualenv")
- path_locations = virtualenv.path_locations(_virtualenv)
- # Make sure we have test.lib.path.Path objects
- venv, lib, include, bin = map(Path, path_locations)
- self.venv_path = venv
- self.lib_path = virtualenv_lib_path(venv, lib)
- self.include_path = include
- self.bin_path = bin
-
- if hasattr(sys, "pypy_version_info"):
- self.site_packages_path = self.venv_path.join("site-packages")
- else:
- self.site_packages_path = self.lib_path.join("site-packages")
+ venv = kwargs.pop("virtualenv")
+ self.venv_path = venv.location
+ self.lib_path = venv.lib
+ self.site_packages_path = venv.site
+ self.bin_path = venv.bin
self.user_base_path = self.venv_path.join("user")
self.user_site_path = self.venv_path.join(
@@ -336,7 +317,7 @@ class PipTestEnvironment(scripttest.TestFileEnvironment):
super(PipTestEnvironment, self).__init__(base_path, *args, **kwargs)
# Expand our absolute path directories into relative
- for name in ["base", "venv", "lib", "include", "bin", "site_packages",
+ for name in ["base", "venv", "bin", "lib", "site_packages",
"user_base", "user_site", "user_bin", "scratch"]:
real_name = "%s_path" % name
setattr(self, name, getattr(self, real_name) - self.base_path)
@@ -358,6 +339,16 @@ class PipTestEnvironment(scripttest.TestFileEnvironment):
result = super(PipTestEnvironment, self)._ignore_file(fn)
return result
+ def _find_traverse(self, path, result):
+ # Ignore symlinked directories to avoid duplicates in `run()`
+ # results because of venv `lib64 -> lib/` symlink on Linux.
+ full = os.path.join(self.base_path, path)
+ if os.path.isdir(full) and os.path.islink(full):
+ if not self.temp_path or path != 'tmp':
+ result[path] = FoundDir(self.base_path, path)
+ else:
+ super(PipTestEnvironment, self)._find_traverse(path, result)
+
def run(self, *args, **kw):
if self.verbose:
print('>> running %s %s' % (args, kw))
@@ -380,7 +371,7 @@ class PipTestEnvironment(scripttest.TestFileEnvironment):
if (pyversion_tuple < (2, 7, 9) and
args and args[0] in ('search', 'install', 'download')):
kwargs['expect_stderr'] = True
- if kwargs.pop('use_module', False):
+ if kwargs.pop('use_module', True):
exe = 'python'
args = ('-m', 'pip') + args
else:
@@ -394,6 +385,10 @@ class PipTestEnvironment(scripttest.TestFileEnvironment):
*args, **kwargs
)
+ def easy_install(self, *args, **kwargs):
+ args = ('-m', 'easy_install') + args
+ return self.run('python', *args, **kwargs)
+
# FIXME ScriptTest does something similar, but only within a single
# ProcResult; this generalizes it so states can be compared across
diff --git a/tests/lib/venv.py b/tests/lib/venv.py
index ce0b1059d..6b63391d9 100644
--- a/tests/lib/venv.py
+++ b/tests/lib/venv.py
@@ -1,12 +1,17 @@
from __future__ import absolute_import
-import distutils
+import compileall
+import sys
+import textwrap
+import six
import virtualenv as _virtualenv
-from . import virtualenv_lib_path
from .path import Path
+if six.PY3:
+ import venv as _venv
+
class VirtualEnvironment(object):
"""
@@ -14,54 +19,165 @@ class VirtualEnvironment(object):
virtualenv but in the future it could use pyvenv.
"""
- def __init__(self, location, system_site_packages=False):
+ def __init__(self, location, template=None, venv_type=None):
+ assert template is None or venv_type is None
+ assert venv_type in (None, 'virtualenv', 'venv')
self.location = Path(location)
- self._system_site_packages = system_site_packages
+ self._venv_type = venv_type or template._venv_type or 'virtualenv'
+ self._user_site_packages = False
+ self._template = template
+ self._sitecustomize = None
+ self._update_paths()
+ self._create()
+
+ def _update_paths(self):
home, lib, inc, bin = _virtualenv.path_locations(self.location)
- self.lib = Path(virtualenv_lib_path(home, lib))
self.bin = Path(bin)
+ self.site = Path(lib) / 'site-packages'
+ # Workaround for https://github.com/pypa/virtualenv/issues/306
+ if hasattr(sys, "pypy_version_info"):
+ version_fmt = '{0}' if six.PY3 else '{0}.{1}'
+ version_dir = version_fmt.format(*sys.version_info)
+ self.lib = Path(home, 'lib-python', version_dir)
+ else:
+ self.lib = Path(lib)
def __repr__(self):
return "<VirtualEnvironment {}>".format(self.location)
- @classmethod
- def create(cls, location, clear=False,
- pip_source_dir=None, relocatable=False):
- obj = cls(location)
- obj._create(clear=clear,
- pip_source_dir=pip_source_dir,
- relocatable=relocatable)
- return obj
-
- def _create(self, clear=False, pip_source_dir=None, relocatable=False):
- # Create the actual virtual environment
- _virtualenv.create_environment(
- self.location,
- clear=clear,
- download=False,
- no_pip=True,
- no_wheel=True,
- )
- _virtualenv.install_wheel([pip_source_dir or '.'],
- self.bin.join("python"))
- if relocatable:
- _virtualenv.make_environment_relocatable(self.location)
- # FIXME: some tests rely on 'easy-install.pth' being already present.
- site_package = distutils.sysconfig.get_python_lib(prefix=self.location)
- Path(site_package).join('easy-install.pth').touch()
+ def _create(self, clear=False):
+ if clear:
+ self.location.rmtree()
+ if self._template:
+ # On Windows, calling `_virtualenv.path_locations(target)`
+ # will have created the `target` directory...
+ if sys.platform == 'win32' and self.location.exists:
+ self.location.rmdir()
+ # Clone virtual environment from template.
+ self._template.location.copytree(self.location)
+ self._sitecustomize = self._template.sitecustomize
+ self._user_site_packages = self._template.user_site_packages
+ else:
+ # Create a new virtual environment.
+ if self._venv_type == 'virtualenv':
+ _virtualenv.create_environment(
+ self.location,
+ no_pip=True,
+ no_wheel=True,
+ no_setuptools=True,
+ )
+ self._fix_virtualenv_site_module()
+ elif self._venv_type == 'venv':
+ builder = _venv.EnvBuilder()
+ context = builder.ensure_directories(self.location)
+ builder.create_configuration(context)
+ builder.setup_python(context)
+ self.site.makedirs()
+ self.sitecustomize = self._sitecustomize
+ self.user_site_packages = self._user_site_packages
+
+ def _fix_virtualenv_site_module(self):
+ # Patch `site.py` so user site work as expected.
+ site_py = self.lib / 'site.py'
+ with open(site_py) as fp:
+ site_contents = fp.read()
+ for pattern, replace in (
+ (
+ # Ensure enabling user site does not result in adding
+ # the real site-packages' directory to `sys.path`.
+ (
+ '\ndef virtual_addsitepackages(known_paths):\n'
+ ),
+ (
+ '\ndef virtual_addsitepackages(known_paths):\n'
+ ' return known_paths\n'
+ ),
+ ),
+ (
+ # Fix sites ordering: user site must be added before system.
+ (
+ '\n paths_in_sys = addsitepackages(paths_in_sys)'
+ '\n paths_in_sys = addusersitepackages(paths_in_sys)\n'
+ ),
+ (
+ '\n paths_in_sys = addusersitepackages(paths_in_sys)'
+ '\n paths_in_sys = addsitepackages(paths_in_sys)\n'
+ ),
+ ),
+ ):
+ assert pattern in site_contents
+ site_contents = site_contents.replace(pattern, replace)
+ with open(site_py, 'w') as fp:
+ fp.write(site_contents)
+ # Make sure bytecode is up-to-date too.
+ assert compileall.compile_file(str(site_py), quiet=1, force=True)
+
+ def _customize_site(self):
+ contents = ''
+ if self._venv_type == 'venv':
+ # Enable user site (before system).
+ contents += textwrap.dedent(
+ '''
+ import os, site, sys
+
+ if not os.environ.get('PYTHONNOUSERSITE', False):
+
+ site.ENABLE_USER_SITE = True
+
+ # First, drop system-sites related paths.
+ original_sys_path = sys.path[:]
+ known_paths = set()
+ for path in site.getsitepackages():
+ site.addsitedir(path, known_paths=known_paths)
+ system_paths = sys.path[len(original_sys_path):]
+ for path in system_paths:
+ if path in original_sys_path:
+ original_sys_path.remove(path)
+ sys.path = original_sys_path
+
+ # Second, add user-site.
+ site.addsitedir(site.getusersitepackages())
+
+ # Third, add back system-sites related paths.
+ for path in site.getsitepackages():
+ site.addsitedir(path)
+ ''').strip()
+ if self._sitecustomize is not None:
+ contents += '\n' + self._sitecustomize
+ sitecustomize = self.site / "sitecustomize.py"
+ sitecustomize.write(contents)
+ # Make sure bytecode is up-to-date too.
+ assert compileall.compile_file(str(sitecustomize), quiet=1, force=True)
def clear(self):
self._create(clear=True)
+ def move(self, location):
+ self.location.move(location)
+ self.location = Path(location)
+ self._update_paths()
+
@property
- def system_site_packages(self):
- return self._system_site_packages
-
- @system_site_packages.setter
- def system_site_packages(self, value):
- marker = self.lib.join("no-global-site-packages.txt")
- if value:
- marker.rm()
- else:
- marker.touch()
- self._system_site_packages = value
+ def sitecustomize(self):
+ return self._sitecustomize
+
+ @sitecustomize.setter
+ def sitecustomize(self, value):
+ self._sitecustomize = value
+ self._customize_site()
+
+ @property
+ def user_site_packages(self):
+ return self._user_site_packages
+
+ @user_site_packages.setter
+ def user_site_packages(self, value):
+ self._user_site_packages = value
+ if self._venv_type == 'virtualenv':
+ marker = self.lib / "no-global-site-packages.txt"
+ if self._user_site_packages:
+ marker.rm()
+ else:
+ marker.touch()
+ elif self._venv_type == 'venv':
+ self._customize_site()
diff --git a/tests/unit/test_locations.py b/tests/unit/test_locations.py
index 5bdfb8e6f..76dd00242 100644
--- a/tests/unit/test_locations.py
+++ b/tests/unit/test_locations.py
@@ -8,6 +8,7 @@ import shutil
import sys
import tempfile
+import pytest
from mock import Mock
from pip._internal.locations import distutils_scheme
@@ -90,6 +91,7 @@ class TestDisutilsScheme:
expected = os.path.join(root, path[1:])
assert os.path.abspath(root_scheme[key]) == expected
+ @pytest.mark.incompatible_with_venv
def test_distutils_config_file_read(self, tmpdir, monkeypatch):
# This deals with nt/posix path differences
install_scripts = os.path.normcase(os.path.abspath(
@@ -106,6 +108,7 @@ class TestDisutilsScheme:
scheme = distutils_scheme('example')
assert scheme['scripts'] == install_scripts
+ @pytest.mark.incompatible_with_venv
# when we request install-lib, we should install everything (.py &
# .so) into that path; i.e. ensure platlib & purelib are set to
# this path
diff --git a/tools/tests-common_wheels-requirements.txt b/tools/tests-common_wheels-requirements.txt
new file mode 100644
index 000000000..0a8547bcc
--- /dev/null
+++ b/tools/tests-common_wheels-requirements.txt
@@ -0,0 +1,2 @@
+setuptools
+wheel
diff --git a/tools/tests-requirements.txt b/tools/tests-requirements.txt
index 75e810980..f6849fc66 100644
--- a/tools/tests-requirements.txt
+++ b/tools/tests-requirements.txt
@@ -7,5 +7,7 @@ pytest-rerunfailures
pytest-timeout
pytest-xdist
pyyaml
+setuptools>=39.2.0 # Needed for `setuptools.wheel.Wheel` support.
scripttest
https://github.com/pypa/virtualenv/archive/master.zip#egg=virtualenv
+wheel
diff --git a/tools/travis/install.sh b/tools/travis/install.sh
index ad67bb65a..3b12d69a2 100755
--- a/tools/travis/install.sh
+++ b/tools/travis/install.sh
@@ -3,4 +3,5 @@ set -e
set -x
pip install --upgrade setuptools
-pip install --upgrade tox
+pip install --upgrade tox tox-venv
+pip freeze --all
diff --git a/tools/travis/run.sh b/tools/travis/run.sh
index df06b8b33..6f4c424e8 100755
--- a/tools/travis/run.sh
+++ b/tools/travis/run.sh
@@ -43,10 +43,10 @@ if [[ "$GROUP" == "1" ]]; then
# Unit tests
tox -- -m unit
# Integration tests (not the ones for 'pip install')
- tox -- -m integration -n 4 --duration=5 -k "not test_install"
+ tox -- --use-venv -m integration -n 4 --duration=5 -k "not test_install"
elif [[ "$GROUP" == "2" ]]; then
# Separate Job for running integration tests for 'pip install'
- tox -- -m integration -n 4 --duration=5 -k "test_install"
+ tox -- --use-venv -m integration -n 4 --duration=5 -k "test_install"
else
# Non-Testing Jobs should run once
tox
diff --git a/tox.ini b/tox.ini
index c48d695de..a885cd06c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -15,6 +15,9 @@ setenv =
# that our tests use.
LC_CTYPE = en_US.UTF-8
deps = -r{toxinidir}/tools/tests-requirements.txt
+commands_pre =
+ python -c 'import shutil, sys; shutil.rmtree(sys.argv[1], ignore_errors=True)' {toxinidir}/tests/data/common_wheels
+ {[helpers]pip} wheel -w {toxinidir}/tests/data/common_wheels -r {toxinidir}/tools/tests-common_wheels-requirements.txt
commands = pytest --timeout 300 []
install_command = {[helpers]pip} install {opts} {packages}
list_dependencies_command = {[helpers]pip} freeze --all
@@ -41,6 +44,7 @@ skip_install = True
deps =
check-manifest
readme_renderer
+commands_pre =
commands =
check-manifest
python setup.py check -m -r -s
@@ -55,18 +59,21 @@ commands =
skip_install = True
basepython = python2
deps = {[lint]deps}
+commands_pre =
commands = {[lint]commands}
[testenv:lint-py3]
skip_install = True
basepython = python3
deps = {[lint]deps}
+commands_pre =
commands = {[lint]commands}
[testenv:mypy]
skip_install = True
basepython = python3
deps = -r{toxinidir}/tools/mypy-requirements.txt
+commands_pre =
commands =
mypy src
mypy src -2