| """Tests for packaging.dist.""" |
| import os |
| import io |
| import sys |
| import logging |
| import textwrap |
| import sysconfig |
| import packaging.dist |
| |
| from packaging.dist import Distribution |
| from packaging.command import set_command |
| from packaging.command.cmd import Command |
| from packaging.metadata import Metadata |
| from packaging.errors import PackagingModuleError, PackagingOptionError |
| from packaging.tests import TESTFN, captured_stdout |
| from packaging.tests import support, unittest |
| from packaging.tests.support import create_distribution |
| from test.support import unload |
| |
| |
| class test_dist(Command): |
| """Sample packaging extension command.""" |
| |
| user_options = [ |
| ("sample-option=", "S", "help text"), |
| ] |
| |
| def initialize_options(self): |
| self.sample_option = None |
| |
| def finalize_options(self): |
| pass |
| |
| |
| class DistributionTestCase(support.TempdirManager, |
| support.LoggingCatcher, |
| support.EnvironRestorer, |
| unittest.TestCase): |
| |
| restore_environ = ['HOME', 'PLAT'] |
| |
| def setUp(self): |
| super(DistributionTestCase, self).setUp() |
| self.argv = sys.argv, sys.argv[:] |
| del sys.argv[1:] |
| |
| def tearDown(self): |
| sys.argv = self.argv[0] |
| sys.argv[:] = self.argv[1] |
| super(DistributionTestCase, self).tearDown() |
| |
| def test_debug_mode(self): |
| self.addCleanup(os.unlink, TESTFN) |
| with open(TESTFN, "w") as f: |
| f.write("[global]\n") |
| f.write("command_packages = foo.bar, splat") |
| |
| files = [TESTFN] |
| sys.argv.append("build") |
| __, stdout = captured_stdout(create_distribution, files) |
| self.assertEqual(stdout, '') |
| packaging.dist.DEBUG = True |
| try: |
| __, stdout = captured_stdout(create_distribution, files) |
| self.assertEqual(stdout, '') |
| finally: |
| packaging.dist.DEBUG = False |
| |
| def test_write_pkg_file(self): |
| # Check Metadata handling of Unicode fields |
| tmp_dir = self.mkdtemp() |
| my_file = os.path.join(tmp_dir, 'f') |
| cls = Distribution |
| |
| dist = cls(attrs={'author': 'Mister Café', |
| 'name': 'my.package', |
| 'maintainer': 'Café Junior', |
| 'summary': 'Café torréfié', |
| 'description': 'Héhéhé'}) |
| |
| # let's make sure the file can be written |
| # with Unicode fields. they are encoded with |
| # PKG_INFO_ENCODING |
| with open(my_file, 'w', encoding='utf-8') as fp: |
| dist.metadata.write_file(fp) |
| |
| # regular ascii is of course always usable |
| dist = cls(attrs={'author': 'Mister Cafe', |
| 'name': 'my.package', |
| 'maintainer': 'Cafe Junior', |
| 'summary': 'Cafe torrefie', |
| 'description': 'Hehehe'}) |
| |
| with open(my_file, 'w') as fp: |
| dist.metadata.write_file(fp) |
| |
| def test_bad_attr(self): |
| Distribution(attrs={'author': 'xxx', |
| 'name': 'xxx', |
| 'version': '1.2', |
| 'home-page': 'xxxx', |
| 'badoptname': 'xxx'}) |
| logs = self.get_logs(logging.WARNING) |
| self.assertEqual(1, len(logs)) |
| self.assertIn('unknown argument', logs[0]) |
| |
| def test_bad_version(self): |
| Distribution(attrs={'author': 'xxx', |
| 'name': 'xxx', |
| 'version': 'xxx', |
| 'home-page': 'xxxx'}) |
| logs = self.get_logs(logging.WARNING) |
| self.assertEqual(1, len(logs)) |
| self.assertIn('not a valid version', logs[0]) |
| |
| def test_empty_options(self): |
| # an empty options dictionary should not stay in the |
| # list of attributes |
| Distribution(attrs={'author': 'xxx', |
| 'name': 'xxx', |
| 'version': '1.2', |
| 'home-page': 'xxxx', |
| 'options': {}}) |
| |
| self.assertEqual([], self.get_logs(logging.WARNING)) |
| |
| def test_non_empty_options(self): |
| # TODO: how to actually use options is not documented except |
| # for a few cryptic comments in dist.py. If this is to stay |
| # in the public API, it deserves some better documentation. |
| |
| # Here is an example of how it's used out there: |
| # http://svn.pythonmac.org/py2app/py2app/trunk/doc/ |
| # index.html#specifying-customizations |
| dist = Distribution(attrs={'author': 'xxx', |
| 'name': 'xxx', |
| 'version': 'xxx', |
| 'home-page': 'xxxx', |
| 'options': {'sdist': {'owner': 'root'}}}) |
| |
| self.assertIn('owner', dist.get_option_dict('sdist')) |
| |
| def test_finalize_options(self): |
| |
| attrs = {'keywords': 'one,two', |
| 'platform': 'one,two'} |
| |
| dist = Distribution(attrs=attrs) |
| dist.finalize_options() |
| |
| # finalize_option splits platforms and keywords |
| self.assertEqual(dist.metadata['platform'], ['one', 'two']) |
| self.assertEqual(dist.metadata['keywords'], ['one', 'two']) |
| |
| def test_find_config_files_disable(self): |
| # Bug #1180: Allow users to disable their own config file. |
| temp_home = self.mkdtemp() |
| if os.name == 'posix': |
| user_filename = os.path.join(temp_home, ".pydistutils.cfg") |
| else: |
| user_filename = os.path.join(temp_home, "pydistutils.cfg") |
| |
| with open(user_filename, 'w') as f: |
| f.write('[distutils2]\n') |
| |
| def _expander(path): |
| return temp_home |
| |
| old_expander = os.path.expanduser |
| os.path.expanduser = _expander |
| try: |
| d = packaging.dist.Distribution() |
| all_files = d.find_config_files() |
| |
| d = packaging.dist.Distribution(attrs={'script_args': |
| ['--no-user-cfg']}) |
| files = d.find_config_files() |
| finally: |
| os.path.expanduser = old_expander |
| |
| # make sure --no-user-cfg disables the user cfg file |
| self.assertEqual((len(all_files) - 1), len(files)) |
| |
| def test_special_hooks_parsing(self): |
| temp_home = self.mkdtemp() |
| config_files = [os.path.join(temp_home, "config1.cfg"), |
| os.path.join(temp_home, "config2.cfg")] |
| |
| # Store two aliased hooks in config files |
| self.write_file((temp_home, "config1.cfg"), |
| '[test_dist]\npre-hook.a = type') |
| self.write_file((temp_home, "config2.cfg"), |
| '[test_dist]\npre-hook.b = type') |
| |
| set_command('packaging.tests.test_dist.test_dist') |
| dist = create_distribution(config_files) |
| cmd = dist.get_command_obj("test_dist") |
| self.assertEqual(cmd.pre_hook, {"a": 'type', "b": 'type'}) |
| |
| def test_hooks_get_run(self): |
| temp_home = self.mkdtemp() |
| module_name = os.path.split(temp_home)[-1] |
| pyname = '%s.py' % module_name |
| config_file = os.path.join(temp_home, "config1.cfg") |
| hooks_module = os.path.join(temp_home, pyname) |
| |
| self.write_file(config_file, textwrap.dedent('''\ |
| [test_dist] |
| pre-hook.test = %(modname)s.log_pre_call |
| post-hook.test = %(modname)s.log_post_call''' |
| % {'modname': module_name})) |
| |
| self.write_file(hooks_module, textwrap.dedent('''\ |
| record = [] |
| |
| def log_pre_call(cmd): |
| record.append('pre-%s' % cmd.get_command_name()) |
| |
| def log_post_call(cmd): |
| record.append('post-%s' % cmd.get_command_name()) |
| ''')) |
| |
| set_command('packaging.tests.test_dist.test_dist') |
| d = create_distribution([config_file]) |
| cmd = d.get_command_obj("test_dist") |
| |
| # prepare the call recorders |
| sys.path.append(temp_home) |
| self.addCleanup(sys.path.remove, temp_home) |
| self.addCleanup(unload, module_name) |
| record = __import__(module_name).record |
| |
| cmd.run = lambda: record.append('run') |
| cmd.finalize_options = lambda: record.append('finalize') |
| d.run_command('test_dist') |
| |
| self.assertEqual(record, ['finalize', |
| 'pre-test_dist', |
| 'run', |
| 'post-test_dist']) |
| |
| def test_hooks_importable(self): |
| temp_home = self.mkdtemp() |
| config_file = os.path.join(temp_home, "config1.cfg") |
| |
| self.write_file(config_file, textwrap.dedent('''\ |
| [test_dist] |
| pre-hook.test = nonexistent.dotted.name''')) |
| |
| set_command('packaging.tests.test_dist.test_dist') |
| d = create_distribution([config_file]) |
| cmd = d.get_command_obj("test_dist") |
| cmd.ensure_finalized() |
| |
| self.assertRaises(PackagingModuleError, d.run_command, 'test_dist') |
| |
| def test_hooks_callable(self): |
| temp_home = self.mkdtemp() |
| config_file = os.path.join(temp_home, "config1.cfg") |
| |
| self.write_file(config_file, textwrap.dedent('''\ |
| [test_dist] |
| pre-hook.test = packaging.tests.test_dist.__doc__''')) |
| |
| set_command('packaging.tests.test_dist.test_dist') |
| d = create_distribution([config_file]) |
| cmd = d.get_command_obj("test_dist") |
| cmd.ensure_finalized() |
| |
| self.assertRaises(PackagingOptionError, d.run_command, 'test_dist') |
| |
| |
| class MetadataTestCase(support.TempdirManager, |
| support.LoggingCatcher, |
| support.EnvironRestorer, |
| unittest.TestCase): |
| |
| restore_environ = ['HOME'] |
| |
| def setUp(self): |
| super(MetadataTestCase, self).setUp() |
| self.argv = sys.argv, sys.argv[:] |
| |
| def tearDown(self): |
| sys.argv = self.argv[0] |
| sys.argv[:] = self.argv[1] |
| super(MetadataTestCase, self).tearDown() |
| |
| def test_simple_metadata(self): |
| attrs = {"name": "package", |
| "version": "1.0"} |
| dist = Distribution(attrs) |
| meta = self.format_metadata(dist) |
| self.assertIn("Metadata-Version: 1.0", meta) |
| self.assertNotIn("provides:", meta.lower()) |
| self.assertNotIn("requires:", meta.lower()) |
| self.assertNotIn("obsoletes:", meta.lower()) |
| |
| def test_provides_dist(self): |
| attrs = {"name": "package", |
| "version": "1.0", |
| "provides_dist": ["package", "package.sub"]} |
| dist = Distribution(attrs) |
| self.assertEqual(dist.metadata['Provides-Dist'], |
| ["package", "package.sub"]) |
| meta = self.format_metadata(dist) |
| self.assertIn("Metadata-Version: 1.2", meta) |
| self.assertNotIn("requires:", meta.lower()) |
| self.assertNotIn("obsoletes:", meta.lower()) |
| |
| def _test_provides_illegal(self): |
| # XXX to do: check the versions |
| self.assertRaises(ValueError, Distribution, |
| {"name": "package", |
| "version": "1.0", |
| "provides_dist": ["my.pkg (splat)"]}) |
| |
| def test_requires_dist(self): |
| attrs = {"name": "package", |
| "version": "1.0", |
| "requires_dist": ["other", "another (==1.0)"]} |
| dist = Distribution(attrs) |
| self.assertEqual(dist.metadata['Requires-Dist'], |
| ["other", "another (==1.0)"]) |
| meta = self.format_metadata(dist) |
| self.assertIn("Metadata-Version: 1.2", meta) |
| self.assertNotIn("provides:", meta.lower()) |
| self.assertIn("Requires-Dist: other", meta) |
| self.assertIn("Requires-Dist: another (==1.0)", meta) |
| self.assertNotIn("obsoletes:", meta.lower()) |
| |
| def _test_requires_illegal(self): |
| # XXX |
| self.assertRaises(ValueError, Distribution, |
| {"name": "package", |
| "version": "1.0", |
| "requires": ["my.pkg (splat)"]}) |
| |
| def test_obsoletes_dist(self): |
| attrs = {"name": "package", |
| "version": "1.0", |
| "obsoletes_dist": ["other", "another (<1.0)"]} |
| dist = Distribution(attrs) |
| self.assertEqual(dist.metadata['Obsoletes-Dist'], |
| ["other", "another (<1.0)"]) |
| meta = self.format_metadata(dist) |
| self.assertIn("Metadata-Version: 1.2", meta) |
| self.assertNotIn("provides:", meta.lower()) |
| self.assertNotIn("requires:", meta.lower()) |
| self.assertIn("Obsoletes-Dist: other", meta) |
| self.assertIn("Obsoletes-Dist: another (<1.0)", meta) |
| |
| def _test_obsoletes_illegal(self): |
| # XXX |
| self.assertRaises(ValueError, Distribution, |
| {"name": "package", |
| "version": "1.0", |
| "obsoletes": ["my.pkg (splat)"]}) |
| |
| def format_metadata(self, dist): |
| sio = io.StringIO() |
| dist.metadata.write_file(sio) |
| return sio.getvalue() |
| |
| def test_custom_pydistutils(self): |
| # fixes #2166 |
| # make sure pydistutils.cfg is found |
| if os.name == 'posix': |
| user_filename = ".pydistutils.cfg" |
| else: |
| user_filename = "pydistutils.cfg" |
| |
| temp_dir = self.mkdtemp() |
| user_filename = os.path.join(temp_dir, user_filename) |
| with open(user_filename, 'w') as f: |
| f.write('.') |
| |
| dist = Distribution() |
| |
| # linux-style |
| if sys.platform in ('linux', 'darwin'): |
| os.environ['HOME'] = temp_dir |
| files = dist.find_config_files() |
| self.assertIn(user_filename, files) |
| |
| # win32-style |
| if sys.platform == 'win32': |
| # home drive should be found |
| os.environ['HOME'] = temp_dir |
| files = dist.find_config_files() |
| self.assertIn(user_filename, files) |
| |
| def test_show_help(self): |
| # smoke test, just makes sure some help is displayed |
| dist = Distribution() |
| sys.argv = [] |
| dist.help = True |
| dist.script_name = os.path.join(sysconfig.get_path('scripts'), |
| 'pysetup') |
| __, stdout = captured_stdout(dist.parse_command_line) |
| output = [line for line in stdout.split('\n') |
| if line.strip() != ''] |
| self.assertGreater(len(output), 0) |
| |
| def test_description(self): |
| desc = textwrap.dedent("""\ |
| example:: |
| We start here |
| and continue here |
| and end here.""") |
| attrs = {"name": "package", |
| "version": "1.0", |
| "description": desc} |
| |
| dist = packaging.dist.Distribution(attrs) |
| meta = self.format_metadata(dist) |
| meta = meta.replace('\n' + 7 * ' ' + '|', '\n') |
| self.assertIn(desc, meta) |
| |
| def test_read_metadata(self): |
| attrs = {"name": "package", |
| "version": "1.0", |
| "description": "desc", |
| "summary": "xxx", |
| "download_url": "http://example.com", |
| "keywords": ['one', 'two'], |
| "requires_dist": ['foo']} |
| |
| dist = Distribution(attrs) |
| PKG_INFO = io.StringIO() |
| dist.metadata.write_file(PKG_INFO) |
| PKG_INFO.seek(0) |
| |
| metadata = Metadata() |
| metadata.read_file(PKG_INFO) |
| |
| self.assertEqual(metadata['name'], "package") |
| self.assertEqual(metadata['version'], "1.0") |
| self.assertEqual(metadata['summary'], "xxx") |
| self.assertEqual(metadata['download_url'], 'http://example.com') |
| self.assertEqual(metadata['keywords'], ['one', 'two']) |
| self.assertEqual(metadata['platform'], []) |
| self.assertEqual(metadata['obsoletes'], []) |
| self.assertEqual(metadata['requires-dist'], ['foo']) |
| |
| |
| def test_suite(): |
| suite = unittest.TestSuite() |
| suite.addTest(unittest.makeSuite(DistributionTestCase)) |
| suite.addTest(unittest.makeSuite(MetadataTestCase)) |
| return suite |
| |
| if __name__ == "__main__": |
| unittest.main(defaultTest="test_suite") |