test_dist.py 16 KB

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