1import configparser 2import contextlib 3import inspect 4from pathlib import Path 5from unittest.mock import Mock, patch 6 7import pytest 8 9from distutils.errors import DistutilsOptionError, DistutilsFileError 10from setuptools.dist import Distribution, _Distribution 11from setuptools.config.setupcfg import ConfigHandler, read_configuration 12from ..textwrap import DALS 13 14 15class ErrConfigHandler(ConfigHandler): 16 """Erroneous handler. Fails to implement required methods.""" 17 section_prefix = "**err**" 18 19 20def make_package_dir(name, base_dir, ns=False): 21 dir_package = base_dir 22 for dir_name in name.split('/'): 23 dir_package = dir_package.mkdir(dir_name) 24 init_file = None 25 if not ns: 26 init_file = dir_package.join('__init__.py') 27 init_file.write('') 28 return dir_package, init_file 29 30 31def fake_env( 32 tmpdir, setup_cfg, setup_py=None, encoding='ascii', package_path='fake_package' 33): 34 35 if setup_py is None: 36 setup_py = 'from setuptools import setup\n' 'setup()\n' 37 38 tmpdir.join('setup.py').write(setup_py) 39 config = tmpdir.join('setup.cfg') 40 config.write(setup_cfg.encode(encoding), mode='wb') 41 42 package_dir, init_file = make_package_dir(package_path, tmpdir) 43 44 init_file.write( 45 'VERSION = (1, 2, 3)\n' 46 '\n' 47 'VERSION_MAJOR = 1' 48 '\n' 49 'def get_version():\n' 50 ' return [3, 4, 5, "dev"]\n' 51 '\n' 52 ) 53 54 return package_dir, config 55 56 57@contextlib.contextmanager 58def get_dist(tmpdir, kwargs_initial=None, parse=True): 59 kwargs_initial = kwargs_initial or {} 60 61 with tmpdir.as_cwd(): 62 dist = Distribution(kwargs_initial) 63 dist.script_name = 'setup.py' 64 parse and dist.parse_config_files() 65 66 yield dist 67 68 69def test_parsers_implemented(): 70 71 with pytest.raises(NotImplementedError): 72 handler = ErrConfigHandler(None, {}, False, Mock()) 73 handler.parsers 74 75 76class TestConfigurationReader: 77 def test_basic(self, tmpdir): 78 _, config = fake_env( 79 tmpdir, 80 '[metadata]\n' 81 'version = 10.1.1\n' 82 'keywords = one, two\n' 83 '\n' 84 '[options]\n' 85 'scripts = bin/a.py, bin/b.py\n', 86 ) 87 config_dict = read_configuration('%s' % config) 88 assert config_dict['metadata']['version'] == '10.1.1' 89 assert config_dict['metadata']['keywords'] == ['one', 'two'] 90 assert config_dict['options']['scripts'] == ['bin/a.py', 'bin/b.py'] 91 92 def test_no_config(self, tmpdir): 93 with pytest.raises(DistutilsFileError): 94 read_configuration('%s' % tmpdir.join('setup.cfg')) 95 96 def test_ignore_errors(self, tmpdir): 97 _, config = fake_env( 98 tmpdir, 99 '[metadata]\n' 'version = attr: none.VERSION\n' 'keywords = one, two\n', 100 ) 101 with pytest.raises(ImportError): 102 read_configuration('%s' % config) 103 104 config_dict = read_configuration('%s' % config, ignore_option_errors=True) 105 106 assert config_dict['metadata']['keywords'] == ['one', 'two'] 107 assert 'version' not in config_dict['metadata'] 108 109 config.remove() 110 111 112class TestMetadata: 113 def test_basic(self, tmpdir): 114 115 fake_env( 116 tmpdir, 117 '[metadata]\n' 118 'version = 10.1.1\n' 119 'description = Some description\n' 120 'long_description_content_type = text/something\n' 121 'long_description = file: README\n' 122 'name = fake_name\n' 123 'keywords = one, two\n' 124 'provides = package, package.sub\n' 125 'license = otherlic\n' 126 'download_url = http://test.test.com/test/\n' 127 'maintainer_email = test@test.com\n', 128 ) 129 130 tmpdir.join('README').write('readme contents\nline2') 131 132 meta_initial = { 133 # This will be used so `otherlic` won't replace it. 134 'license': 'BSD 3-Clause License', 135 } 136 137 with get_dist(tmpdir, meta_initial) as dist: 138 metadata = dist.metadata 139 140 assert metadata.version == '10.1.1' 141 assert metadata.description == 'Some description' 142 assert metadata.long_description_content_type == 'text/something' 143 assert metadata.long_description == 'readme contents\nline2' 144 assert metadata.provides == ['package', 'package.sub'] 145 assert metadata.license == 'BSD 3-Clause License' 146 assert metadata.name == 'fake_name' 147 assert metadata.keywords == ['one', 'two'] 148 assert metadata.download_url == 'http://test.test.com/test/' 149 assert metadata.maintainer_email == 'test@test.com' 150 151 def test_license_cfg(self, tmpdir): 152 fake_env( 153 tmpdir, 154 DALS( 155 """ 156 [metadata] 157 name=foo 158 version=0.0.1 159 license=Apache 2.0 160 """ 161 ), 162 ) 163 164 with get_dist(tmpdir) as dist: 165 metadata = dist.metadata 166 167 assert metadata.name == "foo" 168 assert metadata.version == "0.0.1" 169 assert metadata.license == "Apache 2.0" 170 171 def test_file_mixed(self, tmpdir): 172 173 fake_env( 174 tmpdir, 175 '[metadata]\n' 'long_description = file: README.rst, CHANGES.rst\n' '\n', 176 ) 177 178 tmpdir.join('README.rst').write('readme contents\nline2') 179 tmpdir.join('CHANGES.rst').write('changelog contents\nand stuff') 180 181 with get_dist(tmpdir) as dist: 182 assert dist.metadata.long_description == ( 183 'readme contents\nline2\n' 'changelog contents\nand stuff' 184 ) 185 186 def test_file_sandboxed(self, tmpdir): 187 188 tmpdir.ensure("README") 189 project = tmpdir.join('depth1', 'depth2') 190 project.ensure(dir=True) 191 fake_env(project, '[metadata]\n' 'long_description = file: ../../README\n') 192 193 with get_dist(project, parse=False) as dist: 194 with pytest.raises(DistutilsOptionError): 195 dist.parse_config_files() # file: out of sandbox 196 197 def test_aliases(self, tmpdir): 198 199 fake_env( 200 tmpdir, 201 '[metadata]\n' 202 'author_email = test@test.com\n' 203 'home_page = http://test.test.com/test/\n' 204 'summary = Short summary\n' 205 'platform = a, b\n' 206 'classifier =\n' 207 ' Framework :: Django\n' 208 ' Programming Language :: Python :: 3.5\n', 209 ) 210 211 with get_dist(tmpdir) as dist: 212 metadata = dist.metadata 213 assert metadata.author_email == 'test@test.com' 214 assert metadata.url == 'http://test.test.com/test/' 215 assert metadata.description == 'Short summary' 216 assert metadata.platforms == ['a', 'b'] 217 assert metadata.classifiers == [ 218 'Framework :: Django', 219 'Programming Language :: Python :: 3.5', 220 ] 221 222 def test_multiline(self, tmpdir): 223 224 fake_env( 225 tmpdir, 226 '[metadata]\n' 227 'name = fake_name\n' 228 'keywords =\n' 229 ' one\n' 230 ' two\n' 231 'classifiers =\n' 232 ' Framework :: Django\n' 233 ' Programming Language :: Python :: 3.5\n', 234 ) 235 with get_dist(tmpdir) as dist: 236 metadata = dist.metadata 237 assert metadata.keywords == ['one', 'two'] 238 assert metadata.classifiers == [ 239 'Framework :: Django', 240 'Programming Language :: Python :: 3.5', 241 ] 242 243 def test_dict(self, tmpdir): 244 245 fake_env( 246 tmpdir, 247 '[metadata]\n' 248 'project_urls =\n' 249 ' Link One = https://example.com/one/\n' 250 ' Link Two = https://example.com/two/\n', 251 ) 252 with get_dist(tmpdir) as dist: 253 metadata = dist.metadata 254 assert metadata.project_urls == { 255 'Link One': 'https://example.com/one/', 256 'Link Two': 'https://example.com/two/', 257 } 258 259 def test_version(self, tmpdir): 260 261 package_dir, config = fake_env( 262 tmpdir, '[metadata]\n' 'version = attr: fake_package.VERSION\n' 263 ) 264 265 sub_a = package_dir.mkdir('subpkg_a') 266 sub_a.join('__init__.py').write('') 267 sub_a.join('mod.py').write('VERSION = (2016, 11, 26)') 268 269 sub_b = package_dir.mkdir('subpkg_b') 270 sub_b.join('__init__.py').write('') 271 sub_b.join('mod.py').write( 272 'import third_party_module\n' 'VERSION = (2016, 11, 26)' 273 ) 274 275 with get_dist(tmpdir) as dist: 276 assert dist.metadata.version == '1.2.3' 277 278 config.write('[metadata]\n' 'version = attr: fake_package.get_version\n') 279 with get_dist(tmpdir) as dist: 280 assert dist.metadata.version == '3.4.5.dev' 281 282 config.write('[metadata]\n' 'version = attr: fake_package.VERSION_MAJOR\n') 283 with get_dist(tmpdir) as dist: 284 assert dist.metadata.version == '1' 285 286 config.write( 287 '[metadata]\n' 'version = attr: fake_package.subpkg_a.mod.VERSION\n' 288 ) 289 with get_dist(tmpdir) as dist: 290 assert dist.metadata.version == '2016.11.26' 291 292 config.write( 293 '[metadata]\n' 'version = attr: fake_package.subpkg_b.mod.VERSION\n' 294 ) 295 with get_dist(tmpdir) as dist: 296 assert dist.metadata.version == '2016.11.26' 297 298 def test_version_file(self, tmpdir): 299 300 _, config = fake_env( 301 tmpdir, '[metadata]\n' 'version = file: fake_package/version.txt\n' 302 ) 303 tmpdir.join('fake_package', 'version.txt').write('1.2.3\n') 304 305 with get_dist(tmpdir) as dist: 306 assert dist.metadata.version == '1.2.3' 307 308 tmpdir.join('fake_package', 'version.txt').write('1.2.3\n4.5.6\n') 309 with pytest.raises(DistutilsOptionError): 310 with get_dist(tmpdir) as dist: 311 dist.metadata.version 312 313 def test_version_with_package_dir_simple(self, tmpdir): 314 315 _, config = fake_env( 316 tmpdir, 317 '[metadata]\n' 318 'version = attr: fake_package_simple.VERSION\n' 319 '[options]\n' 320 'package_dir =\n' 321 ' = src\n', 322 package_path='src/fake_package_simple', 323 ) 324 325 with get_dist(tmpdir) as dist: 326 assert dist.metadata.version == '1.2.3' 327 328 def test_version_with_package_dir_rename(self, tmpdir): 329 330 _, config = fake_env( 331 tmpdir, 332 '[metadata]\n' 333 'version = attr: fake_package_rename.VERSION\n' 334 '[options]\n' 335 'package_dir =\n' 336 ' fake_package_rename = fake_dir\n', 337 package_path='fake_dir', 338 ) 339 340 with get_dist(tmpdir) as dist: 341 assert dist.metadata.version == '1.2.3' 342 343 def test_version_with_package_dir_complex(self, tmpdir): 344 345 _, config = fake_env( 346 tmpdir, 347 '[metadata]\n' 348 'version = attr: fake_package_complex.VERSION\n' 349 '[options]\n' 350 'package_dir =\n' 351 ' fake_package_complex = src/fake_dir\n', 352 package_path='src/fake_dir', 353 ) 354 355 with get_dist(tmpdir) as dist: 356 assert dist.metadata.version == '1.2.3' 357 358 def test_unknown_meta_item(self, tmpdir): 359 360 fake_env(tmpdir, '[metadata]\n' 'name = fake_name\n' 'unknown = some\n') 361 with get_dist(tmpdir, parse=False) as dist: 362 dist.parse_config_files() # Skip unknown. 363 364 def test_usupported_section(self, tmpdir): 365 366 fake_env(tmpdir, '[metadata.some]\n' 'key = val\n') 367 with get_dist(tmpdir, parse=False) as dist: 368 with pytest.raises(DistutilsOptionError): 369 dist.parse_config_files() 370 371 def test_classifiers(self, tmpdir): 372 expected = set( 373 [ 374 'Framework :: Django', 375 'Programming Language :: Python :: 3', 376 'Programming Language :: Python :: 3.5', 377 ] 378 ) 379 380 # From file. 381 _, config = fake_env(tmpdir, '[metadata]\n' 'classifiers = file: classifiers\n') 382 383 tmpdir.join('classifiers').write( 384 'Framework :: Django\n' 385 'Programming Language :: Python :: 3\n' 386 'Programming Language :: Python :: 3.5\n' 387 ) 388 389 with get_dist(tmpdir) as dist: 390 assert set(dist.metadata.classifiers) == expected 391 392 # From list notation 393 config.write( 394 '[metadata]\n' 395 'classifiers =\n' 396 ' Framework :: Django\n' 397 ' Programming Language :: Python :: 3\n' 398 ' Programming Language :: Python :: 3.5\n' 399 ) 400 with get_dist(tmpdir) as dist: 401 assert set(dist.metadata.classifiers) == expected 402 403 def test_deprecated_config_handlers(self, tmpdir): 404 fake_env( 405 tmpdir, 406 '[metadata]\n' 407 'version = 10.1.1\n' 408 'description = Some description\n' 409 'requires = some, requirement\n', 410 ) 411 412 with pytest.deprecated_call(): 413 with get_dist(tmpdir) as dist: 414 metadata = dist.metadata 415 416 assert metadata.version == '10.1.1' 417 assert metadata.description == 'Some description' 418 assert metadata.requires == ['some', 'requirement'] 419 420 def test_interpolation(self, tmpdir): 421 fake_env(tmpdir, '[metadata]\n' 'description = %(message)s\n') 422 with pytest.raises(configparser.InterpolationMissingOptionError): 423 with get_dist(tmpdir): 424 pass 425 426 def test_non_ascii_1(self, tmpdir): 427 fake_env(tmpdir, '[metadata]\n' 'description = éàïôñ\n', encoding='utf-8') 428 with get_dist(tmpdir): 429 pass 430 431 def test_non_ascii_3(self, tmpdir): 432 fake_env(tmpdir, '\n' '# -*- coding: invalid\n') 433 with get_dist(tmpdir): 434 pass 435 436 def test_non_ascii_4(self, tmpdir): 437 fake_env( 438 tmpdir, 439 '# -*- coding: utf-8\n' '[metadata]\n' 'description = éàïôñ\n', 440 encoding='utf-8', 441 ) 442 with get_dist(tmpdir) as dist: 443 assert dist.metadata.description == 'éàïôñ' 444 445 def test_not_utf8(self, tmpdir): 446 """ 447 Config files encoded not in UTF-8 will fail 448 """ 449 fake_env( 450 tmpdir, 451 '# vim: set fileencoding=iso-8859-15 :\n' 452 '[metadata]\n' 453 'description = éàïôñ\n', 454 encoding='iso-8859-15', 455 ) 456 with pytest.raises(UnicodeDecodeError): 457 with get_dist(tmpdir): 458 pass 459 460 def test_warn_dash_deprecation(self, tmpdir): 461 # warn_dash_deprecation() is a method in setuptools.dist 462 # remove this test and the method when no longer needed 463 fake_env( 464 tmpdir, 465 '[metadata]\n' 466 'author-email = test@test.com\n' 467 'maintainer_email = foo@foo.com\n', 468 ) 469 msg = ( 470 "Usage of dash-separated 'author-email' will not be supported " 471 "in future versions. " 472 "Please use the underscore name 'author_email' instead" 473 ) 474 with pytest.warns(UserWarning, match=msg): 475 with get_dist(tmpdir) as dist: 476 metadata = dist.metadata 477 478 assert metadata.author_email == 'test@test.com' 479 assert metadata.maintainer_email == 'foo@foo.com' 480 481 def test_make_option_lowercase(self, tmpdir): 482 # remove this test and the method make_option_lowercase() in setuptools.dist 483 # when no longer needed 484 fake_env( 485 tmpdir, '[metadata]\n' 'Name = foo\n' 'description = Some description\n' 486 ) 487 msg = ( 488 "Usage of uppercase key 'Name' in 'metadata' will be deprecated in " 489 "future versions. " 490 "Please use lowercase 'name' instead" 491 ) 492 with pytest.warns(UserWarning, match=msg): 493 with get_dist(tmpdir) as dist: 494 metadata = dist.metadata 495 496 assert metadata.name == 'foo' 497 assert metadata.description == 'Some description' 498 499 500class TestOptions: 501 def test_basic(self, tmpdir): 502 503 fake_env( 504 tmpdir, 505 '[options]\n' 506 'zip_safe = True\n' 507 'include_package_data = yes\n' 508 'package_dir = b=c, =src\n' 509 'packages = pack_a, pack_b.subpack\n' 510 'namespace_packages = pack1, pack2\n' 511 'scripts = bin/one.py, bin/two.py\n' 512 'eager_resources = bin/one.py, bin/two.py\n' 513 'install_requires = docutils>=0.3; pack ==1.1, ==1.3; hey\n' 514 'tests_require = mock==0.7.2; pytest\n' 515 'setup_requires = docutils>=0.3; spack ==1.1, ==1.3; there\n' 516 'dependency_links = http://some.com/here/1, ' 517 'http://some.com/there/2\n' 518 'python_requires = >=1.0, !=2.8\n' 519 'py_modules = module1, module2\n', 520 ) 521 with get_dist(tmpdir) as dist: 522 assert dist.zip_safe 523 assert dist.include_package_data 524 assert dist.package_dir == {'': 'src', 'b': 'c'} 525 assert dist.packages == ['pack_a', 'pack_b.subpack'] 526 assert dist.namespace_packages == ['pack1', 'pack2'] 527 assert dist.scripts == ['bin/one.py', 'bin/two.py'] 528 assert dist.dependency_links == ( 529 ['http://some.com/here/1', 'http://some.com/there/2'] 530 ) 531 assert dist.install_requires == ( 532 ['docutils>=0.3', 'pack==1.1,==1.3', 'hey'] 533 ) 534 assert dist.setup_requires == ( 535 ['docutils>=0.3', 'spack ==1.1, ==1.3', 'there'] 536 ) 537 assert dist.tests_require == ['mock==0.7.2', 'pytest'] 538 assert dist.python_requires == '>=1.0, !=2.8' 539 assert dist.py_modules == ['module1', 'module2'] 540 541 def test_multiline(self, tmpdir): 542 fake_env( 543 tmpdir, 544 '[options]\n' 545 'package_dir = \n' 546 ' b=c\n' 547 ' =src\n' 548 'packages = \n' 549 ' pack_a\n' 550 ' pack_b.subpack\n' 551 'namespace_packages = \n' 552 ' pack1\n' 553 ' pack2\n' 554 'scripts = \n' 555 ' bin/one.py\n' 556 ' bin/two.py\n' 557 'eager_resources = \n' 558 ' bin/one.py\n' 559 ' bin/two.py\n' 560 'install_requires = \n' 561 ' docutils>=0.3\n' 562 ' pack ==1.1, ==1.3\n' 563 ' hey\n' 564 'tests_require = \n' 565 ' mock==0.7.2\n' 566 ' pytest\n' 567 'setup_requires = \n' 568 ' docutils>=0.3\n' 569 ' spack ==1.1, ==1.3\n' 570 ' there\n' 571 'dependency_links = \n' 572 ' http://some.com/here/1\n' 573 ' http://some.com/there/2\n', 574 ) 575 with get_dist(tmpdir) as dist: 576 assert dist.package_dir == {'': 'src', 'b': 'c'} 577 assert dist.packages == ['pack_a', 'pack_b.subpack'] 578 assert dist.namespace_packages == ['pack1', 'pack2'] 579 assert dist.scripts == ['bin/one.py', 'bin/two.py'] 580 assert dist.dependency_links == ( 581 ['http://some.com/here/1', 'http://some.com/there/2'] 582 ) 583 assert dist.install_requires == ( 584 ['docutils>=0.3', 'pack==1.1,==1.3', 'hey'] 585 ) 586 assert dist.setup_requires == ( 587 ['docutils>=0.3', 'spack ==1.1, ==1.3', 'there'] 588 ) 589 assert dist.tests_require == ['mock==0.7.2', 'pytest'] 590 591 def test_package_dir_fail(self, tmpdir): 592 fake_env(tmpdir, '[options]\n' 'package_dir = a b\n') 593 with get_dist(tmpdir, parse=False) as dist: 594 with pytest.raises(DistutilsOptionError): 595 dist.parse_config_files() 596 597 def test_package_data(self, tmpdir): 598 fake_env( 599 tmpdir, 600 '[options.package_data]\n' 601 '* = *.txt, *.rst\n' 602 'hello = *.msg\n' 603 '\n' 604 '[options.exclude_package_data]\n' 605 '* = fake1.txt, fake2.txt\n' 606 'hello = *.dat\n', 607 ) 608 609 with get_dist(tmpdir) as dist: 610 assert dist.package_data == { 611 '': ['*.txt', '*.rst'], 612 'hello': ['*.msg'], 613 } 614 assert dist.exclude_package_data == { 615 '': ['fake1.txt', 'fake2.txt'], 616 'hello': ['*.dat'], 617 } 618 619 def test_packages(self, tmpdir): 620 fake_env(tmpdir, '[options]\n' 'packages = find:\n') 621 622 with get_dist(tmpdir) as dist: 623 assert dist.packages == ['fake_package'] 624 625 def test_find_directive(self, tmpdir): 626 dir_package, config = fake_env(tmpdir, '[options]\n' 'packages = find:\n') 627 628 dir_sub_one, _ = make_package_dir('sub_one', dir_package) 629 dir_sub_two, _ = make_package_dir('sub_two', dir_package) 630 631 with get_dist(tmpdir) as dist: 632 assert set(dist.packages) == set( 633 ['fake_package', 'fake_package.sub_two', 'fake_package.sub_one'] 634 ) 635 636 config.write( 637 '[options]\n' 638 'packages = find:\n' 639 '\n' 640 '[options.packages.find]\n' 641 'where = .\n' 642 'include =\n' 643 ' fake_package.sub_one\n' 644 ' two\n' 645 ) 646 with get_dist(tmpdir) as dist: 647 assert dist.packages == ['fake_package.sub_one'] 648 649 config.write( 650 '[options]\n' 651 'packages = find:\n' 652 '\n' 653 '[options.packages.find]\n' 654 'exclude =\n' 655 ' fake_package.sub_one\n' 656 ) 657 with get_dist(tmpdir) as dist: 658 assert set(dist.packages) == set(['fake_package', 'fake_package.sub_two']) 659 660 def test_find_namespace_directive(self, tmpdir): 661 dir_package, config = fake_env( 662 tmpdir, '[options]\n' 'packages = find_namespace:\n' 663 ) 664 665 dir_sub_one, _ = make_package_dir('sub_one', dir_package) 666 dir_sub_two, _ = make_package_dir('sub_two', dir_package, ns=True) 667 668 with get_dist(tmpdir) as dist: 669 assert set(dist.packages) == { 670 'fake_package', 671 'fake_package.sub_two', 672 'fake_package.sub_one', 673 } 674 675 config.write( 676 '[options]\n' 677 'packages = find_namespace:\n' 678 '\n' 679 '[options.packages.find]\n' 680 'where = .\n' 681 'include =\n' 682 ' fake_package.sub_one\n' 683 ' two\n' 684 ) 685 with get_dist(tmpdir) as dist: 686 assert dist.packages == ['fake_package.sub_one'] 687 688 config.write( 689 '[options]\n' 690 'packages = find_namespace:\n' 691 '\n' 692 '[options.packages.find]\n' 693 'exclude =\n' 694 ' fake_package.sub_one\n' 695 ) 696 with get_dist(tmpdir) as dist: 697 assert set(dist.packages) == {'fake_package', 'fake_package.sub_two'} 698 699 def test_extras_require(self, tmpdir): 700 fake_env( 701 tmpdir, 702 '[options.extras_require]\n' 703 'pdf = ReportLab>=1.2; RXP\n' 704 'rest = \n' 705 ' docutils>=0.3\n' 706 ' pack ==1.1, ==1.3\n', 707 ) 708 709 with get_dist(tmpdir) as dist: 710 assert dist.extras_require == { 711 'pdf': ['ReportLab>=1.2', 'RXP'], 712 'rest': ['docutils>=0.3', 'pack==1.1,==1.3'], 713 } 714 assert dist.metadata.provides_extras == set(['pdf', 'rest']) 715 716 def test_dash_preserved_extras_require(self, tmpdir): 717 fake_env(tmpdir, '[options.extras_require]\n' 'foo-a = foo\n' 'foo_b = test\n') 718 719 with get_dist(tmpdir) as dist: 720 assert dist.extras_require == {'foo-a': ['foo'], 'foo_b': ['test']} 721 722 def test_entry_points(self, tmpdir): 723 _, config = fake_env( 724 tmpdir, 725 '[options.entry_points]\n' 726 'group1 = point1 = pack.module:func, ' 727 '.point2 = pack.module2:func_rest [rest]\n' 728 'group2 = point3 = pack.module:func2\n', 729 ) 730 731 with get_dist(tmpdir) as dist: 732 assert dist.entry_points == { 733 'group1': [ 734 'point1 = pack.module:func', 735 '.point2 = pack.module2:func_rest [rest]', 736 ], 737 'group2': ['point3 = pack.module:func2'], 738 } 739 740 expected = ( 741 '[blogtool.parsers]\n' 742 '.rst = some.nested.module:SomeClass.some_classmethod[reST]\n' 743 ) 744 745 tmpdir.join('entry_points').write(expected) 746 747 # From file. 748 config.write('[options]\n' 'entry_points = file: entry_points\n') 749 750 with get_dist(tmpdir) as dist: 751 assert dist.entry_points == expected 752 753 def test_case_sensitive_entry_points(self, tmpdir): 754 _, config = fake_env( 755 tmpdir, 756 '[options.entry_points]\n' 757 'GROUP1 = point1 = pack.module:func, ' 758 '.point2 = pack.module2:func_rest [rest]\n' 759 'group2 = point3 = pack.module:func2\n', 760 ) 761 762 with get_dist(tmpdir) as dist: 763 assert dist.entry_points == { 764 'GROUP1': [ 765 'point1 = pack.module:func', 766 '.point2 = pack.module2:func_rest [rest]', 767 ], 768 'group2': ['point3 = pack.module:func2'], 769 } 770 771 def test_data_files(self, tmpdir): 772 fake_env( 773 tmpdir, 774 '[options.data_files]\n' 775 'cfg =\n' 776 ' a/b.conf\n' 777 ' c/d.conf\n' 778 'data = e/f.dat, g/h.dat\n', 779 ) 780 781 with get_dist(tmpdir) as dist: 782 expected = [ 783 ('cfg', ['a/b.conf', 'c/d.conf']), 784 ('data', ['e/f.dat', 'g/h.dat']), 785 ] 786 assert sorted(dist.data_files) == sorted(expected) 787 788 def test_data_files_globby(self, tmpdir): 789 fake_env( 790 tmpdir, 791 '[options.data_files]\n' 792 'cfg =\n' 793 ' a/b.conf\n' 794 ' c/d.conf\n' 795 'data = *.dat\n' 796 'icons = \n' 797 ' *.ico\n' 798 'audio = \n' 799 ' *.wav\n' 800 ' sounds.db\n' 801 ) 802 803 # Create dummy files for glob()'s sake: 804 tmpdir.join('a.dat').write('') 805 tmpdir.join('b.dat').write('') 806 tmpdir.join('c.dat').write('') 807 tmpdir.join('a.ico').write('') 808 tmpdir.join('b.ico').write('') 809 tmpdir.join('c.ico').write('') 810 tmpdir.join('beep.wav').write('') 811 tmpdir.join('boop.wav').write('') 812 tmpdir.join('sounds.db').write('') 813 814 with get_dist(tmpdir) as dist: 815 expected = [ 816 ('cfg', ['a/b.conf', 'c/d.conf']), 817 ('data', ['a.dat', 'b.dat', 'c.dat']), 818 ('icons', ['a.ico', 'b.ico', 'c.ico']), 819 ('audio', ['beep.wav', 'boop.wav', 'sounds.db']), 820 ] 821 assert sorted(dist.data_files) == sorted(expected) 822 823 def test_python_requires_simple(self, tmpdir): 824 fake_env( 825 tmpdir, 826 DALS( 827 """ 828 [options] 829 python_requires=>=2.7 830 """ 831 ), 832 ) 833 with get_dist(tmpdir) as dist: 834 dist.parse_config_files() 835 836 def test_python_requires_compound(self, tmpdir): 837 fake_env( 838 tmpdir, 839 DALS( 840 """ 841 [options] 842 python_requires=>=2.7,!=3.0.* 843 """ 844 ), 845 ) 846 with get_dist(tmpdir) as dist: 847 dist.parse_config_files() 848 849 def test_python_requires_invalid(self, tmpdir): 850 fake_env( 851 tmpdir, 852 DALS( 853 """ 854 [options] 855 python_requires=invalid 856 """ 857 ), 858 ) 859 with pytest.raises(Exception): 860 with get_dist(tmpdir) as dist: 861 dist.parse_config_files() 862 863 def test_cmdclass(self, tmpdir): 864 module_path = Path(tmpdir, "src/custom_build.py") # auto discovery for src 865 module_path.parent.mkdir(parents=True, exist_ok=True) 866 module_path.write_text( 867 "from distutils.core import Command\n" 868 "class CustomCmd(Command): pass\n" 869 ) 870 871 setup_cfg = """ 872 [options] 873 cmdclass = 874 customcmd = custom_build.CustomCmd 875 """ 876 fake_env(tmpdir, inspect.cleandoc(setup_cfg)) 877 878 with get_dist(tmpdir) as dist: 879 cmdclass = dist.cmdclass['customcmd'] 880 assert cmdclass.__name__ == "CustomCmd" 881 assert cmdclass.__module__ == "custom_build" 882 assert module_path.samefile(inspect.getfile(cmdclass)) 883 884 885saved_dist_init = _Distribution.__init__ 886 887 888class TestExternalSetters: 889 # During creation of the setuptools Distribution() object, we call 890 # the init of the parent distutils Distribution object via 891 # _Distribution.__init__ (). 892 # 893 # It's possible distutils calls out to various keyword 894 # implementations (i.e. distutils.setup_keywords entry points) 895 # that may set a range of variables. 896 # 897 # This wraps distutil's Distribution.__init__ and simulates 898 # pbr or something else setting these values. 899 def _fake_distribution_init(self, dist, attrs): 900 saved_dist_init(dist, attrs) 901 # see self._DISTUTUILS_UNSUPPORTED_METADATA 902 setattr(dist.metadata, 'long_description_content_type', 'text/something') 903 # Test overwrite setup() args 904 setattr( 905 dist.metadata, 906 'project_urls', 907 { 908 'Link One': 'https://example.com/one/', 909 'Link Two': 'https://example.com/two/', 910 }, 911 ) 912 return None 913 914 @patch.object(_Distribution, '__init__', autospec=True) 915 def test_external_setters(self, mock_parent_init, tmpdir): 916 mock_parent_init.side_effect = self._fake_distribution_init 917 918 dist = Distribution(attrs={'project_urls': {'will_be': 'ignored'}}) 919 920 assert dist.metadata.long_description_content_type == 'text/something' 921 assert dist.metadata.project_urls == { 922 'Link One': 'https://example.com/one/', 923 'Link Two': 'https://example.com/two/', 924 } 925