Imported Upstream version 41.1.0 upstream/41.1.0
authorDongHun Kwak <dh0128.kwak@samsung.com>
Tue, 29 Dec 2020 22:04:09 +0000 (07:04 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Tue, 29 Dec 2020 22:04:09 +0000 (07:04 +0900)
21 files changed:
.bumpversion.cfg
.travis.yml
CHANGES.rst
appveyor.yml
docs/developer-guide.txt
docs/setuptools.txt
pkg_resources/__init__.py
pkg_resources/tests/test_pkg_resources.py
pytest.ini
setup.cfg
setup.py
setuptools/build_meta.py
setuptools/dist.py
setuptools/py33compat.py
setuptools/tests/test_build_meta.py
setuptools/tests/test_dist.py
setuptools/tests/test_egg_info.py
setuptools/tests/test_integration.py
setuptools/tests/test_virtualenv.py
tests/requirements.txt
tox.ini

index 87acb5ef8d55ccc0615c68ca06b529d3f3d81f27..5a4536605c5264982666e05464f0e76c87544031 100644 (file)
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 41.0.1
+current_version = 41.1.0
 commit = True
 tag = True
 
index ffcad99864915a6eb5480334617f1814cfba277d..64d0544c2710777b8e501837343b124daf6f5be1 100644 (file)
@@ -8,9 +8,9 @@ jobs:
     python: 2.7
   - <<: *latest_py2
     env: LANG=C
-  - python: pypy2.7-6.0.0
+  - python: pypy
     env: DISABLE_COVERAGE=1  # Don't run coverage on pypy (too slow).
-  - python: pypy3.5-6.0.0
+  - python: pypy3
     env: DISABLE_COVERAGE=1
   - python: 3.4
   - python: 3.5
index 9da2253792ccc97e40c721318ba78e37b4010bed..9f9603c0438a11f7376755fa33c2ce47d4bf8658 100644 (file)
@@ -1,3 +1,16 @@
+v41.1.0
+-------
+
+* #1697: Moved most of the constants from setup.py to setup.cfg
+* #1749: Fixed issue with the PEP 517 backend where building a source distribution would fail if any tarball existed in the destination directory.
+* #1750: Fixed an issue with PEP 517 backend where wheel builds would fail if the destination directory did not already exist.
+* #1756: Forse metadata-version >= 1.2. when project urls are present.
+* #1769: Improve ``package_data`` check: ensure the dictionary values are lists/tuples of strings.
+* #1788: Changed compatibility fallback logic for ``html.unescape`` to avoid accessing ``HTMLParser.unescape`` when not necessary. ``HTMLParser.unescape`` is deprecated and will be removed in Python 3.9.
+* #1790: Added the file path to the error message when a ``UnicodeDecodeError`` occurs while reading a metadata file.
+* #1776: Use license classifiers rather than the license field.
+
+
 v41.0.1
 -------
 
index 7a3d174da8a4e73833659488b30798dcec734291..08818069683ddeabdd4a74eb5f6c9305fc577051 100644 (file)
@@ -26,7 +26,7 @@ cache:
 test_script:
   - python --version
   - python -m pip install --disable-pip-version-check --upgrade pip setuptools wheel
-  - pip install --upgrade tox tox-venv
+  - pip install --upgrade tox tox-venv virtualenv
   - pip freeze --all
   - python bootstrap.py
   - tox -- --cov
index a5942c8b0f6320bd954aa4f7755c5a4f513a4e4a..d145fba140546448b528ef8364af9ec8c89a1b02 100644 (file)
@@ -137,3 +137,17 @@ To build the docs locally, use tox::
 
 .. _Sphinx: http://www.sphinx-doc.org/en/master/
 .. _published documentation: https://setuptools.readthedocs.io/en/latest/
+
+---------------------
+Vendored Dependencies
+---------------------
+
+Setuptools has some dependencies, but due to `bootstrapping issues
+<https://github.com/pypa/setuptools/issues/980>`, those dependencies
+cannot be declared as they won't be resolved soon enough to build
+setuptools from source. Eventually, this limitation may be lifted as
+PEP 517/518 reach ubiquitous adoption, but for now, Setuptools
+cannot declare dependencies other than through
+``setuptools/_vendor/vendored.txt`` and
+``pkg_reosurces/_vendor/vendored.txt`` and refreshed by way of
+``paver update_vendored`` (pavement.py).
index 64b385cb1d2da282c0d2a97ff7b7feeaab14fd25..2e7fe3bd93344a6e4a3c2d6a3615382160d221f1 100644 (file)
@@ -136,16 +136,18 @@ dependencies, and perhaps some data files and scripts::
         author="Me",
         author_email="me@example.com",
         description="This is an Example Package",
-        license="PSF",
         keywords="hello world example examples",
         url="http://example.com/HelloWorld/",   # project home page, if any
         project_urls={
             "Bug Tracker": "https://bugs.example.com/HelloWorld/",
             "Documentation": "https://docs.example.com/HelloWorld/",
             "Source Code": "https://code.example.com/HelloWorld/",
-        }
+        },
+        classifiers=[
+            'License :: OSI Approved :: Python Software Foundation License'
+        ]
 
-        # could also include long_description, download_url, classifiers, etc.
+        # could also include long_description, download_url, etc.
     )
 
 In the sections that follow, we'll explain what most of these ``setup()``
@@ -2234,6 +2236,7 @@ boilerplate code in some cases.
     license = BSD 3-Clause License
     classifiers =
         Framework :: Django
+        License :: OSI Approved :: BSD License
         Programming Language :: Python :: 3
         Programming Language :: Python :: 3.5
 
index 97e08d6827eb213be621da730e97b2b339290ea2..1f170cfda5944516616e86e70f2850e8f9a2a3c5 100644 (file)
@@ -1416,8 +1416,17 @@ class NullProvider:
     def get_metadata(self, name):
         if not self.egg_info:
             return ""
-        value = self._get(self._fn(self.egg_info, name))
-        return value.decode('utf-8') if six.PY3 else value
+        path = self._get_metadata_path(name)
+        value = self._get(path)
+        if six.PY2:
+            return value
+        try:
+            return value.decode('utf-8')
+        except UnicodeDecodeError as exc:
+            # Include the path in the error message to simplify
+            # troubleshooting, and without changing the exception type.
+            exc.reason += ' in {} file at path: {}'.format(name, path)
+            raise
 
     def get_metadata_lines(self, name):
         return yield_lines(self.get_metadata(name))
index fb77c68587087fa57e19a28d740528022bfc2628..5960868a52eaeb6532d63b2e7797735a8ce72c75 100644 (file)
@@ -18,6 +18,7 @@ except ImportError:
     import mock
 
 from pkg_resources import DistInfoDistribution, Distribution, EggInfoDistribution
+from setuptools.extern import six
 from pkg_resources.extern.six.moves import map
 from pkg_resources.extern.six import text_type, string_types
 
@@ -191,6 +192,59 @@ class TestResourceManager:
         subprocess.check_call(cmd)
 
 
+def make_test_distribution(metadata_path, metadata):
+    """
+    Make a test Distribution object, and return it.
+
+    :param metadata_path: the path to the metadata file that should be
+        created. This should be inside a distribution directory that should
+        also be created. For example, an argument value might end with
+        "<project>.dist-info/METADATA".
+    :param metadata: the desired contents of the metadata file, as bytes.
+    """
+    dist_dir = os.path.dirname(metadata_path)
+    os.mkdir(dist_dir)
+    with open(metadata_path, 'wb') as f:
+        f.write(metadata)
+    dists = list(pkg_resources.distributions_from_metadata(dist_dir))
+    dist, = dists
+
+    return dist
+
+
+def test_get_metadata__bad_utf8(tmpdir):
+    """
+    Test a metadata file with bytes that can't be decoded as utf-8.
+    """
+    filename = 'METADATA'
+    # Convert the tmpdir LocalPath object to a string before joining.
+    metadata_path = os.path.join(str(tmpdir), 'foo.dist-info', filename)
+    # Encode a non-ascii string with the wrong encoding (not utf-8).
+    metadata = 'née'.encode('iso-8859-1')
+    dist = make_test_distribution(metadata_path, metadata=metadata)
+
+    if six.PY2:
+        # In Python 2, get_metadata() doesn't do any decoding.
+        actual = dist.get_metadata(filename)
+        assert actual == metadata
+        return
+
+    # Otherwise, we are in the Python 3 case.
+    with pytest.raises(UnicodeDecodeError) as excinfo:
+        dist.get_metadata(filename)
+
+    exc = excinfo.value
+    actual = str(exc)
+    expected = (
+        # The error message starts with "'utf-8' codec ..." However, the
+        # spelling of "utf-8" can vary (e.g. "utf8") so we don't include it
+        "codec can't decode byte 0xe9 in position 1: "
+        'invalid continuation byte in METADATA file at path: '
+    )
+    assert expected in actual, 'actual: {}'.format(actual)
+    assert actual.endswith(metadata_path), 'actual: {}'.format(actual)
+
+
 # TODO: remove this in favor of Path.touch() when Python 2 is dropped.
 def touch_file(path):
     """
@@ -242,7 +296,7 @@ def test_distribution_version_missing(tmpdir, suffix, expected_filename,
     with pytest.raises(ValueError) as excinfo:
         dist.version
 
-    err = str(excinfo)
+    err = str(excinfo.value)
     # Include a string expression after the assert so the full strings
     # will be visible for inspection on failure.
     assert expected_text in err, str((expected_text, err))
index 1c5b6b0934b042c6bcb2483a9f50fcf094362b8f..612fb91f631b8098ddd98cab761ff62ec006bf63 100644 (file)
@@ -1,5 +1,5 @@
 [pytest]
-addopts=--doctest-modules --doctest-glob=pkg_resources/api_tests.txt -rsxX
+addopts=--doctest-modules --doctest-glob=pkg_resources/api_tests.txt -r sxX
 norecursedirs=dist build *.egg setuptools/extern pkg_resources/extern .*
 flake8-ignore =
     setuptools/site-patch.py F821
index 3945408162b5894f0aed973e8d7380ab40422411..3bcf442987eb9e7dde9802de9ff0f639b5eb6df5 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -18,5 +18,46 @@ formats = zip
 universal = 1
 
 [metadata]
+name = setuptools
+version = 41.1.0
+description = Easily download, build, install, upgrade, and uninstall Python packages
+author = Python Packaging Authority
+author_email = distutils-sig@python.org
+long_description = file: README.rst
+long_description_content_type = text/x-rst; charset=UTF-8
 license_file = LICENSE
-version = 41.0.1
+keywords = CPAN PyPI distutils eggs package management
+url = https://github.com/pypa/setuptools
+project_urls =
+    Documentation = https://setuptools.readthedocs.io/
+classifiers =
+    Development Status :: 5 - Production/Stable
+    Intended Audience :: Developers
+    License :: OSI Approved :: MIT License
+    Operating System :: OS Independent
+    Programming Language :: Python :: 2
+    Programming Language :: Python :: 2.7
+    Programming Language :: Python :: 3
+    Programming Language :: Python :: 3.4
+    Programming Language :: Python :: 3.5
+    Programming Language :: Python :: 3.6
+    Programming Language :: Python :: 3.7
+    Topic :: Software Development :: Libraries :: Python Modules
+    Topic :: System :: Archiving :: Packaging
+    Topic :: System :: Systems Administration
+    Topic :: Utilities
+
+[options]
+zip_safe = True
+python_requires = >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*
+py_modules = easy_install
+packages = find:
+
+[options.packages.find]
+exclude = *.tests
+
+[options.extras_require]
+ssl =
+    wincertstore==0.2; sys_platform=='win32'
+certs =
+    certifi==2016.9.26
index 78d7018c9810bb16c7d5237ccd843e7d743fd977..f5030dd6703d62e4b4af90a163a1cf22d70cc3f7 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -3,10 +3,8 @@
 Distutils setup file, used to install or test 'setuptools'
 """
 
-import io
 import os
 import sys
-import textwrap
 
 import setuptools
 
@@ -49,10 +47,6 @@ def _gen_console_scripts():
     yield tmpl.format(shortver=sys.version[:3])
 
 
-readme_path = os.path.join(here, 'README.rst')
-with io.open(readme_path, encoding='utf-8') as readme_file:
-    long_description = readme_file.read()
-
 package_data = dict(
     setuptools=['script (dev).tmpl', 'script.tmpl', 'site-patch.py'],
 )
@@ -88,25 +82,8 @@ def pypi_link(pkg_filename):
 
 
 setup_params = dict(
-    name="setuptools",
-    description=(
-        "Easily download, build, install, upgrade, and uninstall "
-        "Python packages"
-    ),
-    author="Python Packaging Authority",
-    author_email="distutils-sig@python.org",
-    long_description=long_description,
-    long_description_content_type='text/x-rst; charset=UTF-8',
-    keywords="CPAN PyPI distutils eggs package management",
-    url="https://github.com/pypa/setuptools",
-    project_urls={
-        "Documentation": "https://setuptools.readthedocs.io/",
-    },
     src_root=None,
-    packages=setuptools.find_packages(exclude=['*.tests']),
     package_data=package_data,
-    py_modules=['easy_install'],
-    zip_safe=True,
     entry_points={
         "distutils.commands": [
             "%(cmd)s = setuptools.command.%(cmd)s:%(cmd)s" % locals()
@@ -152,28 +129,6 @@ setup_params = dict(
         "setuptools.installation":
             ['eggsecutable = setuptools.command.easy_install:bootstrap'],
     },
-    classifiers=textwrap.dedent("""
-        Development Status :: 5 - Production/Stable
-        Intended Audience :: Developers
-        License :: OSI Approved :: MIT License
-        Operating System :: OS Independent
-        Programming Language :: Python :: 2
-        Programming Language :: Python :: 2.7
-        Programming Language :: Python :: 3
-        Programming Language :: Python :: 3.4
-        Programming Language :: Python :: 3.5
-        Programming Language :: Python :: 3.6
-        Programming Language :: Python :: 3.7
-        Topic :: Software Development :: Libraries :: Python Modules
-        Topic :: System :: Archiving :: Packaging
-        Topic :: System :: Systems Administration
-        Topic :: Utilities
-        """).strip().splitlines(),
-    python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*',
-    extras_require={
-        "ssl:sys_platform=='win32'": "wincertstore==0.2",
-        "certs": "certifi==2016.9.26",
-    },
     dependency_links=[
         pypi_link(
             'certifi-2016.9.26.tar.gz#md5=baa81e951a29958563689d868ef1064d',
@@ -182,7 +137,6 @@ setup_params = dict(
             'wincertstore-0.2.zip#md5=ae728f2f007185648d0c7a8679b361e2',
         ),
     ],
-    scripts=[],
     setup_requires=[
     ] + wheel,
 )
index e40904a5dfdcf60d73df7d68c3cccaa64f8d2076..10c4b528d996d23a1319e3fed755aef0e6da2eb9 100644 (file)
@@ -38,6 +38,7 @@ import distutils
 from setuptools.py31compat import TemporaryDirectory
 
 from pkg_resources import parse_requirements
+from pkg_resources.py31compat import makedirs
 
 __all__ = ['get_requires_for_build_sdist',
            'get_requires_for_build_wheel',
@@ -179,36 +180,38 @@ class _BuildMetaBackend(object):
 
         return dist_infos[0]
 
-    def build_wheel(self, wheel_directory, config_settings=None,
-                    metadata_directory=None):
+    def _build_with_temp_dir(self, setup_command, result_extension,
+                             result_directory, config_settings):
         config_settings = self._fix_config(config_settings)
-        wheel_directory = os.path.abspath(wheel_directory)
+        result_directory = os.path.abspath(result_directory)
 
-        # Build the wheel in a temporary directory, then copy to the target
-        with TemporaryDirectory(dir=wheel_directory) as tmp_dist_dir:
-            sys.argv = (sys.argv[:1] +
-                        ['bdist_wheel', '--dist-dir', tmp_dist_dir] +
+        # Build in a temporary directory, then copy to the target.
+        makedirs(result_directory, exist_ok=True)
+        with TemporaryDirectory(dir=result_directory) as tmp_dist_dir:
+            sys.argv = (sys.argv[:1] + setup_command +
+                        ['--dist-dir', tmp_dist_dir] +
                         config_settings["--global-option"])
             self.run_setup()
 
-            wheel_basename = _file_with_extension(tmp_dist_dir, '.whl')
-            wheel_path = os.path.join(wheel_directory, wheel_basename)
-            if os.path.exists(wheel_path):
-                # os.rename will fail overwriting on non-unix env
-                os.remove(wheel_path)
-            os.rename(os.path.join(tmp_dist_dir, wheel_basename), wheel_path)
+            result_basename = _file_with_extension(tmp_dist_dir, result_extension)
+            result_path = os.path.join(result_directory, result_basename)
+            if os.path.exists(result_path):
+                # os.rename will fail overwriting on non-Unix.
+                os.remove(result_path)
+            os.rename(os.path.join(tmp_dist_dir, result_basename), result_path)
 
-        return wheel_basename
+        return result_basename
 
-    def build_sdist(self, sdist_directory, config_settings=None):
-        config_settings = self._fix_config(config_settings)
-        sdist_directory = os.path.abspath(sdist_directory)
-        sys.argv = sys.argv[:1] + ['sdist', '--formats', 'gztar'] + \
-            config_settings["--global-option"] + \
-            ["--dist-dir", sdist_directory]
-        self.run_setup()
 
-        return _file_with_extension(sdist_directory, '.tar.gz')
+    def build_wheel(self, wheel_directory, config_settings=None,
+                    metadata_directory=None):
+        return self._build_with_temp_dir(['bdist_wheel'], '.whl',
+                                         wheel_directory, config_settings)
+
+    def build_sdist(self, sdist_directory, config_settings=None):
+        return self._build_with_temp_dir(['sdist', '--formats', 'gztar'],
+                                         '.tar.gz', sdist_directory,
+                                         config_settings)
 
 
 class _BuildMetaLegacyBackend(_BuildMetaBackend):
index 9a165de0dd6b5ee9c216ce37e8895802de6ac6ca..f0f030b550ccd19e8eb4458fe3a3825cc4c18655 100644 (file)
@@ -54,7 +54,8 @@ def get_metadata_version(self):
             mv = StrictVersion('2.1')
         elif (self.maintainer is not None or
               self.maintainer_email is not None or
-              getattr(self, 'python_requires', None) is not None):
+              getattr(self, 'python_requires', None) is not None or
+              self.project_urls):
             mv = StrictVersion('1.2')
         elif (self.provides or self.requires or self.obsoletes or
                 self.classifiers or self.download_url):
@@ -212,8 +213,12 @@ def check_importable(dist, attr, value):
 
 
 def assert_string_list(dist, attr, value):
-    """Verify that value is a string list or None"""
+    """Verify that value is a string list"""
     try:
+        # verify that value is a list or tuple to exclude unordered
+        # or single-use iterables
+        assert isinstance(value, (list, tuple))
+        # verify that elements of value are strings
         assert ''.join(value) != value
     except (TypeError, ValueError, AttributeError, AssertionError):
         raise DistutilsSetupError(
@@ -306,20 +311,17 @@ def check_test_suite(dist, attr, value):
 
 def check_package_data(dist, attr, value):
     """Verify that value is a dictionary of package names to glob lists"""
-    if isinstance(value, dict):
-        for k, v in value.items():
-            if not isinstance(k, str):
-                break
-            try:
-                iter(v)
-            except TypeError:
-                break
-        else:
-            return
-    raise DistutilsSetupError(
-        attr + " must be a dictionary mapping package names to lists of "
-        "wildcard patterns"
-    )
+    if not isinstance(value, dict):
+        raise DistutilsSetupError(
+            "{!r} must be a dictionary mapping package names to lists of "
+            "string wildcard patterns".format(attr))
+    for k, v in value.items():
+        if not isinstance(k, six.string_types):
+            raise DistutilsSetupError(
+                "keys of {!r} dict must be strings (got {!r})"
+                .format(attr, k)
+            )
+        assert_string_list(dist, 'values of {!r} dict'.format(attr), v)
 
 
 def check_packages(dist, attr, value):
index 87cf53983c3bf1a39c5186ce1c67f6ac7f3b64a0..cb69443638354b46b43da5bbf187b4f7cba301f1 100644 (file)
@@ -52,4 +52,8 @@ class Bytecode_compat:
 Bytecode = getattr(dis, 'Bytecode', Bytecode_compat)
 
 
-unescape = getattr(html, 'unescape', html_parser.HTMLParser().unescape)
+unescape = getattr(html, 'unescape', None)
+if unescape is None:
+    # HTMLParser.unescape is deprecated since Python 3.4, and will be removed
+    # from 3.9.
+    unescape = html_parser.HTMLParser().unescape
index 7612ebd7fc4f85d5caad79afe603fcfb14d2c973..e1efe5617c00e08192e95845735b042592f73f87 100644 (file)
@@ -157,9 +157,10 @@ class TestBuildMetaBackend:
 
         assert os.path.isfile(os.path.join(dist_dir, wheel_name))
 
-    def test_build_wheel_with_existing_wheel_file_present(self, tmpdir_cwd):
-        # Building a wheel should still succeed if there's already a wheel
-        # in the wheel directory
+    @pytest.mark.parametrize('build_type', ('wheel', 'sdist'))
+    def test_build_with_existing_file_present(self, build_type, tmpdir_cwd):
+        # Building a sdist/wheel should still succeed if there's
+        # already a sdist/wheel in the destination directory.
         files = {
             'setup.py': "from setuptools import setup\nsetup()",
             'VERSION': "0.0.1",
@@ -177,28 +178,31 @@ class TestBuildMetaBackend:
 
         build_files(files)
 
-        dist_dir = os.path.abspath('pip-wheel-preexisting')
-        os.makedirs(dist_dir)
+        dist_dir = os.path.abspath('preexisting-' + build_type)
 
-        # make first wheel
         build_backend = self.get_build_backend()
-        wheel_one = build_backend.build_wheel(dist_dir)
+        build_method = getattr(build_backend, 'build_' + build_type)
+
+        # Build a first sdist/wheel.
+        # Note: this also check the destination directory is
+        # successfully created if it does not exist already.
+        first_result = build_method(dist_dir)
 
-        # change version
+        # Change version.
         with open("VERSION", "wt") as version_file:
             version_file.write("0.0.2")
 
-        # make *second* wheel
-        wheel_two = self.get_build_backend().build_wheel(dist_dir)
+        # Build a *second* sdist/wheel.
+        second_result = build_method(dist_dir)
 
-        assert os.path.isfile(os.path.join(dist_dir, wheel_one))
-        assert wheel_one != wheel_two
+        assert os.path.isfile(os.path.join(dist_dir, first_result))
+        assert first_result != second_result
 
-        # and if rebuilding the same wheel?
-        open(os.path.join(dist_dir, wheel_two), 'w').close()
-        wheel_three = self.get_build_backend().build_wheel(dist_dir)
-        assert wheel_three == wheel_two
-        assert os.path.getsize(os.path.join(dist_dir, wheel_three)) > 0
+        # And if rebuilding the exact same sdist/wheel?
+        open(os.path.join(dist_dir, second_result), 'w').close()
+        third_result = build_method(dist_dir)
+        assert third_result == second_result
+        assert os.path.getsize(os.path.join(dist_dir, third_result)) > 0
 
     def test_build_sdist(self, build_backend):
         dist_dir = os.path.abspath('pip-sdist')
index 390c3dfcf0140b93b63bdcfb188a4f77dcc7b357..c771a19a19275dad5c409fe103fede1a2d816e3e 100644 (file)
@@ -3,7 +3,13 @@
 from __future__ import unicode_literals
 
 import io
-from setuptools.dist import DistDeprecationWarning, _get_unpatched
+import re
+from distutils.errors import DistutilsSetupError
+from setuptools.dist import (
+    _get_unpatched,
+    check_package_data,
+    DistDeprecationWarning,
+)
 from setuptools import Distribution
 from setuptools.extern.six.moves.urllib.request import pathname2url
 from setuptools.extern.six.moves.urllib_parse import urljoin
@@ -263,3 +269,48 @@ def test_maintainer_author(name, attrs, tmpdir):
         else:
             line = '%s: %s' % (fkey, val)
             assert line in pkg_lines_set
+
+
+CHECK_PACKAGE_DATA_TESTS = (
+    # Valid.
+    ({
+        '': ['*.txt', '*.rst'],
+        'hello': ['*.msg'],
+    }, None),
+    # Not a dictionary.
+    ((
+        ('', ['*.txt', '*.rst']),
+        ('hello', ['*.msg']),
+    ), (
+        "'package_data' must be a dictionary mapping package"
+        " names to lists of string wildcard patterns"
+    )),
+    # Invalid key type.
+    ({
+        400: ['*.txt', '*.rst'],
+    }, (
+        "keys of 'package_data' dict must be strings (got 400)"
+    )),
+    # Invalid value type.
+    ({
+        'hello': str('*.msg'),
+    }, (
+        "\"values of 'package_data' dict\" must be a list of strings (got '*.msg')"
+    )),
+    # Invalid value type (generators are single use)
+    ({
+        'hello': (x for x in "generator"),
+    }, (
+        "\"values of 'package_data' dict\" must be a list of strings "
+        "(got <generator object"
+    )),
+)
+
+
+@pytest.mark.parametrize('package_data, expected_message', CHECK_PACKAGE_DATA_TESTS)
+def test_check_package_data(package_data, expected_message):
+    if expected_message is None:
+        assert check_package_data(None, 'package_data', package_data) is None
+    else:
+        with pytest.raises(DistutilsSetupError, match=re.escape(expected_message)):
+            check_package_data(None, str('package_data'), package_data)
index db9c3873357cc2c321b50fd961c672cc49e28d08..316eb2eddeb87d62b758046418541aa87999e522 100644 (file)
@@ -622,6 +622,7 @@ class TestEggInfo:
         assert expected_line in pkg_info_lines
         expected_line = 'Project-URL: Link Two, https://example.com/two/'
         assert expected_line in pkg_info_lines
+        assert 'Metadata-Version: 1.2' in pkg_info_lines
 
     def test_python_requires_egg_info(self, tmpdir_cwd, env):
         self._setup_script_with_requires(
index e54f3209aafd3cf57559ad244e58a42ec72a604d..1e132188997bf947052af75713b338557e2329c1 100644 (file)
@@ -141,6 +141,7 @@ def test_build_deps_on_distutils(request, tmpdir_factory, build_dep):
         allowed_unknowns = [
             'test_suite',
             'tests_require',
+            'python_requires',
             'install_requires',
         ]
         assert not match or match.group(1).strip('"\'') in allowed_unknowns
@@ -149,8 +150,8 @@ def test_build_deps_on_distutils(request, tmpdir_factory, build_dep):
 def install(pkg_dir, install_dir):
     with open(os.path.join(pkg_dir, 'setuptools.py'), 'w') as breaker:
         breaker.write('raise ImportError()')
-    cmd = [sys.executable, 'setup.py', 'install', '--prefix', install_dir]
-    env = dict(os.environ, PYTHONPATH=pkg_dir)
+    cmd = [sys.executable, 'setup.py', 'install', '--prefix', str(install_dir)]
+    env = dict(os.environ, PYTHONPATH=str(pkg_dir))
     output = subprocess.check_output(
         cmd, cwd=pkg_dir, env=env, stderr=subprocess.STDOUT)
     return output.decode('utf-8')
index d7b98c774145ecbf94a08f2f0211b3c721c5c6a1..74a1284ce715d34ae7cfe1183ea2b068820f69da 100644 (file)
@@ -8,6 +8,8 @@ from pytest_fixture_config import yield_requires_config
 
 import pytest_virtualenv
 
+from setuptools.extern import six
+
 from .textwrap import DALS
 from .test_easy_install import make_nspkg_sdist
 
@@ -75,9 +77,12 @@ def _get_pip_versions():
         'pip==10.0.1',
         'pip==18.1',
         'pip==19.0.1',
-        'https://github.com/pypa/pip/archive/master.zip',
     ]
 
+    # Pip's master dropped support for 3.4.
+    if not six.PY34:
+        network_versions.append('https://github.com/pypa/pip/archive/master.zip')
+
     versions = [None] + [
         pytest.param(v, **({} if network else {'marks': pytest.mark.skip}))
         for v in network_versions
index f944df27404372a84cc9c6ac60d71a5ba98b6915..cb3e67269609b11d7b4e3a81ee679d86b4e0e6f0 100644 (file)
@@ -3,8 +3,7 @@ pytest-flake8; python_version!="3.4"
 pytest-flake8<=1.0.0; python_version=="3.4"
 virtualenv>=13.0.0
 pytest-virtualenv>=1.2.7
-# pytest pinned to <4 due to #1638
-pytest>=3.7,<4
+pytest>=3.7
 wheel
 coverage>=4.5.1
 pytest-cov>=2.5.1
diff --git a/tox.ini b/tox.ini
index bb940ac0728204db9c07078108b6f8da81b1d757..e0eef95a457ed9e3f576da6c71eccdd89bd8df32 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -14,7 +14,7 @@ deps=-rtests/requirements.txt
 # from being added to `sys.path`.
 install_command=python -c 'import sys; sys.path.remove(""); from pkg_resources import load_entry_point; load_entry_point("pip", "console_scripts", "pip")()' install {opts} {packages}
 # Same as above.
-list_dependencies_command={envbindir}/pip freeze
+list_dependencies_command={envbindir}/pip freeze --all
 setenv=COVERAGE_FILE={toxworkdir}/.coverage.{envname}
 # TODO: The passed environment variables came from copying other tox.ini files
 # These should probably be individually annotated to explain what needs them.