1# -*- coding: utf-8 -*- 2"""Easy install Tests 3""" 4from __future__ import absolute_import 5 6import sys 7import os 8import tempfile 9import site 10import contextlib 11import tarfile 12import logging 13import itertools 14import distutils.errors 15import io 16import zipfile 17import mock 18 19import time 20from setuptools.extern.six.moves import urllib 21 22import pytest 23 24from setuptools import sandbox 25from setuptools.sandbox import run_setup 26import setuptools.command.easy_install as ei 27from setuptools.command.easy_install import PthDistributions 28from setuptools.command import easy_install as easy_install_pkg 29from setuptools.dist import Distribution 30from pkg_resources import normalize_path, working_set 31from pkg_resources import Distribution as PRDistribution 32import setuptools.tests.server 33from setuptools.tests import fail_on_ascii 34import pkg_resources 35 36from . import contexts 37from .textwrap import DALS 38 39 40class FakeDist(object): 41 def get_entry_map(self, group): 42 if group != 'console_scripts': 43 return {} 44 return {'name': 'ep'} 45 46 def as_requirement(self): 47 return 'spec' 48 49 50SETUP_PY = DALS(""" 51 from setuptools import setup 52 53 setup(name='foo') 54 """) 55 56 57class TestEasyInstallTest: 58 def test_install_site_py(self, tmpdir): 59 dist = Distribution() 60 cmd = ei.easy_install(dist) 61 cmd.sitepy_installed = False 62 cmd.install_dir = str(tmpdir) 63 cmd.install_site_py() 64 assert (tmpdir / 'site.py').exists() 65 66 def test_get_script_args(self): 67 header = ei.CommandSpec.best().from_environment().as_header() 68 expected = header + DALS(r""" 69 # EASY-INSTALL-ENTRY-SCRIPT: 'spec','console_scripts','name' 70 __requires__ = 'spec' 71 import re 72 import sys 73 from pkg_resources import load_entry_point 74 75 if __name__ == '__main__': 76 sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) 77 sys.exit( 78 load_entry_point('spec', 'console_scripts', 'name')() 79 ) 80 """) 81 dist = FakeDist() 82 83 args = next(ei.ScriptWriter.get_args(dist)) 84 name, script = itertools.islice(args, 2) 85 86 assert script == expected 87 88 def test_no_find_links(self): 89 # new option '--no-find-links', that blocks find-links added at 90 # the project level 91 dist = Distribution() 92 cmd = ei.easy_install(dist) 93 cmd.check_pth_processing = lambda: True 94 cmd.no_find_links = True 95 cmd.find_links = ['link1', 'link2'] 96 cmd.install_dir = os.path.join(tempfile.mkdtemp(), 'ok') 97 cmd.args = ['ok'] 98 cmd.ensure_finalized() 99 assert cmd.package_index.scanned_urls == {} 100 101 # let's try without it (default behavior) 102 cmd = ei.easy_install(dist) 103 cmd.check_pth_processing = lambda: True 104 cmd.find_links = ['link1', 'link2'] 105 cmd.install_dir = os.path.join(tempfile.mkdtemp(), 'ok') 106 cmd.args = ['ok'] 107 cmd.ensure_finalized() 108 keys = sorted(cmd.package_index.scanned_urls.keys()) 109 assert keys == ['link1', 'link2'] 110 111 def test_write_exception(self): 112 """ 113 Test that `cant_write_to_target` is rendered as a DistutilsError. 114 """ 115 dist = Distribution() 116 cmd = ei.easy_install(dist) 117 cmd.install_dir = os.getcwd() 118 with pytest.raises(distutils.errors.DistutilsError): 119 cmd.cant_write_to_target() 120 121 def test_all_site_dirs(self, monkeypatch): 122 """ 123 get_site_dirs should always return site dirs reported by 124 site.getsitepackages. 125 """ 126 path = normalize_path('/setuptools/test/site-packages') 127 mock_gsp = lambda: [path] 128 monkeypatch.setattr(site, 'getsitepackages', mock_gsp, raising=False) 129 assert path in ei.get_site_dirs() 130 131 def test_all_site_dirs_works_without_getsitepackages(self, monkeypatch): 132 monkeypatch.delattr(site, 'getsitepackages', raising=False) 133 assert ei.get_site_dirs() 134 135 @pytest.fixture 136 def sdist_unicode(self, tmpdir): 137 files = [ 138 ( 139 'setup.py', 140 DALS(""" 141 import setuptools 142 setuptools.setup( 143 name="setuptools-test-unicode", 144 version="1.0", 145 packages=["mypkg"], 146 include_package_data=True, 147 ) 148 """), 149 ), 150 ( 151 'mypkg/__init__.py', 152 "", 153 ), 154 ( 155 u'mypkg/\u2603.txt', 156 "", 157 ), 158 ] 159 sdist_name = 'setuptools-test-unicode-1.0.zip' 160 sdist = tmpdir / sdist_name 161 # can't use make_sdist, because the issue only occurs 162 # with zip sdists. 163 sdist_zip = zipfile.ZipFile(str(sdist), 'w') 164 for filename, content in files: 165 sdist_zip.writestr(filename, content) 166 sdist_zip.close() 167 return str(sdist) 168 169 @fail_on_ascii 170 def test_unicode_filename_in_sdist(self, sdist_unicode, tmpdir, monkeypatch): 171 """ 172 The install command should execute correctly even if 173 the package has unicode filenames. 174 """ 175 dist = Distribution({'script_args': ['easy_install']}) 176 target = (tmpdir / 'target').ensure_dir() 177 cmd = ei.easy_install( 178 dist, 179 install_dir=str(target), 180 args=['x'], 181 ) 182 monkeypatch.setitem(os.environ, 'PYTHONPATH', str(target)) 183 cmd.ensure_finalized() 184 cmd.easy_install(sdist_unicode) 185 186 @pytest.fixture 187 def sdist_script(self, tmpdir): 188 files = [ 189 ( 190 'setup.py', 191 DALS(""" 192 import setuptools 193 setuptools.setup( 194 name="setuptools-test-script", 195 version="1.0", 196 scripts=["mypkg_script"], 197 ) 198 """), 199 ), 200 ( 201 u'mypkg_script', 202 DALS(""" 203 #/usr/bin/python 204 print('mypkg_script') 205 """), 206 ), 207 ] 208 sdist_name = 'setuptools-test-script-1.0.zip' 209 sdist = str(tmpdir / sdist_name) 210 make_sdist(sdist, files) 211 return sdist 212 213 @pytest.mark.skipif(not sys.platform.startswith('linux'), 214 reason="Test can only be run on Linux") 215 def test_script_install(self, sdist_script, tmpdir, monkeypatch): 216 """ 217 Check scripts are installed. 218 """ 219 dist = Distribution({'script_args': ['easy_install']}) 220 target = (tmpdir / 'target').ensure_dir() 221 cmd = ei.easy_install( 222 dist, 223 install_dir=str(target), 224 args=['x'], 225 ) 226 monkeypatch.setitem(os.environ, 'PYTHONPATH', str(target)) 227 cmd.ensure_finalized() 228 cmd.easy_install(sdist_script) 229 assert (target / 'mypkg_script').exists() 230 231 232class TestPTHFileWriter: 233 def test_add_from_cwd_site_sets_dirty(self): 234 '''a pth file manager should set dirty 235 if a distribution is in site but also the cwd 236 ''' 237 pth = PthDistributions('does-not_exist', [os.getcwd()]) 238 assert not pth.dirty 239 pth.add(PRDistribution(os.getcwd())) 240 assert pth.dirty 241 242 def test_add_from_site_is_ignored(self): 243 location = '/test/location/does-not-have-to-exist' 244 # PthDistributions expects all locations to be normalized 245 location = pkg_resources.normalize_path(location) 246 pth = PthDistributions('does-not_exist', [location, ]) 247 assert not pth.dirty 248 pth.add(PRDistribution(location)) 249 assert not pth.dirty 250 251 252@pytest.yield_fixture 253def setup_context(tmpdir): 254 with (tmpdir / 'setup.py').open('w') as f: 255 f.write(SETUP_PY) 256 with tmpdir.as_cwd(): 257 yield tmpdir 258 259 260@pytest.mark.usefixtures("user_override") 261@pytest.mark.usefixtures("setup_context") 262class TestUserInstallTest: 263 264 # prevent check that site-packages is writable. easy_install 265 # shouldn't be writing to system site-packages during finalize 266 # options, but while it does, bypass the behavior. 267 prev_sp_write = mock.patch( 268 'setuptools.command.easy_install.easy_install.check_site_dir', 269 mock.Mock(), 270 ) 271 272 # simulate setuptools installed in user site packages 273 @mock.patch('setuptools.command.easy_install.__file__', site.USER_SITE) 274 @mock.patch('site.ENABLE_USER_SITE', True) 275 @prev_sp_write 276 def test_user_install_not_implied_user_site_enabled(self): 277 self.assert_not_user_site() 278 279 @mock.patch('site.ENABLE_USER_SITE', False) 280 @prev_sp_write 281 def test_user_install_not_implied_user_site_disabled(self): 282 self.assert_not_user_site() 283 284 @staticmethod 285 def assert_not_user_site(): 286 # create a finalized easy_install command 287 dist = Distribution() 288 dist.script_name = 'setup.py' 289 cmd = ei.easy_install(dist) 290 cmd.args = ['py'] 291 cmd.ensure_finalized() 292 assert not cmd.user, 'user should not be implied' 293 294 def test_multiproc_atexit(self): 295 pytest.importorskip('multiprocessing') 296 297 log = logging.getLogger('test_easy_install') 298 logging.basicConfig(level=logging.INFO, stream=sys.stderr) 299 log.info('this should not break') 300 301 @pytest.fixture() 302 def foo_package(self, tmpdir): 303 egg_file = tmpdir / 'foo-1.0.egg-info' 304 with egg_file.open('w') as f: 305 f.write('Name: foo\n') 306 return str(tmpdir) 307 308 @pytest.yield_fixture() 309 def install_target(self, tmpdir): 310 target = str(tmpdir) 311 with mock.patch('sys.path', sys.path + [target]): 312 python_path = os.path.pathsep.join(sys.path) 313 with mock.patch.dict(os.environ, PYTHONPATH=python_path): 314 yield target 315 316 def test_local_index(self, foo_package, install_target): 317 """ 318 The local index must be used when easy_install locates installed 319 packages. 320 """ 321 dist = Distribution() 322 dist.script_name = 'setup.py' 323 cmd = ei.easy_install(dist) 324 cmd.install_dir = install_target 325 cmd.args = ['foo'] 326 cmd.ensure_finalized() 327 cmd.local_index.scan([foo_package]) 328 res = cmd.easy_install('foo') 329 actual = os.path.normcase(os.path.realpath(res.location)) 330 expected = os.path.normcase(os.path.realpath(foo_package)) 331 assert actual == expected 332 333 @contextlib.contextmanager 334 def user_install_setup_context(self, *args, **kwargs): 335 """ 336 Wrap sandbox.setup_context to patch easy_install in that context to 337 appear as user-installed. 338 """ 339 with self.orig_context(*args, **kwargs): 340 import setuptools.command.easy_install as ei 341 ei.__file__ = site.USER_SITE 342 yield 343 344 def patched_setup_context(self): 345 self.orig_context = sandbox.setup_context 346 347 return mock.patch( 348 'setuptools.sandbox.setup_context', 349 self.user_install_setup_context, 350 ) 351 352 353@pytest.yield_fixture 354def distutils_package(): 355 distutils_setup_py = SETUP_PY.replace( 356 'from setuptools import setup', 357 'from distutils.core import setup', 358 ) 359 with contexts.tempdir(cd=os.chdir): 360 with open('setup.py', 'w') as f: 361 f.write(distutils_setup_py) 362 yield 363 364 365class TestDistutilsPackage: 366 def test_bdist_egg_available_on_distutils_pkg(self, distutils_package): 367 run_setup('setup.py', ['bdist_egg']) 368 369 370class TestSetupRequires: 371 def test_setup_requires_honors_fetch_params(self): 372 """ 373 When easy_install installs a source distribution which specifies 374 setup_requires, it should honor the fetch parameters (such as 375 allow-hosts, index-url, and find-links). 376 """ 377 # set up a server which will simulate an alternate package index. 378 p_index = setuptools.tests.server.MockServer() 379 p_index.start() 380 netloc = 1 381 p_index_loc = urllib.parse.urlparse(p_index.url)[netloc] 382 if p_index_loc.endswith(':0'): 383 # Some platforms (Jython) don't find a port to which to bind, 384 # so skip this test for them. 385 return 386 with contexts.quiet(): 387 # create an sdist that has a build-time dependency. 388 with TestSetupRequires.create_sdist() as dist_file: 389 with contexts.tempdir() as temp_install_dir: 390 with contexts.environment(PYTHONPATH=temp_install_dir): 391 ei_params = [ 392 '--index-url', p_index.url, 393 '--allow-hosts', p_index_loc, 394 '--exclude-scripts', 395 '--install-dir', temp_install_dir, 396 dist_file, 397 ] 398 with sandbox.save_argv(['easy_install']): 399 # attempt to install the dist. It should fail because 400 # it doesn't exist. 401 with pytest.raises(SystemExit): 402 easy_install_pkg.main(ei_params) 403 # there should have been two or three requests to the server 404 # (three happens on Python 3.3a) 405 assert 2 <= len(p_index.requests) <= 3 406 assert p_index.requests[0].path == '/does-not-exist/' 407 408 @staticmethod 409 @contextlib.contextmanager 410 def create_sdist(): 411 """ 412 Return an sdist with a setup_requires dependency (of something that 413 doesn't exist) 414 """ 415 with contexts.tempdir() as dir: 416 dist_path = os.path.join(dir, 'setuptools-test-fetcher-1.0.tar.gz') 417 make_sdist(dist_path, [ 418 ('setup.py', DALS(""" 419 import setuptools 420 setuptools.setup( 421 name="setuptools-test-fetcher", 422 version="1.0", 423 setup_requires = ['does-not-exist'], 424 ) 425 """))]) 426 yield dist_path 427 428 use_setup_cfg = ( 429 (), 430 ('dependency_links',), 431 ('setup_requires',), 432 ('dependency_links', 'setup_requires'), 433 ) 434 435 @pytest.mark.parametrize('use_setup_cfg', use_setup_cfg) 436 def test_setup_requires_overrides_version_conflict(self, use_setup_cfg): 437 """ 438 Regression test for distribution issue 323: 439 https://bitbucket.org/tarek/distribute/issues/323 440 441 Ensures that a distribution's setup_requires requirements can still be 442 installed and used locally even if a conflicting version of that 443 requirement is already on the path. 444 """ 445 446 fake_dist = PRDistribution('does-not-matter', project_name='foobar', 447 version='0.0') 448 working_set.add(fake_dist) 449 450 with contexts.save_pkg_resources_state(): 451 with contexts.tempdir() as temp_dir: 452 test_pkg = create_setup_requires_package(temp_dir, use_setup_cfg=use_setup_cfg) 453 test_setup_py = os.path.join(test_pkg, 'setup.py') 454 with contexts.quiet() as (stdout, stderr): 455 # Don't even need to install the package, just 456 # running the setup.py at all is sufficient 457 run_setup(test_setup_py, ['--name']) 458 459 lines = stdout.readlines() 460 assert len(lines) > 0 461 assert lines[-1].strip() == 'test_pkg' 462 463 @pytest.mark.parametrize('use_setup_cfg', use_setup_cfg) 464 def test_setup_requires_override_nspkg(self, use_setup_cfg): 465 """ 466 Like ``test_setup_requires_overrides_version_conflict`` but where the 467 ``setup_requires`` package is part of a namespace package that has 468 *already* been imported. 469 """ 470 471 with contexts.save_pkg_resources_state(): 472 with contexts.tempdir() as temp_dir: 473 foobar_1_archive = os.path.join(temp_dir, 'foo.bar-0.1.tar.gz') 474 make_nspkg_sdist(foobar_1_archive, 'foo.bar', '0.1') 475 # Now actually go ahead an extract to the temp dir and add the 476 # extracted path to sys.path so foo.bar v0.1 is importable 477 foobar_1_dir = os.path.join(temp_dir, 'foo.bar-0.1') 478 os.mkdir(foobar_1_dir) 479 with tarfile.open(foobar_1_archive) as tf: 480 tf.extractall(foobar_1_dir) 481 sys.path.insert(1, foobar_1_dir) 482 483 dist = PRDistribution(foobar_1_dir, project_name='foo.bar', 484 version='0.1') 485 working_set.add(dist) 486 487 template = DALS("""\ 488 import foo # Even with foo imported first the 489 # setup_requires package should override 490 import setuptools 491 setuptools.setup(**%r) 492 493 if not (hasattr(foo, '__path__') and 494 len(foo.__path__) == 2): 495 print('FAIL') 496 497 if 'foo.bar-0.2' not in foo.__path__[0]: 498 print('FAIL') 499 """) 500 501 test_pkg = create_setup_requires_package( 502 temp_dir, 'foo.bar', '0.2', make_nspkg_sdist, template, 503 use_setup_cfg=use_setup_cfg) 504 505 test_setup_py = os.path.join(test_pkg, 'setup.py') 506 507 with contexts.quiet() as (stdout, stderr): 508 try: 509 # Don't even need to install the package, just 510 # running the setup.py at all is sufficient 511 run_setup(test_setup_py, ['--name']) 512 except pkg_resources.VersionConflict: 513 self.fail('Installing setup.py requirements ' 514 'caused a VersionConflict') 515 516 assert 'FAIL' not in stdout.getvalue() 517 lines = stdout.readlines() 518 assert len(lines) > 0 519 assert lines[-1].strip() == 'test_pkg' 520 521 @pytest.mark.parametrize('use_setup_cfg', use_setup_cfg) 522 def test_setup_requires_with_attr_version(self, use_setup_cfg): 523 def make_dependency_sdist(dist_path, distname, version): 524 make_sdist(dist_path, [ 525 ('setup.py', 526 DALS(""" 527 import setuptools 528 setuptools.setup( 529 name={name!r}, 530 version={version!r}, 531 py_modules=[{name!r}], 532 ) 533 """.format(name=distname, version=version))), 534 (distname + '.py', 535 DALS(""" 536 version = 42 537 """ 538 ))]) 539 with contexts.save_pkg_resources_state(): 540 with contexts.tempdir() as temp_dir: 541 test_pkg = create_setup_requires_package( 542 temp_dir, setup_attrs=dict(version='attr: foobar.version'), 543 make_package=make_dependency_sdist, 544 use_setup_cfg=use_setup_cfg+('version',), 545 ) 546 test_setup_py = os.path.join(test_pkg, 'setup.py') 547 with contexts.quiet() as (stdout, stderr): 548 run_setup(test_setup_py, ['--version']) 549 lines = stdout.readlines() 550 assert len(lines) > 0 551 assert lines[-1].strip() == '42' 552 553 554def make_trivial_sdist(dist_path, distname, version): 555 """ 556 Create a simple sdist tarball at dist_path, containing just a simple 557 setup.py. 558 """ 559 560 make_sdist(dist_path, [ 561 ('setup.py', 562 DALS("""\ 563 import setuptools 564 setuptools.setup( 565 name=%r, 566 version=%r 567 ) 568 """ % (distname, version)))]) 569 570 571def make_nspkg_sdist(dist_path, distname, version): 572 """ 573 Make an sdist tarball with distname and version which also contains one 574 package with the same name as distname. The top-level package is 575 designated a namespace package). 576 """ 577 578 parts = distname.split('.') 579 nspackage = parts[0] 580 581 packages = ['.'.join(parts[:idx]) for idx in range(1, len(parts) + 1)] 582 583 setup_py = DALS("""\ 584 import setuptools 585 setuptools.setup( 586 name=%r, 587 version=%r, 588 packages=%r, 589 namespace_packages=[%r] 590 ) 591 """ % (distname, version, packages, nspackage)) 592 593 init = "__import__('pkg_resources').declare_namespace(__name__)" 594 595 files = [('setup.py', setup_py), 596 (os.path.join(nspackage, '__init__.py'), init)] 597 for package in packages[1:]: 598 filename = os.path.join(*(package.split('.') + ['__init__.py'])) 599 files.append((filename, '')) 600 601 make_sdist(dist_path, files) 602 603 604def make_sdist(dist_path, files): 605 """ 606 Create a simple sdist tarball at dist_path, containing the files 607 listed in ``files`` as ``(filename, content)`` tuples. 608 """ 609 610 with tarfile.open(dist_path, 'w:gz') as dist: 611 for filename, content in files: 612 file_bytes = io.BytesIO(content.encode('utf-8')) 613 file_info = tarfile.TarInfo(name=filename) 614 file_info.size = len(file_bytes.getvalue()) 615 file_info.mtime = int(time.time()) 616 dist.addfile(file_info, fileobj=file_bytes) 617 618 619def create_setup_requires_package(path, distname='foobar', version='0.1', 620 make_package=make_trivial_sdist, 621 setup_py_template=None, setup_attrs={}, 622 use_setup_cfg=()): 623 """Creates a source tree under path for a trivial test package that has a 624 single requirement in setup_requires--a tarball for that requirement is 625 also created and added to the dependency_links argument. 626 627 ``distname`` and ``version`` refer to the name/version of the package that 628 the test package requires via ``setup_requires``. The name of the test 629 package itself is just 'test_pkg'. 630 """ 631 632 test_setup_attrs = { 633 'name': 'test_pkg', 'version': '0.0', 634 'setup_requires': ['%s==%s' % (distname, version)], 635 'dependency_links': [os.path.abspath(path)] 636 } 637 test_setup_attrs.update(setup_attrs) 638 639 test_pkg = os.path.join(path, 'test_pkg') 640 os.mkdir(test_pkg) 641 642 if use_setup_cfg: 643 test_setup_cfg = os.path.join(test_pkg, 'setup.cfg') 644 options = [] 645 metadata = [] 646 for name in use_setup_cfg: 647 value = test_setup_attrs.pop(name) 648 if name in 'name version'.split(): 649 section = metadata 650 else: 651 section = options 652 if isinstance(value, (tuple, list)): 653 value = ';'.join(value) 654 section.append('%s: %s' % (name, value)) 655 with open(test_setup_cfg, 'w') as f: 656 f.write(DALS( 657 """ 658 [metadata] 659 {metadata} 660 [options] 661 {options} 662 """ 663 ).format( 664 options='\n'.join(options), 665 metadata='\n'.join(metadata), 666 )) 667 668 test_setup_py = os.path.join(test_pkg, 'setup.py') 669 670 if setup_py_template is None: 671 setup_py_template = DALS("""\ 672 import setuptools 673 setuptools.setup(**%r) 674 """) 675 676 with open(test_setup_py, 'w') as f: 677 f.write(setup_py_template % test_setup_attrs) 678 679 foobar_path = os.path.join(path, '%s-%s.tar.gz' % (distname, version)) 680 make_package(foobar_path, distname, version) 681 682 return test_pkg 683 684 685@pytest.mark.skipif( 686 sys.platform.startswith('java') and ei.is_sh(sys.executable), 687 reason="Test cannot run under java when executable is sh" 688) 689class TestScriptHeader: 690 non_ascii_exe = '/Users/José/bin/python' 691 exe_with_spaces = r'C:\Program Files\Python33\python.exe' 692 693 def test_get_script_header(self): 694 expected = '#!%s\n' % ei.nt_quote_arg(os.path.normpath(sys.executable)) 695 actual = ei.ScriptWriter.get_script_header('#!/usr/local/bin/python') 696 assert actual == expected 697 698 def test_get_script_header_args(self): 699 expected = '#!%s -x\n' % ei.nt_quote_arg(os.path.normpath 700 (sys.executable)) 701 actual = ei.ScriptWriter.get_script_header('#!/usr/bin/python -x') 702 assert actual == expected 703 704 def test_get_script_header_non_ascii_exe(self): 705 actual = ei.ScriptWriter.get_script_header('#!/usr/bin/python', 706 executable=self.non_ascii_exe) 707 expected = '#!%s -x\n' % self.non_ascii_exe 708 assert actual == expected 709 710 def test_get_script_header_exe_with_spaces(self): 711 actual = ei.ScriptWriter.get_script_header('#!/usr/bin/python', 712 executable='"' + self.exe_with_spaces + '"') 713 expected = '#!"%s"\n' % self.exe_with_spaces 714 assert actual == expected 715 716 717class TestCommandSpec: 718 def test_custom_launch_command(self): 719 """ 720 Show how a custom CommandSpec could be used to specify a #! executable 721 which takes parameters. 722 """ 723 cmd = ei.CommandSpec(['/usr/bin/env', 'python3']) 724 assert cmd.as_header() == '#!/usr/bin/env python3\n' 725 726 def test_from_param_for_CommandSpec_is_passthrough(self): 727 """ 728 from_param should return an instance of a CommandSpec 729 """ 730 cmd = ei.CommandSpec(['python']) 731 cmd_new = ei.CommandSpec.from_param(cmd) 732 assert cmd is cmd_new 733 734 @mock.patch('sys.executable', TestScriptHeader.exe_with_spaces) 735 @mock.patch.dict(os.environ) 736 def test_from_environment_with_spaces_in_executable(self): 737 os.environ.pop('__PYVENV_LAUNCHER__', None) 738 cmd = ei.CommandSpec.from_environment() 739 assert len(cmd) == 1 740 assert cmd.as_header().startswith('#!"') 741 742 def test_from_simple_string_uses_shlex(self): 743 """ 744 In order to support `executable = /usr/bin/env my-python`, make sure 745 from_param invokes shlex on that input. 746 """ 747 cmd = ei.CommandSpec.from_param('/usr/bin/env my-python') 748 assert len(cmd) == 2 749 assert '"' not in cmd.as_header() 750 751 752class TestWindowsScriptWriter: 753 def test_header(self): 754 hdr = ei.WindowsScriptWriter.get_script_header('') 755 assert hdr.startswith('#!') 756 assert hdr.endswith('\n') 757 hdr = hdr.lstrip('#!') 758 hdr = hdr.rstrip('\n') 759 # header should not start with an escaped quote 760 assert not hdr.startswith('\\"') 761