| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552 |
- """Tests for distutils.dist."""
- import email
- import email.generator
- import email.policy
- import functools
- import io
- import os
- import sys
- import textwrap
- import unittest.mock as mock
- import warnings
- from distutils.cmd import Command
- from distutils.dist import Distribution, fix_help_options
- from distutils.tests import support
- from typing import ClassVar
- import jaraco.path
- import pytest
- pydistutils_cfg = '.' * (os.name == 'posix') + 'pydistutils.cfg'
- class test_dist(Command):
- """Sample distutils extension command."""
- user_options: ClassVar[list[tuple[str, str, str]]] = [
- ("sample-option=", "S", "help text"),
- ]
- def initialize_options(self):
- self.sample_option = None
- class TestDistribution(Distribution):
- """Distribution subclasses that avoids the default search for
- configuration files.
- The ._config_files attribute must be set before
- .parse_config_files() is called.
- """
- def find_config_files(self):
- return self._config_files
- @pytest.fixture
- def clear_argv():
- del sys.argv[1:]
- @support.combine_markers
- @pytest.mark.usefixtures('save_env')
- @pytest.mark.usefixtures('save_argv')
- class TestDistributionBehavior(support.TempdirManager):
- def create_distribution(self, configfiles=()):
- d = TestDistribution()
- d._config_files = configfiles
- d.parse_config_files()
- d.parse_command_line()
- return d
- def test_command_packages_unspecified(self, clear_argv):
- sys.argv.append("build")
- d = self.create_distribution()
- assert d.get_command_packages() == ["distutils.command"]
- def test_command_packages_cmdline(self, clear_argv):
- from distutils.tests.test_dist import test_dist
- sys.argv.extend([
- "--command-packages",
- "foo.bar,distutils.tests",
- "test_dist",
- "-Ssometext",
- ])
- d = self.create_distribution()
- # let's actually try to load our test command:
- assert d.get_command_packages() == [
- "distutils.command",
- "foo.bar",
- "distutils.tests",
- ]
- cmd = d.get_command_obj("test_dist")
- assert isinstance(cmd, test_dist)
- assert cmd.sample_option == "sometext"
- @pytest.mark.skipif(
- 'distutils' not in Distribution.parse_config_files.__module__,
- reason='Cannot test when virtualenv has monkey-patched Distribution',
- )
- def test_venv_install_options(self, tmp_path, clear_argv):
- sys.argv.append("install")
- file = str(tmp_path / 'file')
- fakepath = '/somedir'
- jaraco.path.build({
- file: f"""
- [install]
- install-base = {fakepath}
- install-platbase = {fakepath}
- install-lib = {fakepath}
- install-platlib = {fakepath}
- install-purelib = {fakepath}
- install-headers = {fakepath}
- install-scripts = {fakepath}
- install-data = {fakepath}
- prefix = {fakepath}
- exec-prefix = {fakepath}
- home = {fakepath}
- user = {fakepath}
- root = {fakepath}
- """,
- })
- # Base case: Not in a Virtual Environment
- with mock.patch.multiple(sys, prefix='/a', base_prefix='/a'):
- d = self.create_distribution([file])
- option_tuple = (file, fakepath)
- result_dict = {
- 'install_base': option_tuple,
- 'install_platbase': option_tuple,
- 'install_lib': option_tuple,
- 'install_platlib': option_tuple,
- 'install_purelib': option_tuple,
- 'install_headers': option_tuple,
- 'install_scripts': option_tuple,
- 'install_data': option_tuple,
- 'prefix': option_tuple,
- 'exec_prefix': option_tuple,
- 'home': option_tuple,
- 'user': option_tuple,
- 'root': option_tuple,
- }
- assert sorted(d.command_options.get('install').keys()) == sorted(
- result_dict.keys()
- )
- for key, value in d.command_options.get('install').items():
- assert value == result_dict[key]
- # Test case: In a Virtual Environment
- with mock.patch.multiple(sys, prefix='/a', base_prefix='/b'):
- d = self.create_distribution([file])
- for key in result_dict.keys():
- assert key not in d.command_options.get('install', {})
- def test_command_packages_configfile(self, tmp_path, clear_argv):
- sys.argv.append("build")
- file = str(tmp_path / "file")
- jaraco.path.build({
- file: """
- [global]
- command_packages = foo.bar, splat
- """,
- })
- d = self.create_distribution([file])
- assert d.get_command_packages() == ["distutils.command", "foo.bar", "splat"]
- # ensure command line overrides config:
- sys.argv[1:] = ["--command-packages", "spork", "build"]
- d = self.create_distribution([file])
- assert d.get_command_packages() == ["distutils.command", "spork"]
- # Setting --command-packages to '' should cause the default to
- # be used even if a config file specified something else:
- sys.argv[1:] = ["--command-packages", "", "build"]
- d = self.create_distribution([file])
- assert d.get_command_packages() == ["distutils.command"]
- def test_empty_options(self, request):
- # an empty options dictionary should not stay in the
- # list of attributes
- # catching warnings
- warns = []
- def _warn(msg):
- warns.append(msg)
- request.addfinalizer(
- functools.partial(setattr, warnings, 'warn', warnings.warn)
- )
- warnings.warn = _warn
- dist = Distribution(
- attrs={
- 'author': 'xxx',
- 'name': 'xxx',
- 'version': 'xxx',
- 'url': 'xxxx',
- 'options': {},
- }
- )
- assert len(warns) == 0
- assert 'options' not in dir(dist)
- def test_finalize_options(self):
- attrs = {'keywords': 'one,two', 'platforms': 'one,two'}
- dist = Distribution(attrs=attrs)
- dist.finalize_options()
- # finalize_option splits platforms and keywords
- assert dist.metadata.platforms == ['one', 'two']
- assert dist.metadata.keywords == ['one', 'two']
- attrs = {'keywords': 'foo bar', 'platforms': 'foo bar'}
- dist = Distribution(attrs=attrs)
- dist.finalize_options()
- assert dist.metadata.platforms == ['foo bar']
- assert dist.metadata.keywords == ['foo bar']
- def test_get_command_packages(self):
- dist = Distribution()
- assert dist.command_packages is None
- cmds = dist.get_command_packages()
- assert cmds == ['distutils.command']
- assert dist.command_packages == ['distutils.command']
- dist.command_packages = 'one,two'
- cmds = dist.get_command_packages()
- assert cmds == ['distutils.command', 'one', 'two']
- def test_announce(self):
- # make sure the level is known
- dist = Distribution()
- with pytest.raises(TypeError):
- dist.announce('ok', level='ok2')
- def test_find_config_files_disable(self, temp_home):
- # Ticket #1180: Allow user to disable their home config file.
- jaraco.path.build({pydistutils_cfg: '[distutils]\n'}, temp_home)
- d = Distribution()
- all_files = d.find_config_files()
- d = Distribution(attrs={'script_args': ['--no-user-cfg']})
- files = d.find_config_files()
- # make sure --no-user-cfg disables the user cfg file
- assert len(all_files) - 1 == len(files)
- def test_script_args_list_coercion(self):
- d = Distribution(attrs={'script_args': ('build', '--no-user-cfg')})
- # make sure script_args is a list even if it started as a different iterable
- assert d.script_args == ['build', '--no-user-cfg']
- @pytest.mark.skipif(
- 'platform.system() == "Windows"',
- reason='Windows does not honor chmod 000',
- )
- def test_find_config_files_permission_error(self, fake_home):
- """
- Finding config files should not fail when directory is inaccessible.
- """
- fake_home.joinpath(pydistutils_cfg).write_text('', encoding='utf-8')
- fake_home.chmod(0o000)
- Distribution().find_config_files()
- @pytest.mark.usefixtures('save_env')
- @pytest.mark.usefixtures('save_argv')
- class TestMetadata(support.TempdirManager):
- def format_metadata(self, dist):
- sio = io.StringIO()
- dist.metadata.write_pkg_file(sio)
- return sio.getvalue()
- def test_simple_metadata(self):
- attrs = {"name": "package", "version": "1.0"}
- dist = Distribution(attrs)
- meta = self.format_metadata(dist)
- assert "Metadata-Version: 1.0" in meta
- assert "provides:" not in meta.lower()
- assert "requires:" not in meta.lower()
- assert "obsoletes:" not in meta.lower()
- def test_provides(self):
- attrs = {
- "name": "package",
- "version": "1.0",
- "provides": ["package", "package.sub"],
- }
- dist = Distribution(attrs)
- assert dist.metadata.get_provides() == ["package", "package.sub"]
- assert dist.get_provides() == ["package", "package.sub"]
- meta = self.format_metadata(dist)
- assert "Metadata-Version: 1.1" in meta
- assert "requires:" not in meta.lower()
- assert "obsoletes:" not in meta.lower()
- def test_provides_illegal(self):
- with pytest.raises(ValueError):
- Distribution(
- {"name": "package", "version": "1.0", "provides": ["my.pkg (splat)"]},
- )
- def test_requires(self):
- attrs = {
- "name": "package",
- "version": "1.0",
- "requires": ["other", "another (==1.0)"],
- }
- dist = Distribution(attrs)
- assert dist.metadata.get_requires() == ["other", "another (==1.0)"]
- assert dist.get_requires() == ["other", "another (==1.0)"]
- meta = self.format_metadata(dist)
- assert "Metadata-Version: 1.1" in meta
- assert "provides:" not in meta.lower()
- assert "Requires: other" in meta
- assert "Requires: another (==1.0)" in meta
- assert "obsoletes:" not in meta.lower()
- def test_requires_illegal(self):
- with pytest.raises(ValueError):
- Distribution(
- {"name": "package", "version": "1.0", "requires": ["my.pkg (splat)"]},
- )
- def test_requires_to_list(self):
- attrs = {"name": "package", "requires": iter(["other"])}
- dist = Distribution(attrs)
- assert isinstance(dist.metadata.requires, list)
- def test_obsoletes(self):
- attrs = {
- "name": "package",
- "version": "1.0",
- "obsoletes": ["other", "another (<1.0)"],
- }
- dist = Distribution(attrs)
- assert dist.metadata.get_obsoletes() == ["other", "another (<1.0)"]
- assert dist.get_obsoletes() == ["other", "another (<1.0)"]
- meta = self.format_metadata(dist)
- assert "Metadata-Version: 1.1" in meta
- assert "provides:" not in meta.lower()
- assert "requires:" not in meta.lower()
- assert "Obsoletes: other" in meta
- assert "Obsoletes: another (<1.0)" in meta
- def test_obsoletes_illegal(self):
- with pytest.raises(ValueError):
- Distribution(
- {"name": "package", "version": "1.0", "obsoletes": ["my.pkg (splat)"]},
- )
- def test_obsoletes_to_list(self):
- attrs = {"name": "package", "obsoletes": iter(["other"])}
- dist = Distribution(attrs)
- assert isinstance(dist.metadata.obsoletes, list)
- def test_classifier(self):
- attrs = {
- 'name': 'Boa',
- 'version': '3.0',
- 'classifiers': ['Programming Language :: Python :: 3'],
- }
- dist = Distribution(attrs)
- assert dist.get_classifiers() == ['Programming Language :: Python :: 3']
- meta = self.format_metadata(dist)
- assert 'Metadata-Version: 1.1' in meta
- def test_classifier_invalid_type(self, caplog):
- attrs = {
- 'name': 'Boa',
- 'version': '3.0',
- 'classifiers': ('Programming Language :: Python :: 3',),
- }
- d = Distribution(attrs)
- # should have warning about passing a non-list
- assert 'should be a list' in caplog.messages[0]
- # should be converted to a list
- assert isinstance(d.metadata.classifiers, list)
- assert d.metadata.classifiers == list(attrs['classifiers'])
- def test_keywords(self):
- attrs = {
- 'name': 'Monty',
- 'version': '1.0',
- 'keywords': ['spam', 'eggs', 'life of brian'],
- }
- dist = Distribution(attrs)
- assert dist.get_keywords() == ['spam', 'eggs', 'life of brian']
- def test_keywords_invalid_type(self, caplog):
- attrs = {
- 'name': 'Monty',
- 'version': '1.0',
- 'keywords': ('spam', 'eggs', 'life of brian'),
- }
- d = Distribution(attrs)
- # should have warning about passing a non-list
- assert 'should be a list' in caplog.messages[0]
- # should be converted to a list
- assert isinstance(d.metadata.keywords, list)
- assert d.metadata.keywords == list(attrs['keywords'])
- def test_platforms(self):
- attrs = {
- 'name': 'Monty',
- 'version': '1.0',
- 'platforms': ['GNU/Linux', 'Some Evil Platform'],
- }
- dist = Distribution(attrs)
- assert dist.get_platforms() == ['GNU/Linux', 'Some Evil Platform']
- def test_platforms_invalid_types(self, caplog):
- attrs = {
- 'name': 'Monty',
- 'version': '1.0',
- 'platforms': ('GNU/Linux', 'Some Evil Platform'),
- }
- d = Distribution(attrs)
- # should have warning about passing a non-list
- assert 'should be a list' in caplog.messages[0]
- # should be converted to a list
- assert isinstance(d.metadata.platforms, list)
- assert d.metadata.platforms == list(attrs['platforms'])
- def test_download_url(self):
- attrs = {
- 'name': 'Boa',
- 'version': '3.0',
- 'download_url': 'http://example.org/boa',
- }
- dist = Distribution(attrs)
- meta = self.format_metadata(dist)
- assert 'Metadata-Version: 1.1' in meta
- def test_long_description(self):
- long_desc = textwrap.dedent(
- """\
- example::
- We start here
- and continue here
- and end here."""
- )
- attrs = {"name": "package", "version": "1.0", "long_description": long_desc}
- dist = Distribution(attrs)
- meta = self.format_metadata(dist)
- meta = meta.replace('\n' + 8 * ' ', '\n')
- assert long_desc in meta
- def test_custom_pydistutils(self, temp_home):
- """
- pydistutils.cfg is found
- """
- jaraco.path.build({pydistutils_cfg: ''}, temp_home)
- config_path = temp_home / pydistutils_cfg
- assert str(config_path) in Distribution().find_config_files()
- def test_extra_pydistutils(self, monkeypatch, tmp_path):
- jaraco.path.build({'overrides.cfg': ''}, tmp_path)
- filename = tmp_path / 'overrides.cfg'
- monkeypatch.setenv('DIST_EXTRA_CONFIG', str(filename))
- assert str(filename) in Distribution().find_config_files()
- def test_fix_help_options(self):
- help_tuples = [('a', 'b', 'c', 'd'), (1, 2, 3, 4)]
- fancy_options = fix_help_options(help_tuples)
- assert fancy_options[0] == ('a', 'b', 'c')
- assert fancy_options[1] == (1, 2, 3)
- def test_show_help(self, request, capsys):
- # smoke test, just makes sure some help is displayed
- dist = Distribution()
- sys.argv = []
- dist.help = True
- dist.script_name = 'setup.py'
- dist.parse_command_line()
- output = [
- line for line in capsys.readouterr().out.split('\n') if line.strip() != ''
- ]
- assert output
- def test_read_metadata(self):
- attrs = {
- "name": "package",
- "version": "1.0",
- "long_description": "desc",
- "description": "xxx",
- "download_url": "http://example.com",
- "keywords": ['one', 'two'],
- "requires": ['foo'],
- }
- dist = Distribution(attrs)
- metadata = dist.metadata
- # write it then reloads it
- PKG_INFO = io.StringIO()
- metadata.write_pkg_file(PKG_INFO)
- PKG_INFO.seek(0)
- metadata.read_pkg_file(PKG_INFO)
- assert metadata.name == "package"
- assert metadata.version == "1.0"
- assert metadata.description == "xxx"
- assert metadata.download_url == 'http://example.com'
- assert metadata.keywords == ['one', 'two']
- assert metadata.platforms is None
- assert metadata.obsoletes is None
- assert metadata.requires == ['foo']
- def test_round_trip_through_email_generator(self):
- """
- In pypa/setuptools#4033, it was shown that once PKG-INFO is
- re-generated using ``email.generator.Generator``, some control
- characters might cause problems.
- """
- # Given a PKG-INFO file ...
- attrs = {
- "name": "package",
- "version": "1.0",
- "long_description": "hello\x0b\nworld\n",
- }
- dist = Distribution(attrs)
- metadata = dist.metadata
- with io.StringIO() as buffer:
- metadata.write_pkg_file(buffer)
- msg = buffer.getvalue()
- # ... when it is read and re-written using stdlib's email library,
- orig = email.message_from_string(msg)
- policy = email.policy.EmailPolicy(
- utf8=True,
- mangle_from_=False,
- max_line_length=0,
- )
- with io.StringIO() as buffer:
- email.generator.Generator(buffer, policy=policy).flatten(orig)
- buffer.seek(0)
- regen = email.message_from_file(buffer)
- # ... then it should be the same as the original
- # (except for the specific line break characters)
- orig_desc = set(orig["Description"].splitlines())
- regen_desc = set(regen["Description"].splitlines())
- assert regen_desc == orig_desc
|