1"""Easy install Tests 2""" 3 4import sys 5import os 6import tempfile 7import site 8import contextlib 9import tarfile 10import logging 11import itertools 12import distutils.errors 13import io 14import zipfile 15import mock 16import time 17import re 18import subprocess 19import pathlib 20import warnings 21from collections import namedtuple 22 23import pytest 24from jaraco import path 25 26from setuptools import sandbox 27from setuptools.sandbox import run_setup 28import setuptools.command.easy_install as ei 29from setuptools.command.easy_install import ( 30 EasyInstallDeprecationWarning, ScriptWriter, PthDistributions, 31 WindowsScriptWriter, 32) 33from setuptools.dist import Distribution 34from pkg_resources import normalize_path, working_set 35from pkg_resources import Distribution as PRDistribution 36from setuptools.tests.server import MockServer, path_to_url 37from setuptools.tests import fail_on_ascii 38import pkg_resources 39 40from . import contexts 41from .textwrap import DALS 42 43 44@pytest.fixture(autouse=True) 45def pip_disable_index(monkeypatch): 46 """ 47 Important: Disable the default index for pip to avoid 48 querying packages in the index and potentially resolving 49 and installing packages there. 50 """ 51 monkeypatch.setenv('PIP_NO_INDEX', 'true') 52 53 54class FakeDist: 55 def get_entry_map(self, group): 56 if group != 'console_scripts': 57 return {} 58 return {str('name'): 'ep'} 59 60 def as_requirement(self): 61 return 'spec' 62 63 64SETUP_PY = DALS(""" 65 from setuptools import setup 66 67 setup() 68 """) 69 70 71class TestEasyInstallTest: 72 def test_get_script_args(self): 73 header = ei.CommandSpec.best().from_environment().as_header() 74 dist = FakeDist() 75 args = next(ei.ScriptWriter.get_args(dist)) 76 name, script = itertools.islice(args, 2) 77 assert script.startswith(header) 78 assert "'spec'" in script 79 assert "'console_scripts'" in script 80 assert "'name'" in script 81 assert re.search( 82 '^# EASY-INSTALL-ENTRY-SCRIPT', script, flags=re.MULTILINE) 83 84 def test_no_find_links(self): 85 # new option '--no-find-links', that blocks find-links added at 86 # the project level 87 dist = Distribution() 88 cmd = ei.easy_install(dist) 89 cmd.check_pth_processing = lambda: True 90 cmd.no_find_links = True 91 cmd.find_links = ['link1', 'link2'] 92 cmd.install_dir = os.path.join(tempfile.mkdtemp(), 'ok') 93 cmd.args = ['ok'] 94 cmd.ensure_finalized() 95 assert cmd.package_index.scanned_urls == {} 96 97 # let's try without it (default behavior) 98 cmd = ei.easy_install(dist) 99 cmd.check_pth_processing = lambda: True 100 cmd.find_links = ['link1', 'link2'] 101 cmd.install_dir = os.path.join(tempfile.mkdtemp(), 'ok') 102 cmd.args = ['ok'] 103 cmd.ensure_finalized() 104 keys = sorted(cmd.package_index.scanned_urls.keys()) 105 assert keys == ['link1', 'link2'] 106 107 def test_write_exception(self): 108 """ 109 Test that `cant_write_to_target` is rendered as a DistutilsError. 110 """ 111 dist = Distribution() 112 cmd = ei.easy_install(dist) 113 cmd.install_dir = os.getcwd() 114 with pytest.raises(distutils.errors.DistutilsError): 115 cmd.cant_write_to_target() 116 117 def test_all_site_dirs(self, monkeypatch): 118 """ 119 get_site_dirs should always return site dirs reported by 120 site.getsitepackages. 121 """ 122 path = normalize_path('/setuptools/test/site-packages') 123 124 def mock_gsp(): 125 return [path] 126 monkeypatch.setattr(site, 'getsitepackages', mock_gsp, raising=False) 127 assert path in ei.get_site_dirs() 128 129 def test_all_site_dirs_works_without_getsitepackages(self, monkeypatch): 130 monkeypatch.delattr(site, 'getsitepackages', raising=False) 131 assert ei.get_site_dirs() 132 133 @pytest.fixture 134 def sdist_unicode(self, tmpdir): 135 files = [ 136 ( 137 'setup.py', 138 DALS(""" 139 import setuptools 140 setuptools.setup( 141 name="setuptools-test-unicode", 142 version="1.0", 143 packages=["mypkg"], 144 include_package_data=True, 145 ) 146 """), 147 ), 148 ( 149 'mypkg/__init__.py', 150 "", 151 ), 152 ( 153 'mypkg/☃.txt', 154 "", 155 ), 156 ] 157 sdist_name = 'setuptools-test-unicode-1.0.zip' 158 sdist = tmpdir / sdist_name 159 # can't use make_sdist, because the issue only occurs 160 # with zip sdists. 161 sdist_zip = zipfile.ZipFile(str(sdist), 'w') 162 for filename, content in files: 163 sdist_zip.writestr(filename, content) 164 sdist_zip.close() 165 return str(sdist) 166 167 @fail_on_ascii 168 def test_unicode_filename_in_sdist( 169 self, sdist_unicode, tmpdir, monkeypatch): 170 """ 171 The install command should execute correctly even if 172 the package has unicode filenames. 173 """ 174 dist = Distribution({'script_args': ['easy_install']}) 175 target = (tmpdir / 'target').ensure_dir() 176 cmd = ei.easy_install( 177 dist, 178 install_dir=str(target), 179 args=['x'], 180 ) 181 monkeypatch.setitem(os.environ, 'PYTHONPATH', str(target)) 182 cmd.ensure_finalized() 183 cmd.easy_install(sdist_unicode) 184 185 @pytest.fixture 186 def sdist_unicode_in_script(self, tmpdir): 187 files = [ 188 ( 189 "setup.py", 190 DALS(""" 191 import setuptools 192 setuptools.setup( 193 name="setuptools-test-unicode", 194 version="1.0", 195 packages=["mypkg"], 196 include_package_data=True, 197 scripts=['mypkg/unicode_in_script'], 198 ) 199 """), 200 ), 201 ("mypkg/__init__.py", ""), 202 ( 203 "mypkg/unicode_in_script", 204 DALS( 205 """ 206 #!/bin/sh 207 # á 208 209 non_python_fn() { 210 } 211 """), 212 ), 213 ] 214 sdist_name = "setuptools-test-unicode-script-1.0.zip" 215 sdist = tmpdir / sdist_name 216 # can't use make_sdist, because the issue only occurs 217 # with zip sdists. 218 sdist_zip = zipfile.ZipFile(str(sdist), "w") 219 for filename, content in files: 220 sdist_zip.writestr(filename, content.encode('utf-8')) 221 sdist_zip.close() 222 return str(sdist) 223 224 @fail_on_ascii 225 def test_unicode_content_in_sdist( 226 self, sdist_unicode_in_script, tmpdir, monkeypatch): 227 """ 228 The install command should execute correctly even if 229 the package has unicode in scripts. 230 """ 231 dist = Distribution({"script_args": ["easy_install"]}) 232 target = (tmpdir / "target").ensure_dir() 233 cmd = ei.easy_install(dist, install_dir=str(target), args=["x"]) 234 monkeypatch.setitem(os.environ, "PYTHONPATH", str(target)) 235 cmd.ensure_finalized() 236 cmd.easy_install(sdist_unicode_in_script) 237 238 @pytest.fixture 239 def sdist_script(self, tmpdir): 240 files = [ 241 ( 242 'setup.py', 243 DALS(""" 244 import setuptools 245 setuptools.setup( 246 name="setuptools-test-script", 247 version="1.0", 248 scripts=["mypkg_script"], 249 ) 250 """), 251 ), 252 ( 253 'mypkg_script', 254 DALS(""" 255 #/usr/bin/python 256 print('mypkg_script') 257 """), 258 ), 259 ] 260 sdist_name = 'setuptools-test-script-1.0.zip' 261 sdist = str(tmpdir / sdist_name) 262 make_sdist(sdist, files) 263 return sdist 264 265 @pytest.mark.skipif(not sys.platform.startswith('linux'), 266 reason="Test can only be run on Linux") 267 def test_script_install(self, sdist_script, tmpdir, monkeypatch): 268 """ 269 Check scripts are installed. 270 """ 271 dist = Distribution({'script_args': ['easy_install']}) 272 target = (tmpdir / 'target').ensure_dir() 273 cmd = ei.easy_install( 274 dist, 275 install_dir=str(target), 276 args=['x'], 277 ) 278 monkeypatch.setitem(os.environ, 'PYTHONPATH', str(target)) 279 cmd.ensure_finalized() 280 cmd.easy_install(sdist_script) 281 assert (target / 'mypkg_script').exists() 282 283 def test_dist_get_script_args_deprecated(self): 284 with pytest.warns(EasyInstallDeprecationWarning): 285 ScriptWriter.get_script_args(None, None) 286 287 def test_dist_get_script_header_deprecated(self): 288 with pytest.warns(EasyInstallDeprecationWarning): 289 ScriptWriter.get_script_header("") 290 291 def test_dist_get_writer_deprecated(self): 292 with pytest.warns(EasyInstallDeprecationWarning): 293 ScriptWriter.get_writer(None) 294 295 def test_dist_WindowsScriptWriter_get_writer_deprecated(self): 296 with pytest.warns(EasyInstallDeprecationWarning): 297 WindowsScriptWriter.get_writer() 298 299 300@pytest.mark.filterwarnings('ignore:Unbuilt egg') 301class TestPTHFileWriter: 302 def test_add_from_cwd_site_sets_dirty(self): 303 '''a pth file manager should set dirty 304 if a distribution is in site but also the cwd 305 ''' 306 pth = PthDistributions('does-not_exist', [os.getcwd()]) 307 assert not pth.dirty 308 pth.add(PRDistribution(os.getcwd())) 309 assert pth.dirty 310 311 def test_add_from_site_is_ignored(self): 312 location = '/test/location/does-not-have-to-exist' 313 # PthDistributions expects all locations to be normalized 314 location = pkg_resources.normalize_path(location) 315 pth = PthDistributions('does-not_exist', [location, ]) 316 assert not pth.dirty 317 pth.add(PRDistribution(location)) 318 assert not pth.dirty 319 320 321@pytest.fixture 322def setup_context(tmpdir): 323 with (tmpdir / 'setup.py').open('w') as f: 324 f.write(SETUP_PY) 325 with tmpdir.as_cwd(): 326 yield tmpdir 327 328 329@pytest.mark.usefixtures("user_override") 330@pytest.mark.usefixtures("setup_context") 331class TestUserInstallTest: 332 333 # prevent check that site-packages is writable. easy_install 334 # shouldn't be writing to system site-packages during finalize 335 # options, but while it does, bypass the behavior. 336 prev_sp_write = mock.patch( 337 'setuptools.command.easy_install.easy_install.check_site_dir', 338 mock.Mock(), 339 ) 340 341 # simulate setuptools installed in user site packages 342 @mock.patch('setuptools.command.easy_install.__file__', site.USER_SITE) 343 @mock.patch('site.ENABLE_USER_SITE', True) 344 @prev_sp_write 345 def test_user_install_not_implied_user_site_enabled(self): 346 self.assert_not_user_site() 347 348 @mock.patch('site.ENABLE_USER_SITE', False) 349 @prev_sp_write 350 def test_user_install_not_implied_user_site_disabled(self): 351 self.assert_not_user_site() 352 353 @staticmethod 354 def assert_not_user_site(): 355 # create a finalized easy_install command 356 dist = Distribution() 357 dist.script_name = 'setup.py' 358 cmd = ei.easy_install(dist) 359 cmd.args = ['py'] 360 cmd.ensure_finalized() 361 assert not cmd.user, 'user should not be implied' 362 363 def test_multiproc_atexit(self): 364 pytest.importorskip('multiprocessing') 365 366 log = logging.getLogger('test_easy_install') 367 logging.basicConfig(level=logging.INFO, stream=sys.stderr) 368 log.info('this should not break') 369 370 @pytest.fixture() 371 def foo_package(self, tmpdir): 372 egg_file = tmpdir / 'foo-1.0.egg-info' 373 with egg_file.open('w') as f: 374 f.write('Name: foo\n') 375 return str(tmpdir) 376 377 @pytest.fixture() 378 def install_target(self, tmpdir): 379 target = str(tmpdir) 380 with mock.patch('sys.path', sys.path + [target]): 381 python_path = os.path.pathsep.join(sys.path) 382 with mock.patch.dict(os.environ, PYTHONPATH=python_path): 383 yield target 384 385 def test_local_index(self, foo_package, install_target): 386 """ 387 The local index must be used when easy_install locates installed 388 packages. 389 """ 390 dist = Distribution() 391 dist.script_name = 'setup.py' 392 cmd = ei.easy_install(dist) 393 cmd.install_dir = install_target 394 cmd.args = ['foo'] 395 cmd.ensure_finalized() 396 cmd.local_index.scan([foo_package]) 397 res = cmd.easy_install('foo') 398 actual = os.path.normcase(os.path.realpath(res.location)) 399 expected = os.path.normcase(os.path.realpath(foo_package)) 400 assert actual == expected 401 402 @contextlib.contextmanager 403 def user_install_setup_context(self, *args, **kwargs): 404 """ 405 Wrap sandbox.setup_context to patch easy_install in that context to 406 appear as user-installed. 407 """ 408 with self.orig_context(*args, **kwargs): 409 import setuptools.command.easy_install as ei 410 ei.__file__ = site.USER_SITE 411 yield 412 413 def patched_setup_context(self): 414 self.orig_context = sandbox.setup_context 415 416 return mock.patch( 417 'setuptools.sandbox.setup_context', 418 self.user_install_setup_context, 419 ) 420 421 422@pytest.fixture 423def distutils_package(): 424 distutils_setup_py = SETUP_PY.replace( 425 'from setuptools import setup', 426 'from distutils.core import setup', 427 ) 428 with contexts.tempdir(cd=os.chdir): 429 with open('setup.py', 'w') as f: 430 f.write(distutils_setup_py) 431 yield 432 433 434@pytest.fixture 435def mock_index(): 436 # set up a server which will simulate an alternate package index. 437 p_index = MockServer() 438 if p_index.server_port == 0: 439 # Some platforms (Jython) don't find a port to which to bind, 440 # so skip test for them. 441 pytest.skip("could not find a valid port") 442 p_index.start() 443 return p_index 444 445 446class TestDistutilsPackage: 447 def test_bdist_egg_available_on_distutils_pkg(self, distutils_package): 448 run_setup('setup.py', ['bdist_egg']) 449 450 451class TestSetupRequires: 452 453 def test_setup_requires_honors_fetch_params(self, mock_index, monkeypatch): 454 """ 455 When easy_install installs a source distribution which specifies 456 setup_requires, it should honor the fetch parameters (such as 457 index-url, and find-links). 458 """ 459 monkeypatch.setenv(str('PIP_RETRIES'), str('0')) 460 monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) 461 monkeypatch.setenv('PIP_NO_INDEX', 'false') 462 with contexts.quiet(): 463 # create an sdist that has a build-time dependency. 464 with TestSetupRequires.create_sdist() as dist_file: 465 with contexts.tempdir() as temp_install_dir: 466 with contexts.environment(PYTHONPATH=temp_install_dir): 467 cmd = [ 468 sys.executable, 469 '-m', 'setup', 470 'easy_install', 471 '--index-url', mock_index.url, 472 '--exclude-scripts', 473 '--install-dir', temp_install_dir, 474 dist_file, 475 ] 476 subprocess.Popen(cmd).wait() 477 # there should have been one requests to the server 478 assert [r.path for r in mock_index.requests] == ['/does-not-exist/'] 479 480 @staticmethod 481 @contextlib.contextmanager 482 def create_sdist(): 483 """ 484 Return an sdist with a setup_requires dependency (of something that 485 doesn't exist) 486 """ 487 with contexts.tempdir() as dir: 488 dist_path = os.path.join(dir, 'setuptools-test-fetcher-1.0.tar.gz') 489 make_sdist(dist_path, [ 490 ('setup.py', DALS(""" 491 import setuptools 492 setuptools.setup( 493 name="setuptools-test-fetcher", 494 version="1.0", 495 setup_requires = ['does-not-exist'], 496 ) 497 """)), 498 ('setup.cfg', ''), 499 ]) 500 yield dist_path 501 502 use_setup_cfg = ( 503 (), 504 ('dependency_links',), 505 ('setup_requires',), 506 ('dependency_links', 'setup_requires'), 507 ) 508 509 @pytest.mark.parametrize('use_setup_cfg', use_setup_cfg) 510 def test_setup_requires_overrides_version_conflict(self, use_setup_cfg): 511 """ 512 Regression test for distribution issue 323: 513 https://bitbucket.org/tarek/distribute/issues/323 514 515 Ensures that a distribution's setup_requires requirements can still be 516 installed and used locally even if a conflicting version of that 517 requirement is already on the path. 518 """ 519 520 fake_dist = PRDistribution('does-not-matter', project_name='foobar', 521 version='0.0') 522 working_set.add(fake_dist) 523 524 with contexts.save_pkg_resources_state(): 525 with contexts.tempdir() as temp_dir: 526 test_pkg = create_setup_requires_package( 527 temp_dir, use_setup_cfg=use_setup_cfg) 528 test_setup_py = os.path.join(test_pkg, 'setup.py') 529 with contexts.quiet() as (stdout, stderr): 530 # Don't even need to install the package, just 531 # running the setup.py at all is sufficient 532 run_setup(test_setup_py, [str('--name')]) 533 534 lines = stdout.readlines() 535 assert len(lines) > 0 536 assert lines[-1].strip() == 'test_pkg' 537 538 @pytest.mark.parametrize('use_setup_cfg', use_setup_cfg) 539 def test_setup_requires_override_nspkg(self, use_setup_cfg): 540 """ 541 Like ``test_setup_requires_overrides_version_conflict`` but where the 542 ``setup_requires`` package is part of a namespace package that has 543 *already* been imported. 544 """ 545 546 with contexts.save_pkg_resources_state(): 547 with contexts.tempdir() as temp_dir: 548 foobar_1_archive = os.path.join(temp_dir, 'foo.bar-0.1.tar.gz') 549 make_nspkg_sdist(foobar_1_archive, 'foo.bar', '0.1') 550 # Now actually go ahead an extract to the temp dir and add the 551 # extracted path to sys.path so foo.bar v0.1 is importable 552 foobar_1_dir = os.path.join(temp_dir, 'foo.bar-0.1') 553 os.mkdir(foobar_1_dir) 554 with tarfile.open(foobar_1_archive) as tf: 555 tf.extractall(foobar_1_dir) 556 sys.path.insert(1, foobar_1_dir) 557 558 dist = PRDistribution(foobar_1_dir, project_name='foo.bar', 559 version='0.1') 560 working_set.add(dist) 561 562 template = DALS("""\ 563 import foo # Even with foo imported first the 564 # setup_requires package should override 565 import setuptools 566 setuptools.setup(**%r) 567 568 if not (hasattr(foo, '__path__') and 569 len(foo.__path__) == 2): 570 print('FAIL') 571 572 if 'foo.bar-0.2' not in foo.__path__[0]: 573 print('FAIL') 574 """) 575 576 test_pkg = create_setup_requires_package( 577 temp_dir, 'foo.bar', '0.2', make_nspkg_sdist, template, 578 use_setup_cfg=use_setup_cfg) 579 580 test_setup_py = os.path.join(test_pkg, 'setup.py') 581 582 with contexts.quiet() as (stdout, stderr): 583 try: 584 # Don't even need to install the package, just 585 # running the setup.py at all is sufficient 586 run_setup(test_setup_py, [str('--name')]) 587 except pkg_resources.VersionConflict: 588 self.fail( 589 'Installing setup.py requirements ' 590 'caused a VersionConflict') 591 592 assert 'FAIL' not in stdout.getvalue() 593 lines = stdout.readlines() 594 assert len(lines) > 0 595 assert lines[-1].strip() == 'test_pkg' 596 597 @pytest.mark.parametrize('use_setup_cfg', use_setup_cfg) 598 def test_setup_requires_with_attr_version(self, use_setup_cfg): 599 def make_dependency_sdist(dist_path, distname, version): 600 files = [( 601 'setup.py', 602 DALS(""" 603 import setuptools 604 setuptools.setup( 605 name={name!r}, 606 version={version!r}, 607 py_modules=[{name!r}], 608 ) 609 """.format(name=distname, version=version)), 610 ), ( 611 distname + '.py', 612 DALS(""" 613 version = 42 614 """), 615 )] 616 make_sdist(dist_path, files) 617 with contexts.save_pkg_resources_state(): 618 with contexts.tempdir() as temp_dir: 619 test_pkg = create_setup_requires_package( 620 temp_dir, setup_attrs=dict(version='attr: foobar.version'), 621 make_package=make_dependency_sdist, 622 use_setup_cfg=use_setup_cfg + ('version',), 623 ) 624 test_setup_py = os.path.join(test_pkg, 'setup.py') 625 with contexts.quiet() as (stdout, stderr): 626 run_setup(test_setup_py, [str('--version')]) 627 lines = stdout.readlines() 628 assert len(lines) > 0 629 assert lines[-1].strip() == '42' 630 631 def test_setup_requires_honors_pip_env(self, mock_index, monkeypatch): 632 monkeypatch.setenv(str('PIP_RETRIES'), str('0')) 633 monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) 634 monkeypatch.setenv('PIP_NO_INDEX', 'false') 635 monkeypatch.setenv(str('PIP_INDEX_URL'), mock_index.url) 636 with contexts.save_pkg_resources_state(): 637 with contexts.tempdir() as temp_dir: 638 test_pkg = create_setup_requires_package( 639 temp_dir, 'python-xlib', '0.19', 640 setup_attrs=dict(dependency_links=[])) 641 test_setup_cfg = os.path.join(test_pkg, 'setup.cfg') 642 with open(test_setup_cfg, 'w') as fp: 643 fp.write(DALS( 644 ''' 645 [easy_install] 646 index_url = https://pypi.org/legacy/ 647 ''')) 648 test_setup_py = os.path.join(test_pkg, 'setup.py') 649 with pytest.raises(distutils.errors.DistutilsError): 650 run_setup(test_setup_py, [str('--version')]) 651 assert len(mock_index.requests) == 1 652 assert mock_index.requests[0].path == '/python-xlib/' 653 654 def test_setup_requires_with_pep508_url(self, mock_index, monkeypatch): 655 monkeypatch.setenv(str('PIP_RETRIES'), str('0')) 656 monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) 657 monkeypatch.setenv(str('PIP_INDEX_URL'), mock_index.url) 658 with contexts.save_pkg_resources_state(): 659 with contexts.tempdir() as temp_dir: 660 dep_sdist = os.path.join(temp_dir, 'dep.tar.gz') 661 make_trivial_sdist(dep_sdist, 'dependency', '42') 662 dep_url = path_to_url(dep_sdist, authority='localhost') 663 test_pkg = create_setup_requires_package( 664 temp_dir, 665 # Ignored (overridden by setup_attrs) 666 'python-xlib', '0.19', 667 setup_attrs=dict( 668 setup_requires='dependency @ %s' % dep_url)) 669 test_setup_py = os.path.join(test_pkg, 'setup.py') 670 run_setup(test_setup_py, [str('--version')]) 671 assert len(mock_index.requests) == 0 672 673 def test_setup_requires_with_allow_hosts(self, mock_index): 674 ''' The `allow-hosts` option in not supported anymore. ''' 675 files = { 676 'test_pkg': { 677 'setup.py': DALS(''' 678 from setuptools import setup 679 setup(setup_requires='python-xlib') 680 '''), 681 'setup.cfg': DALS(''' 682 [easy_install] 683 allow_hosts = * 684 '''), 685 } 686 } 687 with contexts.save_pkg_resources_state(): 688 with contexts.tempdir() as temp_dir: 689 path.build(files, prefix=temp_dir) 690 setup_py = str(pathlib.Path(temp_dir, 'test_pkg', 'setup.py')) 691 with pytest.raises(distutils.errors.DistutilsError): 692 run_setup(setup_py, [str('--version')]) 693 assert len(mock_index.requests) == 0 694 695 def test_setup_requires_with_python_requires(self, monkeypatch, tmpdir): 696 ''' Check `python_requires` is honored. ''' 697 monkeypatch.setenv(str('PIP_RETRIES'), str('0')) 698 monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) 699 monkeypatch.setenv(str('PIP_NO_INDEX'), str('1')) 700 monkeypatch.setenv(str('PIP_VERBOSE'), str('1')) 701 dep_1_0_sdist = 'dep-1.0.tar.gz' 702 dep_1_0_url = path_to_url(str(tmpdir / dep_1_0_sdist)) 703 dep_1_0_python_requires = '>=2.7' 704 make_python_requires_sdist( 705 str(tmpdir / dep_1_0_sdist), 'dep', '1.0', dep_1_0_python_requires) 706 dep_2_0_sdist = 'dep-2.0.tar.gz' 707 dep_2_0_url = path_to_url(str(tmpdir / dep_2_0_sdist)) 708 dep_2_0_python_requires = '!=' + '.'.join( 709 map(str, sys.version_info[:2])) + '.*' 710 make_python_requires_sdist( 711 str(tmpdir / dep_2_0_sdist), 'dep', '2.0', dep_2_0_python_requires) 712 index = tmpdir / 'index.html' 713 index.write_text(DALS( 714 ''' 715 <!DOCTYPE html> 716 <html><head><title>Links for dep</title></head> 717 <body> 718 <h1>Links for dep</h1> 719 <a href="{dep_1_0_url}" data-requires-python="{dep_1_0_python_requires}">{dep_1_0_sdist}</a><br/> 720 <a href="{dep_2_0_url}" data-requires-python="{dep_2_0_python_requires}">{dep_2_0_sdist}</a><br/> 721 </body> 722 </html> 723 ''').format( # noqa 724 dep_1_0_url=dep_1_0_url, 725 dep_1_0_sdist=dep_1_0_sdist, 726 dep_1_0_python_requires=dep_1_0_python_requires, 727 dep_2_0_url=dep_2_0_url, 728 dep_2_0_sdist=dep_2_0_sdist, 729 dep_2_0_python_requires=dep_2_0_python_requires, 730 ), 'utf-8') 731 index_url = path_to_url(str(index)) 732 with contexts.save_pkg_resources_state(): 733 test_pkg = create_setup_requires_package( 734 str(tmpdir), 735 'python-xlib', '0.19', # Ignored (overridden by setup_attrs). 736 setup_attrs=dict( 737 setup_requires='dep', dependency_links=[index_url])) 738 test_setup_py = os.path.join(test_pkg, 'setup.py') 739 run_setup(test_setup_py, [str('--version')]) 740 eggs = list(map(str, pkg_resources.find_distributions( 741 os.path.join(test_pkg, '.eggs')))) 742 assert eggs == ['dep 1.0'] 743 744 @pytest.mark.parametrize( 745 'with_dependency_links_in_setup_py', 746 (False, True)) 747 def test_setup_requires_with_find_links_in_setup_cfg( 748 self, monkeypatch, 749 with_dependency_links_in_setup_py): 750 monkeypatch.setenv(str('PIP_RETRIES'), str('0')) 751 monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) 752 with contexts.save_pkg_resources_state(): 753 with contexts.tempdir() as temp_dir: 754 make_trivial_sdist( 755 os.path.join(temp_dir, 'python-xlib-42.tar.gz'), 756 'python-xlib', 757 '42') 758 test_pkg = os.path.join(temp_dir, 'test_pkg') 759 test_setup_py = os.path.join(test_pkg, 'setup.py') 760 test_setup_cfg = os.path.join(test_pkg, 'setup.cfg') 761 os.mkdir(test_pkg) 762 with open(test_setup_py, 'w') as fp: 763 if with_dependency_links_in_setup_py: 764 dependency_links = [os.path.join(temp_dir, 'links')] 765 else: 766 dependency_links = [] 767 fp.write(DALS( 768 ''' 769 from setuptools import installer, setup 770 setup(setup_requires='python-xlib==42', 771 dependency_links={dependency_links!r}) 772 ''').format( 773 dependency_links=dependency_links)) 774 with open(test_setup_cfg, 'w') as fp: 775 fp.write(DALS( 776 ''' 777 [easy_install] 778 index_url = {index_url} 779 find_links = {find_links} 780 ''').format(index_url=os.path.join(temp_dir, 'index'), 781 find_links=temp_dir)) 782 run_setup(test_setup_py, [str('--version')]) 783 784 def test_setup_requires_with_transitive_extra_dependency( 785 self, monkeypatch): 786 # Use case: installing a package with a build dependency on 787 # an already installed `dep[extra]`, which in turn depends 788 # on `extra_dep` (whose is not already installed). 789 with contexts.save_pkg_resources_state(): 790 with contexts.tempdir() as temp_dir: 791 # Create source distribution for `extra_dep`. 792 make_trivial_sdist( 793 os.path.join(temp_dir, 'extra_dep-1.0.tar.gz'), 794 'extra_dep', '1.0') 795 # Create source tree for `dep`. 796 dep_pkg = os.path.join(temp_dir, 'dep') 797 os.mkdir(dep_pkg) 798 path.build({ 799 'setup.py': 800 DALS(""" 801 import setuptools 802 setuptools.setup( 803 name='dep', version='2.0', 804 extras_require={'extra': ['extra_dep']}, 805 ) 806 """), 807 'setup.cfg': '', 808 }, prefix=dep_pkg) 809 # "Install" dep. 810 run_setup( 811 os.path.join(dep_pkg, 'setup.py'), [str('dist_info')]) 812 working_set.add_entry(dep_pkg) 813 # Create source tree for test package. 814 test_pkg = os.path.join(temp_dir, 'test_pkg') 815 test_setup_py = os.path.join(test_pkg, 'setup.py') 816 os.mkdir(test_pkg) 817 with open(test_setup_py, 'w') as fp: 818 fp.write(DALS( 819 ''' 820 from setuptools import installer, setup 821 setup(setup_requires='dep[extra]') 822 ''')) 823 # Check... 824 monkeypatch.setenv(str('PIP_FIND_LINKS'), str(temp_dir)) 825 monkeypatch.setenv(str('PIP_NO_INDEX'), str('1')) 826 monkeypatch.setenv(str('PIP_RETRIES'), str('0')) 827 monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) 828 run_setup(test_setup_py, [str('--version')]) 829 830 831def make_trivial_sdist(dist_path, distname, version): 832 """ 833 Create a simple sdist tarball at dist_path, containing just a simple 834 setup.py. 835 """ 836 837 make_sdist(dist_path, [ 838 ('setup.py', 839 DALS("""\ 840 import setuptools 841 setuptools.setup( 842 name=%r, 843 version=%r 844 ) 845 """ % (distname, version))), 846 ('setup.cfg', ''), 847 ]) 848 849 850def make_nspkg_sdist(dist_path, distname, version): 851 """ 852 Make an sdist tarball with distname and version which also contains one 853 package with the same name as distname. The top-level package is 854 designated a namespace package). 855 """ 856 857 parts = distname.split('.') 858 nspackage = parts[0] 859 860 packages = ['.'.join(parts[:idx]) for idx in range(1, len(parts) + 1)] 861 862 setup_py = DALS("""\ 863 import setuptools 864 setuptools.setup( 865 name=%r, 866 version=%r, 867 packages=%r, 868 namespace_packages=[%r] 869 ) 870 """ % (distname, version, packages, nspackage)) 871 872 init = "__import__('pkg_resources').declare_namespace(__name__)" 873 874 files = [('setup.py', setup_py), 875 (os.path.join(nspackage, '__init__.py'), init)] 876 for package in packages[1:]: 877 filename = os.path.join(*(package.split('.') + ['__init__.py'])) 878 files.append((filename, '')) 879 880 make_sdist(dist_path, files) 881 882 883def make_python_requires_sdist(dist_path, distname, version, python_requires): 884 make_sdist(dist_path, [ 885 ( 886 'setup.py', 887 DALS("""\ 888 import setuptools 889 setuptools.setup( 890 name={name!r}, 891 version={version!r}, 892 python_requires={python_requires!r}, 893 ) 894 """).format( 895 name=distname, version=version, 896 python_requires=python_requires)), 897 ('setup.cfg', ''), 898 ]) 899 900 901def make_sdist(dist_path, files): 902 """ 903 Create a simple sdist tarball at dist_path, containing the files 904 listed in ``files`` as ``(filename, content)`` tuples. 905 """ 906 907 # Distributions with only one file don't play well with pip. 908 assert len(files) > 1 909 with tarfile.open(dist_path, 'w:gz') as dist: 910 for filename, content in files: 911 file_bytes = io.BytesIO(content.encode('utf-8')) 912 file_info = tarfile.TarInfo(name=filename) 913 file_info.size = len(file_bytes.getvalue()) 914 file_info.mtime = int(time.time()) 915 dist.addfile(file_info, fileobj=file_bytes) 916 917 918def create_setup_requires_package(path, distname='foobar', version='0.1', 919 make_package=make_trivial_sdist, 920 setup_py_template=None, setup_attrs={}, 921 use_setup_cfg=()): 922 """Creates a source tree under path for a trivial test package that has a 923 single requirement in setup_requires--a tarball for that requirement is 924 also created and added to the dependency_links argument. 925 926 ``distname`` and ``version`` refer to the name/version of the package that 927 the test package requires via ``setup_requires``. The name of the test 928 package itself is just 'test_pkg'. 929 """ 930 931 test_setup_attrs = { 932 'name': 'test_pkg', 'version': '0.0', 933 'setup_requires': ['%s==%s' % (distname, version)], 934 'dependency_links': [os.path.abspath(path)] 935 } 936 test_setup_attrs.update(setup_attrs) 937 938 test_pkg = os.path.join(path, 'test_pkg') 939 os.mkdir(test_pkg) 940 941 # setup.cfg 942 if use_setup_cfg: 943 options = [] 944 metadata = [] 945 for name in use_setup_cfg: 946 value = test_setup_attrs.pop(name) 947 if name in 'name version'.split(): 948 section = metadata 949 else: 950 section = options 951 if isinstance(value, (tuple, list)): 952 value = ';'.join(value) 953 section.append('%s: %s' % (name, value)) 954 test_setup_cfg_contents = DALS( 955 """ 956 [metadata] 957 {metadata} 958 [options] 959 {options} 960 """ 961 ).format( 962 options='\n'.join(options), 963 metadata='\n'.join(metadata), 964 ) 965 else: 966 test_setup_cfg_contents = '' 967 with open(os.path.join(test_pkg, 'setup.cfg'), 'w') as f: 968 f.write(test_setup_cfg_contents) 969 970 # setup.py 971 if setup_py_template is None: 972 setup_py_template = DALS("""\ 973 import setuptools 974 setuptools.setup(**%r) 975 """) 976 with open(os.path.join(test_pkg, 'setup.py'), 'w') as f: 977 f.write(setup_py_template % test_setup_attrs) 978 979 foobar_path = os.path.join(path, '%s-%s.tar.gz' % (distname, version)) 980 make_package(foobar_path, distname, version) 981 982 return test_pkg 983 984 985@pytest.mark.skipif( 986 sys.platform.startswith('java') and ei.is_sh(sys.executable), 987 reason="Test cannot run under java when executable is sh" 988) 989class TestScriptHeader: 990 non_ascii_exe = '/Users/José/bin/python' 991 exe_with_spaces = r'C:\Program Files\Python36\python.exe' 992 993 def test_get_script_header(self): 994 expected = '#!%s\n' % ei.nt_quote_arg(os.path.normpath(sys.executable)) 995 actual = ei.ScriptWriter.get_header('#!/usr/local/bin/python') 996 assert actual == expected 997 998 def test_get_script_header_args(self): 999 expected = '#!%s -x\n' % ei.nt_quote_arg( 1000 os.path.normpath(sys.executable)) 1001 actual = ei.ScriptWriter.get_header('#!/usr/bin/python -x') 1002 assert actual == expected 1003 1004 def test_get_script_header_non_ascii_exe(self): 1005 actual = ei.ScriptWriter.get_header( 1006 '#!/usr/bin/python', 1007 executable=self.non_ascii_exe) 1008 expected = str('#!%s -x\n') % self.non_ascii_exe 1009 assert actual == expected 1010 1011 def test_get_script_header_exe_with_spaces(self): 1012 actual = ei.ScriptWriter.get_header( 1013 '#!/usr/bin/python', 1014 executable='"' + self.exe_with_spaces + '"') 1015 expected = '#!"%s"\n' % self.exe_with_spaces 1016 assert actual == expected 1017 1018 1019class TestCommandSpec: 1020 def test_custom_launch_command(self): 1021 """ 1022 Show how a custom CommandSpec could be used to specify a #! executable 1023 which takes parameters. 1024 """ 1025 cmd = ei.CommandSpec(['/usr/bin/env', 'python3']) 1026 assert cmd.as_header() == '#!/usr/bin/env python3\n' 1027 1028 def test_from_param_for_CommandSpec_is_passthrough(self): 1029 """ 1030 from_param should return an instance of a CommandSpec 1031 """ 1032 cmd = ei.CommandSpec(['python']) 1033 cmd_new = ei.CommandSpec.from_param(cmd) 1034 assert cmd is cmd_new 1035 1036 @mock.patch('sys.executable', TestScriptHeader.exe_with_spaces) 1037 @mock.patch.dict(os.environ) 1038 def test_from_environment_with_spaces_in_executable(self): 1039 os.environ.pop('__PYVENV_LAUNCHER__', None) 1040 cmd = ei.CommandSpec.from_environment() 1041 assert len(cmd) == 1 1042 assert cmd.as_header().startswith('#!"') 1043 1044 def test_from_simple_string_uses_shlex(self): 1045 """ 1046 In order to support `executable = /usr/bin/env my-python`, make sure 1047 from_param invokes shlex on that input. 1048 """ 1049 cmd = ei.CommandSpec.from_param('/usr/bin/env my-python') 1050 assert len(cmd) == 2 1051 assert '"' not in cmd.as_header() 1052 1053 1054class TestWindowsScriptWriter: 1055 def test_header(self): 1056 hdr = ei.WindowsScriptWriter.get_header('') 1057 assert hdr.startswith('#!') 1058 assert hdr.endswith('\n') 1059 hdr = hdr.lstrip('#!') 1060 hdr = hdr.rstrip('\n') 1061 # header should not start with an escaped quote 1062 assert not hdr.startswith('\\"') 1063 1064 1065VersionStub = namedtuple("VersionStub", "major, minor, micro, releaselevel, serial") 1066 1067 1068def test_use_correct_python_version_string(tmpdir, tmpdir_cwd, monkeypatch): 1069 # In issue #3001, easy_install wrongly uses the `python3.1` directory 1070 # when the interpreter is `python3.10` and the `--user` option is given. 1071 # See pypa/setuptools#3001. 1072 dist = Distribution() 1073 cmd = dist.get_command_obj('easy_install') 1074 cmd.args = ['ok'] 1075 cmd.optimize = 0 1076 cmd.user = True 1077 cmd.install_userbase = str(tmpdir) 1078 cmd.install_usersite = None 1079 install_cmd = dist.get_command_obj('install') 1080 install_cmd.install_userbase = str(tmpdir) 1081 install_cmd.install_usersite = None 1082 1083 with monkeypatch.context() as patch, warnings.catch_warnings(): 1084 warnings.simplefilter("ignore") 1085 version = '3.10.1 (main, Dec 21 2021, 09:17:12) [GCC 10.2.1 20210110]' 1086 info = VersionStub(3, 10, 1, "final", 0) 1087 patch.setattr('site.ENABLE_USER_SITE', True) 1088 patch.setattr('sys.version', version) 1089 patch.setattr('sys.version_info', info) 1090 patch.setattr(cmd, 'create_home_path', mock.Mock()) 1091 cmd.finalize_options() 1092 1093 name = "pypy" if hasattr(sys, 'pypy_version_info') else "python" 1094 install_dir = cmd.install_dir.lower() 1095 1096 # In some platforms (e.g. Windows), install_dir is mostly determined 1097 # via `sysconfig`, which define constants eagerly at module creation. 1098 # This means that monkeypatching `sys.version` to emulate 3.10 for testing 1099 # may have no effect. 1100 # The safest test here is to rely on the fact that 3.1 is no longer 1101 # supported/tested, and make sure that if 'python3.1' ever appears in the string 1102 # it is followed by another digit (e.g. 'python3.10'). 1103 if re.search(name + r'3\.?1', install_dir): 1104 assert re.search(name + r'3\.?1\d', install_dir) 1105 1106 # The following "variables" are used for interpolation in distutils 1107 # installation schemes, so it should be fair to treat them as "semi-public", 1108 # or at least public enough so we can have a test to make sure they are correct 1109 assert cmd.config_vars['py_version'] == '3.10.1' 1110 assert cmd.config_vars['py_version_short'] == '3.10' 1111 assert cmd.config_vars['py_version_nodot'] == '310' 1112