mirror of https://github.com/pypa/pip
tests: rework tests virtual environment
- cleanup virtualenv creation code - ensure all testing virtual environments use a recent version of setuptools / wheel, making it easier to switch to custom versions of those, as well as reducing network accesses - reduce size of testing virtual environment, slightly speeding up the testsuite
This commit is contained in:
parent
c30b10a7a0
commit
add3801163
|
@ -26,6 +26,7 @@ htmlcov/
|
|||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
tests/data/common_wheels/
|
||||
|
||||
# Misc
|
||||
*~
|
||||
|
|
|
@ -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
|
||||
|
@ -163,62 +165,69 @@ 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(tmpdir_factory, pip_src,
|
||||
setuptools_install, common_wheels):
|
||||
|
||||
# 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"))
|
||||
|
||||
# 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
|
||||
venv.move(venv_template)
|
||||
yield venv
|
||||
tmpdir.rmtree(noerrors=True)
|
||||
|
||||
|
||||
|
@ -231,12 +240,15 @@ 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
|
||||
yield VirtualEnvironment(venv_location, virtualenv_template)
|
||||
venv_location.rmtree(noerrors=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def with_wheel(virtualenv, wheel_install):
|
||||
install_egg_link(virtualenv, 'wheel', wheel_install)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def script(tmpdir, virtualenv):
|
||||
"""
|
||||
|
@ -250,7 +262,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 +278,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
|
||||
|
|
|
@ -3,48 +3,16 @@ 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 = """\
|
||||
function _pip_completion {
|
||||
local words cword
|
||||
read -Ac words
|
||||
read -cn cword
|
||||
reply=( $( COMP_WORDS="$words[*]" \\
|
||||
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'
|
||||
|
||||
|
||||
def test_completion_for_fish(script):
|
||||
"""
|
||||
Test getting completion for fish shell
|
||||
"""
|
||||
fish_completion = """\
|
||||
complete -o default -F _pip_completion pip"""),
|
||||
('fish', """\
|
||||
function __fish_complete_pip
|
||||
set -lx COMP_WORDS (commandline -o) ""
|
||||
set -lx COMP_CWORD ( \\
|
||||
|
@ -53,10 +21,34 @@ function __fish_complete_pip
|
|||
set -lx PIP_AUTO_COMPLETE 1
|
||||
string split \\ -- (eval $COMP_WORDS[1])
|
||||
end
|
||||
complete -fa "(__fish_complete_pip)" -c pip"""
|
||||
complete -fa "(__fish_complete_pip)" -c pip"""),
|
||||
('zsh', """\
|
||||
function _pip_completion {
|
||||
local words cword
|
||||
read -Ac words
|
||||
read -cn cword
|
||||
reply=( $( COMP_WORDS="$words[*]" \\
|
||||
COMP_CWORD=$(( cword-1 )) \\
|
||||
PIP_AUTO_COMPLETE=1 $words[1] ) )
|
||||
}
|
||||
compctl -K _pip_completion pip"""),
|
||||
)
|
||||
|
||||
result = script.pip('completion', '--fish')
|
||||
assert fish_completion in result.stdout, 'fish completion is wrong'
|
||||
|
||||
@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 bash shell
|
||||
"""
|
||||
# Re-install pip so we get the launchers.
|
||||
script.pip_install_local('--no-build-isolation', pip_src)
|
||||
|
||||
result = script.pip('completion', '--' + shell, use_module=False)
|
||||
assert completion in result.stdout, str(result.stdout)
|
||||
|
||||
|
||||
def test_completion_for_unknown_shell(script):
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,14 @@ class Tests_UserSite:
|
|||
)
|
||||
assert dist_info_folder in result.files_created
|
||||
|
||||
def test_install_user_venv_nositepkgs_fails(self, script, data):
|
||||
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 +97,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 +120,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 +150,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 +180,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 +216,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')
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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())
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -11,8 +11,6 @@ import subprocess
|
|||
|
||||
import pytest
|
||||
import scripttest
|
||||
import six
|
||||
import virtualenv
|
||||
|
||||
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
|
||||
"""
|
||||
|
@ -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)
|
||||
|
@ -380,7 +361,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 +375,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
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import distutils
|
||||
import compileall
|
||||
import sys
|
||||
|
||||
import six
|
||||
import virtualenv as _virtualenv
|
||||
|
||||
from . import virtualenv_lib_path
|
||||
from .path import Path
|
||||
|
||||
|
||||
|
@ -14,54 +15,124 @@ 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):
|
||||
self.location = Path(location)
|
||||
self._system_site_packages = system_site_packages
|
||||
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):
|
||||
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.
|
||||
_virtualenv.create_environment(
|
||||
self.location,
|
||||
no_pip=True,
|
||||
no_wheel=True,
|
||||
no_setuptools=True,
|
||||
)
|
||||
self._fix_site_module()
|
||||
self.sitecustomize = self._sitecustomize
|
||||
self.user_site_packages = self._user_site_packages
|
||||
|
||||
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 _fix_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._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)
|
||||
|
||||
@property
|
||||
def system_site_packages(self):
|
||||
return self._system_site_packages
|
||||
def move(self, location):
|
||||
self.location.move(location)
|
||||
self.location = Path(location)
|
||||
self._update_paths()
|
||||
|
||||
@system_site_packages.setter
|
||||
def system_site_packages(self, value):
|
||||
marker = self.lib.join("no-global-site-packages.txt")
|
||||
if value:
|
||||
@property
|
||||
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
|
||||
marker = self.lib / "no-global-site-packages.txt"
|
||||
if self._user_site_packages:
|
||||
marker.rm()
|
||||
else:
|
||||
marker.touch()
|
||||
self._system_site_packages = value
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
setuptools
|
||||
wheel
|
|
@ -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
|
||||
|
|
7
tox.ini
7
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
|
||||
|
|
Loading…
Reference in New Issue