test_dist.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. """Tests for distutils.dist."""
  2. import os
  3. import io
  4. import sys
  5. import unittest
  6. import warnings
  7. import textwrap
  8. from unittest import mock
  9. from distutils.dist import Distribution, fix_help_options
  10. from distutils.cmd import Command
  11. from test.support import (
  12. captured_stdout, captured_stderr, run_unittest
  13. )
  14. from test.support.os_helper import TESTFN
  15. from distutils.tests import support
  16. from distutils import log
  17. class test_dist(Command):
  18. """Sample distutils extension command."""
  19. user_options = [
  20. ("sample-option=", "S", "help text"),
  21. ]
  22. def initialize_options(self):
  23. self.sample_option = None
  24. class TestDistribution(Distribution):
  25. """Distribution subclasses that avoids the default search for
  26. configuration files.
  27. The ._config_files attribute must be set before
  28. .parse_config_files() is called.
  29. """
  30. def find_config_files(self):
  31. return self._config_files
  32. class DistributionTestCase(support.LoggingSilencer,
  33. support.TempdirManager,
  34. support.EnvironGuard,
  35. unittest.TestCase):
  36. def setUp(self):
  37. super(DistributionTestCase, self).setUp()
  38. self.argv = sys.argv, sys.argv[:]
  39. del sys.argv[1:]
  40. def tearDown(self):
  41. sys.argv = self.argv[0]
  42. sys.argv[:] = self.argv[1]
  43. super(DistributionTestCase, self).tearDown()
  44. def create_distribution(self, configfiles=()):
  45. d = TestDistribution()
  46. d._config_files = configfiles
  47. d.parse_config_files()
  48. d.parse_command_line()
  49. return d
  50. def test_command_packages_unspecified(self):
  51. sys.argv.append("build")
  52. d = self.create_distribution()
  53. self.assertEqual(d.get_command_packages(), ["distutils.command"])
  54. def test_command_packages_cmdline(self):
  55. from distutils.tests.test_dist import test_dist
  56. sys.argv.extend(["--command-packages",
  57. "foo.bar,distutils.tests",
  58. "test_dist",
  59. "-Ssometext",
  60. ])
  61. d = self.create_distribution()
  62. # let's actually try to load our test command:
  63. self.assertEqual(d.get_command_packages(),
  64. ["distutils.command", "foo.bar", "distutils.tests"])
  65. cmd = d.get_command_obj("test_dist")
  66. self.assertIsInstance(cmd, test_dist)
  67. self.assertEqual(cmd.sample_option, "sometext")
  68. def test_venv_install_options(self):
  69. sys.argv.append("install")
  70. self.addCleanup(os.unlink, TESTFN)
  71. fakepath = '/somedir'
  72. with open(TESTFN, "w") as f:
  73. print(("[install]\n"
  74. "install-base = {0}\n"
  75. "install-platbase = {0}\n"
  76. "install-lib = {0}\n"
  77. "install-platlib = {0}\n"
  78. "install-purelib = {0}\n"
  79. "install-headers = {0}\n"
  80. "install-scripts = {0}\n"
  81. "install-data = {0}\n"
  82. "prefix = {0}\n"
  83. "exec-prefix = {0}\n"
  84. "home = {0}\n"
  85. "user = {0}\n"
  86. "root = {0}").format(fakepath), file=f)
  87. # Base case: Not in a Virtual Environment
  88. with mock.patch.multiple(sys, prefix='/a', base_prefix='/a') as values:
  89. d = self.create_distribution([TESTFN])
  90. option_tuple = (TESTFN, fakepath)
  91. result_dict = {
  92. 'install_base': option_tuple,
  93. 'install_platbase': option_tuple,
  94. 'install_lib': option_tuple,
  95. 'install_platlib': option_tuple,
  96. 'install_purelib': option_tuple,
  97. 'install_headers': option_tuple,
  98. 'install_scripts': option_tuple,
  99. 'install_data': option_tuple,
  100. 'prefix': option_tuple,
  101. 'exec_prefix': option_tuple,
  102. 'home': option_tuple,
  103. 'user': option_tuple,
  104. 'root': option_tuple,
  105. }
  106. self.assertEqual(
  107. sorted(d.command_options.get('install').keys()),
  108. sorted(result_dict.keys()))
  109. for (key, value) in d.command_options.get('install').items():
  110. self.assertEqual(value, result_dict[key])
  111. # Test case: In a Virtual Environment
  112. with mock.patch.multiple(sys, prefix='/a', base_prefix='/b') as values:
  113. d = self.create_distribution([TESTFN])
  114. for key in result_dict.keys():
  115. self.assertNotIn(key, d.command_options.get('install', {}))
  116. def test_command_packages_configfile(self):
  117. sys.argv.append("build")
  118. self.addCleanup(os.unlink, TESTFN)
  119. f = open(TESTFN, "w")
  120. try:
  121. print("[global]", file=f)
  122. print("command_packages = foo.bar, splat", file=f)
  123. finally:
  124. f.close()
  125. d = self.create_distribution([TESTFN])
  126. self.assertEqual(d.get_command_packages(),
  127. ["distutils.command", "foo.bar", "splat"])
  128. # ensure command line overrides config:
  129. sys.argv[1:] = ["--command-packages", "spork", "build"]
  130. d = self.create_distribution([TESTFN])
  131. self.assertEqual(d.get_command_packages(),
  132. ["distutils.command", "spork"])
  133. # Setting --command-packages to '' should cause the default to
  134. # be used even if a config file specified something else:
  135. sys.argv[1:] = ["--command-packages", "", "build"]
  136. d = self.create_distribution([TESTFN])
  137. self.assertEqual(d.get_command_packages(), ["distutils.command"])
  138. def test_empty_options(self):
  139. # an empty options dictionary should not stay in the
  140. # list of attributes
  141. # catching warnings
  142. warns = []
  143. def _warn(msg):
  144. warns.append(msg)
  145. self.addCleanup(setattr, warnings, 'warn', warnings.warn)
  146. warnings.warn = _warn
  147. dist = Distribution(attrs={'author': 'xxx', 'name': 'xxx',
  148. 'version': 'xxx', 'url': 'xxxx',
  149. 'options': {}})
  150. self.assertEqual(len(warns), 0)
  151. self.assertNotIn('options', dir(dist))
  152. def test_finalize_options(self):
  153. attrs = {'keywords': 'one,two',
  154. 'platforms': 'one,two'}
  155. dist = Distribution(attrs=attrs)
  156. dist.finalize_options()
  157. # finalize_option splits platforms and keywords
  158. self.assertEqual(dist.metadata.platforms, ['one', 'two'])
  159. self.assertEqual(dist.metadata.keywords, ['one', 'two'])
  160. attrs = {'keywords': 'foo bar',
  161. 'platforms': 'foo bar'}
  162. dist = Distribution(attrs=attrs)
  163. dist.finalize_options()
  164. self.assertEqual(dist.metadata.platforms, ['foo bar'])
  165. self.assertEqual(dist.metadata.keywords, ['foo bar'])
  166. def test_get_command_packages(self):
  167. dist = Distribution()
  168. self.assertEqual(dist.command_packages, None)
  169. cmds = dist.get_command_packages()
  170. self.assertEqual(cmds, ['distutils.command'])
  171. self.assertEqual(dist.command_packages,
  172. ['distutils.command'])
  173. dist.command_packages = 'one,two'
  174. cmds = dist.get_command_packages()
  175. self.assertEqual(cmds, ['distutils.command', 'one', 'two'])
  176. def test_announce(self):
  177. # make sure the level is known
  178. dist = Distribution()
  179. args = ('ok',)
  180. kwargs = {'level': 'ok2'}
  181. self.assertRaises(ValueError, dist.announce, args, kwargs)
  182. def test_find_config_files_disable(self):
  183. # Ticket #1180: Allow user to disable their home config file.
  184. temp_home = self.mkdtemp()
  185. if os.name == 'posix':
  186. user_filename = os.path.join(temp_home, ".pydistutils.cfg")
  187. else:
  188. user_filename = os.path.join(temp_home, "pydistutils.cfg")
  189. with open(user_filename, 'w') as f:
  190. f.write('[distutils]\n')
  191. def _expander(path):
  192. return temp_home
  193. old_expander = os.path.expanduser
  194. os.path.expanduser = _expander
  195. try:
  196. d = Distribution()
  197. all_files = d.find_config_files()
  198. d = Distribution(attrs={'script_args': ['--no-user-cfg']})
  199. files = d.find_config_files()
  200. finally:
  201. os.path.expanduser = old_expander
  202. # make sure --no-user-cfg disables the user cfg file
  203. self.assertEqual(len(all_files)-1, len(files))
  204. class MetadataTestCase(support.TempdirManager, support.EnvironGuard,
  205. unittest.TestCase):
  206. def setUp(self):
  207. super(MetadataTestCase, self).setUp()
  208. self.argv = sys.argv, sys.argv[:]
  209. def tearDown(self):
  210. sys.argv = self.argv[0]
  211. sys.argv[:] = self.argv[1]
  212. super(MetadataTestCase, self).tearDown()
  213. def format_metadata(self, dist):
  214. sio = io.StringIO()
  215. dist.metadata.write_pkg_file(sio)
  216. return sio.getvalue()
  217. def test_simple_metadata(self):
  218. attrs = {"name": "package",
  219. "version": "1.0"}
  220. dist = Distribution(attrs)
  221. meta = self.format_metadata(dist)
  222. self.assertIn("Metadata-Version: 1.0", meta)
  223. self.assertNotIn("provides:", meta.lower())
  224. self.assertNotIn("requires:", meta.lower())
  225. self.assertNotIn("obsoletes:", meta.lower())
  226. def test_provides(self):
  227. attrs = {"name": "package",
  228. "version": "1.0",
  229. "provides": ["package", "package.sub"]}
  230. dist = Distribution(attrs)
  231. self.assertEqual(dist.metadata.get_provides(),
  232. ["package", "package.sub"])
  233. self.assertEqual(dist.get_provides(),
  234. ["package", "package.sub"])
  235. meta = self.format_metadata(dist)
  236. self.assertIn("Metadata-Version: 1.1", meta)
  237. self.assertNotIn("requires:", meta.lower())
  238. self.assertNotIn("obsoletes:", meta.lower())
  239. def test_provides_illegal(self):
  240. self.assertRaises(ValueError, Distribution,
  241. {"name": "package",
  242. "version": "1.0",
  243. "provides": ["my.pkg (splat)"]})
  244. def test_requires(self):
  245. attrs = {"name": "package",
  246. "version": "1.0",
  247. "requires": ["other", "another (==1.0)"]}
  248. dist = Distribution(attrs)
  249. self.assertEqual(dist.metadata.get_requires(),
  250. ["other", "another (==1.0)"])
  251. self.assertEqual(dist.get_requires(),
  252. ["other", "another (==1.0)"])
  253. meta = self.format_metadata(dist)
  254. self.assertIn("Metadata-Version: 1.1", meta)
  255. self.assertNotIn("provides:", meta.lower())
  256. self.assertIn("Requires: other", meta)
  257. self.assertIn("Requires: another (==1.0)", meta)
  258. self.assertNotIn("obsoletes:", meta.lower())
  259. def test_requires_illegal(self):
  260. self.assertRaises(ValueError, Distribution,
  261. {"name": "package",
  262. "version": "1.0",
  263. "requires": ["my.pkg (splat)"]})
  264. def test_requires_to_list(self):
  265. attrs = {"name": "package",
  266. "requires": iter(["other"])}
  267. dist = Distribution(attrs)
  268. self.assertIsInstance(dist.metadata.requires, list)
  269. def test_obsoletes(self):
  270. attrs = {"name": "package",
  271. "version": "1.0",
  272. "obsoletes": ["other", "another (<1.0)"]}
  273. dist = Distribution(attrs)
  274. self.assertEqual(dist.metadata.get_obsoletes(),
  275. ["other", "another (<1.0)"])
  276. self.assertEqual(dist.get_obsoletes(),
  277. ["other", "another (<1.0)"])
  278. meta = self.format_metadata(dist)
  279. self.assertIn("Metadata-Version: 1.1", meta)
  280. self.assertNotIn("provides:", meta.lower())
  281. self.assertNotIn("requires:", meta.lower())
  282. self.assertIn("Obsoletes: other", meta)
  283. self.assertIn("Obsoletes: another (<1.0)", meta)
  284. def test_obsoletes_illegal(self):
  285. self.assertRaises(ValueError, Distribution,
  286. {"name": "package",
  287. "version": "1.0",
  288. "obsoletes": ["my.pkg (splat)"]})
  289. def test_obsoletes_to_list(self):
  290. attrs = {"name": "package",
  291. "obsoletes": iter(["other"])}
  292. dist = Distribution(attrs)
  293. self.assertIsInstance(dist.metadata.obsoletes, list)
  294. def test_classifier(self):
  295. attrs = {'name': 'Boa', 'version': '3.0',
  296. 'classifiers': ['Programming Language :: Python :: 3']}
  297. dist = Distribution(attrs)
  298. self.assertEqual(dist.get_classifiers(),
  299. ['Programming Language :: Python :: 3'])
  300. meta = self.format_metadata(dist)
  301. self.assertIn('Metadata-Version: 1.1', meta)
  302. def test_classifier_invalid_type(self):
  303. attrs = {'name': 'Boa', 'version': '3.0',
  304. 'classifiers': ('Programming Language :: Python :: 3',)}
  305. with captured_stderr() as error:
  306. d = Distribution(attrs)
  307. # should have warning about passing a non-list
  308. self.assertIn('should be a list', error.getvalue())
  309. # should be converted to a list
  310. self.assertIsInstance(d.metadata.classifiers, list)
  311. self.assertEqual(d.metadata.classifiers,
  312. list(attrs['classifiers']))
  313. def test_keywords(self):
  314. attrs = {'name': 'Monty', 'version': '1.0',
  315. 'keywords': ['spam', 'eggs', 'life of brian']}
  316. dist = Distribution(attrs)
  317. self.assertEqual(dist.get_keywords(),
  318. ['spam', 'eggs', 'life of brian'])
  319. def test_keywords_invalid_type(self):
  320. attrs = {'name': 'Monty', 'version': '1.0',
  321. 'keywords': ('spam', 'eggs', 'life of brian')}
  322. with captured_stderr() as error:
  323. d = Distribution(attrs)
  324. # should have warning about passing a non-list
  325. self.assertIn('should be a list', error.getvalue())
  326. # should be converted to a list
  327. self.assertIsInstance(d.metadata.keywords, list)
  328. self.assertEqual(d.metadata.keywords, list(attrs['keywords']))
  329. def test_platforms(self):
  330. attrs = {'name': 'Monty', 'version': '1.0',
  331. 'platforms': ['GNU/Linux', 'Some Evil Platform']}
  332. dist = Distribution(attrs)
  333. self.assertEqual(dist.get_platforms(),
  334. ['GNU/Linux', 'Some Evil Platform'])
  335. def test_platforms_invalid_types(self):
  336. attrs = {'name': 'Monty', 'version': '1.0',
  337. 'platforms': ('GNU/Linux', 'Some Evil Platform')}
  338. with captured_stderr() as error:
  339. d = Distribution(attrs)
  340. # should have warning about passing a non-list
  341. self.assertIn('should be a list', error.getvalue())
  342. # should be converted to a list
  343. self.assertIsInstance(d.metadata.platforms, list)
  344. self.assertEqual(d.metadata.platforms, list(attrs['platforms']))
  345. def test_download_url(self):
  346. attrs = {'name': 'Boa', 'version': '3.0',
  347. 'download_url': 'http://example.org/boa'}
  348. dist = Distribution(attrs)
  349. meta = self.format_metadata(dist)
  350. self.assertIn('Metadata-Version: 1.1', meta)
  351. def test_long_description(self):
  352. long_desc = textwrap.dedent("""\
  353. example::
  354. We start here
  355. and continue here
  356. and end here.""")
  357. attrs = {"name": "package",
  358. "version": "1.0",
  359. "long_description": long_desc}
  360. dist = Distribution(attrs)
  361. meta = self.format_metadata(dist)
  362. meta = meta.replace('\n' + 8 * ' ', '\n')
  363. self.assertIn(long_desc, meta)
  364. def test_custom_pydistutils(self):
  365. # fixes #2166
  366. # make sure pydistutils.cfg is found
  367. if os.name == 'posix':
  368. user_filename = ".pydistutils.cfg"
  369. else:
  370. user_filename = "pydistutils.cfg"
  371. temp_dir = self.mkdtemp()
  372. user_filename = os.path.join(temp_dir, user_filename)
  373. f = open(user_filename, 'w')
  374. try:
  375. f.write('.')
  376. finally:
  377. f.close()
  378. try:
  379. dist = Distribution()
  380. # linux-style
  381. if sys.platform in ('linux', 'darwin'):
  382. os.environ['HOME'] = temp_dir
  383. files = dist.find_config_files()
  384. self.assertIn(user_filename, files)
  385. # win32-style
  386. if sys.platform == 'win32':
  387. # home drive should be found
  388. os.environ['USERPROFILE'] = temp_dir
  389. files = dist.find_config_files()
  390. self.assertIn(user_filename, files,
  391. '%r not found in %r' % (user_filename, files))
  392. finally:
  393. os.remove(user_filename)
  394. def test_fix_help_options(self):
  395. help_tuples = [('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
  396. fancy_options = fix_help_options(help_tuples)
  397. self.assertEqual(fancy_options[0], ('a', 'b', 'c'))
  398. self.assertEqual(fancy_options[1], (1, 2, 3))
  399. def test_show_help(self):
  400. # smoke test, just makes sure some help is displayed
  401. self.addCleanup(log.set_threshold, log._global_log.threshold)
  402. dist = Distribution()
  403. sys.argv = []
  404. dist.help = 1
  405. dist.script_name = 'setup.py'
  406. with captured_stdout() as s:
  407. dist.parse_command_line()
  408. output = [line for line in s.getvalue().split('\n')
  409. if line.strip() != '']
  410. self.assertTrue(output)
  411. def test_read_metadata(self):
  412. attrs = {"name": "package",
  413. "version": "1.0",
  414. "long_description": "desc",
  415. "description": "xxx",
  416. "download_url": "http://example.com",
  417. "keywords": ['one', 'two'],
  418. "requires": ['foo']}
  419. dist = Distribution(attrs)
  420. metadata = dist.metadata
  421. # write it then reloads it
  422. PKG_INFO = io.StringIO()
  423. metadata.write_pkg_file(PKG_INFO)
  424. PKG_INFO.seek(0)
  425. metadata.read_pkg_file(PKG_INFO)
  426. self.assertEqual(metadata.name, "package")
  427. self.assertEqual(metadata.version, "1.0")
  428. self.assertEqual(metadata.description, "xxx")
  429. self.assertEqual(metadata.download_url, 'http://example.com')
  430. self.assertEqual(metadata.keywords, ['one', 'two'])
  431. self.assertEqual(metadata.platforms, ['UNKNOWN'])
  432. self.assertEqual(metadata.obsoletes, None)
  433. self.assertEqual(metadata.requires, ['foo'])
  434. def test_suite():
  435. suite = unittest.TestSuite()
  436. suite.addTest(unittest.TestLoader().loadTestsFromTestCase(DistributionTestCase))
  437. suite.addTest(unittest.TestLoader().loadTestsFromTestCase(MetadataTestCase))
  438. return suite
  439. if __name__ == "__main__":
  440. run_unittest(test_suite())