| | """sdist tests""" |
| |
|
| | from __future__ import annotations |
| |
|
| | import contextlib |
| | import io |
| | import itertools |
| | import logging |
| | import os |
| | import shutil |
| | import sys |
| | import tempfile |
| |
|
| | import pytest |
| |
|
| | from setuptools.command.egg_info import FileList, egg_info, translate_pattern |
| | from setuptools.dist import Distribution |
| | from setuptools.tests.textwrap import DALS |
| |
|
| | from distutils import log |
| | from distutils.errors import DistutilsTemplateError |
| |
|
| | IS_PYPY = '__pypy__' in sys.builtin_module_names |
| |
|
| |
|
| | def make_local_path(s): |
| | """Converts '/' in a string to os.sep""" |
| | return s.replace('/', os.sep) |
| |
|
| |
|
| | SETUP_ATTRS = { |
| | 'name': 'app', |
| | 'version': '0.0', |
| | 'packages': ['app'], |
| | } |
| |
|
| | SETUP_PY = f"""\ |
| | from setuptools import setup |
| | |
| | setup(**{SETUP_ATTRS!r}) |
| | """ |
| |
|
| |
|
| | @contextlib.contextmanager |
| | def quiet(): |
| | old_stdout, old_stderr = sys.stdout, sys.stderr |
| | sys.stdout, sys.stderr = io.StringIO(), io.StringIO() |
| | try: |
| | yield |
| | finally: |
| | sys.stdout, sys.stderr = old_stdout, old_stderr |
| |
|
| |
|
| | def touch(filename): |
| | open(filename, 'wb').close() |
| |
|
| |
|
| | |
| | |
| | default_files = frozenset( |
| | map( |
| | make_local_path, |
| | [ |
| | 'README.rst', |
| | 'MANIFEST.in', |
| | 'setup.py', |
| | 'app.egg-info/PKG-INFO', |
| | 'app.egg-info/SOURCES.txt', |
| | 'app.egg-info/dependency_links.txt', |
| | 'app.egg-info/top_level.txt', |
| | 'app/__init__.py', |
| | ], |
| | ) |
| | ) |
| |
|
| |
|
| | translate_specs: list[tuple[str, list[str], list[str]]] = [ |
| | ('foo', ['foo'], ['bar', 'foobar']), |
| | ('foo/bar', ['foo/bar'], ['foo/bar/baz', './foo/bar', 'foo']), |
| | |
| | ('*.txt', ['foo.txt', 'bar.txt'], ['foo/foo.txt']), |
| | ('dir/*.txt', ['dir/foo.txt', 'dir/bar.txt', 'dir/.txt'], ['notdir/foo.txt']), |
| | ('*/*.py', ['bin/start.py'], []), |
| | ('docs/page-?.txt', ['docs/page-9.txt'], ['docs/page-10.txt']), |
| | |
| | ( |
| | 'foo/**/bar', |
| | ['foo/bing/bar', 'foo/bing/bang/bar', 'foo/bar'], |
| | ['foo/abar'], |
| | ), |
| | ( |
| | 'foo/**', |
| | ['foo/bar/bing.py', 'foo/x'], |
| | ['/foo/x'], |
| | ), |
| | ( |
| | '**', |
| | ['x', 'abc/xyz', '@nything'], |
| | [], |
| | ), |
| | |
| | ( |
| | 'pre[one]post', |
| | ['preopost', 'prenpost', 'preepost'], |
| | ['prepost', 'preonepost'], |
| | ), |
| | ( |
| | 'hello[!one]world', |
| | ['helloxworld', 'helloyworld'], |
| | ['hellooworld', 'helloworld', 'hellooneworld'], |
| | ), |
| | ( |
| | '[]one].txt', |
| | ['o.txt', '].txt', 'e.txt'], |
| | ['one].txt'], |
| | ), |
| | ( |
| | 'foo[!]one]bar', |
| | ['fooybar'], |
| | ['foo]bar', 'fooobar', 'fooebar'], |
| | ), |
| | ] |
| | """ |
| | A spec of inputs for 'translate_pattern' and matches and mismatches |
| | for that input. |
| | """ |
| |
|
| | match_params = itertools.chain.from_iterable( |
| | zip(itertools.repeat(pattern), matches) |
| | for pattern, matches, mismatches in translate_specs |
| | ) |
| |
|
| |
|
| | @pytest.fixture(params=match_params) |
| | def pattern_match(request): |
| | return map(make_local_path, request.param) |
| |
|
| |
|
| | mismatch_params = itertools.chain.from_iterable( |
| | zip(itertools.repeat(pattern), mismatches) |
| | for pattern, matches, mismatches in translate_specs |
| | ) |
| |
|
| |
|
| | @pytest.fixture(params=mismatch_params) |
| | def pattern_mismatch(request): |
| | return map(make_local_path, request.param) |
| |
|
| |
|
| | def test_translated_pattern_match(pattern_match): |
| | pattern, target = pattern_match |
| | assert translate_pattern(pattern).match(target) |
| |
|
| |
|
| | def test_translated_pattern_mismatch(pattern_mismatch): |
| | pattern, target = pattern_mismatch |
| | assert not translate_pattern(pattern).match(target) |
| |
|
| |
|
| | class TempDirTestCase: |
| | def setup_method(self, method): |
| | self.temp_dir = tempfile.mkdtemp() |
| | self.old_cwd = os.getcwd() |
| | os.chdir(self.temp_dir) |
| |
|
| | def teardown_method(self, method): |
| | os.chdir(self.old_cwd) |
| | shutil.rmtree(self.temp_dir) |
| |
|
| |
|
| | class TestManifestTest(TempDirTestCase): |
| | def setup_method(self, method): |
| | super().setup_method(method) |
| |
|
| | f = open(os.path.join(self.temp_dir, 'setup.py'), 'w', encoding="utf-8") |
| | f.write(SETUP_PY) |
| | f.close() |
| | """ |
| | Create a file tree like: |
| | - LICENSE |
| | - README.rst |
| | - testing.rst |
| | - .hidden.rst |
| | - app/ |
| | - __init__.py |
| | - a.txt |
| | - b.txt |
| | - c.rst |
| | - static/ |
| | - app.js |
| | - app.js.map |
| | - app.css |
| | - app.css.map |
| | """ |
| |
|
| | for fname in ['README.rst', '.hidden.rst', 'testing.rst', 'LICENSE']: |
| | touch(os.path.join(self.temp_dir, fname)) |
| |
|
| | |
| | test_pkg = os.path.join(self.temp_dir, 'app') |
| | os.mkdir(test_pkg) |
| | for fname in ['__init__.py', 'a.txt', 'b.txt', 'c.rst']: |
| | touch(os.path.join(test_pkg, fname)) |
| |
|
| | |
| | static = os.path.join(test_pkg, 'static') |
| | os.mkdir(static) |
| | for fname in ['app.js', 'app.js.map', 'app.css', 'app.css.map']: |
| | touch(os.path.join(static, fname)) |
| |
|
| | def make_manifest(self, contents): |
| | """Write a MANIFEST.in.""" |
| | manifest = os.path.join(self.temp_dir, 'MANIFEST.in') |
| | with open(manifest, 'w', encoding="utf-8") as f: |
| | f.write(DALS(contents)) |
| |
|
| | def get_files(self): |
| | """Run egg_info and get all the files to include, as a set""" |
| | dist = Distribution(SETUP_ATTRS) |
| | dist.script_name = 'setup.py' |
| | cmd = egg_info(dist) |
| | cmd.ensure_finalized() |
| |
|
| | cmd.run() |
| |
|
| | return set(cmd.filelist.files) |
| |
|
| | def test_no_manifest(self): |
| | """Check a missing MANIFEST.in includes only the standard files.""" |
| | assert (default_files - set(['MANIFEST.in'])) == self.get_files() |
| |
|
| | def test_empty_files(self): |
| | """Check an empty MANIFEST.in includes only the standard files.""" |
| | self.make_manifest("") |
| | assert default_files == self.get_files() |
| |
|
| | def test_include(self): |
| | """Include extra rst files in the project root.""" |
| | self.make_manifest("include *.rst") |
| | files = default_files | set(['testing.rst', '.hidden.rst']) |
| | assert files == self.get_files() |
| |
|
| | def test_exclude(self): |
| | """Include everything in app/ except the text files""" |
| | ml = make_local_path |
| | self.make_manifest( |
| | """ |
| | include app/* |
| | exclude app/*.txt |
| | """ |
| | ) |
| | files = default_files | set([ml('app/c.rst')]) |
| | assert files == self.get_files() |
| |
|
| | def test_include_multiple(self): |
| | """Include with multiple patterns.""" |
| | ml = make_local_path |
| | self.make_manifest("include app/*.txt app/static/*") |
| | files = default_files | set([ |
| | ml('app/a.txt'), |
| | ml('app/b.txt'), |
| | ml('app/static/app.js'), |
| | ml('app/static/app.js.map'), |
| | ml('app/static/app.css'), |
| | ml('app/static/app.css.map'), |
| | ]) |
| | assert files == self.get_files() |
| |
|
| | def test_graft(self): |
| | """Include the whole app/static/ directory.""" |
| | ml = make_local_path |
| | self.make_manifest("graft app/static") |
| | files = default_files | set([ |
| | ml('app/static/app.js'), |
| | ml('app/static/app.js.map'), |
| | ml('app/static/app.css'), |
| | ml('app/static/app.css.map'), |
| | ]) |
| | assert files == self.get_files() |
| |
|
| | def test_graft_glob_syntax(self): |
| | """Include the whole app/static/ directory.""" |
| | ml = make_local_path |
| | self.make_manifest("graft */static") |
| | files = default_files | set([ |
| | ml('app/static/app.js'), |
| | ml('app/static/app.js.map'), |
| | ml('app/static/app.css'), |
| | ml('app/static/app.css.map'), |
| | ]) |
| | assert files == self.get_files() |
| |
|
| | def test_graft_global_exclude(self): |
| | """Exclude all *.map files in the project.""" |
| | ml = make_local_path |
| | self.make_manifest( |
| | """ |
| | graft app/static |
| | global-exclude *.map |
| | """ |
| | ) |
| | files = default_files | set([ml('app/static/app.js'), ml('app/static/app.css')]) |
| | assert files == self.get_files() |
| |
|
| | def test_global_include(self): |
| | """Include all *.rst, *.js, and *.css files in the whole tree.""" |
| | ml = make_local_path |
| | self.make_manifest( |
| | """ |
| | global-include *.rst *.js *.css |
| | """ |
| | ) |
| | files = default_files | set([ |
| | '.hidden.rst', |
| | 'testing.rst', |
| | ml('app/c.rst'), |
| | ml('app/static/app.js'), |
| | ml('app/static/app.css'), |
| | ]) |
| | assert files == self.get_files() |
| |
|
| | def test_graft_prune(self): |
| | """Include all files in app/, except for the whole app/static/ dir.""" |
| | ml = make_local_path |
| | self.make_manifest( |
| | """ |
| | graft app |
| | prune app/static |
| | """ |
| | ) |
| | files = default_files | set([ml('app/a.txt'), ml('app/b.txt'), ml('app/c.rst')]) |
| | assert files == self.get_files() |
| |
|
| |
|
| | class TestFileListTest(TempDirTestCase): |
| | """ |
| | A copy of the relevant bits of distutils/tests/test_filelist.py, |
| | to ensure setuptools' version of FileList keeps parity with distutils. |
| | """ |
| |
|
| | @pytest.fixture(autouse=os.getenv("SETUPTOOLS_USE_DISTUTILS") == "stdlib") |
| | def _compat_record_logs(self, monkeypatch, caplog): |
| | """Account for stdlib compatibility""" |
| |
|
| | def _log(_logger, level, msg, args): |
| | exc = sys.exc_info() |
| | rec = logging.LogRecord("distutils", level, "", 0, msg, args, exc) |
| | caplog.records.append(rec) |
| |
|
| | monkeypatch.setattr(log.Log, "_log", _log) |
| |
|
| | def get_records(self, caplog, *levels): |
| | return [r for r in caplog.records if r.levelno in levels] |
| |
|
| | def assertNoWarnings(self, caplog): |
| | assert self.get_records(caplog, log.WARN) == [] |
| | caplog.clear() |
| |
|
| | def assertWarnings(self, caplog): |
| | if IS_PYPY and not caplog.records: |
| | pytest.xfail("caplog checks may not work well in PyPy") |
| | else: |
| | assert len(self.get_records(caplog, log.WARN)) > 0 |
| | caplog.clear() |
| |
|
| | def make_files(self, files): |
| | for file in files: |
| | file = os.path.join(self.temp_dir, file) |
| | dirname, _basename = os.path.split(file) |
| | os.makedirs(dirname, exist_ok=True) |
| | touch(file) |
| |
|
| | def test_process_template_line(self): |
| | |
| | file_list = FileList() |
| | ml = make_local_path |
| |
|
| | |
| | self.make_files([ |
| | 'foo.tmp', |
| | 'ok', |
| | 'xo', |
| | 'four.txt', |
| | 'buildout.cfg', |
| | |
| | |
| | ml('.hg/last-message.txt'), |
| | ml('global/one.txt'), |
| | ml('global/two.txt'), |
| | ml('global/files.x'), |
| | ml('global/here.tmp'), |
| | ml('f/o/f.oo'), |
| | ml('dir/graft-one'), |
| | ml('dir/dir2/graft2'), |
| | ml('dir3/ok'), |
| | ml('dir3/sub/ok.txt'), |
| | ]) |
| |
|
| | MANIFEST_IN = DALS( |
| | """\ |
| | include ok |
| | include xo |
| | exclude xo |
| | include foo.tmp |
| | include buildout.cfg |
| | global-include *.x |
| | global-include *.txt |
| | global-exclude *.tmp |
| | recursive-include f *.oo |
| | recursive-exclude global *.x |
| | graft dir |
| | prune dir3 |
| | """ |
| | ) |
| |
|
| | for line in MANIFEST_IN.split('\n'): |
| | if not line: |
| | continue |
| | file_list.process_template_line(line) |
| |
|
| | wanted = [ |
| | 'buildout.cfg', |
| | 'four.txt', |
| | 'ok', |
| | ml('.hg/last-message.txt'), |
| | ml('dir/graft-one'), |
| | ml('dir/dir2/graft2'), |
| | ml('f/o/f.oo'), |
| | ml('global/one.txt'), |
| | ml('global/two.txt'), |
| | ] |
| |
|
| | file_list.sort() |
| | assert file_list.files == wanted |
| |
|
| | def test_exclude_pattern(self): |
| | |
| | file_list = FileList() |
| | assert not file_list.exclude_pattern('*.py') |
| |
|
| | |
| | file_list = FileList() |
| | file_list.files = ['a.py', 'b.py'] |
| | assert file_list.exclude_pattern('*.py') |
| |
|
| | |
| | file_list = FileList() |
| | file_list.files = ['a.py', 'a.txt'] |
| | file_list.exclude_pattern('*.py') |
| | file_list.sort() |
| | assert file_list.files == ['a.txt'] |
| |
|
| | def test_include_pattern(self): |
| | |
| | file_list = FileList() |
| | self.make_files([]) |
| | assert not file_list.include_pattern('*.py') |
| |
|
| | |
| | file_list = FileList() |
| | self.make_files(['a.py', 'b.txt']) |
| | assert file_list.include_pattern('*.py') |
| |
|
| | |
| | file_list = FileList() |
| | self.make_files(['a.py', 'b.txt']) |
| | file_list.include_pattern('*') |
| | file_list.sort() |
| | assert file_list.files == ['a.py', 'b.txt'] |
| |
|
| | def test_process_template_line_invalid(self): |
| | |
| | file_list = FileList() |
| | for action in ( |
| | 'include', |
| | 'exclude', |
| | 'global-include', |
| | 'global-exclude', |
| | 'recursive-include', |
| | 'recursive-exclude', |
| | 'graft', |
| | 'prune', |
| | 'blarg', |
| | ): |
| | with pytest.raises(DistutilsTemplateError): |
| | file_list.process_template_line(action) |
| |
|
| | def test_include(self, caplog): |
| | caplog.set_level(logging.DEBUG) |
| | ml = make_local_path |
| | |
| | file_list = FileList() |
| | self.make_files(['a.py', 'b.txt', ml('d/c.py')]) |
| |
|
| | file_list.process_template_line('include *.py') |
| | file_list.sort() |
| | assert file_list.files == ['a.py'] |
| | self.assertNoWarnings(caplog) |
| |
|
| | file_list.process_template_line('include *.rb') |
| | file_list.sort() |
| | assert file_list.files == ['a.py'] |
| | self.assertWarnings(caplog) |
| |
|
| | def test_exclude(self, caplog): |
| | caplog.set_level(logging.DEBUG) |
| | ml = make_local_path |
| | |
| | file_list = FileList() |
| | file_list.files = ['a.py', 'b.txt', ml('d/c.py')] |
| |
|
| | file_list.process_template_line('exclude *.py') |
| | file_list.sort() |
| | assert file_list.files == ['b.txt', ml('d/c.py')] |
| | self.assertNoWarnings(caplog) |
| |
|
| | file_list.process_template_line('exclude *.rb') |
| | file_list.sort() |
| | assert file_list.files == ['b.txt', ml('d/c.py')] |
| | self.assertWarnings(caplog) |
| |
|
| | def test_global_include(self, caplog): |
| | caplog.set_level(logging.DEBUG) |
| | ml = make_local_path |
| | |
| | file_list = FileList() |
| | self.make_files(['a.py', 'b.txt', ml('d/c.py')]) |
| |
|
| | file_list.process_template_line('global-include *.py') |
| | file_list.sort() |
| | assert file_list.files == ['a.py', ml('d/c.py')] |
| | self.assertNoWarnings(caplog) |
| |
|
| | file_list.process_template_line('global-include *.rb') |
| | file_list.sort() |
| | assert file_list.files == ['a.py', ml('d/c.py')] |
| | self.assertWarnings(caplog) |
| |
|
| | def test_global_exclude(self, caplog): |
| | caplog.set_level(logging.DEBUG) |
| | ml = make_local_path |
| | |
| | file_list = FileList() |
| | file_list.files = ['a.py', 'b.txt', ml('d/c.py')] |
| |
|
| | file_list.process_template_line('global-exclude *.py') |
| | file_list.sort() |
| | assert file_list.files == ['b.txt'] |
| | self.assertNoWarnings(caplog) |
| |
|
| | file_list.process_template_line('global-exclude *.rb') |
| | file_list.sort() |
| | assert file_list.files == ['b.txt'] |
| | self.assertWarnings(caplog) |
| |
|
| | def test_recursive_include(self, caplog): |
| | caplog.set_level(logging.DEBUG) |
| | ml = make_local_path |
| | |
| | file_list = FileList() |
| | self.make_files(['a.py', ml('d/b.py'), ml('d/c.txt'), ml('d/d/e.py')]) |
| |
|
| | file_list.process_template_line('recursive-include d *.py') |
| | file_list.sort() |
| | assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')] |
| | self.assertNoWarnings(caplog) |
| |
|
| | file_list.process_template_line('recursive-include e *.py') |
| | file_list.sort() |
| | assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')] |
| | self.assertWarnings(caplog) |
| |
|
| | def test_recursive_exclude(self, caplog): |
| | caplog.set_level(logging.DEBUG) |
| | ml = make_local_path |
| | |
| | file_list = FileList() |
| | file_list.files = ['a.py', ml('d/b.py'), ml('d/c.txt'), ml('d/d/e.py')] |
| |
|
| | file_list.process_template_line('recursive-exclude d *.py') |
| | file_list.sort() |
| | assert file_list.files == ['a.py', ml('d/c.txt')] |
| | self.assertNoWarnings(caplog) |
| |
|
| | file_list.process_template_line('recursive-exclude e *.py') |
| | file_list.sort() |
| | assert file_list.files == ['a.py', ml('d/c.txt')] |
| | self.assertWarnings(caplog) |
| |
|
| | def test_graft(self, caplog): |
| | caplog.set_level(logging.DEBUG) |
| | ml = make_local_path |
| | |
| | file_list = FileList() |
| | self.make_files(['a.py', ml('d/b.py'), ml('d/d/e.py'), ml('f/f.py')]) |
| |
|
| | file_list.process_template_line('graft d') |
| | file_list.sort() |
| | assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')] |
| | self.assertNoWarnings(caplog) |
| |
|
| | file_list.process_template_line('graft e') |
| | file_list.sort() |
| | assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')] |
| | self.assertWarnings(caplog) |
| |
|
| | def test_prune(self, caplog): |
| | caplog.set_level(logging.DEBUG) |
| | ml = make_local_path |
| | |
| | file_list = FileList() |
| | file_list.files = ['a.py', ml('d/b.py'), ml('d/d/e.py'), ml('f/f.py')] |
| |
|
| | file_list.process_template_line('prune d') |
| | file_list.sort() |
| | assert file_list.files == ['a.py', ml('f/f.py')] |
| | self.assertNoWarnings(caplog) |
| |
|
| | file_list.process_template_line('prune e') |
| | file_list.sort() |
| | assert file_list.files == ['a.py', ml('f/f.py')] |
| | self.assertWarnings(caplog) |
| |
|