test_easy_install.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. # -*- coding: utf-8 -*-
  2. """Easy install Tests
  3. """
  4. from __future__ import absolute_import
  5. import sys
  6. import os
  7. import shutil
  8. import tempfile
  9. import site
  10. import contextlib
  11. import tarfile
  12. import logging
  13. import itertools
  14. import distutils.errors
  15. import pytest
  16. try:
  17. from unittest import mock
  18. except ImportError:
  19. import mock
  20. from setuptools import sandbox
  21. from setuptools import compat
  22. from setuptools.compat import StringIO, BytesIO, urlparse
  23. from setuptools.sandbox import run_setup
  24. import setuptools.command.easy_install as ei
  25. from setuptools.command.easy_install import PthDistributions
  26. from setuptools.command import easy_install as easy_install_pkg
  27. from setuptools.dist import Distribution
  28. from pkg_resources import working_set
  29. from pkg_resources import Distribution as PRDistribution
  30. import setuptools.tests.server
  31. import pkg_resources
  32. from .py26compat import tarfile_open
  33. from . import contexts
  34. from .textwrap import DALS
  35. class FakeDist(object):
  36. def get_entry_map(self, group):
  37. if group != 'console_scripts':
  38. return {}
  39. return {'name': 'ep'}
  40. def as_requirement(self):
  41. return 'spec'
  42. SETUP_PY = DALS("""
  43. from setuptools import setup
  44. setup(name='foo')
  45. """)
  46. class TestEasyInstallTest:
  47. def test_install_site_py(self):
  48. dist = Distribution()
  49. cmd = ei.easy_install(dist)
  50. cmd.sitepy_installed = False
  51. cmd.install_dir = tempfile.mkdtemp()
  52. try:
  53. cmd.install_site_py()
  54. sitepy = os.path.join(cmd.install_dir, 'site.py')
  55. assert os.path.exists(sitepy)
  56. finally:
  57. shutil.rmtree(cmd.install_dir)
  58. def test_get_script_args(self):
  59. header = ei.CommandSpec.best().from_environment().as_header()
  60. expected = header + DALS("""
  61. # EASY-INSTALL-ENTRY-SCRIPT: 'spec','console_scripts','name'
  62. __requires__ = 'spec'
  63. import sys
  64. from pkg_resources import load_entry_point
  65. if __name__ == '__main__':
  66. sys.exit(
  67. load_entry_point('spec', 'console_scripts', 'name')()
  68. )
  69. """)
  70. dist = FakeDist()
  71. args = next(ei.ScriptWriter.get_args(dist))
  72. name, script = itertools.islice(args, 2)
  73. assert script == expected
  74. def test_no_find_links(self):
  75. # new option '--no-find-links', that blocks find-links added at
  76. # the project level
  77. dist = Distribution()
  78. cmd = ei.easy_install(dist)
  79. cmd.check_pth_processing = lambda: True
  80. cmd.no_find_links = True
  81. cmd.find_links = ['link1', 'link2']
  82. cmd.install_dir = os.path.join(tempfile.mkdtemp(), 'ok')
  83. cmd.args = ['ok']
  84. cmd.ensure_finalized()
  85. assert cmd.package_index.scanned_urls == {}
  86. # let's try without it (default behavior)
  87. cmd = ei.easy_install(dist)
  88. cmd.check_pth_processing = lambda: True
  89. cmd.find_links = ['link1', 'link2']
  90. cmd.install_dir = os.path.join(tempfile.mkdtemp(), 'ok')
  91. cmd.args = ['ok']
  92. cmd.ensure_finalized()
  93. keys = sorted(cmd.package_index.scanned_urls.keys())
  94. assert keys == ['link1', 'link2']
  95. def test_write_exception(self):
  96. """
  97. Test that `cant_write_to_target` is rendered as a DistutilsError.
  98. """
  99. dist = Distribution()
  100. cmd = ei.easy_install(dist)
  101. cmd.install_dir = os.getcwd()
  102. with pytest.raises(distutils.errors.DistutilsError):
  103. cmd.cant_write_to_target()
  104. class TestPTHFileWriter:
  105. def test_add_from_cwd_site_sets_dirty(self):
  106. '''a pth file manager should set dirty
  107. if a distribution is in site but also the cwd
  108. '''
  109. pth = PthDistributions('does-not_exist', [os.getcwd()])
  110. assert not pth.dirty
  111. pth.add(PRDistribution(os.getcwd()))
  112. assert pth.dirty
  113. def test_add_from_site_is_ignored(self):
  114. location = '/test/location/does-not-have-to-exist'
  115. # PthDistributions expects all locations to be normalized
  116. location = pkg_resources.normalize_path(location)
  117. pth = PthDistributions('does-not_exist', [location, ])
  118. assert not pth.dirty
  119. pth.add(PRDistribution(location))
  120. assert not pth.dirty
  121. @pytest.yield_fixture
  122. def setup_context(tmpdir):
  123. with (tmpdir/'setup.py').open('w') as f:
  124. f.write(SETUP_PY)
  125. with tmpdir.as_cwd():
  126. yield tmpdir
  127. @pytest.mark.usefixtures("user_override")
  128. @pytest.mark.usefixtures("setup_context")
  129. class TestUserInstallTest:
  130. # prevent check that site-packages is writable. easy_install
  131. # shouldn't be writing to system site-packages during finalize
  132. # options, but while it does, bypass the behavior.
  133. prev_sp_write = mock.patch(
  134. 'setuptools.command.easy_install.easy_install.check_site_dir',
  135. mock.Mock(),
  136. )
  137. # simulate setuptools installed in user site packages
  138. @mock.patch('setuptools.command.easy_install.__file__', site.USER_SITE)
  139. @mock.patch('site.ENABLE_USER_SITE', True)
  140. @prev_sp_write
  141. def test_user_install_not_implied_user_site_enabled(self):
  142. self.assert_not_user_site()
  143. @mock.patch('site.ENABLE_USER_SITE', False)
  144. @prev_sp_write
  145. def test_user_install_not_implied_user_site_disabled(self):
  146. self.assert_not_user_site()
  147. @staticmethod
  148. def assert_not_user_site():
  149. # create a finalized easy_install command
  150. dist = Distribution()
  151. dist.script_name = 'setup.py'
  152. cmd = ei.easy_install(dist)
  153. cmd.args = ['py']
  154. cmd.ensure_finalized()
  155. assert not cmd.user, 'user should not be implied'
  156. def test_multiproc_atexit(self):
  157. pytest.importorskip('multiprocessing')
  158. log = logging.getLogger('test_easy_install')
  159. logging.basicConfig(level=logging.INFO, stream=sys.stderr)
  160. log.info('this should not break')
  161. @pytest.fixture()
  162. def foo_package(self, tmpdir):
  163. egg_file = tmpdir / 'foo-1.0.egg-info'
  164. with egg_file.open('w') as f:
  165. f.write('Name: foo\n')
  166. return str(tmpdir)
  167. @pytest.yield_fixture()
  168. def install_target(self, tmpdir):
  169. target = str(tmpdir)
  170. with mock.patch('sys.path', sys.path + [target]):
  171. python_path = os.path.pathsep.join(sys.path)
  172. with mock.patch.dict(os.environ, PYTHONPATH=python_path):
  173. yield target
  174. def test_local_index(self, foo_package, install_target):
  175. """
  176. The local index must be used when easy_install locates installed
  177. packages.
  178. """
  179. dist = Distribution()
  180. dist.script_name = 'setup.py'
  181. cmd = ei.easy_install(dist)
  182. cmd.install_dir = install_target
  183. cmd.args = ['foo']
  184. cmd.ensure_finalized()
  185. cmd.local_index.scan([foo_package])
  186. res = cmd.easy_install('foo')
  187. actual = os.path.normcase(os.path.realpath(res.location))
  188. expected = os.path.normcase(os.path.realpath(foo_package))
  189. assert actual == expected
  190. @contextlib.contextmanager
  191. def user_install_setup_context(self, *args, **kwargs):
  192. """
  193. Wrap sandbox.setup_context to patch easy_install in that context to
  194. appear as user-installed.
  195. """
  196. with self.orig_context(*args, **kwargs):
  197. import setuptools.command.easy_install as ei
  198. ei.__file__ = site.USER_SITE
  199. yield
  200. def patched_setup_context(self):
  201. self.orig_context = sandbox.setup_context
  202. return mock.patch(
  203. 'setuptools.sandbox.setup_context',
  204. self.user_install_setup_context,
  205. )
  206. @pytest.yield_fixture
  207. def distutils_package():
  208. distutils_setup_py = SETUP_PY.replace(
  209. 'from setuptools import setup',
  210. 'from distutils.core import setup',
  211. )
  212. with contexts.tempdir(cd=os.chdir):
  213. with open('setup.py', 'w') as f:
  214. f.write(distutils_setup_py)
  215. yield
  216. class TestDistutilsPackage:
  217. def test_bdist_egg_available_on_distutils_pkg(self, distutils_package):
  218. run_setup('setup.py', ['bdist_egg'])
  219. class TestSetupRequires:
  220. def test_setup_requires_honors_fetch_params(self):
  221. """
  222. When easy_install installs a source distribution which specifies
  223. setup_requires, it should honor the fetch parameters (such as
  224. allow-hosts, index-url, and find-links).
  225. """
  226. # set up a server which will simulate an alternate package index.
  227. p_index = setuptools.tests.server.MockServer()
  228. p_index.start()
  229. netloc = 1
  230. p_index_loc = urlparse(p_index.url)[netloc]
  231. if p_index_loc.endswith(':0'):
  232. # Some platforms (Jython) don't find a port to which to bind,
  233. # so skip this test for them.
  234. return
  235. with contexts.quiet():
  236. # create an sdist that has a build-time dependency.
  237. with TestSetupRequires.create_sdist() as dist_file:
  238. with contexts.tempdir() as temp_install_dir:
  239. with contexts.environment(PYTHONPATH=temp_install_dir):
  240. ei_params = [
  241. '--index-url', p_index.url,
  242. '--allow-hosts', p_index_loc,
  243. '--exclude-scripts',
  244. '--install-dir', temp_install_dir,
  245. dist_file,
  246. ]
  247. with sandbox.save_argv(['easy_install']):
  248. # attempt to install the dist. It should fail because
  249. # it doesn't exist.
  250. with pytest.raises(SystemExit):
  251. easy_install_pkg.main(ei_params)
  252. # there should have been two or three requests to the server
  253. # (three happens on Python 3.3a)
  254. assert 2 <= len(p_index.requests) <= 3
  255. assert p_index.requests[0].path == '/does-not-exist/'
  256. @staticmethod
  257. @contextlib.contextmanager
  258. def create_sdist():
  259. """
  260. Return an sdist with a setup_requires dependency (of something that
  261. doesn't exist)
  262. """
  263. with contexts.tempdir() as dir:
  264. dist_path = os.path.join(dir, 'setuptools-test-fetcher-1.0.tar.gz')
  265. script = DALS("""
  266. import setuptools
  267. setuptools.setup(
  268. name="setuptools-test-fetcher",
  269. version="1.0",
  270. setup_requires = ['does-not-exist'],
  271. )
  272. """)
  273. make_trivial_sdist(dist_path, script)
  274. yield dist_path
  275. def test_setup_requires_overrides_version_conflict(self):
  276. """
  277. Regression test for issue #323.
  278. Ensures that a distribution's setup_requires requirements can still be
  279. installed and used locally even if a conflicting version of that
  280. requirement is already on the path.
  281. """
  282. pr_state = pkg_resources.__getstate__()
  283. fake_dist = PRDistribution('does-not-matter', project_name='foobar',
  284. version='0.0')
  285. working_set.add(fake_dist)
  286. try:
  287. with contexts.tempdir() as temp_dir:
  288. test_pkg = create_setup_requires_package(temp_dir)
  289. test_setup_py = os.path.join(test_pkg, 'setup.py')
  290. with contexts.quiet() as (stdout, stderr):
  291. # Don't even need to install the package, just
  292. # running the setup.py at all is sufficient
  293. run_setup(test_setup_py, ['--name'])
  294. lines = stdout.readlines()
  295. assert len(lines) > 0
  296. assert lines[-1].strip(), 'test_pkg'
  297. finally:
  298. pkg_resources.__setstate__(pr_state)
  299. def create_setup_requires_package(path):
  300. """Creates a source tree under path for a trivial test package that has a
  301. single requirement in setup_requires--a tarball for that requirement is
  302. also created and added to the dependency_links argument.
  303. """
  304. test_setup_attrs = {
  305. 'name': 'test_pkg', 'version': '0.0',
  306. 'setup_requires': ['foobar==0.1'],
  307. 'dependency_links': [os.path.abspath(path)]
  308. }
  309. test_pkg = os.path.join(path, 'test_pkg')
  310. test_setup_py = os.path.join(test_pkg, 'setup.py')
  311. os.mkdir(test_pkg)
  312. with open(test_setup_py, 'w') as f:
  313. f.write(DALS("""
  314. import setuptools
  315. setuptools.setup(**%r)
  316. """ % test_setup_attrs))
  317. foobar_path = os.path.join(path, 'foobar-0.1.tar.gz')
  318. make_trivial_sdist(
  319. foobar_path,
  320. DALS("""
  321. import setuptools
  322. setuptools.setup(
  323. name='foobar',
  324. version='0.1'
  325. )
  326. """))
  327. return test_pkg
  328. def make_trivial_sdist(dist_path, setup_py):
  329. """Create a simple sdist tarball at dist_path, containing just a
  330. setup.py, the contents of which are provided by the setup_py string.
  331. """
  332. setup_py_file = tarfile.TarInfo(name='setup.py')
  333. try:
  334. # Python 3 (StringIO gets converted to io module)
  335. MemFile = BytesIO
  336. except AttributeError:
  337. MemFile = StringIO
  338. setup_py_bytes = MemFile(setup_py.encode('utf-8'))
  339. setup_py_file.size = len(setup_py_bytes.getvalue())
  340. with tarfile_open(dist_path, 'w:gz') as dist:
  341. dist.addfile(setup_py_file, fileobj=setup_py_bytes)
  342. class TestScriptHeader:
  343. non_ascii_exe = '/Users/José/bin/python'
  344. exe_with_spaces = r'C:\Program Files\Python33\python.exe'
  345. @pytest.mark.skipif(
  346. sys.platform.startswith('java') and ei.is_sh(sys.executable),
  347. reason="Test cannot run under java when executable is sh"
  348. )
  349. def test_get_script_header(self):
  350. expected = '#!%s\n' % ei.nt_quote_arg(os.path.normpath(sys.executable))
  351. actual = ei.ScriptWriter.get_script_header('#!/usr/local/bin/python')
  352. assert actual == expected
  353. expected = '#!%s -x\n' % ei.nt_quote_arg(os.path.normpath
  354. (sys.executable))
  355. actual = ei.ScriptWriter.get_script_header('#!/usr/bin/python -x')
  356. assert actual == expected
  357. actual = ei.ScriptWriter.get_script_header('#!/usr/bin/python',
  358. executable=self.non_ascii_exe)
  359. expected = '#!%s -x\n' % self.non_ascii_exe
  360. assert actual == expected
  361. actual = ei.ScriptWriter.get_script_header('#!/usr/bin/python',
  362. executable='"'+self.exe_with_spaces+'"')
  363. expected = '#!"%s"\n' % self.exe_with_spaces
  364. assert actual == expected
  365. @pytest.mark.xfail(
  366. compat.PY3 and os.environ.get("LC_CTYPE") in ("C", "POSIX"),
  367. reason="Test fails in this locale on Python 3"
  368. )
  369. @mock.patch.dict(sys.modules, java=mock.Mock(lang=mock.Mock(System=
  370. mock.Mock(getProperty=mock.Mock(return_value="")))))
  371. @mock.patch('sys.platform', 'java1.5.0_13')
  372. def test_get_script_header_jython_workaround(self, tmpdir):
  373. # Create a mock sys.executable that uses a shebang line
  374. header = DALS("""
  375. #!/usr/bin/python
  376. # -*- coding: utf-8 -*-
  377. """)
  378. exe = tmpdir / 'exe.py'
  379. with exe.open('w') as f:
  380. f.write(header)
  381. exe = str(exe)
  382. header = ei.ScriptWriter.get_script_header('#!/usr/local/bin/python',
  383. executable=exe)
  384. assert header == '#!/usr/bin/env %s\n' % exe
  385. expect_out = 'stdout' if sys.version_info < (2,7) else 'stderr'
  386. with contexts.quiet() as (stdout, stderr):
  387. # When options are included, generate a broken shebang line
  388. # with a warning emitted
  389. candidate = ei.ScriptWriter.get_script_header('#!/usr/bin/python -x',
  390. executable=exe)
  391. assert candidate == '#!%s -x\n' % exe
  392. output = locals()[expect_out]
  393. assert 'Unable to adapt shebang line' in output.getvalue()
  394. with contexts.quiet() as (stdout, stderr):
  395. candidate = ei.ScriptWriter.get_script_header('#!/usr/bin/python',
  396. executable=self.non_ascii_exe)
  397. assert candidate == '#!%s -x\n' % self.non_ascii_exe
  398. output = locals()[expect_out]
  399. assert 'Unable to adapt shebang line' in output.getvalue()
  400. class TestCommandSpec:
  401. def test_custom_launch_command(self):
  402. """
  403. Show how a custom CommandSpec could be used to specify a #! executable
  404. which takes parameters.
  405. """
  406. cmd = ei.CommandSpec(['/usr/bin/env', 'python3'])
  407. assert cmd.as_header() == '#!/usr/bin/env python3\n'
  408. def test_from_param_for_CommandSpec_is_passthrough(self):
  409. """
  410. from_param should return an instance of a CommandSpec
  411. """
  412. cmd = ei.CommandSpec(['python'])
  413. cmd_new = ei.CommandSpec.from_param(cmd)
  414. assert cmd is cmd_new
  415. def test_from_environment_with_spaces_in_executable(self):
  416. with mock.patch('sys.executable', TestScriptHeader.exe_with_spaces):
  417. cmd = ei.CommandSpec.from_environment()
  418. assert len(cmd) == 1
  419. assert cmd.as_header().startswith('#!"')
  420. def test_from_simple_string_uses_shlex(self):
  421. """
  422. In order to support `executable = /usr/bin/env my-python`, make sure
  423. from_param invokes shlex on that input.
  424. """
  425. cmd = ei.CommandSpec.from_param('/usr/bin/env my-python')
  426. assert len(cmd) == 2
  427. assert '"' not in cmd.as_header()
  428. def test_sys_executable(self):
  429. """
  430. CommandSpec.from_string(sys.executable) should contain just that param.
  431. """
  432. writer = ei.ScriptWriter.best()
  433. cmd = writer.command_spec_class.from_string(sys.executable)
  434. assert len(cmd) == 1
  435. assert cmd[0] == sys.executable
  436. class TestWindowsScriptWriter:
  437. def test_header(self):
  438. hdr = ei.WindowsScriptWriter.get_script_header('')
  439. assert hdr.startswith('#!')
  440. assert hdr.endswith('\n')
  441. hdr = hdr.lstrip('#!')
  442. hdr = hdr.rstrip('\n')
  443. # header should not start with an escaped quote
  444. assert not hdr.startswith('\\"')