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