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