| | """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() |
| | |
| | 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} |
| | """, |
| | }) |
| |
|
| | |
| | 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] |
| |
|
| | |
| | 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"] |
| |
|
| | |
| | sys.argv[1:] = ["--command-packages", "spork", "build"] |
| | d = self.create_distribution([file]) |
| | assert d.get_command_packages() == ["distutils.command", "spork"] |
| |
|
| | |
| | |
| | sys.argv[1:] = ["--command-packages", "", "build"] |
| | d = self.create_distribution([file]) |
| | assert d.get_command_packages() == ["distutils.command"] |
| |
|
| | def test_empty_options(self, request): |
| | |
| | |
| |
|
| | |
| | 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() |
| |
|
| | |
| | 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): |
| | |
| | dist = Distribution() |
| | with pytest.raises(TypeError): |
| | dist.announce('ok', level='ok2') |
| |
|
| | def test_find_config_files_disable(self, temp_home): |
| | |
| | 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() |
| |
|
| | |
| | assert len(all_files) - 1 == len(files) |
| |
|
| | def test_script_args_list_coercion(self): |
| | d = Distribution(attrs={'script_args': ('build', '--no-user-cfg')}) |
| |
|
| | |
| | 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) |
| | |
| | assert 'should be a list' in caplog.messages[0] |
| | |
| | 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) |
| | |
| | assert 'should be a list' in caplog.messages[0] |
| | |
| | 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) |
| | |
| | assert 'should be a list' in caplog.messages[0] |
| | |
| | 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): |
| | |
| | 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 |
| |
|
| | |
| | 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. |
| | """ |
| | |
| | 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() |
| |
|
| | |
| | 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) |
| |
|
| | |
| | |
| | orig_desc = set(orig["Description"].splitlines()) |
| | regen_desc = set(regen["Description"].splitlines()) |
| | assert regen_desc == orig_desc |
| |
|