mirror of
https://github.com/pypa/pip
synced 2023-12-13 21:30:23 +01:00
999 lines
36 KiB
Python
999 lines
36 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
util tests
|
|
|
|
"""
|
|
import codecs
|
|
import os
|
|
import shutil
|
|
import stat
|
|
import sys
|
|
import time
|
|
import warnings
|
|
from io import BytesIO
|
|
|
|
import pytest
|
|
from mock import Mock, patch
|
|
|
|
from pip._internal.exceptions import (
|
|
HashMismatch,
|
|
HashMissing,
|
|
InstallationError,
|
|
)
|
|
from pip._internal.utils.deprecation import PipDeprecationWarning, deprecated
|
|
from pip._internal.utils.encoding import BOMS, auto_decode
|
|
from pip._internal.utils.glibc import (
|
|
check_glibc_version,
|
|
glibc_version_string,
|
|
glibc_version_string_confstr,
|
|
glibc_version_string_ctypes,
|
|
)
|
|
from pip._internal.utils.hashes import Hashes, MissingHashes
|
|
from pip._internal.utils.misc import (
|
|
HiddenText,
|
|
build_netloc,
|
|
build_url_from_netloc,
|
|
egg_link_path,
|
|
get_installed_distributions,
|
|
get_prog,
|
|
hide_url,
|
|
hide_value,
|
|
normalize_path,
|
|
normalize_version_info,
|
|
parse_netloc,
|
|
path_to_display,
|
|
redact_auth_from_url,
|
|
redact_netloc,
|
|
remove_auth_from_url,
|
|
rmtree,
|
|
rmtree_errorhandler,
|
|
split_auth_from_netloc,
|
|
split_auth_netloc_from_url,
|
|
is_console_interactive)
|
|
from pip._internal.utils.setuptools_build import make_setuptools_shim_args
|
|
|
|
|
|
class Tests_EgglinkPath:
|
|
"util.egg_link_path() tests"
|
|
|
|
def setup(self):
|
|
|
|
project = 'foo'
|
|
|
|
self.mock_dist = Mock(project_name=project)
|
|
self.site_packages = 'SITE_PACKAGES'
|
|
self.user_site = 'USER_SITE'
|
|
self.user_site_egglink = os.path.join(
|
|
self.user_site,
|
|
'%s.egg-link' % project
|
|
)
|
|
self.site_packages_egglink = os.path.join(
|
|
self.site_packages,
|
|
'%s.egg-link' % project,
|
|
)
|
|
|
|
# patches
|
|
from pip._internal.utils import misc as utils
|
|
self.old_site_packages = utils.site_packages
|
|
self.mock_site_packages = utils.site_packages = 'SITE_PACKAGES'
|
|
self.old_running_under_virtualenv = utils.running_under_virtualenv
|
|
self.mock_running_under_virtualenv = utils.running_under_virtualenv = \
|
|
Mock()
|
|
self.old_virtualenv_no_global = utils.virtualenv_no_global
|
|
self.mock_virtualenv_no_global = utils.virtualenv_no_global = Mock()
|
|
self.old_user_site = utils.user_site
|
|
self.mock_user_site = utils.user_site = self.user_site
|
|
from os import path
|
|
self.old_isfile = path.isfile
|
|
self.mock_isfile = path.isfile = Mock()
|
|
|
|
def teardown(self):
|
|
from pip._internal.utils import misc as utils
|
|
utils.site_packages = self.old_site_packages
|
|
utils.running_under_virtualenv = self.old_running_under_virtualenv
|
|
utils.virtualenv_no_global = self.old_virtualenv_no_global
|
|
utils.user_site = self.old_user_site
|
|
from os import path
|
|
path.isfile = self.old_isfile
|
|
|
|
def eggLinkInUserSite(self, egglink):
|
|
return egglink == self.user_site_egglink
|
|
|
|
def eggLinkInSitePackages(self, egglink):
|
|
return egglink == self.site_packages_egglink
|
|
|
|
# ####################### #
|
|
# # egglink in usersite # #
|
|
# ####################### #
|
|
def test_egglink_in_usersite_notvenv(self):
|
|
self.mock_virtualenv_no_global.return_value = False
|
|
self.mock_running_under_virtualenv.return_value = False
|
|
self.mock_isfile.side_effect = self.eggLinkInUserSite
|
|
assert egg_link_path(self.mock_dist) == self.user_site_egglink
|
|
|
|
def test_egglink_in_usersite_venv_noglobal(self):
|
|
self.mock_virtualenv_no_global.return_value = True
|
|
self.mock_running_under_virtualenv.return_value = True
|
|
self.mock_isfile.side_effect = self.eggLinkInUserSite
|
|
assert egg_link_path(self.mock_dist) is None
|
|
|
|
def test_egglink_in_usersite_venv_global(self):
|
|
self.mock_virtualenv_no_global.return_value = False
|
|
self.mock_running_under_virtualenv.return_value = True
|
|
self.mock_isfile.side_effect = self.eggLinkInUserSite
|
|
assert egg_link_path(self.mock_dist) == self.user_site_egglink
|
|
|
|
# ####################### #
|
|
# # egglink in sitepkgs # #
|
|
# ####################### #
|
|
def test_egglink_in_sitepkgs_notvenv(self):
|
|
self.mock_virtualenv_no_global.return_value = False
|
|
self.mock_running_under_virtualenv.return_value = False
|
|
self.mock_isfile.side_effect = self.eggLinkInSitePackages
|
|
assert egg_link_path(self.mock_dist) == self.site_packages_egglink
|
|
|
|
def test_egglink_in_sitepkgs_venv_noglobal(self):
|
|
self.mock_virtualenv_no_global.return_value = True
|
|
self.mock_running_under_virtualenv.return_value = True
|
|
self.mock_isfile.side_effect = self.eggLinkInSitePackages
|
|
assert egg_link_path(self.mock_dist) == self.site_packages_egglink
|
|
|
|
def test_egglink_in_sitepkgs_venv_global(self):
|
|
self.mock_virtualenv_no_global.return_value = False
|
|
self.mock_running_under_virtualenv.return_value = True
|
|
self.mock_isfile.side_effect = self.eggLinkInSitePackages
|
|
assert egg_link_path(self.mock_dist) == self.site_packages_egglink
|
|
|
|
# ################################## #
|
|
# # egglink in usersite & sitepkgs # #
|
|
# ################################## #
|
|
def test_egglink_in_both_notvenv(self):
|
|
self.mock_virtualenv_no_global.return_value = False
|
|
self.mock_running_under_virtualenv.return_value = False
|
|
self.mock_isfile.return_value = True
|
|
assert egg_link_path(self.mock_dist) == self.user_site_egglink
|
|
|
|
def test_egglink_in_both_venv_noglobal(self):
|
|
self.mock_virtualenv_no_global.return_value = True
|
|
self.mock_running_under_virtualenv.return_value = True
|
|
self.mock_isfile.return_value = True
|
|
assert egg_link_path(self.mock_dist) == self.site_packages_egglink
|
|
|
|
def test_egglink_in_both_venv_global(self):
|
|
self.mock_virtualenv_no_global.return_value = False
|
|
self.mock_running_under_virtualenv.return_value = True
|
|
self.mock_isfile.return_value = True
|
|
assert egg_link_path(self.mock_dist) == self.site_packages_egglink
|
|
|
|
# ############## #
|
|
# # no egglink # #
|
|
# ############## #
|
|
def test_noegglink_in_sitepkgs_notvenv(self):
|
|
self.mock_virtualenv_no_global.return_value = False
|
|
self.mock_running_under_virtualenv.return_value = False
|
|
self.mock_isfile.return_value = False
|
|
assert egg_link_path(self.mock_dist) is None
|
|
|
|
def test_noegglink_in_sitepkgs_venv_noglobal(self):
|
|
self.mock_virtualenv_no_global.return_value = True
|
|
self.mock_running_under_virtualenv.return_value = True
|
|
self.mock_isfile.return_value = False
|
|
assert egg_link_path(self.mock_dist) is None
|
|
|
|
def test_noegglink_in_sitepkgs_venv_global(self):
|
|
self.mock_virtualenv_no_global.return_value = False
|
|
self.mock_running_under_virtualenv.return_value = True
|
|
self.mock_isfile.return_value = False
|
|
assert egg_link_path(self.mock_dist) is None
|
|
|
|
|
|
@patch('pip._internal.utils.misc.dist_in_usersite')
|
|
@patch('pip._internal.utils.misc.dist_is_local')
|
|
@patch('pip._internal.utils.misc.dist_is_editable')
|
|
class Tests_get_installed_distributions:
|
|
"""test util.get_installed_distributions"""
|
|
|
|
workingset = [
|
|
Mock(test_name="global"),
|
|
Mock(test_name="editable"),
|
|
Mock(test_name="normal"),
|
|
Mock(test_name="user"),
|
|
]
|
|
|
|
workingset_stdlib = [
|
|
Mock(test_name='normal', key='argparse'),
|
|
Mock(test_name='normal', key='wsgiref')
|
|
]
|
|
|
|
workingset_freeze = [
|
|
Mock(test_name='normal', key='pip'),
|
|
Mock(test_name='normal', key='setuptools'),
|
|
Mock(test_name='normal', key='distribute')
|
|
]
|
|
|
|
def dist_is_editable(self, dist):
|
|
return dist.test_name == "editable"
|
|
|
|
def dist_is_local(self, dist):
|
|
return dist.test_name != "global" and dist.test_name != 'user'
|
|
|
|
def dist_in_usersite(self, dist):
|
|
return dist.test_name == "user"
|
|
|
|
@patch('pip._vendor.pkg_resources.working_set', workingset)
|
|
def test_editables_only(self, mock_dist_is_editable,
|
|
mock_dist_is_local,
|
|
mock_dist_in_usersite):
|
|
mock_dist_is_editable.side_effect = self.dist_is_editable
|
|
mock_dist_is_local.side_effect = self.dist_is_local
|
|
mock_dist_in_usersite.side_effect = self.dist_in_usersite
|
|
dists = get_installed_distributions(editables_only=True)
|
|
assert len(dists) == 1, dists
|
|
assert dists[0].test_name == "editable"
|
|
|
|
@patch('pip._vendor.pkg_resources.working_set', workingset)
|
|
def test_exclude_editables(self, mock_dist_is_editable,
|
|
mock_dist_is_local,
|
|
mock_dist_in_usersite):
|
|
mock_dist_is_editable.side_effect = self.dist_is_editable
|
|
mock_dist_is_local.side_effect = self.dist_is_local
|
|
mock_dist_in_usersite.side_effect = self.dist_in_usersite
|
|
dists = get_installed_distributions(include_editables=False)
|
|
assert len(dists) == 1
|
|
assert dists[0].test_name == "normal"
|
|
|
|
@patch('pip._vendor.pkg_resources.working_set', workingset)
|
|
def test_include_globals(self, mock_dist_is_editable,
|
|
mock_dist_is_local,
|
|
mock_dist_in_usersite):
|
|
mock_dist_is_editable.side_effect = self.dist_is_editable
|
|
mock_dist_is_local.side_effect = self.dist_is_local
|
|
mock_dist_in_usersite.side_effect = self.dist_in_usersite
|
|
dists = get_installed_distributions(local_only=False)
|
|
assert len(dists) == 4
|
|
|
|
@patch('pip._vendor.pkg_resources.working_set', workingset)
|
|
def test_user_only(self, mock_dist_is_editable,
|
|
mock_dist_is_local,
|
|
mock_dist_in_usersite):
|
|
mock_dist_is_editable.side_effect = self.dist_is_editable
|
|
mock_dist_is_local.side_effect = self.dist_is_local
|
|
mock_dist_in_usersite.side_effect = self.dist_in_usersite
|
|
dists = get_installed_distributions(local_only=False,
|
|
user_only=True)
|
|
assert len(dists) == 1
|
|
assert dists[0].test_name == "user"
|
|
|
|
@patch('pip._vendor.pkg_resources.working_set', workingset_stdlib)
|
|
def test_gte_py27_excludes(self, mock_dist_is_editable,
|
|
mock_dist_is_local,
|
|
mock_dist_in_usersite):
|
|
mock_dist_is_editable.side_effect = self.dist_is_editable
|
|
mock_dist_is_local.side_effect = self.dist_is_local
|
|
mock_dist_in_usersite.side_effect = self.dist_in_usersite
|
|
dists = get_installed_distributions()
|
|
assert len(dists) == 0
|
|
|
|
@patch('pip._vendor.pkg_resources.working_set', workingset_freeze)
|
|
def test_freeze_excludes(self, mock_dist_is_editable,
|
|
mock_dist_is_local,
|
|
mock_dist_in_usersite):
|
|
mock_dist_is_editable.side_effect = self.dist_is_editable
|
|
mock_dist_is_local.side_effect = self.dist_is_local
|
|
mock_dist_in_usersite.side_effect = self.dist_in_usersite
|
|
dists = get_installed_distributions(
|
|
skip=('setuptools', 'pip', 'distribute'))
|
|
assert len(dists) == 0
|
|
|
|
|
|
def test_rmtree_errorhandler_nonexistent_directory(tmpdir):
|
|
"""
|
|
Test rmtree_errorhandler ignores the given non-existing directory.
|
|
"""
|
|
nonexistent_path = str(tmpdir / 'foo')
|
|
mock_func = Mock()
|
|
rmtree_errorhandler(mock_func, nonexistent_path, None)
|
|
mock_func.assert_not_called()
|
|
|
|
|
|
def test_rmtree_errorhandler_readonly_directory(tmpdir):
|
|
"""
|
|
Test rmtree_errorhandler makes the given read-only directory writable.
|
|
"""
|
|
# Create read only directory
|
|
path = str((tmpdir / 'subdir').mkdir())
|
|
os.chmod(path, stat.S_IREAD)
|
|
|
|
# Make sure mock_func is called with the given path
|
|
mock_func = Mock()
|
|
rmtree_errorhandler(mock_func, path, None)
|
|
mock_func.assert_called_with(path)
|
|
|
|
# Make sure the path is now writable
|
|
assert os.stat(path).st_mode & stat.S_IWRITE
|
|
|
|
|
|
def test_rmtree_errorhandler_reraises_error(tmpdir):
|
|
"""
|
|
Test rmtree_errorhandler reraises an exception
|
|
by the given unreadable directory.
|
|
"""
|
|
# Create directory without read permission
|
|
path = str((tmpdir / 'subdir').mkdir())
|
|
os.chmod(path, stat.S_IWRITE)
|
|
|
|
mock_func = Mock()
|
|
|
|
try:
|
|
raise RuntimeError('test message')
|
|
except RuntimeError:
|
|
# Make sure the handler reraises an exception
|
|
with pytest.raises(RuntimeError, match='test message'):
|
|
rmtree_errorhandler(mock_func, path, None)
|
|
|
|
mock_func.assert_not_called()
|
|
|
|
|
|
def test_rmtree_skips_nonexistent_directory():
|
|
"""
|
|
Test wrapped rmtree doesn't raise an error
|
|
by the given nonexistent directory.
|
|
"""
|
|
rmtree.__wrapped__('nonexistent-subdir')
|
|
|
|
|
|
class Failer:
|
|
def __init__(self, duration=1):
|
|
self.succeed_after = time.time() + duration
|
|
|
|
def call(self, *args, **kw):
|
|
"""Fail with OSError self.max_fails times"""
|
|
if time.time() < self.succeed_after:
|
|
raise OSError("Failed")
|
|
|
|
|
|
def test_rmtree_retries(tmpdir, monkeypatch):
|
|
"""
|
|
Test pip._internal.utils.rmtree will retry failures
|
|
"""
|
|
monkeypatch.setattr(shutil, 'rmtree', Failer(duration=1).call)
|
|
rmtree('foo')
|
|
|
|
|
|
def test_rmtree_retries_for_3sec(tmpdir, monkeypatch):
|
|
"""
|
|
Test pip._internal.utils.rmtree will retry failures for no more than 3 sec
|
|
"""
|
|
monkeypatch.setattr(shutil, 'rmtree', Failer(duration=5).call)
|
|
with pytest.raises(OSError):
|
|
rmtree('foo')
|
|
|
|
|
|
@pytest.mark.parametrize('path, fs_encoding, expected', [
|
|
(None, None, None),
|
|
# Test passing a text (unicode) string.
|
|
(u'/path/déf', None, u'/path/déf'),
|
|
# Test a bytes object with a non-ascii character.
|
|
(u'/path/déf'.encode('utf-8'), 'utf-8', u'/path/déf'),
|
|
# Test a bytes object with a character that can't be decoded.
|
|
(u'/path/déf'.encode('utf-8'), 'ascii', u"b'/path/d\\xc3\\xa9f'"),
|
|
(u'/path/déf'.encode('utf-16'), 'utf-8',
|
|
u"b'\\xff\\xfe/\\x00p\\x00a\\x00t\\x00h\\x00/"
|
|
"\\x00d\\x00\\xe9\\x00f\\x00'"),
|
|
])
|
|
def test_path_to_display(monkeypatch, path, fs_encoding, expected):
|
|
monkeypatch.setattr(sys, 'getfilesystemencoding', lambda: fs_encoding)
|
|
actual = path_to_display(path)
|
|
assert actual == expected, 'actual: {!r}'.format(actual)
|
|
|
|
|
|
class Test_normalize_path(object):
|
|
# Technically, symlinks are possible on Windows, but you need a special
|
|
# permission bit to create them, and Python 2 doesn't support it anyway, so
|
|
# it's easiest just to skip this test on Windows altogether.
|
|
@pytest.mark.skipif("sys.platform == 'win32'")
|
|
def test_resolve_symlinks(self, tmpdir):
|
|
print(type(tmpdir))
|
|
print(dir(tmpdir))
|
|
orig_working_dir = os.getcwd()
|
|
os.chdir(tmpdir)
|
|
try:
|
|
d = os.path.join('foo', 'bar')
|
|
f = os.path.join(d, 'file1')
|
|
os.makedirs(d)
|
|
with open(f, 'w'): # Create the file
|
|
pass
|
|
|
|
os.symlink(d, 'dir_link')
|
|
os.symlink(f, 'file_link')
|
|
|
|
assert normalize_path(
|
|
'dir_link/file1', resolve_symlinks=True
|
|
) == os.path.join(tmpdir, f)
|
|
assert normalize_path(
|
|
'dir_link/file1', resolve_symlinks=False
|
|
) == os.path.join(tmpdir, 'dir_link', 'file1')
|
|
|
|
assert normalize_path(
|
|
'file_link', resolve_symlinks=True
|
|
) == os.path.join(tmpdir, f)
|
|
assert normalize_path(
|
|
'file_link', resolve_symlinks=False
|
|
) == os.path.join(tmpdir, 'file_link')
|
|
finally:
|
|
os.chdir(orig_working_dir)
|
|
|
|
|
|
class TestHashes(object):
|
|
"""Tests for pip._internal.utils.hashes"""
|
|
|
|
@pytest.mark.parametrize('hash_name, hex_digest, expected', [
|
|
# Test a value that matches but with the wrong hash_name.
|
|
('sha384', 128 * 'a', False),
|
|
# Test matching values, including values other than the first.
|
|
('sha512', 128 * 'a', True),
|
|
('sha512', 128 * 'b', True),
|
|
# Test a matching hash_name with a value that doesn't match.
|
|
('sha512', 128 * 'c', False),
|
|
])
|
|
def test_is_hash_allowed(self, hash_name, hex_digest, expected):
|
|
hashes_data = {
|
|
'sha512': [128 * 'a', 128 * 'b'],
|
|
}
|
|
hashes = Hashes(hashes_data)
|
|
assert hashes.is_hash_allowed(hash_name, hex_digest) == expected
|
|
|
|
def test_success(self, tmpdir):
|
|
"""Make sure no error is raised when at least one hash matches.
|
|
|
|
Test check_against_path because it calls everything else.
|
|
|
|
"""
|
|
file = tmpdir / 'to_hash'
|
|
file.write_text('hello')
|
|
hashes = Hashes({
|
|
'sha256': ['2cf24dba5fb0a30e26e83b2ac5b9e29e'
|
|
'1b161e5c1fa7425e73043362938b9824'],
|
|
'sha224': ['wrongwrong'],
|
|
'md5': ['5d41402abc4b2a76b9719d911017c592']})
|
|
hashes.check_against_path(file)
|
|
|
|
def test_failure(self):
|
|
"""Hashes should raise HashMismatch when no hashes match."""
|
|
hashes = Hashes({'sha256': ['wrongwrong']})
|
|
with pytest.raises(HashMismatch):
|
|
hashes.check_against_file(BytesIO(b'hello'))
|
|
|
|
def test_missing_hashes(self):
|
|
"""MissingHashes should raise HashMissing when any check is done."""
|
|
with pytest.raises(HashMissing):
|
|
MissingHashes().check_against_file(BytesIO(b'hello'))
|
|
|
|
def test_unknown_hash(self):
|
|
"""Hashes should raise InstallationError when it encounters an unknown
|
|
hash."""
|
|
hashes = Hashes({'badbad': ['dummy']})
|
|
with pytest.raises(InstallationError):
|
|
hashes.check_against_file(BytesIO(b'hello'))
|
|
|
|
def test_non_zero(self):
|
|
"""Test that truthiness tests tell whether any known-good hashes
|
|
exist."""
|
|
assert Hashes({'sha256': 'dummy'})
|
|
assert not Hashes()
|
|
assert not Hashes({})
|
|
|
|
|
|
class TestEncoding(object):
|
|
"""Tests for pip._internal.utils.encoding"""
|
|
|
|
def test_auto_decode_utf_16_le(self):
|
|
data = (
|
|
b'\xff\xfeD\x00j\x00a\x00n\x00g\x00o\x00=\x00'
|
|
b'=\x001\x00.\x004\x00.\x002\x00'
|
|
)
|
|
assert data.startswith(codecs.BOM_UTF16_LE)
|
|
assert auto_decode(data) == "Django==1.4.2"
|
|
|
|
def test_auto_decode_utf_16_be(self):
|
|
data = (
|
|
b'\xfe\xff\x00D\x00j\x00a\x00n\x00g\x00o\x00='
|
|
b'\x00=\x001\x00.\x004\x00.\x002'
|
|
)
|
|
assert data.startswith(codecs.BOM_UTF16_BE)
|
|
assert auto_decode(data) == "Django==1.4.2"
|
|
|
|
def test_auto_decode_no_bom(self):
|
|
assert auto_decode(b'foobar') == u'foobar'
|
|
|
|
def test_auto_decode_pep263_headers(self):
|
|
latin1_req = u'# coding=latin1\n# Pas trop de café'
|
|
assert auto_decode(latin1_req.encode('latin1')) == latin1_req
|
|
|
|
def test_auto_decode_no_preferred_encoding(self):
|
|
om, em = Mock(), Mock()
|
|
om.return_value = 'ascii'
|
|
em.return_value = None
|
|
data = u'data'
|
|
with patch('sys.getdefaultencoding', om):
|
|
with patch('locale.getpreferredencoding', em):
|
|
ret = auto_decode(data.encode(sys.getdefaultencoding()))
|
|
assert ret == data
|
|
|
|
@pytest.mark.parametrize('encoding', [encoding for bom, encoding in BOMS])
|
|
def test_all_encodings_are_valid(self, encoding):
|
|
# we really only care that there is no LookupError
|
|
assert ''.encode(encoding).decode(encoding) == ''
|
|
|
|
|
|
def raises(error):
|
|
raise error
|
|
|
|
|
|
class TestGlibc(object):
|
|
def test_manylinux_check_glibc_version(self):
|
|
"""
|
|
Test that the check_glibc_version function is robust against weird
|
|
glibc version strings.
|
|
"""
|
|
for two_twenty in ["2.20",
|
|
# used by "linaro glibc", see gh-3588
|
|
"2.20-2014.11",
|
|
# weird possibilities that I just made up
|
|
"2.20+dev",
|
|
"2.20-custom",
|
|
"2.20.1",
|
|
]:
|
|
assert check_glibc_version(two_twenty, 2, 15)
|
|
assert check_glibc_version(two_twenty, 2, 20)
|
|
assert not check_glibc_version(two_twenty, 2, 21)
|
|
assert not check_glibc_version(two_twenty, 3, 15)
|
|
assert not check_glibc_version(two_twenty, 1, 15)
|
|
|
|
# For strings that we just can't parse at all, we should warn and
|
|
# return false
|
|
for bad_string in ["asdf", "", "foo.bar"]:
|
|
with warnings.catch_warnings(record=True) as ws:
|
|
warnings.filterwarnings("always")
|
|
assert not check_glibc_version(bad_string, 2, 5)
|
|
for w in ws:
|
|
if "Expected glibc version with" in str(w.message):
|
|
break
|
|
else:
|
|
# Didn't find the warning we were expecting
|
|
assert False
|
|
|
|
def test_glibc_version_string(self, monkeypatch):
|
|
monkeypatch.setattr(
|
|
os, "confstr", lambda x: "glibc 2.20", raising=False,
|
|
)
|
|
assert glibc_version_string() == "2.20"
|
|
|
|
def test_glibc_version_string_confstr(self, monkeypatch):
|
|
monkeypatch.setattr(
|
|
os, "confstr", lambda x: "glibc 2.20", raising=False,
|
|
)
|
|
assert glibc_version_string_confstr() == "2.20"
|
|
|
|
@pytest.mark.parametrize("failure", [
|
|
lambda x: raises(ValueError),
|
|
lambda x: raises(OSError),
|
|
lambda x: "XXX",
|
|
])
|
|
def test_glibc_version_string_confstr_fail(self, monkeypatch, failure):
|
|
monkeypatch.setattr(os, "confstr", failure, raising=False)
|
|
assert glibc_version_string_confstr() is None
|
|
|
|
def test_glibc_version_string_confstr_missing(self, monkeypatch):
|
|
monkeypatch.delattr(os, "confstr", raising=False)
|
|
assert glibc_version_string_confstr() is None
|
|
|
|
def test_glibc_version_string_ctypes_missing(self, monkeypatch):
|
|
monkeypatch.setitem(sys.modules, "ctypes", None)
|
|
assert glibc_version_string_ctypes() is None
|
|
|
|
|
|
@pytest.mark.parametrize('version_info, expected', [
|
|
((), (0, 0, 0)),
|
|
((3, ), (3, 0, 0)),
|
|
((3, 6), (3, 6, 0)),
|
|
((3, 6, 2), (3, 6, 2)),
|
|
((3, 6, 2, 4), (3, 6, 2)),
|
|
])
|
|
def test_normalize_version_info(version_info, expected):
|
|
actual = normalize_version_info(version_info)
|
|
assert actual == expected
|
|
|
|
|
|
class TestGetProg(object):
|
|
|
|
@pytest.mark.parametrize(
|
|
("argv", "executable", "expected"),
|
|
[
|
|
('/usr/bin/pip', '', 'pip'),
|
|
('-c', '/usr/bin/python', '/usr/bin/python -m pip'),
|
|
('__main__.py', '/usr/bin/python', '/usr/bin/python -m pip'),
|
|
('/usr/bin/pip3', '', 'pip3'),
|
|
]
|
|
)
|
|
def test_get_prog(self, monkeypatch, argv, executable, expected):
|
|
monkeypatch.setattr('pip._internal.utils.misc.sys.argv', [argv])
|
|
monkeypatch.setattr(
|
|
'pip._internal.utils.misc.sys.executable',
|
|
executable
|
|
)
|
|
assert get_prog() == expected
|
|
|
|
|
|
@pytest.mark.parametrize('host_port, expected_netloc', [
|
|
# Test domain name.
|
|
(('example.com', None), 'example.com'),
|
|
(('example.com', 5000), 'example.com:5000'),
|
|
# Test IPv4 address.
|
|
(('127.0.0.1', None), '127.0.0.1'),
|
|
(('127.0.0.1', 5000), '127.0.0.1:5000'),
|
|
# Test bare IPv6 address.
|
|
(('2001:db6::1', None), '2001:db6::1'),
|
|
# Test IPv6 with port.
|
|
(('2001:db6::1', 5000), '[2001:db6::1]:5000'),
|
|
])
|
|
def test_build_netloc(host_port, expected_netloc):
|
|
assert build_netloc(*host_port) == expected_netloc
|
|
|
|
|
|
@pytest.mark.parametrize('netloc, expected_url, expected_host_port', [
|
|
# Test domain name.
|
|
('example.com', 'https://example.com', ('example.com', None)),
|
|
('example.com:5000', 'https://example.com:5000', ('example.com', 5000)),
|
|
# Test IPv4 address.
|
|
('127.0.0.1', 'https://127.0.0.1', ('127.0.0.1', None)),
|
|
('127.0.0.1:5000', 'https://127.0.0.1:5000', ('127.0.0.1', 5000)),
|
|
# Test bare IPv6 address.
|
|
('2001:db6::1', 'https://[2001:db6::1]', ('2001:db6::1', None)),
|
|
# Test IPv6 with port.
|
|
(
|
|
'[2001:db6::1]:5000',
|
|
'https://[2001:db6::1]:5000',
|
|
('2001:db6::1', 5000)
|
|
),
|
|
# Test netloc with auth.
|
|
(
|
|
'user:password@localhost:5000',
|
|
'https://user:password@localhost:5000',
|
|
('localhost', 5000)
|
|
)
|
|
])
|
|
def test_build_url_from_netloc_and_parse_netloc(
|
|
netloc, expected_url, expected_host_port,
|
|
):
|
|
assert build_url_from_netloc(netloc) == expected_url
|
|
assert parse_netloc(netloc) == expected_host_port
|
|
|
|
|
|
@pytest.mark.parametrize('netloc, expected', [
|
|
# Test a basic case.
|
|
('example.com', ('example.com', (None, None))),
|
|
# Test with username and no password.
|
|
('user@example.com', ('example.com', ('user', None))),
|
|
# Test with username and password.
|
|
('user:pass@example.com', ('example.com', ('user', 'pass'))),
|
|
# Test with username and empty password.
|
|
('user:@example.com', ('example.com', ('user', ''))),
|
|
# Test the password containing an @ symbol.
|
|
('user:pass@word@example.com', ('example.com', ('user', 'pass@word'))),
|
|
# Test the password containing a : symbol.
|
|
('user:pass:word@example.com', ('example.com', ('user', 'pass:word'))),
|
|
# Test URL-encoded reserved characters.
|
|
('user%3Aname:%23%40%5E@example.com',
|
|
('example.com', ('user:name', '#@^'))),
|
|
])
|
|
def test_split_auth_from_netloc(netloc, expected):
|
|
actual = split_auth_from_netloc(netloc)
|
|
assert actual == expected
|
|
|
|
|
|
@pytest.mark.parametrize('url, expected', [
|
|
# Test a basic case.
|
|
('http://example.com/path#anchor',
|
|
('http://example.com/path#anchor', 'example.com', (None, None))),
|
|
# Test with username and no password.
|
|
('http://user@example.com/path#anchor',
|
|
('http://example.com/path#anchor', 'example.com', ('user', None))),
|
|
# Test with username and password.
|
|
('http://user:pass@example.com/path#anchor',
|
|
('http://example.com/path#anchor', 'example.com', ('user', 'pass'))),
|
|
# Test with username and empty password.
|
|
('http://user:@example.com/path#anchor',
|
|
('http://example.com/path#anchor', 'example.com', ('user', ''))),
|
|
# Test the password containing an @ symbol.
|
|
('http://user:pass@word@example.com/path#anchor',
|
|
('http://example.com/path#anchor', 'example.com', ('user', 'pass@word'))),
|
|
# Test the password containing a : symbol.
|
|
('http://user:pass:word@example.com/path#anchor',
|
|
('http://example.com/path#anchor', 'example.com', ('user', 'pass:word'))),
|
|
# Test URL-encoded reserved characters.
|
|
('http://user%3Aname:%23%40%5E@example.com/path#anchor',
|
|
('http://example.com/path#anchor', 'example.com', ('user:name', '#@^'))),
|
|
])
|
|
def test_split_auth_netloc_from_url(url, expected):
|
|
actual = split_auth_netloc_from_url(url)
|
|
assert actual == expected
|
|
|
|
|
|
@pytest.mark.parametrize('netloc, expected', [
|
|
# Test a basic case.
|
|
('example.com', 'example.com'),
|
|
# Test with username and no password.
|
|
('accesstoken@example.com', '****@example.com'),
|
|
# Test with username and password.
|
|
('user:pass@example.com', 'user:****@example.com'),
|
|
# Test with username and empty password.
|
|
('user:@example.com', 'user:****@example.com'),
|
|
# Test the password containing an @ symbol.
|
|
('user:pass@word@example.com', 'user:****@example.com'),
|
|
# Test the password containing a : symbol.
|
|
('user:pass:word@example.com', 'user:****@example.com'),
|
|
# Test URL-encoded reserved characters.
|
|
('user%3Aname:%23%40%5E@example.com', 'user%3Aname:****@example.com'),
|
|
])
|
|
def test_redact_netloc(netloc, expected):
|
|
actual = redact_netloc(netloc)
|
|
assert actual == expected
|
|
|
|
|
|
@pytest.mark.parametrize('auth_url, expected_url', [
|
|
('https://user:pass@domain.tld/project/tags/v0.2',
|
|
'https://domain.tld/project/tags/v0.2'),
|
|
('https://domain.tld/project/tags/v0.2',
|
|
'https://domain.tld/project/tags/v0.2',),
|
|
('https://user:pass@domain.tld/svn/project/trunk@8181',
|
|
'https://domain.tld/svn/project/trunk@8181'),
|
|
('https://domain.tld/project/trunk@8181',
|
|
'https://domain.tld/project/trunk@8181',),
|
|
('git+https://pypi.org/something',
|
|
'git+https://pypi.org/something'),
|
|
('git+https://user:pass@pypi.org/something',
|
|
'git+https://pypi.org/something'),
|
|
('git+ssh://git@pypi.org/something',
|
|
'git+ssh://pypi.org/something'),
|
|
])
|
|
def test_remove_auth_from_url(auth_url, expected_url):
|
|
url = remove_auth_from_url(auth_url)
|
|
assert url == expected_url
|
|
|
|
|
|
@pytest.mark.parametrize('auth_url, expected_url', [
|
|
('https://accesstoken@example.com/abc', 'https://****@example.com/abc'),
|
|
('https://user:password@example.com', 'https://user:****@example.com'),
|
|
('https://user:@example.com', 'https://user:****@example.com'),
|
|
('https://example.com', 'https://example.com'),
|
|
# Test URL-encoded reserved characters.
|
|
('https://user%3Aname:%23%40%5E@example.com',
|
|
'https://user%3Aname:****@example.com'),
|
|
])
|
|
def test_redact_auth_from_url(auth_url, expected_url):
|
|
url = redact_auth_from_url(auth_url)
|
|
assert url == expected_url
|
|
|
|
|
|
class TestHiddenText:
|
|
|
|
def test_basic(self):
|
|
"""
|
|
Test str(), repr(), and attribute access.
|
|
"""
|
|
hidden = HiddenText('my-secret', redacted='######')
|
|
assert repr(hidden) == "<HiddenText '######'>"
|
|
assert str(hidden) == '######'
|
|
assert hidden.redacted == '######'
|
|
assert hidden.secret == 'my-secret'
|
|
|
|
def test_equality_with_str(self):
|
|
"""
|
|
Test equality (and inequality) with str objects.
|
|
"""
|
|
hidden = HiddenText('secret', redacted='****')
|
|
|
|
# Test that the object doesn't compare equal to either its original
|
|
# or redacted forms.
|
|
assert hidden != hidden.secret
|
|
assert hidden.secret != hidden
|
|
|
|
assert hidden != hidden.redacted
|
|
assert hidden.redacted != hidden
|
|
|
|
def test_equality_same_secret(self):
|
|
"""
|
|
Test equality with an object having the same secret.
|
|
"""
|
|
# Choose different redactions for the two objects.
|
|
hidden1 = HiddenText('secret', redacted='****')
|
|
hidden2 = HiddenText('secret', redacted='####')
|
|
|
|
assert hidden1 == hidden2
|
|
# Also test __ne__. This assertion fails in Python 2 without
|
|
# defining HiddenText.__ne__.
|
|
assert not hidden1 != hidden2
|
|
|
|
def test_equality_different_secret(self):
|
|
"""
|
|
Test equality with an object having a different secret.
|
|
"""
|
|
hidden1 = HiddenText('secret-1', redacted='****')
|
|
hidden2 = HiddenText('secret-2', redacted='****')
|
|
|
|
assert hidden1 != hidden2
|
|
# Also test __eq__.
|
|
assert not hidden1 == hidden2
|
|
|
|
|
|
def test_hide_value():
|
|
hidden = hide_value('my-secret')
|
|
assert repr(hidden) == "<HiddenText '****'>"
|
|
assert str(hidden) == '****'
|
|
assert hidden.redacted == '****'
|
|
assert hidden.secret == 'my-secret'
|
|
|
|
|
|
def test_hide_url():
|
|
hidden_url = hide_url('https://user:password@example.com')
|
|
assert repr(hidden_url) == "<HiddenText 'https://user:****@example.com'>"
|
|
assert str(hidden_url) == 'https://user:****@example.com'
|
|
assert hidden_url.redacted == 'https://user:****@example.com'
|
|
assert hidden_url.secret == 'https://user:password@example.com'
|
|
|
|
|
|
@pytest.fixture()
|
|
def patch_deprecation_check_version():
|
|
# We do this, so that the deprecation tests are easier to write.
|
|
import pip._internal.utils.deprecation as d
|
|
old_version = d.current_version
|
|
d.current_version = "1.0"
|
|
yield
|
|
d.current_version = old_version
|
|
|
|
|
|
@pytest.mark.usefixtures("patch_deprecation_check_version")
|
|
@pytest.mark.parametrize("replacement", [None, "a magic 8 ball"])
|
|
@pytest.mark.parametrize("gone_in", [None, "2.0"])
|
|
@pytest.mark.parametrize("issue", [None, 988])
|
|
def test_deprecated_message_contains_information(gone_in, replacement, issue):
|
|
with pytest.warns(PipDeprecationWarning) as record:
|
|
deprecated(
|
|
"Stop doing this!",
|
|
replacement=replacement,
|
|
gone_in=gone_in,
|
|
issue=issue,
|
|
)
|
|
|
|
assert len(record) == 1
|
|
message = record[0].message.args[0]
|
|
|
|
assert "DEPRECATION: Stop doing this!" in message
|
|
# Ensure non-None values are mentioned.
|
|
for item in [gone_in, replacement, issue]:
|
|
if item is not None:
|
|
assert str(item) in message
|
|
|
|
|
|
@pytest.mark.usefixtures("patch_deprecation_check_version")
|
|
@pytest.mark.parametrize("replacement", [None, "a magic 8 ball"])
|
|
@pytest.mark.parametrize("issue", [None, 988])
|
|
def test_deprecated_raises_error_if_too_old(replacement, issue):
|
|
with pytest.raises(PipDeprecationWarning) as exception:
|
|
deprecated(
|
|
"Stop doing this!",
|
|
gone_in="1.0", # this matches the patched version.
|
|
replacement=replacement,
|
|
issue=issue,
|
|
)
|
|
|
|
message = exception.value.args[0]
|
|
|
|
assert "DEPRECATION: Stop doing this!" in message
|
|
assert "1.0" in message
|
|
# Ensure non-None values are mentioned.
|
|
for item in [replacement, issue]:
|
|
if item is not None:
|
|
assert str(item) in message
|
|
|
|
|
|
@pytest.mark.usefixtures("patch_deprecation_check_version")
|
|
def test_deprecated_message_reads_well():
|
|
with pytest.raises(PipDeprecationWarning) as exception:
|
|
deprecated(
|
|
"Stop doing this!",
|
|
gone_in="1.0", # this matches the patched version.
|
|
replacement="to be nicer",
|
|
issue="100000", # I hope we never reach this number.
|
|
)
|
|
|
|
message = exception.value.args[0]
|
|
|
|
assert message == (
|
|
"DEPRECATION: Stop doing this! "
|
|
"pip 1.0 will remove support for this functionality. "
|
|
"A possible replacement is to be nicer. "
|
|
"You can find discussion regarding this at "
|
|
"https://github.com/pypa/pip/issues/100000."
|
|
)
|
|
|
|
|
|
def test_make_setuptools_shim_args():
|
|
# Test all arguments at once, including the overall ordering.
|
|
args = make_setuptools_shim_args(
|
|
'/dir/path/setup.py',
|
|
global_options=['--some', '--option'],
|
|
no_user_config=True,
|
|
unbuffered_output=True,
|
|
)
|
|
|
|
assert args[1:3] == ['-u', '-c']
|
|
# Spot-check key aspects of the command string.
|
|
assert "sys.argv[0] = '/dir/path/setup.py'" in args[3]
|
|
assert "__file__='/dir/path/setup.py'" in args[3]
|
|
assert args[4:] == ['--some', '--option', '--no-user-cfg']
|
|
|
|
|
|
@pytest.mark.parametrize('global_options', [
|
|
None,
|
|
[],
|
|
['--some', '--option']
|
|
])
|
|
def test_make_setuptools_shim_args__global_options(global_options):
|
|
args = make_setuptools_shim_args(
|
|
'/dir/path/setup.py',
|
|
global_options=global_options,
|
|
)
|
|
|
|
if global_options:
|
|
assert len(args) == 5
|
|
for option in global_options:
|
|
assert option in args
|
|
else:
|
|
assert len(args) == 3
|
|
|
|
|
|
@pytest.mark.parametrize('no_user_config', [False, True])
|
|
def test_make_setuptools_shim_args__no_user_config(no_user_config):
|
|
args = make_setuptools_shim_args(
|
|
'/dir/path/setup.py',
|
|
no_user_config=no_user_config,
|
|
)
|
|
assert ('--no-user-cfg' in args) == no_user_config
|
|
|
|
|
|
@pytest.mark.parametrize('unbuffered_output', [False, True])
|
|
def test_make_setuptools_shim_args__unbuffered_output(unbuffered_output):
|
|
args = make_setuptools_shim_args(
|
|
'/dir/path/setup.py',
|
|
unbuffered_output=unbuffered_output
|
|
)
|
|
assert ('-u' in args) == unbuffered_output
|
|
|
|
|
|
def mock_stdin_isatty(monkeypatch, return_value):
|
|
monkeypatch.setattr(sys.stdin, 'isatty', Mock(return_value=return_value))
|
|
|
|
|
|
def test_is_console_interactive_when_stdin_is_none_and_isatty_true(monkeypatch):
|
|
mock_stdin_isatty(monkeypatch, True)
|
|
monkeypatch.setattr(sys, 'stdin', None)
|
|
assert is_console_interactive() is False
|
|
|
|
|
|
def test_is_console_interactive_when_stdin_is_none_and_isatty_false(monkeypatch):
|
|
mock_stdin_isatty(monkeypatch, False)
|
|
monkeypatch.setattr(sys, 'stdin', None)
|
|
assert is_console_interactive() is False
|
|
|
|
|
|
def test_is_console_interactive_when_stdin_is_not_none_and_isatty_true(monkeypatch):
|
|
mock_stdin_isatty(monkeypatch, True)
|
|
assert is_console_interactive() is True
|
|
|
|
|
|
def test_is_console_interactive_when_stdin_is_not_none_and_isatty_false(monkeypatch):
|
|
mock_stdin_isatty(monkeypatch, False)
|
|
assert is_console_interactive() is False
|