codekingpro/portable-devtools
114k
1import configparser2import contextlib3import inspect4import re5import sys6from pathlib import Path7from unittest.mock import Mock, patch8 9import pytest10from packaging.requirements import InvalidRequirement11 12from setuptools.config.setupcfg import ConfigHandler, Target, read_configuration13from setuptools.dist import Distribution, _Distribution14from setuptools.warnings import SetuptoolsDeprecationWarning15 16from ..textwrap import DALS17 18from distutils.errors import DistutilsFileError, DistutilsOptionError19 20IS_PYPY = '__pypy__' in sys.builtin_module_names21 22 23class ErrConfigHandler(ConfigHandler[Target]):24 """Erroneous handler. Fails to implement required methods."""25 26 section_prefix = "**err**"27 28 29def make_package_dir(name, base_dir, ns=False):30 dir_package = base_dir31 for dir_name in name.split('/'):32 dir_package = dir_package.mkdir(dir_name)33 init_file = None34 if not ns:35 init_file = dir_package.join('__init__.py')36 init_file.write('')37 return dir_package, init_file38 39 40def fake_env(41 tmpdir, setup_cfg, setup_py=None, encoding='ascii', package_path='fake_package'42):43 if setup_py is None:44 setup_py = 'from setuptools import setup\nsetup()\n'45 46 tmpdir.join('setup.py').write(setup_py)47 config = tmpdir.join('setup.cfg')48 config.write(setup_cfg.encode(encoding), mode='wb')49 50 package_dir, init_file = make_package_dir(package_path, tmpdir)51 52 init_file.write(53 'VERSION = (1, 2, 3)\n'54 '\n'55 'VERSION_MAJOR = 1'56 '\n'57 'def get_version():\n'58 ' return [3, 4, 5, "dev"]\n'59 '\n'60 )61 62 return package_dir, config63 64 65@contextlib.contextmanager66def get_dist(tmpdir, kwargs_initial=None, parse=True):67 kwargs_initial = kwargs_initial or {}68 69 with tmpdir.as_cwd():70 dist = Distribution(kwargs_initial)71 dist.script_name = 'setup.py'72 parse and dist.parse_config_files()73 74 yield dist75 76 77def test_parsers_implemented():78 with pytest.raises(NotImplementedError):79 handler = ErrConfigHandler(None, {}, False, Mock())80 handler.parsers81 82 83class TestConfigurationReader:84 def test_basic(self, tmpdir):85 _, config = fake_env(86 tmpdir,87 '[metadata]\n'88 'version = 10.1.1\n'89 'keywords = one, two\n'90 '\n'91 '[options]\n'92 'scripts = bin/a.py, bin/b.py\n',93 )94 config_dict = read_configuration(str(config))95 assert config_dict['metadata']['version'] == '10.1.1'96 assert config_dict['metadata']['keywords'] == ['one', 'two']97 assert config_dict['options']['scripts'] == ['bin/a.py', 'bin/b.py']98 99 def test_no_config(self, tmpdir):100 with pytest.raises(DistutilsFileError):101 read_configuration(str(tmpdir.join('setup.cfg')))102 103 def test_ignore_errors(self, tmpdir):104 _, config = fake_env(105 tmpdir,106 '[metadata]\nversion = attr: none.VERSION\nkeywords = one, two\n',107 )108 with pytest.raises(ImportError):109 read_configuration(str(config))110 111 config_dict = read_configuration(str(config), ignore_option_errors=True)112 113 assert config_dict['metadata']['keywords'] == ['one', 'two']114 assert 'version' not in config_dict['metadata']115 116 config.remove()117 118 119class TestMetadata:120 def test_basic(self, tmpdir):121 fake_env(122 tmpdir,123 '[metadata]\n'124 'version = 10.1.1\n'125 'description = Some description\n'126 'long_description_content_type = text/something\n'127 'long_description = file: README\n'128 'name = fake_name\n'129 'keywords = one, two\n'130 'provides = package, package.sub\n'131 'license = otherlic\n'132 'download_url = http://test.test.com/test/\n'133 'maintainer_email = test@test.com\n',134 )135 136 tmpdir.join('README').write('readme contents\nline2')137 138 meta_initial = {139 # This will be used so `otherlic` won't replace it.140 'license': 'BSD 3-Clause License',141 }142 143 with get_dist(tmpdir, meta_initial) as dist:144 metadata = dist.metadata145 146 assert metadata.version == '10.1.1'147 assert metadata.description == 'Some description'148 assert metadata.long_description_content_type == 'text/something'149 assert metadata.long_description == 'readme contents\nline2'150 assert metadata.provides == ['package', 'package.sub']151 assert metadata.license == 'BSD 3-Clause License'152 assert metadata.name == 'fake_name'153 assert metadata.keywords == ['one', 'two']154 assert metadata.download_url == 'http://test.test.com/test/'155 assert metadata.maintainer_email == 'test@test.com'156 157 def test_license_cfg(self, tmpdir):158 fake_env(159 tmpdir,160 DALS(161 """162 [metadata]163 name=foo164 version=0.0.1165 license=Apache 2.0166 """167 ),168 )169 170 with get_dist(tmpdir) as dist:171 metadata = dist.metadata172 173 assert metadata.name == "foo"174 assert metadata.version == "0.0.1"175 assert metadata.license == "Apache 2.0"176 177 def test_file_mixed(self, tmpdir):178 fake_env(179 tmpdir,180 '[metadata]\nlong_description = file: README.rst, CHANGES.rst\n\n',181 )182 183 tmpdir.join('README.rst').write('readme contents\nline2')184 tmpdir.join('CHANGES.rst').write('changelog contents\nand stuff')185 186 with get_dist(tmpdir) as dist:187 assert dist.metadata.long_description == (188 'readme contents\nline2\nchangelog contents\nand stuff'189 )190 191 def test_file_sandboxed(self, tmpdir):192 tmpdir.ensure("README")193 project = tmpdir.join('depth1', 'depth2')194 project.ensure(dir=True)195 fake_env(project, '[metadata]\nlong_description = file: ../../README\n')196 197 with get_dist(project, parse=False) as dist:198 with pytest.raises(DistutilsOptionError):199 dist.parse_config_files() # file: out of sandbox200 201 def test_aliases(self, tmpdir):202 fake_env(203 tmpdir,204 '[metadata]\n'205 'author_email = test@test.com\n'206 'home_page = http://test.test.com/test/\n'207 'summary = Short summary\n'208 'platform = a, b\n'209 'classifier =\n'210 ' Framework :: Django\n'211 ' Programming Language :: Python :: 3.5\n',212 )213 214 with get_dist(tmpdir) as dist:215 metadata = dist.metadata216 assert metadata.author_email == 'test@test.com'217 assert metadata.url == 'http://test.test.com/test/'218 assert metadata.description == 'Short summary'219 assert metadata.platforms == ['a', 'b']220 assert metadata.classifiers == [221 'Framework :: Django',222 'Programming Language :: Python :: 3.5',223 ]224 225 def test_multiline(self, tmpdir):226 fake_env(227 tmpdir,228 '[metadata]\n'229 'name = fake_name\n'230 'keywords =\n'231 ' one\n'232 ' two\n'233 'classifiers =\n'234 ' Framework :: Django\n'235 ' Programming Language :: Python :: 3.5\n',236 )237 with get_dist(tmpdir) as dist:238 metadata = dist.metadata239 assert metadata.keywords == ['one', 'two']240 assert metadata.classifiers == [241 'Framework :: Django',242 'Programming Language :: Python :: 3.5',243 ]244 245 def test_dict(self, tmpdir):246 fake_env(247 tmpdir,248 '[metadata]\n'249 'project_urls =\n'250 ' Link One = https://example.com/one/\n'251 ' Link Two = https://example.com/two/\n',252 )253 with get_dist(tmpdir) as dist:254 metadata = dist.metadata255 assert metadata.project_urls == {256 'Link One': 'https://example.com/one/',257 'Link Two': 'https://example.com/two/',258 }259 260 def test_version(self, tmpdir):261 package_dir, config = fake_env(262 tmpdir, '[metadata]\nversion = 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\nVERSION = (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]\nversion = 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]\nversion = attr: fake_package.VERSION_MAJOR\n')283 with get_dist(tmpdir) as dist:284 assert dist.metadata.version == '1'285 286 config.write('[metadata]\nversion = attr: fake_package.subpkg_a.mod.VERSION\n')287 with get_dist(tmpdir) as dist:288 assert dist.metadata.version == '2016.11.26'289 290 config.write('[metadata]\nversion = attr: fake_package.subpkg_b.mod.VERSION\n')291 with get_dist(tmpdir) as dist:292 assert dist.metadata.version == '2016.11.26'293 294 def test_version_file(self, tmpdir):295 fake_env(tmpdir, '[metadata]\nversion = file: fake_package/version.txt\n')296 tmpdir.join('fake_package', 'version.txt').write('1.2.3\n')297 298 with get_dist(tmpdir) as dist:299 assert dist.metadata.version == '1.2.3'300 301 tmpdir.join('fake_package', 'version.txt').write('1.2.3\n4.5.6\n')302 with pytest.raises(DistutilsOptionError):303 with get_dist(tmpdir) as dist:304 dist.metadata.version305 306 def test_version_with_package_dir_simple(self, tmpdir):307 fake_env(308 tmpdir,309 '[metadata]\n'310 'version = attr: fake_package_simple.VERSION\n'311 '[options]\n'312 'package_dir =\n'313 ' = src\n',314 package_path='src/fake_package_simple',315 )316 317 with get_dist(tmpdir) as dist:318 assert dist.metadata.version == '1.2.3'319 320 def test_version_with_package_dir_rename(self, tmpdir):321 fake_env(322 tmpdir,323 '[metadata]\n'324 'version = attr: fake_package_rename.VERSION\n'325 '[options]\n'326 'package_dir =\n'327 ' fake_package_rename = fake_dir\n',328 package_path='fake_dir',329 )330 331 with get_dist(tmpdir) as dist:332 assert dist.metadata.version == '1.2.3'333 334 def test_version_with_package_dir_complex(self, tmpdir):335 fake_env(336 tmpdir,337 '[metadata]\n'338 'version = attr: fake_package_complex.VERSION\n'339 '[options]\n'340 'package_dir =\n'341 ' fake_package_complex = src/fake_dir\n',342 package_path='src/fake_dir',343 )344 345 with get_dist(tmpdir) as dist:346 assert dist.metadata.version == '1.2.3'347 348 def test_unknown_meta_item(self, tmpdir):349 fake_env(tmpdir, '[metadata]\nname = fake_name\nunknown = some\n')350 with get_dist(tmpdir, parse=False) as dist:351 dist.parse_config_files() # Skip unknown.352 353 def test_usupported_section(self, tmpdir):354 fake_env(tmpdir, '[metadata.some]\nkey = val\n')355 with get_dist(tmpdir, parse=False) as dist:356 with pytest.raises(DistutilsOptionError):357 dist.parse_config_files()358 359 def test_classifiers(self, tmpdir):360 expected = set([361 'Framework :: Django',362 'Programming Language :: Python :: 3',363 'Programming Language :: Python :: 3.5',364 ])365 366 # From file.367 _, config = fake_env(tmpdir, '[metadata]\nclassifiers = file: classifiers\n')368 369 tmpdir.join('classifiers').write(370 'Framework :: Django\n'371 'Programming Language :: Python :: 3\n'372 'Programming Language :: Python :: 3.5\n'373 )374 375 with get_dist(tmpdir) as dist:376 assert set(dist.metadata.classifiers) == expected377 378 # From list notation379 config.write(380 '[metadata]\n'381 'classifiers =\n'382 ' Framework :: Django\n'383 ' Programming Language :: Python :: 3\n'384 ' Programming Language :: Python :: 3.5\n'385 )386 with get_dist(tmpdir) as dist:387 assert set(dist.metadata.classifiers) == expected388 389 def test_interpolation(self, tmpdir):390 fake_env(tmpdir, '[metadata]\ndescription = %(message)s\n')391 with pytest.raises(configparser.InterpolationMissingOptionError):392 with get_dist(tmpdir):393 pass394 395 def test_non_ascii_1(self, tmpdir):396 fake_env(tmpdir, '[metadata]\ndescription = éàïôñ\n', encoding='utf-8')397 with get_dist(tmpdir):398 pass399 400 def test_non_ascii_3(self, tmpdir):401 fake_env(tmpdir, '\n# -*- coding: invalid\n')402 with get_dist(tmpdir):403 pass404 405 def test_non_ascii_4(self, tmpdir):406 fake_env(407 tmpdir,408 '# -*- coding: utf-8\n[metadata]\ndescription = éàïôñ\n',409 encoding='utf-8',410 )411 with get_dist(tmpdir) as dist:412 assert dist.metadata.description == 'éàïôñ'413 414 def test_not_utf8(self, tmpdir):415 """416 Config files encoded not in UTF-8 will fail417 """418 fake_env(419 tmpdir,420 '# vim: set fileencoding=iso-8859-15 :\n[metadata]\ndescription = éàïôñ\n',421 encoding='iso-8859-15',422 )423 with pytest.raises(UnicodeDecodeError):424 with get_dist(tmpdir):425 pass426 427 @pytest.mark.parametrize(428 ("error_msg", "config", "invalid"),429 [430 (431 "Invalid dash-separated key 'author-email' in 'metadata' (setup.cfg)",432 DALS(433 """434 [metadata]435 author-email = test@test.com436 maintainer_email = foo@foo.com437 """438 ),439 {"author-email": "test@test.com"},440 ),441 (442 "Invalid uppercase key 'Name' in 'metadata' (setup.cfg)",443 DALS(444 """445 [metadata]446 Name = foo447 description = Some description448 """449 ),450 {"Name": "foo"},451 ),452 ],453 )454 def test_invalid_options_previously_deprecated(455 self, tmpdir, error_msg, config, invalid456 ):457 # This test and related methods can be removed when no longer needed.458 # Deprecation postponed due to push-back from the community in459 # https://github.com/pypa/setuptools/issues/4910460 fake_env(tmpdir, config)461 with pytest.warns(SetuptoolsDeprecationWarning, match=re.escape(error_msg)):462 dist = get_dist(tmpdir).__enter__()463 464 tmpdir.join('setup.cfg').remove()465 466 for field, value in invalid.items():467 attr = field.replace("-", "_").lower()468 assert getattr(dist.metadata, attr) == value469 470 471class TestOptions:472 def test_basic(self, tmpdir):473 fake_env(474 tmpdir,475 '[options]\n'476 'zip_safe = True\n'477 'include_package_data = yes\n'478 'package_dir = b=c, =src\n'479 'packages = pack_a, pack_b.subpack\n'480 'namespace_packages = pack1, pack2\n'481 'scripts = bin/one.py, bin/two.py\n'482 'eager_resources = bin/one.py, bin/two.py\n'483 'install_requires = docutils>=0.3; pack ==1.1, ==1.3; hey\n'484 'setup_requires = docutils>=0.3; spack ==1.1, ==1.3; there\n'485 'dependency_links = http://some.com/here/1, '486 'http://some.com/there/2\n'487 'python_requires = >=1.0, !=2.8\n'488 'py_modules = module1, module2\n',489 )490 deprec = pytest.warns(SetuptoolsDeprecationWarning, match="namespace_packages")491 with deprec, get_dist(tmpdir) as dist:492 assert dist.zip_safe493 assert dist.include_package_data494 assert dist.package_dir == {'': 'src', 'b': 'c'}495 assert dist.packages == ['pack_a', 'pack_b.subpack']496 assert dist.namespace_packages == ['pack1', 'pack2']497 assert dist.scripts == ['bin/one.py', 'bin/two.py']498 assert dist.dependency_links == ([499 'http://some.com/here/1',500 'http://some.com/there/2',501 ])502 assert dist.install_requires == ([503 'docutils>=0.3',504 'pack==1.1,==1.3',505 'hey',506 ])507 assert dist.setup_requires == ([508 'docutils>=0.3',509 'spack ==1.1, ==1.3',510 'there',511 ])512 assert dist.python_requires == '>=1.0, !=2.8'513 assert dist.py_modules == ['module1', 'module2']514 515 def test_multiline(self, tmpdir):516 fake_env(517 tmpdir,518 '[options]\n'519 'package_dir = \n'520 ' b=c\n'521 ' =src\n'522 'packages = \n'523 ' pack_a\n'524 ' pack_b.subpack\n'525 'namespace_packages = \n'526 ' pack1\n'527 ' pack2\n'528 'scripts = \n'529 ' bin/one.py\n'530 ' bin/two.py\n'531 'eager_resources = \n'532 ' bin/one.py\n'533 ' bin/two.py\n'534 'install_requires = \n'535 ' docutils>=0.3\n'536 ' pack ==1.1, ==1.3\n'537 ' hey\n'538 'setup_requires = \n'539 ' docutils>=0.3\n'540 ' spack ==1.1, ==1.3\n'541 ' there\n'542 'dependency_links = \n'543 ' http://some.com/here/1\n'544 ' http://some.com/there/2\n',545 )546 deprec = pytest.warns(SetuptoolsDeprecationWarning, match="namespace_packages")547 with deprec, get_dist(tmpdir) as dist:548 assert dist.package_dir == {'': 'src', 'b': 'c'}549 assert dist.packages == ['pack_a', 'pack_b.subpack']550 assert dist.namespace_packages == ['pack1', 'pack2']551 assert dist.scripts == ['bin/one.py', 'bin/two.py']552 assert dist.dependency_links == ([553 'http://some.com/here/1',554 'http://some.com/there/2',555 ])556 assert dist.install_requires == ([557 'docutils>=0.3',558 'pack==1.1,==1.3',559 'hey',560 ])561 assert dist.setup_requires == ([562 'docutils>=0.3',563 'spack ==1.1, ==1.3',564 'there',565 ])566 567 def test_package_dir_fail(self, tmpdir):568 fake_env(tmpdir, '[options]\npackage_dir = a b\n')569 with get_dist(tmpdir, parse=False) as dist:570 with pytest.raises(DistutilsOptionError):571 dist.parse_config_files()572 573 def test_package_data(self, tmpdir):574 fake_env(575 tmpdir,576 '[options.package_data]\n'577 '* = *.txt, *.rst\n'578 'hello = *.msg\n'579 '\n'580 '[options.exclude_package_data]\n'581 '* = fake1.txt, fake2.txt\n'582 'hello = *.dat\n',583 )584 585 with get_dist(tmpdir) as dist:586 assert dist.package_data == {587 '': ['*.txt', '*.rst'],588 'hello': ['*.msg'],589 }590 assert dist.exclude_package_data == {591 '': ['fake1.txt', 'fake2.txt'],592 'hello': ['*.dat'],593 }594 595 def test_packages(self, tmpdir):596 fake_env(tmpdir, '[options]\npackages = find:\n')597 598 with get_dist(tmpdir) as dist:599 assert dist.packages == ['fake_package']600 601 def test_find_directive(self, tmpdir):602 dir_package, config = fake_env(tmpdir, '[options]\npackages = find:\n')603 604 make_package_dir('sub_one', dir_package)605 make_package_dir('sub_two', dir_package)606 607 with get_dist(tmpdir) as dist:608 assert set(dist.packages) == set([609 'fake_package',610 'fake_package.sub_two',611 'fake_package.sub_one',612 ])613 614 config.write(615 '[options]\n'616 'packages = find:\n'617 '\n'618 '[options.packages.find]\n'619 'where = .\n'620 'include =\n'621 ' fake_package.sub_one\n'622 ' two\n'623 )624 with get_dist(tmpdir) as dist:625 assert dist.packages == ['fake_package.sub_one']626 627 config.write(628 '[options]\n'629 'packages = find:\n'630 '\n'631 '[options.packages.find]\n'632 'exclude =\n'633 ' fake_package.sub_one\n'634 )635 with get_dist(tmpdir) as dist:636 assert set(dist.packages) == set(['fake_package', 'fake_package.sub_two'])637 638 def test_find_namespace_directive(self, tmpdir):639 dir_package, config = fake_env(640 tmpdir, '[options]\npackages = find_namespace:\n'641 )642 643 make_package_dir('sub_one', dir_package)644 make_package_dir('sub_two', dir_package, ns=True)645 646 with get_dist(tmpdir) as dist:647 assert set(dist.packages) == {648 'fake_package',649 'fake_package.sub_two',650 'fake_package.sub_one',651 }652 653 config.write(654 '[options]\n'655 'packages = find_namespace:\n'656 '\n'657 '[options.packages.find]\n'658 'where = .\n'659 'include =\n'660 ' fake_package.sub_one\n'661 ' two\n'662 )663 with get_dist(tmpdir) as dist:664 assert dist.packages == ['fake_package.sub_one']665 666 config.write(667 '[options]\n'668 'packages = find_namespace:\n'669 '\n'670 '[options.packages.find]\n'671 'exclude =\n'672 ' fake_package.sub_one\n'673 )674 with get_dist(tmpdir) as dist:675 assert set(dist.packages) == {'fake_package', 'fake_package.sub_two'}676 677 def test_extras_require(self, tmpdir):678 fake_env(679 tmpdir,680 '[options.extras_require]\n'681 'pdf = ReportLab>=1.2; RXP\n'682 'rest = \n'683 ' docutils>=0.3\n'684 ' pack ==1.1, ==1.3\n',685 )686 687 with get_dist(tmpdir) as dist:688 assert dist.extras_require == {689 'pdf': ['ReportLab>=1.2', 'RXP'],690 'rest': ['docutils>=0.3', 'pack==1.1,==1.3'],691 }692 assert set(dist.metadata.provides_extras) == {'pdf', 'rest'}693 694 @pytest.mark.parametrize(695 "config",696 [697 "[options.extras_require]\nfoo = bar;python_version<'3'",698 "[options.extras_require]\nfoo = bar;os_name=='linux'",699 "[options.extras_require]\nfoo = bar;python_version<'3'\n",700 "[options.extras_require]\nfoo = bar;os_name=='linux'\n",701 "[options]\ninstall_requires = bar;python_version<'3'",702 "[options]\ninstall_requires = bar;os_name=='linux'",703 "[options]\ninstall_requires = bar;python_version<'3'\n",704 "[options]\ninstall_requires = bar;os_name=='linux'\n",705 ],706 )707 @pytest.mark.xfail(IS_PYPY, reason="Exceptions missing on PyPy")708 # TODO: investigate PyPy problem709 def test_raises_accidental_env_marker_misconfig(self, config, tmpdir):710 fake_env(tmpdir, config)711 match = (712 r"One of the parsed requirements in `(install_requires|extras_require.+)` "713 "looks like a valid environment marker.*"714 )715 with pytest.raises(InvalidRequirement, match=match):716 with get_dist(tmpdir) as _:717 pass718 719 @pytest.mark.parametrize(720 "config",721 [722 "[options.extras_require]\nfoo = bar;python_version<3",723 "[options.extras_require]\nfoo = bar;python_version<3\n",724 "[options]\ninstall_requires = bar;python_version<3",725 "[options]\ninstall_requires = bar;python_version<3\n",726 ],727 )728 @pytest.mark.xfail(IS_PYPY, reason="Warnings missing on PyPy (minor issue)")729 # TODO: investigate PyPy problem730 def test_warn_accidental_env_marker_misconfig(self, config, tmpdir):731 fake_env(tmpdir, config)732 match = (733 r"One of the parsed requirements in `(install_requires|extras_require.+)` "734 "looks like a valid environment marker.*"735 )736 with pytest.warns(SetuptoolsDeprecationWarning, match=match):737 with get_dist(tmpdir) as _:738 pass739 740 @pytest.mark.parametrize(741 "config",742 [743 "[options.extras_require]\nfoo =\n bar;python_version<'3'",744 "[options.extras_require]\nfoo = bar;baz\nboo = xxx;yyy",745 "[options.extras_require]\nfoo =\n bar;python_version<'3'\n",746 "[options.extras_require]\nfoo = bar;baz\nboo = xxx;yyy\n",747 "[options.extras_require]\nfoo =\n bar\n python_version<3\n",748 "[options]\ninstall_requires =\n bar;python_version<'3'",749 "[options]\ninstall_requires = bar;baz\nboo = xxx;yyy",750 "[options]\ninstall_requires =\n bar;python_version<'3'\n",751 "[options]\ninstall_requires = bar;baz\nboo = xxx;yyy\n",752 "[options]\ninstall_requires =\n bar\n python_version<3\n",753 ],754 )755 @pytest.mark.filterwarnings("error::setuptools.SetuptoolsDeprecationWarning")756 def test_nowarn_accidental_env_marker_misconfig(self, config, tmpdir, recwarn):757 fake_env(tmpdir, config)758 num_warnings = len(recwarn)759 with get_dist(tmpdir) as _:760 pass761 # The examples are valid, no warnings shown762 assert len(recwarn) == num_warnings763 764 def test_dash_preserved_extras_require(self, tmpdir):765 fake_env(tmpdir, '[options.extras_require]\nfoo-a = foo\nfoo_b = test\n')766 767 with get_dist(tmpdir) as dist:768 assert dist.extras_require == {'foo-a': ['foo'], 'foo_b': ['test']}769 770 def test_entry_points(self, tmpdir):771 _, config = fake_env(772 tmpdir,773 '[options.entry_points]\n'774 'group1 = point1 = pack.module:func, '775 '.point2 = pack.module2:func_rest [rest]\n'776 'group2 = point3 = pack.module:func2\n',777 )778 779 with get_dist(tmpdir) as dist:780 assert dist.entry_points == {781 'group1': [782 'point1 = pack.module:func',783 '.point2 = pack.module2:func_rest [rest]',784 ],785 'group2': ['point3 = pack.module:func2'],786 }787 788 expected = (789 '[blogtool.parsers]\n'790 '.rst = some.nested.module:SomeClass.some_classmethod[reST]\n'791 )792 793 tmpdir.join('entry_points').write(expected)794 795 # From file.796 config.write('[options]\nentry_points = file: entry_points\n')797 798 with get_dist(tmpdir) as dist:799 assert dist.entry_points == expected800 801 def test_case_sensitive_entry_points(self, tmpdir):802 fake_env(803 tmpdir,804 '[options.entry_points]\n'805 'GROUP1 = point1 = pack.module:func, '806 '.point2 = pack.module2:func_rest [rest]\n'807 'group2 = point3 = pack.module:func2\n',808 )809 810 with get_dist(tmpdir) as dist:811 assert dist.entry_points == {812 'GROUP1': [813 'point1 = pack.module:func',814 '.point2 = pack.module2:func_rest [rest]',815 ],816 'group2': ['point3 = pack.module:func2'],817 }818 819 def test_data_files(self, tmpdir):820 fake_env(821 tmpdir,822 '[options.data_files]\n'823 'cfg =\n'824 ' a/b.conf\n'825 ' c/d.conf\n'826 'data = e/f.dat, g/h.dat\n',827 )828 829 with get_dist(tmpdir) as dist:830 expected = [831 ('cfg', ['a/b.conf', 'c/d.conf']),832 ('data', ['e/f.dat', 'g/h.dat']),833 ]834 assert sorted(dist.data_files) == sorted(expected)835 836 def test_data_files_globby(self, tmpdir):837 fake_env(838 tmpdir,839 '[options.data_files]\n'840 'cfg =\n'841 ' a/b.conf\n'842 ' c/d.conf\n'843 'data = *.dat\n'844 'icons = \n'845 ' *.ico\n'846 'audio = \n'847 ' *.wav\n'848 ' sounds.db\n',849 )850 851 # Create dummy files for glob()'s sake:852 tmpdir.join('a.dat').write('')853 tmpdir.join('b.dat').write('')854 tmpdir.join('c.dat').write('')855 tmpdir.join('a.ico').write('')856 tmpdir.join('b.ico').write('')857 tmpdir.join('c.ico').write('')858 tmpdir.join('beep.wav').write('')859 tmpdir.join('boop.wav').write('')860 tmpdir.join('sounds.db').write('')861 862 with get_dist(tmpdir) as dist:863 expected = [864 ('cfg', ['a/b.conf', 'c/d.conf']),865 ('data', ['a.dat', 'b.dat', 'c.dat']),866 ('icons', ['a.ico', 'b.ico', 'c.ico']),867 ('audio', ['beep.wav', 'boop.wav', 'sounds.db']),868 ]869 assert sorted(dist.data_files) == sorted(expected)870 871 def test_python_requires_simple(self, tmpdir):872 fake_env(873 tmpdir,874 DALS(875 """876 [options]877 python_requires=>=2.7878 """879 ),880 )881 with get_dist(tmpdir) as dist:882 dist.parse_config_files()883 884 def test_python_requires_compound(self, tmpdir):885 fake_env(886 tmpdir,887 DALS(888 """889 [options]890 python_requires=>=2.7,!=3.0.*891 """892 ),893 )894 with get_dist(tmpdir) as dist:895 dist.parse_config_files()896 897 def test_python_requires_invalid(self, tmpdir):898 fake_env(899 tmpdir,900 DALS(901 """902 [options]903 python_requires=invalid904 """905 ),906 )907 with pytest.raises(Exception):908 with get_dist(tmpdir) as dist:909 dist.parse_config_files()910 911 def test_cmdclass(self, tmpdir):912 module_path = Path(tmpdir, "src/custom_build.py") # auto discovery for src913 module_path.parent.mkdir(parents=True, exist_ok=True)914 module_path.write_text(915 "from distutils.core import Command\nclass CustomCmd(Command): pass\n",916 encoding="utf-8",917 )918 919 setup_cfg = """920 [options]921 cmdclass =922 customcmd = custom_build.CustomCmd923 """924 fake_env(tmpdir, inspect.cleandoc(setup_cfg))925 926 with get_dist(tmpdir) as dist:927 cmdclass = dist.cmdclass['customcmd']928 assert cmdclass.__name__ == "CustomCmd"929 assert cmdclass.__module__ == "custom_build"930 assert module_path.samefile(inspect.getfile(cmdclass))931 932 def test_requirements_file(self, tmpdir):933 fake_env(934 tmpdir,935 DALS(936 """937 [options]938 install_requires = file:requirements.txt939 [options.extras_require]940 colors = file:requirements-extra.txt941 """942 ),943 )944 945 tmpdir.join('requirements.txt').write('\ndocutils>=0.3\n\n')946 tmpdir.join('requirements-extra.txt').write('colorama')947 948 with get_dist(tmpdir) as dist:949 assert dist.install_requires == ['docutils>=0.3']950 assert dist.extras_require == {'colors': ['colorama']}951 952 953saved_dist_init = _Distribution.__init__954 955 956class TestExternalSetters:957 # During creation of the setuptools Distribution() object, we call958 # the init of the parent distutils Distribution object via959 # _Distribution.__init__ ().960 #961 # It's possible distutils calls out to various keyword962 # implementations (i.e. distutils.setup_keywords entry points)963 # that may set a range of variables.964 #965 # This wraps distutil's Distribution.__init__ and simulates966 # pbr or something else setting these values.967 def _fake_distribution_init(self, dist, attrs):968 saved_dist_init(dist, attrs)969 # see self._DISTUTILS_UNSUPPORTED_METADATA970 dist.metadata.long_description_content_type = 'text/something'971 # Test overwrite setup() args972 dist.metadata.project_urls = {973 'Link One': 'https://example.com/one/',974 'Link Two': 'https://example.com/two/',975 }976 977 @patch.object(_Distribution, '__init__', autospec=True)978 def test_external_setters(self, mock_parent_init, tmpdir):979 mock_parent_init.side_effect = self._fake_distribution_init980 981 dist = Distribution(attrs={'project_urls': {'will_be': 'ignored'}})982 983 assert dist.metadata.long_description_content_type == 'text/something'984 assert dist.metadata.project_urls == {985 'Link One': 'https://example.com/one/',986 'Link Two': 'https://example.com/two/',987 }988 