2017-03-26 17:18:29 +02:00
|
|
|
import io
|
2014-05-07 16:20:14 +02:00
|
|
|
import os
|
2013-08-22 13:49:53 +02:00
|
|
|
import shutil
|
2017-05-14 00:23:17 +02:00
|
|
|
import subprocess
|
2017-03-26 17:18:29 +02:00
|
|
|
import sys
|
2013-08-22 13:49:53 +02:00
|
|
|
|
2017-06-13 14:17:00 +02:00
|
|
|
import pytest
|
2017-03-26 17:49:02 +02:00
|
|
|
import six
|
|
|
|
|
2017-08-31 17:48:18 +02:00
|
|
|
import pip._internal
|
2013-08-24 12:43:01 +02:00
|
|
|
from tests.lib import SRC_DIR, TestData
|
2013-08-22 04:28:15 +02:00
|
|
|
from tests.lib.path import Path
|
2013-08-22 06:39:07 +02:00
|
|
|
from tests.lib.scripttest import PipTestEnvironment
|
|
|
|
from tests.lib.venv import VirtualEnvironment
|
2013-08-22 04:28:15 +02:00
|
|
|
|
|
|
|
|
2018-07-30 16:27:58 +02:00
|
|
|
def pytest_addoption(parser):
|
|
|
|
parser.addoption(
|
|
|
|
"--keep-tmpdir", action="store_true",
|
|
|
|
default=False, help="keep temporary test directories"
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2014-07-01 02:00:35 +02:00
|
|
|
def pytest_collection_modifyitems(items):
|
|
|
|
for item in items:
|
2015-03-19 22:24:25 +01:00
|
|
|
if not hasattr(item, 'module'): # e.g.: DoctestTextfile
|
|
|
|
continue
|
2017-05-19 05:18:45 +02:00
|
|
|
|
|
|
|
# Mark network tests as flaky
|
2017-06-17 18:21:14 +02:00
|
|
|
if item.get_marker('network') is not None and "CI" in os.environ:
|
2017-05-19 05:18:45 +02:00
|
|
|
item.add_marker(pytest.mark.flaky(reruns=3))
|
|
|
|
|
2014-07-01 02:00:35 +02:00
|
|
|
module_path = os.path.relpath(
|
|
|
|
item.module.__file__,
|
|
|
|
os.path.commonprefix([__file__, item.module.__file__]),
|
|
|
|
)
|
|
|
|
|
2014-08-29 21:42:06 +02:00
|
|
|
module_root_dir = module_path.split(os.pathsep)[0]
|
2015-02-24 13:46:10 +01:00
|
|
|
if (module_root_dir.startswith("functional") or
|
|
|
|
module_root_dir.startswith("integration") or
|
|
|
|
module_root_dir.startswith("lib")):
|
2014-07-01 02:00:35 +02:00
|
|
|
item.add_marker(pytest.mark.integration)
|
2014-08-29 21:42:06 +02:00
|
|
|
elif module_root_dir.startswith("unit"):
|
2014-07-01 02:00:35 +02:00
|
|
|
item.add_marker(pytest.mark.unit)
|
|
|
|
|
|
|
|
# We don't want to allow using the script resource if this is a
|
|
|
|
# unit test, as unit tests should not need all that heavy lifting
|
2017-12-15 06:56:04 +01:00
|
|
|
if set(getattr(item, "funcargnames", [])) & {"script"}:
|
2014-07-01 02:00:35 +02:00
|
|
|
raise RuntimeError(
|
|
|
|
"Cannot use the ``script`` funcarg in a unit test: "
|
2017-12-15 06:56:04 +01:00
|
|
|
"(filename = {}, item = {})".format(module_path, item)
|
2014-07-01 02:00:35 +02:00
|
|
|
)
|
|
|
|
else:
|
|
|
|
raise RuntimeError(
|
2017-12-15 06:56:04 +01:00
|
|
|
"Unknown test type (filename = {})".format(module_path)
|
2014-07-01 02:00:35 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2016-07-22 02:24:31 +02:00
|
|
|
@pytest.yield_fixture
|
2018-07-30 16:27:58 +02:00
|
|
|
def tmpdir(request, tmpdir):
|
2013-08-22 04:28:15 +02:00
|
|
|
"""
|
|
|
|
Return a temporary directory path object which is unique to each test
|
|
|
|
function invocation, created as a sub directory of the base temporary
|
|
|
|
directory. The returned object is a ``tests.lib.path.Path`` object.
|
|
|
|
|
2016-07-22 02:24:31 +02:00
|
|
|
This uses the built-in tmpdir fixture from pytest itself but modified
|
|
|
|
to return our typical path object instead of py.path.local as well as
|
|
|
|
deleting the temporary directories at the end of each test case.
|
2013-08-22 04:28:15 +02:00
|
|
|
"""
|
2016-07-22 02:24:31 +02:00
|
|
|
assert tmpdir.isdir()
|
|
|
|
yield Path(str(tmpdir))
|
2014-02-08 03:35:37 +01:00
|
|
|
# Clear out the temporary directory after the test has finished using it.
|
|
|
|
# This should prevent us from needing a multiple gigabyte temporary
|
|
|
|
# directory while running the tests.
|
2018-07-30 16:27:58 +02:00
|
|
|
if not request.config.getoption("--keep-tmpdir"):
|
2018-07-30 15:19:41 +02:00
|
|
|
tmpdir.remove(ignore_errors=True)
|
2013-08-22 06:39:07 +02:00
|
|
|
|
|
|
|
|
2014-05-07 16:20:14 +02:00
|
|
|
@pytest.fixture(autouse=True)
|
|
|
|
def isolate(tmpdir):
|
|
|
|
"""
|
|
|
|
Isolate our tests so that things like global configuration files and the
|
|
|
|
like do not affect our test results.
|
|
|
|
|
|
|
|
We use an autouse function scoped fixture because we want to ensure that
|
|
|
|
every test has it's own isolated home directory.
|
|
|
|
"""
|
|
|
|
|
|
|
|
# TODO: Figure out how to isolate from *system* level configuration files
|
|
|
|
# as well as user level configuration files.
|
|
|
|
|
|
|
|
# Create a directory to use as our home location.
|
|
|
|
home_dir = os.path.join(str(tmpdir), "home")
|
|
|
|
os.makedirs(home_dir)
|
|
|
|
|
|
|
|
# Create a directory to use as a fake root
|
|
|
|
fake_root = os.path.join(str(tmpdir), "fake-root")
|
|
|
|
os.makedirs(fake_root)
|
|
|
|
|
2017-10-06 21:51:42 +02:00
|
|
|
if sys.platform == 'win32':
|
|
|
|
# Note: this will only take effect in subprocesses...
|
|
|
|
home_drive, home_path = os.path.splitdrive(home_dir)
|
|
|
|
os.environ.update({
|
|
|
|
'USERPROFILE': home_dir,
|
|
|
|
'HOMEDRIVE': home_drive,
|
|
|
|
'HOMEPATH': home_path,
|
|
|
|
})
|
|
|
|
for env_var, sub_path in (
|
|
|
|
('APPDATA', 'AppData/Roaming'),
|
|
|
|
('LOCALAPPDATA', 'AppData/Local'),
|
|
|
|
):
|
|
|
|
path = os.path.join(home_dir, *sub_path.split('/'))
|
|
|
|
os.environ[env_var] = path
|
|
|
|
os.makedirs(path)
|
|
|
|
else:
|
|
|
|
# Set our home directory to our temporary directory, this should force
|
|
|
|
# all of our relative configuration files to be read from here instead
|
|
|
|
# of the user's actual $HOME directory.
|
|
|
|
os.environ["HOME"] = home_dir
|
|
|
|
# Isolate ourselves from XDG directories
|
|
|
|
os.environ["XDG_DATA_HOME"] = os.path.join(home_dir, ".local", "share")
|
|
|
|
os.environ["XDG_CONFIG_HOME"] = os.path.join(home_dir, ".config")
|
|
|
|
os.environ["XDG_CACHE_HOME"] = os.path.join(home_dir, ".cache")
|
|
|
|
os.environ["XDG_RUNTIME_DIR"] = os.path.join(home_dir, ".runtime")
|
|
|
|
os.environ["XDG_DATA_DIRS"] = ":".join([
|
|
|
|
os.path.join(fake_root, "usr", "local", "share"),
|
|
|
|
os.path.join(fake_root, "usr", "share"),
|
|
|
|
])
|
|
|
|
os.environ["XDG_CONFIG_DIRS"] = os.path.join(fake_root, "etc", "xdg")
|
2014-05-07 16:20:14 +02:00
|
|
|
|
|
|
|
# Configure git, because without an author name/email git will complain
|
|
|
|
# and cause test failures.
|
|
|
|
os.environ["GIT_CONFIG_NOSYSTEM"] = "1"
|
|
|
|
os.environ["GIT_AUTHOR_NAME"] = "pip"
|
|
|
|
os.environ["GIT_AUTHOR_EMAIL"] = "pypa-dev@googlegroups.com"
|
|
|
|
|
2014-12-24 01:37:00 +01:00
|
|
|
# We want to disable the version check from running in the tests
|
|
|
|
os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = "true"
|
|
|
|
|
2018-04-23 11:41:34 +02:00
|
|
|
# Make sure tests don't share a requirements tracker.
|
|
|
|
os.environ.pop('PIP_REQ_TRACKER', None)
|
|
|
|
|
2017-10-06 21:51:42 +02:00
|
|
|
# FIXME: Windows...
|
2014-05-07 16:20:14 +02:00
|
|
|
os.makedirs(os.path.join(home_dir, ".config", "git"))
|
|
|
|
with open(os.path.join(home_dir, ".config", "git", "config"), "wb") as fp:
|
|
|
|
fp.write(
|
|
|
|
b"[user]\n\tname = pip\n\temail = pypa-dev@googlegroups.com\n"
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2018-04-15 00:33:28 +02:00
|
|
|
@pytest.fixture(scope='session')
|
|
|
|
def pip_src(tmpdir_factory):
|
|
|
|
pip_src = Path(str(tmpdir_factory.mktemp('pip_src'))).join('pip_src')
|
|
|
|
# Copy over our source tree so that each use is self contained
|
2014-01-28 15:17:51 +01:00
|
|
|
shutil.copytree(
|
|
|
|
SRC_DIR,
|
2018-04-15 00:33:28 +02:00
|
|
|
pip_src.abspath,
|
2013-08-24 12:43:01 +02:00
|
|
|
ignore=shutil.ignore_patterns(
|
2015-03-15 18:55:36 +01:00
|
|
|
"*.pyc", "__pycache__", "contrib", "docs", "tasks", "*.txt",
|
2015-03-15 12:45:50 +01:00
|
|
|
"tests", "pip.egg-info", "build", "dist", ".tox", ".git",
|
2013-08-24 12:43:01 +02:00
|
|
|
),
|
|
|
|
)
|
2018-04-15 00:33:28 +02:00
|
|
|
return pip_src
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.yield_fixture(scope='session')
|
|
|
|
def virtualenv_template(tmpdir_factory, pip_src):
|
|
|
|
tmpdir = Path(str(tmpdir_factory.mktemp('virtualenv')))
|
2013-08-24 12:43:01 +02:00
|
|
|
# Create the virtual environment
|
|
|
|
venv = VirtualEnvironment.create(
|
2017-09-01 23:31:03 +02:00
|
|
|
tmpdir.join("venv_orig"),
|
2013-08-24 12:43:01 +02:00
|
|
|
pip_source_dir=pip_src,
|
2017-09-01 23:31:03 +02:00
|
|
|
relocatable=True,
|
2013-08-24 12:43:01 +02:00
|
|
|
)
|
2018-04-15 00:29:23 +02:00
|
|
|
# Fix `site.py`.
|
2018-04-08 07:42:28 +02:00
|
|
|
site_py = venv.lib / 'site.py'
|
|
|
|
with open(site_py) as fp:
|
|
|
|
site_contents = fp.read()
|
2018-04-15 00:29:23 +02:00
|
|
|
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)
|
2018-04-08 07:42:28 +02:00
|
|
|
with open(site_py, 'w') as fp:
|
|
|
|
fp.write(site_contents)
|
2017-10-06 21:51:42 +02:00
|
|
|
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')
|
|
|
|
|
2017-09-01 23:31:03 +02:00
|
|
|
# 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)
|
2013-08-24 12:43:01 +02:00
|
|
|
|
2015-04-01 03:39:45 +02:00
|
|
|
|
2017-09-01 23:31:03 +02:00
|
|
|
@pytest.yield_fixture
|
|
|
|
def virtualenv(virtualenv_template, tmpdir, isolate):
|
|
|
|
"""
|
|
|
|
Return a virtual environment which is unique to each test function
|
|
|
|
invocation created inside of a sub directory of the test function's
|
|
|
|
temporary directory. The returned object is a
|
|
|
|
``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)
|
2013-08-22 06:39:07 +02:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def script(tmpdir, virtualenv):
|
|
|
|
"""
|
|
|
|
Return a PipTestEnvironment which is unique to each test function and
|
|
|
|
will execute all commands inside of the unique virtual environment for this
|
|
|
|
test function. The returned object is a
|
|
|
|
``tests.lib.scripttest.PipTestEnvironment``.
|
|
|
|
"""
|
|
|
|
return PipTestEnvironment(
|
|
|
|
# The base location for our test environment
|
2013-08-23 12:42:31 +02:00
|
|
|
tmpdir.join("workspace"),
|
2013-08-22 06:39:07 +02:00
|
|
|
|
|
|
|
# Tell the Test Environment where our virtualenv is located
|
|
|
|
virtualenv=virtualenv.location,
|
|
|
|
|
|
|
|
# Do not ignore hidden files, they need to be checked as well
|
|
|
|
ignore_hidden=False,
|
|
|
|
|
|
|
|
# We are starting with an already empty directory
|
|
|
|
start_clear=False,
|
|
|
|
|
|
|
|
# We want to ensure no temporary files are left behind, so the
|
|
|
|
# PipTestEnvironment needs to capture and assert against temp
|
|
|
|
capture_temp=True,
|
|
|
|
assert_no_temp=True,
|
|
|
|
)
|
2013-08-23 13:09:53 +02:00
|
|
|
|
|
|
|
|
2017-05-14 00:23:17 +02:00
|
|
|
@pytest.fixture(scope="session")
|
|
|
|
def common_wheels(tmpdir_factory):
|
|
|
|
"""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)
|
|
|
|
|
|
|
|
|
2013-08-23 13:09:53 +02:00
|
|
|
@pytest.fixture
|
|
|
|
def data(tmpdir):
|
|
|
|
return TestData.copy(tmpdir.join("data"))
|
2017-03-26 17:17:02 +02:00
|
|
|
|
|
|
|
|
|
|
|
class InMemoryPipResult(object):
|
|
|
|
def __init__(self, returncode, stdout):
|
|
|
|
self.returncode = returncode
|
|
|
|
self.stdout = stdout
|
|
|
|
|
|
|
|
|
|
|
|
class InMemoryPip(object):
|
|
|
|
def pip(self, *args):
|
|
|
|
orig_stdout = sys.stdout
|
2017-03-26 17:49:02 +02:00
|
|
|
if six.PY3:
|
|
|
|
stdout = io.StringIO()
|
|
|
|
else:
|
|
|
|
stdout = io.BytesIO()
|
|
|
|
sys.stdout = stdout
|
2017-03-26 17:17:02 +02:00
|
|
|
try:
|
2017-08-31 17:48:18 +02:00
|
|
|
returncode = pip._internal.main(list(args))
|
2017-03-26 17:17:02 +02:00
|
|
|
except SystemExit as e:
|
|
|
|
returncode = e.code or 0
|
|
|
|
finally:
|
|
|
|
sys.stdout = orig_stdout
|
|
|
|
return InMemoryPipResult(returncode, stdout.getvalue())
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def in_memory_pip():
|
|
|
|
return InMemoryPip()
|