diff --git a/Doc/install/pysetup.rst b/Doc/install/pysetup.rst index 08ba08ed2e5..f6f1f837da3 100644 --- a/Doc/install/pysetup.rst +++ b/Doc/install/pysetup.rst @@ -19,13 +19,12 @@ Finding out what's installed Pysetup makes it easy to find out what Python packages are installed:: - $ pysetup search virtualenv - virtualenv 1.6 at /opt/python3.3/lib/python3.3/site-packages/virtualenv-1.6-py3.3.egg-info + $ pysetup list virtualenv + 'virtualenv' 1.6 at '/opt/python3.3/lib/python3.3/site-packages/virtualenv-1.6-py3.3.egg-info' - $ pysetup search --all - pyverify 0.8.1 at /opt/python3.3/lib/python3.3/site-packages/pyverify-0.8.1.dist-info - virtualenv 1.6 at /opt/python3.3/lib/python3.3/site-packages/virtualenv-1.6-py3.3.egg-info - wsgiref 0.1.2 at /opt/python3.3/lib/python3.3/wsgiref.egg-info + $ pysetup list + 'pyverify' 0.8.1 at '/opt/python3.3/lib/python3.3/site-packages/pyverify-0.8.1.dist-info' + 'virtualenv' 1.6 at '/opt/python3.3/lib/python3.3/site-packages/virtualenv-1.6-py3.3.egg-info' ... @@ -146,9 +145,11 @@ Getting a list of all pysetup actions and global options:: metadata: Display the metadata of a project install: Install a project remove: Remove a project - search: Search for a project + search: Search for a project in the indexes + list: List installed projects graph: Display a graph - create: Create a Project + create: Create a project + generate-setup: Generate a backward-comptatible setup.py To get more help on an action, use: diff --git a/Lib/distutils/dist.py b/Lib/distutils/dist.py index 8ca5b6f4f12..69825f206f2 100644 --- a/Lib/distutils/dist.py +++ b/Lib/distutils/dist.py @@ -1018,7 +1018,8 @@ class DistributionMetadata: """Write the PKG-INFO format data to a file object. """ version = '1.0' - if self.provides or self.requires or self.obsoletes: + if (self.provides or self.requires or self.obsoletes or + self.classifiers or self.download_url): version = '1.1' file.write('Metadata-Version: %s\n' % version) diff --git a/Lib/distutils/tests/test_dist.py b/Lib/distutils/tests/test_dist.py index a20d6c8ffcc..8aaae88cae5 100644 --- a/Lib/distutils/tests/test_dist.py +++ b/Lib/distutils/tests/test_dist.py @@ -74,7 +74,7 @@ class DistributionTestCase(support.LoggingSilencer, self.assertEqual(d.get_command_packages(), ["distutils.command", "foo.bar", "distutils.tests"]) cmd = d.get_command_obj("test_dist") - self.assertTrue(isinstance(cmd, test_dist)) + self.assertIsInstance(cmd, test_dist) self.assertEqual(cmd.sample_option, "sometext") def test_command_packages_configfile(self): @@ -106,28 +106,23 @@ class DistributionTestCase(support.LoggingSilencer, def test_empty_options(self): # an empty options dictionary should not stay in the # list of attributes - klass = Distribution # catching warnings warns = [] + def _warn(msg): warns.append(msg) - old_warn = warnings.warn + self.addCleanup(setattr, warnings, 'warn', warnings.warn) warnings.warn = _warn - try: - dist = klass(attrs={'author': 'xxx', - 'name': 'xxx', - 'version': 'xxx', - 'url': 'xxxx', - 'options': {}}) - finally: - warnings.warn = old_warn + dist = Distribution(attrs={'author': 'xxx', 'name': 'xxx', + 'version': 'xxx', 'url': 'xxxx', + 'options': {}}) self.assertEqual(len(warns), 0) + self.assertNotIn('options', dir(dist)) def test_finalize_options(self): - attrs = {'keywords': 'one,two', 'platforms': 'one,two'} @@ -150,7 +145,6 @@ class DistributionTestCase(support.LoggingSilencer, cmds = dist.get_command_packages() self.assertEqual(cmds, ['distutils.command', 'one', 'two']) - def test_announce(self): # make sure the level is known dist = Distribution() @@ -158,6 +152,7 @@ class DistributionTestCase(support.LoggingSilencer, kwargs = {'level': 'ok2'} self.assertRaises(ValueError, dist.announce, args, kwargs) + class MetadataTestCase(support.TempdirManager, support.EnvironGuard, unittest.TestCase): @@ -170,15 +165,20 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, sys.argv[:] = self.argv[1] super(MetadataTestCase, self).tearDown() + 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) - self.assertTrue("Metadata-Version: 1.0" in meta) - self.assertTrue("provides:" not in meta.lower()) - self.assertTrue("requires:" not in meta.lower()) - self.assertTrue("obsoletes:" not in meta.lower()) + 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(self): attrs = {"name": "package", @@ -190,9 +190,9 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, self.assertEqual(dist.get_provides(), ["package", "package.sub"]) meta = self.format_metadata(dist) - self.assertTrue("Metadata-Version: 1.1" in meta) - self.assertTrue("requires:" not in meta.lower()) - self.assertTrue("obsoletes:" not in meta.lower()) + self.assertIn("Metadata-Version: 1.1", meta) + self.assertNotIn("requires:", meta.lower()) + self.assertNotIn("obsoletes:", meta.lower()) def test_provides_illegal(self): self.assertRaises(ValueError, Distribution, @@ -210,11 +210,11 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, self.assertEqual(dist.get_requires(), ["other", "another (==1.0)"]) meta = self.format_metadata(dist) - self.assertTrue("Metadata-Version: 1.1" in meta) - self.assertTrue("provides:" not in meta.lower()) - self.assertTrue("Requires: other" in meta) - self.assertTrue("Requires: another (==1.0)" in meta) - self.assertTrue("obsoletes:" not in meta.lower()) + self.assertIn("Metadata-Version: 1.1", meta) + self.assertNotIn("provides:", meta.lower()) + self.assertIn("Requires: other", meta) + self.assertIn("Requires: another (==1.0)", meta) + self.assertNotIn("obsoletes:", meta.lower()) def test_requires_illegal(self): self.assertRaises(ValueError, Distribution, @@ -232,11 +232,11 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, self.assertEqual(dist.get_obsoletes(), ["other", "another (<1.0)"]) meta = self.format_metadata(dist) - self.assertTrue("Metadata-Version: 1.1" in meta) - self.assertTrue("provides:" not in meta.lower()) - self.assertTrue("requires:" not in meta.lower()) - self.assertTrue("Obsoletes: other" in meta) - self.assertTrue("Obsoletes: another (<1.0)" in meta) + self.assertIn("Metadata-Version: 1.1", meta) + self.assertNotIn("provides:", meta.lower()) + self.assertNotIn("requires:", meta.lower()) + self.assertIn("Obsoletes: other", meta) + self.assertIn("Obsoletes: another (<1.0)", meta) def test_obsoletes_illegal(self): self.assertRaises(ValueError, Distribution, @@ -244,10 +244,34 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, "version": "1.0", "obsoletes": ["my.pkg (splat)"]}) - def format_metadata(self, dist): - sio = io.StringIO() - dist.metadata.write_pkg_file(sio) - return sio.getvalue() + def test_classifier(self): + attrs = {'name': 'Boa', 'version': '3.0', + 'classifiers': ['Programming Language :: Python :: 3']} + dist = Distribution(attrs) + meta = self.format_metadata(dist) + self.assertIn('Metadata-Version: 1.1', meta) + + 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) + self.assertIn('Metadata-Version: 1.1', 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') + self.assertIn(long_desc, meta) def test_custom_pydistutils(self): # fixes #2166 @@ -272,14 +296,14 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, if sys.platform in ('linux', 'darwin'): os.environ['HOME'] = temp_dir files = dist.find_config_files() - self.assertTrue(user_filename in 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.assertTrue(user_filename in files, + self.assertIn(user_filename, files, '%r not found in %r' % (user_filename, files)) finally: os.remove(user_filename) @@ -301,22 +325,8 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, output = [line for line in s.getvalue().split('\n') if line.strip() != ''] - self.assertTrue(len(output) > 0) + self.assertTrue(output) - 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') - self.assertTrue(long_desc in meta) def test_suite(): suite = unittest.TestSuite() diff --git a/Lib/packaging/command/bdist_wininst.py b/Lib/packaging/command/bdist_wininst.py index 7dbb39b3117..6c1e225f9f9 100644 --- a/Lib/packaging/command/bdist_wininst.py +++ b/Lib/packaging/command/bdist_wininst.py @@ -1,7 +1,5 @@ """Create an executable installer for Windows.""" -# FIXME synchronize bytes/str use with same file in distutils - import sys import os @@ -186,9 +184,8 @@ class bdist_wininst(Command): os.remove(arcname) if not self.keep_temp: - if self.dry_run: - logger.info('removing %s', self.bdist_dir) - else: + logger.info('removing %s', self.bdist_dir) + if not self.dry_run: rmtree(self.bdist_dir) def get_inidata(self): @@ -265,14 +262,17 @@ class bdist_wininst(Command): cfgdata = cfgdata.encode("mbcs") # Append the pre-install script - cfgdata = cfgdata + "\0" + cfgdata = cfgdata + b"\0" if self.pre_install_script: - with open(self.pre_install_script) as fp: - script_data = fp.read() - cfgdata = cfgdata + script_data + "\n\0" + # We need to normalize newlines, so we open in text mode and + # convert back to bytes. "latin-1" simply avoids any possible + # failures. + with open(self.pre_install_script, encoding="latin-1") as fp: + script_data = fp.read().encode("latin-1") + cfgdata = cfgdata + script_data + b"\n\0" else: # empty pre-install script - cfgdata = cfgdata + "\0" + cfgdata = cfgdata + b"\0" file.write(cfgdata) # The 'magic number' 0x1234567B is used to make sure that the diff --git a/Lib/packaging/command/build_ext.py b/Lib/packaging/command/build_ext.py index fa8d11f39c4..2bffae38ac2 100644 --- a/Lib/packaging/command/build_ext.py +++ b/Lib/packaging/command/build_ext.py @@ -1,9 +1,5 @@ """Build extension modules.""" -# FIXME Is this module limited to C extensions or do C++ extensions work too? -# The docstring of this module said that C++ was not supported, but other -# comments contradict that. - import os import re import sys diff --git a/Lib/packaging/command/install_distinfo.py b/Lib/packaging/command/install_distinfo.py index 0e1577e7dc2..1f48eedab73 100644 --- a/Lib/packaging/command/install_distinfo.py +++ b/Lib/packaging/command/install_distinfo.py @@ -28,7 +28,7 @@ class install_distinfo(Command): ('no-record', None, "do not generate a RECORD file"), ('no-resources', None, - "do not generate a RESSOURCES list installed file") + "do not generate a RESSOURCES list installed file"), ] boolean_options = ['requested', 'no-record', 'no-resources'] @@ -70,56 +70,56 @@ class install_distinfo(Command): self.distinfo_dir = os.path.join(self.distinfo_dir, basename) def run(self): - # FIXME dry-run should be used at a finer level, so that people get - # useful logging output and can have an idea of what the command would - # have done - if not self.dry_run: - target = self.distinfo_dir + target = self.distinfo_dir - if os.path.isdir(target) and not os.path.islink(target): + if os.path.isdir(target) and not os.path.islink(target): + if not self.dry_run: rmtree(target) - elif os.path.exists(target): - self.execute(os.unlink, (self.distinfo_dir,), - "removing " + target) + elif os.path.exists(target): + self.execute(os.unlink, (self.distinfo_dir,), + "removing " + target) - self.execute(os.makedirs, (target,), "creating " + target) + self.execute(os.makedirs, (target,), "creating " + target) - metadata_path = os.path.join(self.distinfo_dir, 'METADATA') - logger.info('creating %s', metadata_path) - self.distribution.metadata.write(metadata_path) - self.outfiles.append(metadata_path) + metadata_path = os.path.join(self.distinfo_dir, 'METADATA') + self.execute(self.distribution.metadata.write, (metadata_path,), + "creating " + metadata_path) + self.outfiles.append(metadata_path) - installer_path = os.path.join(self.distinfo_dir, 'INSTALLER') - logger.info('creating %s', installer_path) + installer_path = os.path.join(self.distinfo_dir, 'INSTALLER') + logger.info('creating %s', installer_path) + if not self.dry_run: with open(installer_path, 'w') as f: f.write(self.installer) - self.outfiles.append(installer_path) + self.outfiles.append(installer_path) - if self.requested: - requested_path = os.path.join(self.distinfo_dir, 'REQUESTED') - logger.info('creating %s', requested_path) + if self.requested: + requested_path = os.path.join(self.distinfo_dir, 'REQUESTED') + logger.info('creating %s', requested_path) + if not self.dry_run: open(requested_path, 'wb').close() - self.outfiles.append(requested_path) + self.outfiles.append(requested_path) - - if not self.no_resources: - install_data = self.get_finalized_command('install_data') - if install_data.get_resources_out() != []: - resources_path = os.path.join(self.distinfo_dir, - 'RESOURCES') - logger.info('creating %s', resources_path) + if not self.no_resources: + install_data = self.get_finalized_command('install_data') + if install_data.get_resources_out() != []: + resources_path = os.path.join(self.distinfo_dir, + 'RESOURCES') + logger.info('creating %s', resources_path) + if not self.dry_run: with open(resources_path, 'wb') as f: writer = csv.writer(f, delimiter=',', lineterminator='\n', quotechar='"') - for tuple in install_data.get_resources_out(): - writer.writerow(tuple) + for row in install_data.get_resources_out(): + writer.writerow(row) - self.outfiles.append(resources_path) + self.outfiles.append(resources_path) - if not self.no_record: - record_path = os.path.join(self.distinfo_dir, 'RECORD') - logger.info('creating %s', record_path) + if not self.no_record: + record_path = os.path.join(self.distinfo_dir, 'RECORD') + logger.info('creating %s', record_path) + if not self.dry_run: with open(record_path, 'w', encoding='utf-8') as f: writer = csv.writer(f, delimiter=',', lineterminator='\n', @@ -141,7 +141,7 @@ class install_distinfo(Command): # add the RECORD file itself writer.writerow((record_path, '', '')) - self.outfiles.append(record_path) + self.outfiles.append(record_path) def get_outputs(self): return self.outfiles diff --git a/Lib/packaging/config.py b/Lib/packaging/config.py index e02800e9605..366faea4ede 100644 --- a/Lib/packaging/config.py +++ b/Lib/packaging/config.py @@ -227,10 +227,11 @@ class Config: self.dist.scripts = [self.dist.scripts] self.dist.package_data = {} - for data in files.get('package_data', []): - data = data.split('=') + for line in files.get('package_data', []): + data = line.split('=') if len(data) != 2: - continue # FIXME errors should never pass silently + raise ValueError('invalid line for package_data: %s ' + '(misses "=")' % line) key, value = data self.dist.package_data[key.strip()] = value.strip() diff --git a/Lib/packaging/metadata.py b/Lib/packaging/metadata.py index 104600b54e9..dbb53b265c3 100644 --- a/Lib/packaging/metadata.py +++ b/Lib/packaging/metadata.py @@ -61,7 +61,8 @@ _314_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'License', 'Classifier', 'Download-URL', 'Obsoletes', 'Provides', 'Requires') -_314_MARKERS = ('Obsoletes', 'Provides', 'Requires') +_314_MARKERS = ('Obsoletes', 'Provides', 'Requires', 'Classifier', + 'Download-URL') _345_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', 'Supported-Platform', 'Summary', 'Description', @@ -354,11 +355,20 @@ class Metadata: Keys that don't match a metadata field or that have an empty value are dropped. """ + # XXX the code should just use self.set, which does tbe same checks and + # conversions already, but that would break packaging.pypi: it uses the + # update method, which does not call _set_best_version (which set + # does), and thus allows having a Metadata object (as long as you don't + # modify or write it) with extra fields from PyPI that are not fields + # defined in Metadata PEPs. to solve it, the best_version system + # should be reworked so that it's called only for writing, or in a new + # strict mode, or with a new, more lax Metadata subclass in p7g.pypi def _set(key, value): if key in _ATTR2FIELD and value: self.set(self._convert_name(key), value) - if other is None: + if not other: + # other is None or empty container pass elif hasattr(other, 'keys'): for k in other.keys(): @@ -368,7 +378,8 @@ class Metadata: _set(k, v) if kwargs: - self.update(kwargs) + for k, v in kwargs.items(): + _set(k, v) def set(self, name, value): """Control then set a metadata field.""" diff --git a/Lib/packaging/pypi/simple.py b/Lib/packaging/pypi/simple.py index 710355d6480..76aad02416d 100644 --- a/Lib/packaging/pypi/simple.py +++ b/Lib/packaging/pypi/simple.py @@ -159,22 +159,20 @@ class Crawler(BaseClient): Return a list of names. """ - with self._open_url(self.index_url) as index: - if '*' in name: - name.replace('*', '.*') - else: - name = "%s%s%s" % ('*.?', name, '*.?') - name = name.replace('*', '[^<]*') # avoid matching end tag - projectname = re.compile(']*>(%s)' % name, re.I) - matching_projects = [] + if '*' in name: + name.replace('*', '.*') + else: + name = "%s%s%s" % ('*.?', name, '*.?') + name = name.replace('*', '[^<]*') # avoid matching end tag + pattern = (']*>(%s)' % name).encode('utf-8') + projectname = re.compile(pattern, re.I) + matching_projects = [] + with self._open_url(self.index_url) as index: index_content = index.read() - # FIXME should use bytes I/O and regexes instead of decoding - index_content = index_content.decode() - for match in projectname.finditer(index_content): - project_name = match.group(1) + project_name = match.group(1).decode('utf-8') matching_projects.append(self._get_project(project_name)) return matching_projects diff --git a/Lib/packaging/run.py b/Lib/packaging/run.py index ef20f35865d..5affb17974f 100644 --- a/Lib/packaging/run.py +++ b/Lib/packaging/run.py @@ -290,27 +290,23 @@ def _run(dispatcher, args, **kw): @action_help("""\ -Usage: pysetup list dist [dist ...] +Usage: pysetup list [dist ...] or: pysetup list --help - or: pysetup list --all Print name, version and location for the matching installed distributions. positional arguments: - dist installed distribution name - -optional arguments: - --all list all installed distributions + dist installed distribution name; omit to get all distributions """) def _list(dispatcher, args, **kw): - opts = _parse_args(args[1:], '', ['all']) + opts = _parse_args(args[1:], '', []) dists = get_distributions(use_egg_info=True) - if 'all' in opts or opts['args'] == []: - results = dists - listall = True - else: + if opts['args']: results = (d for d in dists if d.name.lower() in opts['args']) listall = False + else: + results = dists + listall = True number = 0 for dist in results: @@ -368,7 +364,7 @@ actions = [ ('install', 'Install a project', _install), ('remove', 'Remove a project', _remove), ('search', 'Search for a project in the indexes', _search), - ('list', 'List installed releases', _list), + ('list', 'List installed projects', _list), ('graph', 'Display a graph', _graph), ('create', 'Create a project', _create), ('generate-setup', 'Generate a backward-comptatible setup.py', _generate), diff --git a/Lib/packaging/tests/test_command_install_data.py b/Lib/packaging/tests/test_command_install_data.py index 0486427a2a8..35ce847f354 100644 --- a/Lib/packaging/tests/test_command_install_data.py +++ b/Lib/packaging/tests/test_command_install_data.py @@ -35,6 +35,7 @@ class InstallDataTestCase(support.TempdirManager, two = os.path.join(pkg_dir, 'two') self.write_file(two, 'xxx') + # FIXME this creates a literal \{inst2\} directory! cmd.data_files = {one: '{inst}/one', two: '{inst2}/two'} self.assertCountEqual(cmd.get_inputs(), [one, two]) diff --git a/Lib/packaging/tests/test_dist.py b/Lib/packaging/tests/test_dist.py index 1d78fa9b986..bd862450b2f 100644 --- a/Lib/packaging/tests/test_dist.py +++ b/Lib/packaging/tests/test_dist.py @@ -1,16 +1,13 @@ """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 @@ -49,6 +46,7 @@ class DistributionTestCase(support.TempdirManager, sys.argv[:] = self.argv[1] super(DistributionTestCase, self).tearDown() + @unittest.skip('needs to be updated') def test_debug_mode(self): self.addCleanup(os.unlink, TESTFN) with open(TESTFN, "w") as f: @@ -59,6 +57,8 @@ class DistributionTestCase(support.TempdirManager, sys.argv.append("build") __, stdout = captured_stdout(create_distribution, files) self.assertEqual(stdout, '') + # XXX debug mode does not exist anymore, test logging levels in this + # test instead packaging.dist.DEBUG = True try: __, stdout = captured_stdout(create_distribution, files) @@ -66,34 +66,6 @@ class DistributionTestCase(support.TempdirManager, 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', @@ -101,28 +73,18 @@ class DistributionTestCase(support.TempdirManager, 'home-page': 'xxxx', 'badoptname': 'xxx'}) logs = self.get_logs(logging.WARNING) - self.assertEqual(1, len(logs)) + self.assertEqual(len(logs), 1) 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': {}}) + dist = Distribution(attrs={'author': 'xxx', 'name': 'xxx', + 'version': '1.2', 'home-page': 'xxxx', + 'options': {}}) self.assertEqual([], self.get_logs(logging.WARNING)) + self.assertNotIn('options', dir(dist)) def test_non_empty_options(self): # TODO: how to actually use options is not documented except @@ -141,7 +103,6 @@ class DistributionTestCase(support.TempdirManager, self.assertIn('owner', dist.get_option_dict('sdist')) def test_finalize_options(self): - attrs = {'keywords': 'one,two', 'platform': 'one,two'} @@ -152,6 +113,24 @@ class DistributionTestCase(support.TempdirManager, self.assertEqual(dist.metadata['platform'], ['one', 'two']) self.assertEqual(dist.metadata['keywords'], ['one', 'two']) + def test_custom_pydistutils(self): + # Bug #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() + + os.environ['HOME'] = temp_dir + files = dist.find_config_files() + self.assertIn(user_filename, files) + def test_find_config_files_disable(self): # Bug #1180: Allow users to disable their own config file. temp_home = self.mkdtemp() @@ -270,185 +249,8 @@ class DistributionTestCase(support.TempdirManager, 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 + return unittest.makeSuite(DistributionTestCase) if __name__ == "__main__": unittest.main(defaultTest="test_suite") diff --git a/Lib/packaging/tests/test_metadata.py b/Lib/packaging/tests/test_metadata.py index 904019c34e2..68b3d9722ad 100644 --- a/Lib/packaging/tests/test_metadata.py +++ b/Lib/packaging/tests/test_metadata.py @@ -2,6 +2,7 @@ import os import sys import logging +from textwrap import dedent from io import StringIO from packaging.errors import (MetadataConflictError, MetadataMissingError, @@ -9,12 +10,29 @@ from packaging.errors import (MetadataConflictError, MetadataMissingError, from packaging.metadata import Metadata, PKG_INFO_PREFERRED_VERSION from packaging.tests import unittest -from packaging.tests.support import LoggingCatcher +from packaging.tests.support import (LoggingCatcher, TempdirManager, + EnvironRestorer) class MetadataTestCase(LoggingCatcher, + TempdirManager, + EnvironRestorer, unittest.TestCase): + maxDiff = None + 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() + + #### Test various methods of the Metadata class + def test_instantiation(self): PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO') with open(PKG_INFO, 'r', encoding='utf-8') as f: @@ -43,17 +61,6 @@ class MetadataTestCase(LoggingCatcher, self.assertRaises(TypeError, Metadata, PKG_INFO, mapping=m, fileobj=fp) - def test_metadata_read_write(self): - PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO') - metadata = Metadata(PKG_INFO) - out = StringIO() - metadata.write_file(out) - out.seek(0) - res = Metadata() - res.read_file(out) - for k in metadata: - self.assertEqual(metadata[k], res[k]) - def test_metadata_markers(self): # see if we can be platform-aware PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO') @@ -70,31 +77,10 @@ class MetadataTestCase(LoggingCatcher, # test with context context = {'sys.platform': 'okook'} - metadata = Metadata(platform_dependent=True, - execution_context=context) + metadata = Metadata(platform_dependent=True, execution_context=context) metadata.read_file(StringIO(content)) self.assertEqual(metadata['Requires-Dist'], ['foo']) - def test_description(self): - PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO') - with open(PKG_INFO, 'r', encoding='utf-8') as f: - content = f.read() % sys.platform - metadata = Metadata() - metadata.read_file(StringIO(content)) - - # see if we can read the description now - DESC = os.path.join(os.path.dirname(__file__), 'LONG_DESC.txt') - with open(DESC) as f: - wanted = f.read() - self.assertEqual(wanted, metadata['Description']) - - # save the file somewhere and make sure we can read it back - out = StringIO() - metadata.write_file(out) - out.seek(0) - metadata.read_file(out) - self.assertEqual(wanted, metadata['Description']) - def test_mapping_api(self): PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO') with open(PKG_INFO, 'r', encoding='utf-8') as f: @@ -109,73 +95,86 @@ class MetadataTestCase(LoggingCatcher, metadata.update([('version', '0.7')]) self.assertEqual(metadata['Version'], '0.7') + # make sure update method checks values like the set method does + metadata.update({'version': '1--2'}) + self.assertEqual(len(self.get_logs()), 1) + + # XXX caveat: the keys method and friends are not 3.x-style views + # should be changed or documented self.assertEqual(list(metadata), list(metadata.keys())) - def test_versions(self): - metadata = Metadata() - metadata['Obsoletes'] = 'ok' - self.assertEqual(metadata['Metadata-Version'], '1.1') + def test_read_metadata(self): + fields = {'name': 'project', + 'version': '1.0', + 'description': 'desc', + 'summary': 'xxx', + 'download_url': 'http://example.com', + 'keywords': ['one', 'two'], + 'requires_dist': ['foo']} - del metadata['Obsoletes'] - metadata['Obsoletes-Dist'] = 'ok' - self.assertEqual(metadata['Metadata-Version'], '1.2') + metadata = Metadata(mapping=fields) + PKG_INFO = StringIO() + metadata.write_file(PKG_INFO) + PKG_INFO.seek(0) - self.assertRaises(MetadataConflictError, metadata.set, - 'Obsoletes', 'ok') + metadata = Metadata(fileobj=PKG_INFO) - del metadata['Obsoletes'] - del metadata['Obsoletes-Dist'] - metadata['Version'] = '1' - self.assertEqual(metadata['Metadata-Version'], '1.0') + self.assertEqual(metadata['name'], 'project') + 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']) - PKG_INFO = os.path.join(os.path.dirname(__file__), - 'SETUPTOOLS-PKG-INFO') - with open(PKG_INFO, 'r', encoding='utf-8') as f: - content = f.read() - metadata.read_file(StringIO(content)) - self.assertEqual(metadata['Metadata-Version'], '1.0') + def test_write_metadata(self): + # check support of non-ASCII values + tmp_dir = self.mkdtemp() + my_file = os.path.join(tmp_dir, 'f') - PKG_INFO = os.path.join(os.path.dirname(__file__), - 'SETUPTOOLS-PKG-INFO2') - with open(PKG_INFO, 'r', encoding='utf-8') as f: - content = f.read() - metadata.read_file(StringIO(content)) - self.assertEqual(metadata['Metadata-Version'], '1.1') + metadata = Metadata(mapping={'author': 'Mister Café', + 'name': 'my.project', + 'author': 'Café Junior', + 'summary': 'Café torréfié', + 'description': 'Héhéhé', + 'keywords': ['café', 'coffee']}) + metadata.write(my_file) - # Update the _fields dict directly to prevent 'Metadata-Version' - # from being updated by the _set_best_version() method. - metadata._fields['Metadata-Version'] = '1.618' - self.assertRaises(MetadataUnrecognizedVersionError, metadata.keys) + # the file should use UTF-8 + metadata2 = Metadata() + with open(my_file, encoding='utf-8') as fp: + metadata2.read_file(fp) - def test_warnings(self): - metadata = Metadata() + # XXX when keywords are not defined, metadata will have + # 'Keywords': [] but metadata2 will have 'Keywords': [''] + # because of a value.split(',') in Metadata.get + self.assertEqual(metadata.items(), metadata2.items()) - # these should raise a warning - values = (('Requires-Dist', 'Funky (Groovie)'), - ('Requires-Python', '1-4')) + # ASCII also works, it's a subset of UTF-8 + metadata = Metadata(mapping={'author': 'Mister Cafe', + 'name': 'my.project', + 'author': 'Cafe Junior', + 'summary': 'Cafe torrefie', + 'description': 'Hehehe'}) + metadata.write(my_file) - for name, value in values: - metadata.set(name, value) + metadata2 = Metadata() + with open(my_file, encoding='utf-8') as fp: + metadata2.read_file(fp) - # we should have a certain amount of warnings - self.assertEqual(len(self.get_logs()), 2) + def test_metadata_read_write(self): + PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO') + metadata = Metadata(PKG_INFO) + out = StringIO() + metadata.write_file(out) - def test_multiple_predicates(self): - metadata = Metadata() + out.seek(0) + res = Metadata() + res.read_file(out) + self.assertEqual(metadata.values(), res.values()) - # see for "3" instead of "3.0" ??? - # its seems like the MINOR VERSION can be omitted - metadata['Requires-Python'] = '>=2.6, <3.0' - metadata['Requires-Dist'] = ['Foo (>=2.6, <3.0)'] - - self.assertEqual([], self.get_logs(logging.WARNING)) - - def test_project_url(self): - metadata = Metadata() - metadata['Project-URL'] = [('one', 'http://ok')] - self.assertEqual(metadata['Project-URL'], - [('one', 'http://ok')]) - self.assertEqual(metadata['Metadata-Version'], '1.2') + #### Test checks def test_check_version(self): metadata = Metadata() @@ -238,39 +237,216 @@ class MetadataTestCase(LoggingCatcher, metadata['Requires-dist'] = ['Foo (a)'] metadata['Obsoletes-dist'] = ['Foo (a)'] metadata['Provides-dist'] = ['Foo (a)'] - if metadata.docutils_support: - missing, warnings = metadata.check() - self.assertEqual(len(warnings), 4) - metadata.docutils_support = False missing, warnings = metadata.check() self.assertEqual(len(warnings), 4) - def test_best_choice(self): - metadata = Metadata() - metadata['Version'] = '1.0' + #### Test fields and metadata versions + + def test_metadata_versions(self): + metadata = Metadata(mapping={'name': 'project', 'version': '1.0'}) self.assertEqual(metadata['Metadata-Version'], PKG_INFO_PREFERRED_VERSION) + self.assertNotIn('Provides', metadata) + self.assertNotIn('Requires', metadata) + self.assertNotIn('Obsoletes', metadata) + metadata['Classifier'] = ['ok'] + self.assertEqual(metadata['Metadata-Version'], '1.1') + + metadata = Metadata() + metadata['Download-URL'] = 'ok' + self.assertEqual(metadata['Metadata-Version'], '1.1') + + metadata = Metadata() + metadata['Obsoletes'] = 'ok' + self.assertEqual(metadata['Metadata-Version'], '1.1') + + del metadata['Obsoletes'] + metadata['Obsoletes-Dist'] = 'ok' self.assertEqual(metadata['Metadata-Version'], '1.2') - def test_project_urls(self): - # project-url is a bit specific, make sure we write it - # properly in PKG-INFO - metadata = Metadata() - metadata['Version'] = '1.0' - metadata['Project-Url'] = [('one', 'http://ok')] - self.assertEqual(metadata['Project-Url'], [('one', 'http://ok')]) - file_ = StringIO() - metadata.write_file(file_) - file_.seek(0) - res = file_.read().split('\n') - self.assertIn('Project-URL: one,http://ok', res) + self.assertRaises(MetadataConflictError, metadata.set, + 'Obsoletes', 'ok') - file_.seek(0) + del metadata['Obsoletes'] + del metadata['Obsoletes-Dist'] + metadata['Version'] = '1' + self.assertEqual(metadata['Metadata-Version'], '1.0') + + # make sure the _best_version function works okay with + # non-conflicting fields from 1.1 and 1.2 (i.e. we want only the + # requires/requires-dist and co. pairs to cause a conflict, not all + # fields in _314_MARKERS) metadata = Metadata() - metadata.read_file(file_) + metadata['Requires-Python'] = '3' + metadata['Classifier'] = ['Programming language :: Python :: 3'] + self.assertEqual(metadata['Metadata-Version'], '1.2') + + PKG_INFO = os.path.join(os.path.dirname(__file__), + 'SETUPTOOLS-PKG-INFO') + metadata = Metadata(PKG_INFO) + self.assertEqual(metadata['Metadata-Version'], '1.0') + + PKG_INFO = os.path.join(os.path.dirname(__file__), + 'SETUPTOOLS-PKG-INFO2') + metadata = Metadata(PKG_INFO) + self.assertEqual(metadata['Metadata-Version'], '1.1') + + # Update the _fields dict directly to prevent 'Metadata-Version' + # from being updated by the _set_best_version() method. + metadata._fields['Metadata-Version'] = '1.618' + self.assertRaises(MetadataUnrecognizedVersionError, metadata.keys) + + def test_version(self): + Metadata(mapping={'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_description(self): + PKG_INFO = os.path.join(os.path.dirname(__file__), 'PKG-INFO') + with open(PKG_INFO, 'r', encoding='utf-8') as f: + content = f.read() % sys.platform + metadata = Metadata() + metadata.read_file(StringIO(content)) + + # see if we can read the description now + DESC = os.path.join(os.path.dirname(__file__), 'LONG_DESC.txt') + with open(DESC) as f: + wanted = f.read() + self.assertEqual(wanted, metadata['Description']) + + # save the file somewhere and make sure we can read it back + out = StringIO() + metadata.write_file(out) + out.seek(0) + + out.seek(0) + metadata = Metadata() + metadata.read_file(out) + self.assertEqual(wanted, metadata['Description']) + + def test_description_folding(self): + # make sure the indentation is preserved + out = StringIO() + desc = dedent("""\ + example:: + We start here + and continue here + and end here. + """) + + metadata = Metadata() + metadata['description'] = desc + metadata.write_file(out) + + folded_desc = desc.replace('\n', '\n' + (7 * ' ') + '|') + self.assertIn(folded_desc, out.getvalue()) + + def test_project_url(self): + metadata = Metadata() + metadata['Project-URL'] = [('one', 'http://ok')] + self.assertEqual(metadata['Project-URL'], [('one', 'http://ok')]) + self.assertEqual(metadata['Metadata-Version'], '1.2') + + # make sure this particular field is handled properly when written + fp = StringIO() + metadata.write_file(fp) + self.assertIn('Project-URL: one,http://ok', fp.getvalue().split('\n')) + + fp.seek(0) + metadata = Metadata() + metadata.read_file(fp) self.assertEqual(metadata['Project-Url'], [('one', 'http://ok')]) + # TODO copy tests for v1.1 requires, obsoletes and provides from distutils + # (they're useless but we support them so we should test them anyway) + + def test_provides_dist(self): + fields = {'name': 'project', + 'version': '1.0', + 'provides_dist': ['project', 'my.project']} + metadata = Metadata(mapping=fields) + self.assertEqual(metadata['Provides-Dist'], + ['project', 'my.project']) + self.assertEqual(metadata['Metadata-Version'], '1.2', metadata) + self.assertNotIn('Requires', metadata) + self.assertNotIn('Obsoletes', metadata) + + @unittest.skip('needs to be implemented') + def test_provides_illegal(self): + # TODO check the versions (like distutils does for old provides field) + self.assertRaises(ValueError, Metadata, + mapping={'name': 'project', + 'version': '1.0', + 'provides_dist': ['my.pkg (splat)']}) + + def test_requires_dist(self): + fields = {'name': 'project', + 'version': '1.0', + 'requires_dist': ['other', 'another (==1.0)']} + metadata = Metadata(mapping=fields) + self.assertEqual(metadata['Requires-Dist'], + ['other', 'another (==1.0)']) + self.assertEqual(metadata['Metadata-Version'], '1.2') + self.assertNotIn('Provides', metadata) + self.assertEqual(metadata['Requires-Dist'], + ['other', 'another (==1.0)']) + self.assertNotIn('Obsoletes', metadata) + + # make sure write_file uses one RFC 822 header per item + fp = StringIO() + metadata.write_file(fp) + lines = fp.getvalue().split('\n') + self.assertIn('Requires-Dist: other', lines) + self.assertIn('Requires-Dist: another (==1.0)', lines) + + # test warnings for invalid version predicates + # XXX this would cause no warnings if we used update (or the mapping + # argument of the constructor), see comment in Metadata.update + metadata = Metadata() + metadata['Requires-Dist'] = 'Funky (Groovie)' + metadata['Requires-Python'] = '1-4' + self.assertEqual(len(self.get_logs()), 2) + + # test multiple version predicates + metadata = Metadata() + + # XXX check PEP and see if 3 == 3.0 + metadata['Requires-Python'] = '>=2.6, <3.0' + metadata['Requires-Dist'] = ['Foo (>=2.6, <3.0)'] + self.assertEqual([], self.get_logs(logging.WARNING)) + + @unittest.skip('needs to be implemented') + def test_requires_illegal(self): + self.assertRaises(ValueError, Metadata, + mapping={'name': 'project', + 'version': '1.0', + 'requires': ['my.pkg (splat)']}) + + def test_obsoletes_dist(self): + fields = {'name': 'project', + 'version': '1.0', + 'obsoletes_dist': ['other', 'another (<1.0)']} + metadata = Metadata(mapping=fields) + self.assertEqual(metadata['Obsoletes-Dist'], + ['other', 'another (<1.0)']) + self.assertEqual(metadata['Metadata-Version'], '1.2') + self.assertNotIn('Provides', metadata) + self.assertNotIn('Requires', metadata) + self.assertEqual(metadata['Obsoletes-Dist'], + ['other', 'another (<1.0)']) + + @unittest.skip('needs to be implemented') + def test_obsoletes_illegal(self): + self.assertRaises(ValueError, Metadata, + mapping={'name': 'project', + 'version': '1.0', + 'obsoletes': ['my.pkg (splat)']}) + def test_suite(): return unittest.makeSuite(MetadataTestCase) diff --git a/Lib/packaging/tests/test_run.py b/Lib/packaging/tests/test_run.py index cb576b7c1db..6a3b8fb1309 100644 --- a/Lib/packaging/tests/test_run.py +++ b/Lib/packaging/tests/test_run.py @@ -3,16 +3,16 @@ import os import sys import shutil -from tempfile import mkstemp from io import StringIO from packaging import install from packaging.tests import unittest, support, TESTFN from packaging.run import main +from test.script_helper import assert_python_ok + # setup script that uses __file__ setup_using___file__ = """\ - __file__ from packaging.run import setup @@ -20,7 +20,6 @@ setup() """ setup_prints_cwd = """\ - import os print os.getcwd() @@ -29,11 +28,12 @@ setup() """ -class CoreTestCase(support.TempdirManager, support.LoggingCatcher, - unittest.TestCase): +class RunTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): def setUp(self): - super(CoreTestCase, self).setUp() + super(RunTestCase, self).setUp() self.old_stdout = sys.stdout self.cleanup_testfn() self.old_argv = sys.argv, sys.argv[:] @@ -43,7 +43,7 @@ class CoreTestCase(support.TempdirManager, support.LoggingCatcher, self.cleanup_testfn() sys.argv = self.old_argv[0] sys.argv[:] = self.old_argv[1] - super(CoreTestCase, self).tearDown() + super(RunTestCase, self).tearDown() def cleanup_testfn(self): path = TESTFN @@ -77,9 +77,16 @@ class CoreTestCase(support.TempdirManager, support.LoggingCatcher, os.chmod(install_path, old_mod) install.get_path = old_get_path + def test_show_help(self): + # smoke test, just makes sure some help is displayed + status, out, err = assert_python_ok('-m', 'packaging.run', '--help') + self.assertEqual(status, 0) + self.assertGreater(out, b'') + self.assertEqual(err, b'') + def test_suite(): - return unittest.makeSuite(CoreTestCase) + return unittest.makeSuite(RunTestCase) if __name__ == "__main__": unittest.main(defaultTest="test_suite") diff --git a/Misc/NEWS b/Misc/NEWS index 96e9ff5020f..7218bfd49d9 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -278,6 +278,10 @@ Library ZLIB_RUNTIME_VERSION, in the zlib module. Patch by Torsten Landschoff. - Issue #12959: Add collections.ChainMap to collections.__all__. + +- Issue #8933: distutils' PKG-INFO files and packaging's METADATA files will + now correctly report Metadata-Version: 1.1 instead of 1.0 if a Classifier or + Download-URL field is present. - Issue #12567: Add curses.unget_wch() function. Push a character so the next get_wch() will return it.