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