test_dist.py 19 KB

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