tests: add support for using venv for the virtual environment

Add a new testsuite option `--use-venv` to enable the use of `venv`
for creating a test virtual environment. The option is opt-in because
creating a `venv` environment does not work right when running under a
`virtualenv`; which is why `tox-venv` must be used in combination with
tox.
This commit is contained in:
Benoit Pierre 2018-10-09 08:23:03 +02:00
parent add3801163
commit a4209aa0fb
9 changed files with 104 additions and 25 deletions

View File

@ -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

View File

@ -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

View File

@ -21,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
@ -32,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__]),
@ -197,12 +209,17 @@ def install_egg_link(venv, project_name, egg_info_dir):
@pytest.yield_fixture(scope='session')
def virtualenv_template(tmpdir_factory, pip_src,
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
tmpdir = Path(str(tmpdir_factory.mktemp('virtualenv')))
venv = VirtualEnvironment(tmpdir.join("venv_orig"))
venv = VirtualEnvironment(tmpdir.join("venv_orig"), venv_type=venv_type)
# Install setuptools and pip.
install_egg_link(venv, 'setuptools', setuptools_install)

View File

@ -77,6 +77,7 @@ class Tests_UserSite:
)
assert dist_info_folder in result.files_created
@pytest.mark.incompatible_with_test_venv
def test_install_user_venv_nositepkgs_fails(self, virtualenv,
script, data):
"""

View File

@ -10,7 +10,7 @@ import shutil
import subprocess
import pytest
import scripttest
from scripttest import FoundDir, TestFileEnvironment
from tests.lib.path import Path, curdir
@ -248,7 +248,7 @@ class TestPipResult(object):
)
class PipTestEnvironment(scripttest.TestFileEnvironment):
class PipTestEnvironment(TestFileEnvironment):
"""
A specialized TestFileEnvironment for testing pip
"""
@ -339,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))

View File

@ -2,12 +2,16 @@ from __future__ import absolute_import
import compileall
import sys
import textwrap
import six
import virtualenv as _virtualenv
from .path import Path
if six.PY3:
import venv as _venv
class VirtualEnvironment(object):
"""
@ -15,8 +19,11 @@ class VirtualEnvironment(object):
virtualenv but in the future it could use pyvenv.
"""
def __init__(self, location, template=None):
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._venv_type = venv_type or template._venv_type or 'virtualenv'
self._user_site_packages = False
self._template = template
self._sitecustomize = None
@ -52,17 +59,24 @@ class VirtualEnvironment(object):
self._user_site_packages = self._template.user_site_packages
else:
# Create a new virtual environment.
_virtualenv.create_environment(
self.location,
no_pip=True,
no_wheel=True,
no_setuptools=True,
)
self._fix_site_module()
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_site_module(self):
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:
@ -100,6 +114,34 @@ class VirtualEnvironment(object):
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"
@ -131,8 +173,11 @@ class VirtualEnvironment(object):
@user_site_packages.setter
def user_site_packages(self, value):
self._user_site_packages = value
marker = self.lib / "no-global-site-packages.txt"
if self._user_site_packages:
marker.rm()
else:
marker.touch()
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()

View File

@ -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

View File

@ -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

View File

@ -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