Skip to content

Commit

Permalink
Merge pull request #5886 from benoit-pierre/rework_tests_virtualenv_w…
Browse files Browse the repository at this point in the history
…ith_support_for_venv

Rework tests virtualenv, also adding support for using venv
  • Loading branch information
pradyunsg committed Oct 23, 2018
2 parents 3b8c076 + 0b45fda commit 51819cc
Show file tree
Hide file tree
Showing 23 changed files with 424 additions and 433 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ htmlcov/
nosetests.xml
coverage.xml
*.cover
tests/data/common_wheels/

# Misc
*~
Expand Down
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
language: python
cache: pip
dist: trusty
python: 3.6

stages:
- primary
Expand All @@ -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
Expand Down
5 changes: 3 additions & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
163 changes: 97 additions & 66 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import compileall
import io
import os
import shutil
Expand All @@ -6,9 +7,10 @@

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
Expand All @@ -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
Expand All @@ -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__]),
Expand All @@ -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):
"""
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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
Expand Down
62 changes: 27 additions & 35 deletions tests/functional/test_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,26 @@

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
Expand All @@ -34,29 +31,24 @@ def test_completion_for_zsh(script):
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):
Expand Down
1 change: 0 additions & 1 deletion tests/functional/test_freeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 51819cc

Please sign in to comment.