• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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