• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import contextlib
2import pytest
3from distutils.errors import DistutilsOptionError, DistutilsFileError
4from setuptools.dist import Distribution
5from setuptools.config import ConfigHandler, read_configuration
6
7
8class ErrConfigHandler(ConfigHandler):
9    """Erroneous handler. Fails to implement required methods."""
10
11
12def make_package_dir(name, base_dir):
13    dir_package = base_dir.mkdir(name)
14    init_file = dir_package.join('__init__.py')
15    init_file.write('')
16    return dir_package, init_file
17
18
19def fake_env(tmpdir, setup_cfg, setup_py=None):
20
21    if setup_py is None:
22        setup_py = (
23            'from setuptools import setup\n'
24            'setup()\n'
25        )
26
27    tmpdir.join('setup.py').write(setup_py)
28    config = tmpdir.join('setup.cfg')
29    config.write(setup_cfg)
30
31    package_dir, init_file = make_package_dir('fake_package', tmpdir)
32
33    init_file.write(
34        'VERSION = (1, 2, 3)\n'
35        '\n'
36        'VERSION_MAJOR = 1'
37        '\n'
38        'def get_version():\n'
39        '    return [3, 4, 5, "dev"]\n'
40        '\n'
41    )
42    return package_dir, config
43
44
45@contextlib.contextmanager
46def get_dist(tmpdir, kwargs_initial=None, parse=True):
47    kwargs_initial = kwargs_initial or {}
48
49    with tmpdir.as_cwd():
50        dist = Distribution(kwargs_initial)
51        dist.script_name = 'setup.py'
52        parse and dist.parse_config_files()
53
54        yield dist
55
56
57def test_parsers_implemented():
58
59    with pytest.raises(NotImplementedError):
60        handler = ErrConfigHandler(None, {})
61        handler.parsers
62
63
64class TestConfigurationReader:
65
66    def test_basic(self, tmpdir):
67        _, config = fake_env(
68            tmpdir,
69            '[metadata]\n'
70            'version = 10.1.1\n'
71            'keywords = one, two\n'
72            '\n'
73            '[options]\n'
74            'scripts = bin/a.py, bin/b.py\n'
75        )
76        config_dict = read_configuration('%s' % config)
77        assert config_dict['metadata']['version'] == '10.1.1'
78        assert config_dict['metadata']['keywords'] == ['one', 'two']
79        assert config_dict['options']['scripts'] == ['bin/a.py', 'bin/b.py']
80
81    def test_no_config(self, tmpdir):
82        with pytest.raises(DistutilsFileError):
83            read_configuration('%s' % tmpdir.join('setup.cfg'))
84
85    def test_ignore_errors(self, tmpdir):
86        _, config = fake_env(
87            tmpdir,
88            '[metadata]\n'
89            'version = attr: none.VERSION\n'
90            'keywords = one, two\n'
91        )
92        with pytest.raises(ImportError):
93            read_configuration('%s' % config)
94
95        config_dict = read_configuration(
96            '%s' % config, ignore_option_errors=True)
97
98        assert config_dict['metadata']['keywords'] == ['one', 'two']
99        assert 'version' not in config_dict['metadata']
100
101        config.remove()
102
103
104class TestMetadata:
105
106    def test_basic(self, tmpdir):
107
108        fake_env(
109            tmpdir,
110            '[metadata]\n'
111            'version = 10.1.1\n'
112            'description = Some description\n'
113            'long_description_content_type = text/something\n'
114            'long_description = file: README\n'
115            'name = fake_name\n'
116            'keywords = one, two\n'
117            'provides = package, package.sub\n'
118            'license = otherlic\n'
119            'download_url = http://test.test.com/test/\n'
120            'maintainer_email = test@test.com\n'
121        )
122
123        tmpdir.join('README').write('readme contents\nline2')
124
125        meta_initial = {
126            # This will be used so `otherlic` won't replace it.
127            'license': 'BSD 3-Clause License',
128        }
129
130        with get_dist(tmpdir, meta_initial) as dist:
131            metadata = dist.metadata
132
133            assert metadata.version == '10.1.1'
134            assert metadata.description == 'Some description'
135            assert metadata.long_description_content_type == 'text/something'
136            assert metadata.long_description == 'readme contents\nline2'
137            assert metadata.provides == ['package', 'package.sub']
138            assert metadata.license == 'BSD 3-Clause License'
139            assert metadata.name == 'fake_name'
140            assert metadata.keywords == ['one', 'two']
141            assert metadata.download_url == 'http://test.test.com/test/'
142            assert metadata.maintainer_email == 'test@test.com'
143
144    def test_file_mixed(self, tmpdir):
145
146        fake_env(
147            tmpdir,
148            '[metadata]\n'
149            'long_description = file: README.rst, CHANGES.rst\n'
150            '\n'
151        )
152
153        tmpdir.join('README.rst').write('readme contents\nline2')
154        tmpdir.join('CHANGES.rst').write('changelog contents\nand stuff')
155
156        with get_dist(tmpdir) as dist:
157            assert dist.metadata.long_description == (
158                'readme contents\nline2\n'
159                'changelog contents\nand stuff'
160            )
161
162    def test_file_sandboxed(self, tmpdir):
163
164        fake_env(
165            tmpdir,
166            '[metadata]\n'
167            'long_description = file: ../../README\n'
168        )
169
170        with get_dist(tmpdir, parse=False) as dist:
171            with pytest.raises(DistutilsOptionError):
172                dist.parse_config_files()  # file: out of sandbox
173
174    def test_aliases(self, tmpdir):
175
176        fake_env(
177            tmpdir,
178            '[metadata]\n'
179            'author-email = test@test.com\n'
180            'home-page = http://test.test.com/test/\n'
181            'summary = Short summary\n'
182            'platform = a, b\n'
183            'classifier =\n'
184            '  Framework :: Django\n'
185            '  Programming Language :: Python :: 3.5\n'
186        )
187
188        with get_dist(tmpdir) as dist:
189            metadata = dist.metadata
190            assert metadata.author_email == 'test@test.com'
191            assert metadata.url == 'http://test.test.com/test/'
192            assert metadata.description == 'Short summary'
193            assert metadata.platforms == ['a', 'b']
194            assert metadata.classifiers == [
195                'Framework :: Django',
196                'Programming Language :: Python :: 3.5',
197            ]
198
199    def test_multiline(self, tmpdir):
200
201        fake_env(
202            tmpdir,
203            '[metadata]\n'
204            'name = fake_name\n'
205            'keywords =\n'
206            '  one\n'
207            '  two\n'
208            'classifiers =\n'
209            '  Framework :: Django\n'
210            '  Programming Language :: Python :: 3.5\n'
211        )
212        with get_dist(tmpdir) as dist:
213            metadata = dist.metadata
214            assert metadata.keywords == ['one', 'two']
215            assert metadata.classifiers == [
216                'Framework :: Django',
217                'Programming Language :: Python :: 3.5',
218            ]
219
220    def test_dict(self, tmpdir):
221
222        fake_env(
223            tmpdir,
224            '[metadata]\n'
225            'project_urls =\n'
226            '  Link One = https://example.com/one/\n'
227            '  Link Two = https://example.com/two/\n'
228        )
229        with get_dist(tmpdir) as dist:
230            metadata = dist.metadata
231            assert metadata.project_urls == {
232                'Link One': 'https://example.com/one/',
233                'Link Two': 'https://example.com/two/',
234            }
235
236    def test_version(self, tmpdir):
237
238        _, config = fake_env(
239            tmpdir,
240            '[metadata]\n'
241            'version = attr: fake_package.VERSION\n'
242        )
243        with get_dist(tmpdir) as dist:
244            assert dist.metadata.version == '1.2.3'
245
246        config.write(
247            '[metadata]\n'
248            'version = attr: fake_package.get_version\n'
249        )
250        with get_dist(tmpdir) as dist:
251            assert dist.metadata.version == '3.4.5.dev'
252
253        config.write(
254            '[metadata]\n'
255            'version = attr: fake_package.VERSION_MAJOR\n'
256        )
257        with get_dist(tmpdir) as dist:
258            assert dist.metadata.version == '1'
259
260        subpack = tmpdir.join('fake_package').mkdir('subpackage')
261        subpack.join('__init__.py').write('')
262        subpack.join('submodule.py').write('VERSION = (2016, 11, 26)')
263
264        config.write(
265            '[metadata]\n'
266            'version = attr: fake_package.subpackage.submodule.VERSION\n'
267        )
268        with get_dist(tmpdir) as dist:
269            assert dist.metadata.version == '2016.11.26'
270
271    def test_unknown_meta_item(self, tmpdir):
272
273        fake_env(
274            tmpdir,
275            '[metadata]\n'
276            'name = fake_name\n'
277            'unknown = some\n'
278        )
279        with get_dist(tmpdir, parse=False) as dist:
280            dist.parse_config_files()  # Skip unknown.
281
282    def test_usupported_section(self, tmpdir):
283
284        fake_env(
285            tmpdir,
286            '[metadata.some]\n'
287            'key = val\n'
288        )
289        with get_dist(tmpdir, parse=False) as dist:
290            with pytest.raises(DistutilsOptionError):
291                dist.parse_config_files()
292
293    def test_classifiers(self, tmpdir):
294        expected = set([
295            'Framework :: Django',
296            'Programming Language :: Python :: 3',
297            'Programming Language :: Python :: 3.5',
298        ])
299
300        # From file.
301        _, config = fake_env(
302            tmpdir,
303            '[metadata]\n'
304            'classifiers = file: classifiers\n'
305        )
306
307        tmpdir.join('classifiers').write(
308            'Framework :: Django\n'
309            'Programming Language :: Python :: 3\n'
310            'Programming Language :: Python :: 3.5\n'
311        )
312
313        with get_dist(tmpdir) as dist:
314            assert set(dist.metadata.classifiers) == expected
315
316        # From list notation
317        config.write(
318            '[metadata]\n'
319            'classifiers =\n'
320            '    Framework :: Django\n'
321            '    Programming Language :: Python :: 3\n'
322            '    Programming Language :: Python :: 3.5\n'
323        )
324        with get_dist(tmpdir) as dist:
325            assert set(dist.metadata.classifiers) == expected
326
327
328class TestOptions:
329
330    def test_basic(self, tmpdir):
331
332        fake_env(
333            tmpdir,
334            '[options]\n'
335            'zip_safe = True\n'
336            'use_2to3 = 1\n'
337            'include_package_data = yes\n'
338            'package_dir = b=c, =src\n'
339            'packages = pack_a, pack_b.subpack\n'
340            'namespace_packages = pack1, pack2\n'
341            'use_2to3_fixers = your.fixers, or.here\n'
342            'use_2to3_exclude_fixers = one.here, two.there\n'
343            'convert_2to3_doctests = src/tests/one.txt, src/two.txt\n'
344            'scripts = bin/one.py, bin/two.py\n'
345            'eager_resources = bin/one.py, bin/two.py\n'
346            'install_requires = docutils>=0.3; pack ==1.1, ==1.3; hey\n'
347            'tests_require = mock==0.7.2; pytest\n'
348            'setup_requires = docutils>=0.3; spack ==1.1, ==1.3; there\n'
349            'dependency_links = http://some.com/here/1, '
350                'http://some.com/there/2\n'
351            'python_requires = >=1.0, !=2.8\n'
352            'py_modules = module1, module2\n'
353        )
354        with get_dist(tmpdir) as dist:
355            assert dist.zip_safe
356            assert dist.use_2to3
357            assert dist.include_package_data
358            assert dist.package_dir == {'': 'src', 'b': 'c'}
359            assert dist.packages == ['pack_a', 'pack_b.subpack']
360            assert dist.namespace_packages == ['pack1', 'pack2']
361            assert dist.use_2to3_fixers == ['your.fixers', 'or.here']
362            assert dist.use_2to3_exclude_fixers == ['one.here', 'two.there']
363            assert dist.convert_2to3_doctests == ([
364                'src/tests/one.txt', 'src/two.txt'])
365            assert dist.scripts == ['bin/one.py', 'bin/two.py']
366            assert dist.dependency_links == ([
367                'http://some.com/here/1',
368                'http://some.com/there/2'
369            ])
370            assert dist.install_requires == ([
371                'docutils>=0.3',
372                'pack==1.1,==1.3',
373                'hey'
374            ])
375            assert dist.setup_requires == ([
376                'docutils>=0.3',
377                'spack ==1.1, ==1.3',
378                'there'
379            ])
380            assert dist.tests_require == ['mock==0.7.2', 'pytest']
381            assert dist.python_requires == '>=1.0, !=2.8'
382            assert dist.py_modules == ['module1', 'module2']
383
384    def test_multiline(self, tmpdir):
385        fake_env(
386            tmpdir,
387            '[options]\n'
388            'package_dir = \n'
389            '  b=c\n'
390            '  =src\n'
391            'packages = \n'
392            '  pack_a\n'
393            '  pack_b.subpack\n'
394            'namespace_packages = \n'
395            '  pack1\n'
396            '  pack2\n'
397            'use_2to3_fixers = \n'
398            '  your.fixers\n'
399            '  or.here\n'
400            'use_2to3_exclude_fixers = \n'
401            '  one.here\n'
402            '  two.there\n'
403            'convert_2to3_doctests = \n'
404            '  src/tests/one.txt\n'
405            '  src/two.txt\n'
406            'scripts = \n'
407            '  bin/one.py\n'
408            '  bin/two.py\n'
409            'eager_resources = \n'
410            '  bin/one.py\n'
411            '  bin/two.py\n'
412            'install_requires = \n'
413            '  docutils>=0.3\n'
414            '  pack ==1.1, ==1.3\n'
415            '  hey\n'
416            'tests_require = \n'
417            '  mock==0.7.2\n'
418            '  pytest\n'
419            'setup_requires = \n'
420            '  docutils>=0.3\n'
421            '  spack ==1.1, ==1.3\n'
422            '  there\n'
423            'dependency_links = \n'
424            '  http://some.com/here/1\n'
425            '  http://some.com/there/2\n'
426        )
427        with get_dist(tmpdir) as dist:
428            assert dist.package_dir == {'': 'src', 'b': 'c'}
429            assert dist.packages == ['pack_a', 'pack_b.subpack']
430            assert dist.namespace_packages == ['pack1', 'pack2']
431            assert dist.use_2to3_fixers == ['your.fixers', 'or.here']
432            assert dist.use_2to3_exclude_fixers == ['one.here', 'two.there']
433            assert dist.convert_2to3_doctests == (
434                ['src/tests/one.txt', 'src/two.txt'])
435            assert dist.scripts == ['bin/one.py', 'bin/two.py']
436            assert dist.dependency_links == ([
437                'http://some.com/here/1',
438                'http://some.com/there/2'
439            ])
440            assert dist.install_requires == ([
441                'docutils>=0.3',
442                'pack==1.1,==1.3',
443                'hey'
444            ])
445            assert dist.setup_requires == ([
446                'docutils>=0.3',
447                'spack ==1.1, ==1.3',
448                'there'
449            ])
450            assert dist.tests_require == ['mock==0.7.2', 'pytest']
451
452    def test_package_dir_fail(self, tmpdir):
453        fake_env(
454            tmpdir,
455            '[options]\n'
456            'package_dir = a b\n'
457        )
458        with get_dist(tmpdir, parse=False) as dist:
459            with pytest.raises(DistutilsOptionError):
460                dist.parse_config_files()
461
462    def test_package_data(self, tmpdir):
463        fake_env(
464            tmpdir,
465            '[options.package_data]\n'
466            '* = *.txt, *.rst\n'
467            'hello = *.msg\n'
468            '\n'
469            '[options.exclude_package_data]\n'
470            '* = fake1.txt, fake2.txt\n'
471            'hello = *.dat\n'
472        )
473
474        with get_dist(tmpdir) as dist:
475            assert dist.package_data == {
476                '': ['*.txt', '*.rst'],
477                'hello': ['*.msg'],
478            }
479            assert dist.exclude_package_data == {
480                '': ['fake1.txt', 'fake2.txt'],
481                'hello': ['*.dat'],
482            }
483
484    def test_packages(self, tmpdir):
485        fake_env(
486            tmpdir,
487            '[options]\n'
488            'packages = find:\n'
489        )
490
491        with get_dist(tmpdir) as dist:
492            assert dist.packages == ['fake_package']
493
494    def test_find_directive(self, tmpdir):
495        dir_package, config = fake_env(
496            tmpdir,
497            '[options]\n'
498            'packages = find:\n'
499        )
500
501        dir_sub_one, _ = make_package_dir('sub_one', dir_package)
502        dir_sub_two, _ = make_package_dir('sub_two', dir_package)
503
504        with get_dist(tmpdir) as dist:
505            assert set(dist.packages) == set([
506                'fake_package', 'fake_package.sub_two', 'fake_package.sub_one'
507            ])
508
509        config.write(
510            '[options]\n'
511            'packages = find:\n'
512            '\n'
513            '[options.packages.find]\n'
514            'where = .\n'
515            'include =\n'
516            '    fake_package.sub_one\n'
517            '    two\n'
518        )
519        with get_dist(tmpdir) as dist:
520            assert dist.packages == ['fake_package.sub_one']
521
522        config.write(
523            '[options]\n'
524            'packages = find:\n'
525            '\n'
526            '[options.packages.find]\n'
527            'exclude =\n'
528            '    fake_package.sub_one\n'
529        )
530        with get_dist(tmpdir) as dist:
531            assert set(dist.packages) == set(
532                ['fake_package',  'fake_package.sub_two'])
533
534    def test_extras_require(self, tmpdir):
535        fake_env(
536            tmpdir,
537            '[options.extras_require]\n'
538            'pdf = ReportLab>=1.2; RXP\n'
539            'rest = \n'
540            '  docutils>=0.3\n'
541            '  pack ==1.1, ==1.3\n'
542        )
543
544        with get_dist(tmpdir) as dist:
545            assert dist.extras_require == {
546                'pdf': ['ReportLab>=1.2', 'RXP'],
547                'rest': ['docutils>=0.3', 'pack==1.1,==1.3']
548            }
549            assert dist.metadata.provides_extras == set(['pdf', 'rest'])
550
551    def test_entry_points(self, tmpdir):
552        _, config = fake_env(
553            tmpdir,
554            '[options.entry_points]\n'
555            'group1 = point1 = pack.module:func, '
556                '.point2 = pack.module2:func_rest [rest]\n'
557            'group2 = point3 = pack.module:func2\n'
558        )
559
560        with get_dist(tmpdir) as dist:
561            assert dist.entry_points == {
562                'group1': [
563                    'point1 = pack.module:func',
564                    '.point2 = pack.module2:func_rest [rest]',
565                ],
566                'group2': ['point3 = pack.module:func2']
567            }
568
569        expected = (
570            '[blogtool.parsers]\n'
571            '.rst = some.nested.module:SomeClass.some_classmethod[reST]\n'
572        )
573
574        tmpdir.join('entry_points').write(expected)
575
576        # From file.
577        config.write(
578            '[options]\n'
579            'entry_points = file: entry_points\n'
580        )
581
582        with get_dist(tmpdir) as dist:
583            assert dist.entry_points == expected
584