test_utils.py 35.3 KB
Newer Older
1 2
# -*- coding: utf-8 -*-

3 4 5 6
"""
util tests

"""
7
import codecs
8
import os
P
Pradyun S. Gedam 已提交
9
import shutil
10
import stat
11
import sys
P
Pradyun S. Gedam 已提交
12
import time
13
import warnings
14
from io import BytesIO
15

P
Pradyun S. Gedam 已提交
16
import pytest
17
from mock import Mock, patch
18

19
from pip._internal.exceptions import (
20 21 22
    HashMismatch,
    HashMissing,
    InstallationError,
P
Pradyun S. Gedam 已提交
23
)
24
from pip._internal.utils.deprecation import PipDeprecationWarning, deprecated
25
from pip._internal.utils.encoding import BOMS, auto_decode
26
from pip._internal.utils.glibc import (
27 28 29
    check_glibc_version,
    glibc_version_string,
    glibc_version_string_confstr,
30 31
    glibc_version_string_ctypes,
)
32 33
from pip._internal.utils.hashes import Hashes, MissingHashes
from pip._internal.utils.misc import (
34
    HiddenText,
35
    build_netloc,
36
    build_url_from_netloc,
37 38 39
    egg_link_path,
    get_installed_distributions,
    get_prog,
40 41
    hide_url,
    hide_value,
E
Emil Burzo 已提交
42
    is_console_interactive,
43 44
    normalize_path,
    normalize_version_info,
45
    parse_netloc,
46
    path_to_display,
47
    redact_auth_from_url,
48 49 50
    redact_netloc,
    remove_auth_from_url,
    rmtree,
51
    rmtree_errorhandler,
52
    split_auth_from_netloc,
53
    split_auth_netloc_from_url,
E
Emil Burzo 已提交
54
)
55
from pip._internal.utils.setuptools_build import make_setuptools_shim_args
56 57 58 59 60 61 62 63 64 65 66 67


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'
68 69 70 71 72 73 74 75
        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,
        )
76

77
        # patches
78
        from pip._internal.utils import misc as utils
79 80 81 82
        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 = \
83
            Mock()
84 85 86 87
        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
88
        from os import path
89
        self.old_isfile = path.isfile
90 91
        self.mock_isfile = path.isfile = Mock()

92
    def teardown(self):
93
        from pip._internal.utils import misc as utils
94 95 96 97
        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
98 99 100
        from os import path
        path.isfile = self.old_isfile

101 102
    def eggLinkInUserSite(self, egglink):
        return egglink == self.user_site_egglink
103

104 105
    def eggLinkInSitePackages(self, egglink):
        return egglink == self.site_packages_egglink
106

107 108 109
    # ####################### #
    # # egglink in usersite # #
    # ####################### #
110 111 112 113
    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
114
        assert egg_link_path(self.mock_dist) == self.user_site_egglink
115 116 117 118 119

    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
120
        assert egg_link_path(self.mock_dist) is None
121 122 123 124 125

    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
126
        assert egg_link_path(self.mock_dist) == self.user_site_egglink
127

128 129 130
    # ####################### #
    # # egglink in sitepkgs # #
    # ####################### #
131 132 133 134
    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
135
        assert egg_link_path(self.mock_dist) == self.site_packages_egglink
136 137 138 139 140

    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
141
        assert egg_link_path(self.mock_dist) == self.site_packages_egglink
142 143 144 145 146

    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
147
        assert egg_link_path(self.mock_dist) == self.site_packages_egglink
148

149 150 151
    # ################################## #
    # # egglink in usersite & sitepkgs # #
    # ################################## #
152 153 154 155
    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
156
        assert egg_link_path(self.mock_dist) == self.user_site_egglink
157 158 159 160 161

    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
162
        assert egg_link_path(self.mock_dist) == self.site_packages_egglink
163 164 165 166 167

    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
168
        assert egg_link_path(self.mock_dist) == self.site_packages_egglink
169

170 171 172
    # ############## #
    # # no egglink # #
    # ############## #
173 174 175 176
    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
177
        assert egg_link_path(self.mock_dist) is None
178 179 180 181 182

    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
183
        assert egg_link_path(self.mock_dist) is None
184 185 186 187 188

    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
189
        assert egg_link_path(self.mock_dist) is None
190

191

192 193 194
@patch('pip._internal.utils.misc.dist_in_usersite')
@patch('pip._internal.utils.misc.dist_is_local')
@patch('pip._internal.utils.misc.dist_is_editable')
195 196 197 198
class Tests_get_installed_distributions:
    """test util.get_installed_distributions"""

    workingset = [
199 200
        Mock(test_name="global"),
        Mock(test_name="editable"),
201
        Mock(test_name="normal"),
202
        Mock(test_name="user"),
203 204 205 206 207 208 209 210 211 212 213
    ]

    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')
214
    ]
215 216 217 218 219

    def dist_is_editable(self, dist):
        return dist.test_name == "editable"

    def dist_is_local(self, dist):
220 221 222 223
        return dist.test_name != "global" and dist.test_name != 'user'

    def dist_in_usersite(self, dist):
        return dist.test_name == "user"
224

225
    @patch('pip._vendor.pkg_resources.working_set', workingset)
226 227 228
    def test_editables_only(self, mock_dist_is_editable,
                            mock_dist_is_local,
                            mock_dist_in_usersite):
229 230
        mock_dist_is_editable.side_effect = self.dist_is_editable
        mock_dist_is_local.side_effect = self.dist_is_local
231
        mock_dist_in_usersite.side_effect = self.dist_in_usersite
232 233 234 235
        dists = get_installed_distributions(editables_only=True)
        assert len(dists) == 1, dists
        assert dists[0].test_name == "editable"

236
    @patch('pip._vendor.pkg_resources.working_set', workingset)
237 238 239
    def test_exclude_editables(self, mock_dist_is_editable,
                               mock_dist_is_local,
                               mock_dist_in_usersite):
240 241
        mock_dist_is_editable.side_effect = self.dist_is_editable
        mock_dist_is_local.side_effect = self.dist_is_local
242
        mock_dist_in_usersite.side_effect = self.dist_in_usersite
243 244 245 246
        dists = get_installed_distributions(include_editables=False)
        assert len(dists) == 1
        assert dists[0].test_name == "normal"

247
    @patch('pip._vendor.pkg_resources.working_set', workingset)
248 249 250
    def test_include_globals(self, mock_dist_is_editable,
                             mock_dist_is_local,
                             mock_dist_in_usersite):
251 252
        mock_dist_is_editable.side_effect = self.dist_is_editable
        mock_dist_is_local.side_effect = self.dist_is_local
253
        mock_dist_in_usersite.side_effect = self.dist_in_usersite
254
        dists = get_installed_distributions(local_only=False)
255 256 257 258 259 260 261 262 263 264 265 266 267
        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"
268

269 270
    @patch('pip._vendor.pkg_resources.working_set', workingset_stdlib)
    def test_gte_py27_excludes(self, mock_dist_is_editable,
271 272
                               mock_dist_is_local,
                               mock_dist_in_usersite):
273 274
        mock_dist_is_editable.side_effect = self.dist_is_editable
        mock_dist_is_local.side_effect = self.dist_is_local
275
        mock_dist_in_usersite.side_effect = self.dist_in_usersite
276 277 278 279
        dists = get_installed_distributions()
        assert len(dists) == 0

    @patch('pip._vendor.pkg_resources.working_set', workingset_freeze)
280 281 282
    def test_freeze_excludes(self, mock_dist_is_editable,
                             mock_dist_is_local,
                             mock_dist_in_usersite):
283 284
        mock_dist_is_editable.side_effect = self.dist_is_editable
        mock_dist_is_local.side_effect = self.dist_is_local
285
        mock_dist_in_usersite.side_effect = self.dist_in_usersite
286 287
        dists = get_installed_distributions(
            skip=('setuptools', 'pip', 'distribute'))
288 289
        assert len(dists) == 0

290

A
Albert Tugushev 已提交
291
def test_rmtree_errorhandler_nonexistent_directory(tmpdir):
292 293 294
    """
    Test rmtree_errorhandler ignores the given non-existing directory.
    """
A
Albert Tugushev 已提交
295
    nonexistent_path = str(tmpdir / 'foo')
296
    mock_func = Mock()
A
Albert Tugushev 已提交
297
    rmtree_errorhandler(mock_func, nonexistent_path, None)
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
    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)

A
Albert Tugushev 已提交
314
    # Make sure the path is now writable
315 316 317 318 319 320
    assert os.stat(path).st_mode & stat.S_IWRITE


def test_rmtree_errorhandler_reraises_error(tmpdir):
    """
    Test rmtree_errorhandler reraises an exception
A
Albert Tugushev 已提交
321
    by the given unreadable directory.
322 323 324
    """
    # Create directory without read permission
    path = str((tmpdir / 'subdir').mkdir())
325
    os.chmod(path, stat.S_IWRITE)
326 327

    mock_func = Mock()
A
Albert Tugushev 已提交
328 329 330 331 332 333 334

    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)
335 336 337 338

    mock_func.assert_not_called()


A
Albert Tugushev 已提交
339
def test_rmtree_skips_nonexistent_directory():
340 341
    """
    Test wrapped rmtree doesn't raise an error
A
Albert Tugushev 已提交
342
    by the given nonexistent directory.
343
    """
A
Albert Tugushev 已提交
344
    rmtree.__wrapped__('nonexistent-subdir')
345 346


347 348 349 350 351 352 353 354 355 356 357 358
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):
    """
359
    Test pip._internal.utils.rmtree will retry failures
360 361 362 363 364 365 366
    """
    monkeypatch.setattr(shutil, 'rmtree', Failer(duration=1).call)
    rmtree('foo')


def test_rmtree_retries_for_3sec(tmpdir, monkeypatch):
    """
367
    Test pip._internal.utils.rmtree will retry failures for no more than 3 sec
368 369 370 371
    """
    monkeypatch.setattr(shutil, 'rmtree', Failer(duration=5).call)
    with pytest.raises(OSError):
        rmtree('foo')
T
Thomas Kluyver 已提交
372

T
Thomas Kluyver 已提交
373

374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
@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)


T
Thomas Kluyver 已提交
392 393 394 395 396
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'")
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
    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)
427 428 429


class TestHashes(object):
430
    """Tests for pip._internal.utils.hashes"""
431

C
Chris Jerdonek 已提交
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
    @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

448 449 450 451 452 453 454
    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'
455
        file.write_text('hello')
456 457 458 459 460 461 462 463 464 465 466
        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):
467
            hashes.check_against_file(BytesIO(b'hello'))
468 469 470 471

    def test_missing_hashes(self):
        """MissingHashes should raise HashMissing when any check is done."""
        with pytest.raises(HashMissing):
472
            MissingHashes().check_against_file(BytesIO(b'hello'))
473 474 475 476 477 478

    def test_unknown_hash(self):
        """Hashes should raise InstallationError when it encounters an unknown
        hash."""
        hashes = Hashes({'badbad': ['dummy']})
        with pytest.raises(InstallationError):
479
            hashes.check_against_file(BytesIO(b'hello'))
480 481 482 483 484 485 486

    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({})
487 488 489


class TestEncoding(object):
490
    """Tests for pip._internal.utils.encoding"""
491

492
    def test_auto_decode_utf_16_le(self):
493 494 495 496
        data = (
            b'\xff\xfeD\x00j\x00a\x00n\x00g\x00o\x00=\x00'
            b'=\x001\x00.\x004\x00.\x002\x00'
        )
497 498 499 500 501 502 503 504 505
        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)
506 507
        assert auto_decode(data) == "Django==1.4.2"

X
Xavier Fernandez 已提交
508 509
    def test_auto_decode_no_bom(self):
        assert auto_decode(b'foobar') == u'foobar'
510 511 512 513

    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
514

515 516 517 518 519 520 521 522 523 524
    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

525 526 527 528 529
    @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) == ''

530

531 532 533 534
def raises(error):
    raise error


535
class TestGlibc(object):
M
Mark Williams 已提交
536
    def test_manylinux_check_glibc_version(self):
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
        """
        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
567

568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596
    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

597

598 599 600 601 602 603 604 605 606 607 608 609
@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


610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627
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
P
Pradyun Gedam 已提交
628 629


630
@pytest.mark.parametrize('host_port, expected_netloc', [
631
    # Test domain name.
632 633
    (('example.com', None), 'example.com'),
    (('example.com', 5000), 'example.com:5000'),
634
    # Test IPv4 address.
635 636
    (('127.0.0.1', None), '127.0.0.1'),
    (('127.0.0.1', 5000), '127.0.0.1:5000'),
637
    # Test bare IPv6 address.
638
    (('2001:db6::1', None), '2001:db6::1'),
639
    # Test IPv6 with port.
640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
    (('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)
    ),
661 662 663 664
    # Test netloc with auth.
    (
        'user:password@localhost:5000',
        'https://user:password@localhost:5000',
665
        ('localhost', 5000)
666 667
    )
])
668 669
def test_build_url_from_netloc_and_parse_netloc(
    netloc, expected_url, expected_host_port,
670 671
):
    assert build_url_from_netloc(netloc) == expected_url
672
    assert parse_netloc(netloc) == expected_host_port
673 674


675 676 677 678 679 680 681 682 683 684 685 686 687
@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'))),
C
Chris Jerdonek 已提交
688 689 690
    # Test URL-encoded reserved characters.
    ('user%3Aname:%23%40%5E@example.com',
     ('example.com', ('user:name', '#@^'))),
691 692 693 694 695 696
])
def test_split_auth_from_netloc(netloc, expected):
    actual = split_auth_from_netloc(netloc)
    assert actual == expected


697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724
@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


725 726 727 728
@pytest.mark.parametrize('netloc, expected', [
    # Test a basic case.
    ('example.com', 'example.com'),
    # Test with username and no password.
729
    ('accesstoken@example.com', '****@example.com'),
730 731 732 733 734 735 736 737
    # 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'),
738 739
    # Test URL-encoded reserved characters.
    ('user%3Aname:%23%40%5E@example.com', 'user%3Aname:****@example.com'),
740 741 742 743 744 745
])
def test_redact_netloc(netloc, expected):
    actual = redact_netloc(netloc)
    assert actual == expected


746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763
@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)
764
    assert url == expected_url
765 766 767


@pytest.mark.parametrize('auth_url, expected_url', [
768
    ('https://accesstoken@example.com/abc', 'https://****@example.com/abc'),
769 770
    ('https://user:password@example.com', 'https://user:****@example.com'),
    ('https://user:@example.com', 'https://user:****@example.com'),
771 772 773 774
    ('https://example.com', 'https://example.com'),
    # Test URL-encoded reserved characters.
    ('https://user%3Aname:%23%40%5E@example.com',
     'https://user%3Aname:****@example.com'),
775
])
776 777
def test_redact_auth_from_url(auth_url, expected_url):
    url = redact_auth_from_url(auth_url)
778
    assert url == expected_url
779 780


781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847
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'


848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921
@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."
    )
922 923


924 925
def test_make_setuptools_shim_args():
    # Test all arguments at once, including the overall ordering.
926
    args = make_setuptools_shim_args(
927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948
        '/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,
949 950
    )

951 952 953 954 955 956
    if global_options:
        assert len(args) == 5
        for option in global_options:
            assert option in args
    else:
        assert len(args) == 3
957 958


959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974
@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
E
Emil Burzo 已提交
975 976


977 978 979 980 981 982 983 984
@pytest.mark.parametrize('isatty,no_stdin,expected', [
    (True, False, True),
    (False, False, False),
    (True, True, False),
    (False, True, False),
])
def test_is_console_interactive(monkeypatch, isatty, no_stdin, expected):
    monkeypatch.setattr(sys.stdin, 'isatty', Mock(return_value=isatty))
E
Emil Burzo 已提交
985

986 987
    if no_stdin:
        monkeypatch.setattr(sys, 'stdin', None)
E
Emil Burzo 已提交
988

989
    assert is_console_interactive() is expected