Imported Upstream version 63.4.3 upstream/63.4.3
authorJinWang An <jinwang.an@samsung.com>
Mon, 27 Mar 2023 08:02:53 +0000 (17:02 +0900)
committerJinWang An <jinwang.an@samsung.com>
Mon, 27 Mar 2023 08:02:53 +0000 (17:02 +0900)
43 files changed:
.bumpversion.cfg
CHANGES.rst
docs/userguide/dependency_management.rst
setup.cfg
setuptools/_distutils/_msvccompiler.py
setuptools/_distutils/archive_util.py
setuptools/_distutils/ccompiler.py
setuptools/_distutils/command/check.py
setuptools/_distutils/command/register.py
setuptools/_distutils/tests/py38compat.py
setuptools/_distutils/tests/support.py
setuptools/_distutils/tests/test_archive_util.py
setuptools/_distutils/tests/test_bdist_rpm.py
setuptools/_distutils/tests/test_bdist_wininst.py
setuptools/_distutils/tests/test_build.py
setuptools/_distutils/tests/test_build_clib.py
setuptools/_distutils/tests/test_build_ext.py
setuptools/_distutils/tests/test_build_py.py
setuptools/_distutils/tests/test_ccompiler.py [new file with mode: 0644]
setuptools/_distutils/tests/test_check.py
setuptools/_distutils/tests/test_cmd.py
setuptools/_distutils/tests/test_config.py
setuptools/_distutils/tests/test_config_cmd.py
setuptools/_distutils/tests/test_core.py
setuptools/_distutils/tests/test_cygwinccompiler.py
setuptools/_distutils/tests/test_dir_util.py
setuptools/_distutils/tests/test_dist.py
setuptools/_distutils/tests/test_file_util.py
setuptools/_distutils/tests/test_install.py
setuptools/_distutils/tests/test_install_lib.py
setuptools/_distutils/tests/test_log.py
setuptools/_distutils/tests/test_msvccompiler.py
setuptools/_distutils/tests/test_register.py
setuptools/_distutils/tests/test_sdist.py
setuptools/_distutils/tests/test_spawn.py
setuptools/_distutils/tests/test_sysconfig.py
setuptools/_distutils/tests/test_unixccompiler.py
setuptools/_distutils/tests/test_upload.py
setuptools/_distutils/tests/test_util.py
setuptools/_distutils/tests/test_version.py
setuptools/_distutils/tests/unix_compat.py
setuptools/_distutils/tests/xxmodule-3.8.c [new file with mode: 0644]
setuptools/_distutils/tests/xxmodule.c [new file with mode: 0644]

index d25ccab37e1a15cdb1985954a36c7c39ef8e7fae..6662e83097589e2d24a583c4024c00482fb1d1bc 100644 (file)
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 63.4.2
+current_version = 63.4.3
 commit = True
 tag = True
 
index 9244bc6d53c615261ae426bb574a4c31eac7b516..1e1ba615f64eb0511e664aedf9fb52cb53b8c935 100644 (file)
@@ -1,3 +1,12 @@
+v63.4.3
+-------
+
+
+Misc
+^^^^
+* #3496: Update to pypa/distutils@b65aa40 including more robust support for library/include dir handling in msvccompiler (pypa/distutils#153) and test suite improvements.
+
+
 v63.4.2
 -------
 
index 56fbd0bdd3add99581ca9b3d66d0ec3a24c2dd59..33aaf6c656fde7998b1d38bab7dd61c23a489745 100644 (file)
@@ -6,13 +6,10 @@ There are three types of dependency styles offered by setuptools:
 1) build system requirement, 2) required dependency and 3) optional
 dependency.
 
-.. attention::
-   Each dependency, regardless of type, needs to be specified according to :pep:`508`.
-   This allows adding version :pep:`range restrictions <440#version-specifiers>`
-   and :ref:`environment markers <environment-markers>`.
-   Please note however that public package indexes, such as `PyPI`_
-   might not accept packages that declare dependencies using
-   :pep:`direct URLs <440#direct-references>`.
+Each dependency, regardless of type, needs to be specified according to :pep:`508`
+and :pep:`440`.
+This allows adding version :pep:`range restrictions <440#version-specifiers>`
+and :ref:`environment markers <environment-markers>`.
 
 
 .. _build-requires:
@@ -181,6 +178,58 @@ detailed in :pep:`508`.
    to implement custom detection logic.
 
 
+Direct URL dependencies
+-----------------------
+
+.. attention::
+   `PyPI`_ and other standards-conformant package indices **do not** accept
+   packages that declare dependencies using direct URLs. ``pip`` will accept them
+   when installing packages from the local filesystem or from another URL,
+   however.
+
+Dependencies that are not available on a package index but can be downloaded
+elsewhere in the form of a source repository or archive may be specified
+using a variant of :pep:`PEP 440's direct references <440#direct-references>`:
+
+.. tab:: pyproject.toml
+
+    .. code-block:: toml
+
+        [project]
+        # ...
+        dependencies = [
+            "Package-A @ git+https://example.net/package-a.git@main",
+            "Package-B @ https://example.net/archives/package-b.whl",
+        ]
+
+.. tab:: setup.cfg
+
+    .. code-block:: ini
+
+        [options]
+        #...
+        install_requires =
+            Package-A @ git+https://example.net/package-a.git@main
+            Package-B @ https://example.net/archives/package-b.whl
+
+.. tab:: setup.py
+
+    .. code-block:: python
+
+        setup(
+            install_requires=[
+               "Package-A @ git+https://example.net/package-a.git@main",
+               "Package-B @ https://example.net/archives/package-b.whl",
+            ],
+            ...,
+        )
+
+For source repository URLs, a list of supported protocols and VCS-specific
+features such as selecting certain branches or tags can be found in pip's
+documentation on `VCS support <https://pip.pypa.io/en/latest/topics/vcs-support/>`_.
+Supported formats for archive URLs are sdists and wheels.
+
+
 Optional dependencies
 =====================
 Setuptools allows you to declare dependencies that are not installed by default.
@@ -235,7 +284,7 @@ The name ``PDF`` is an arbitrary :pep:`identifier <685>` of such a list of depen
 which other components can refer and have them installed.
 
 A use case for this approach is that other package can use this "extra" for their
-own dependencies. For example, if ``Package-B`` needs ``Package-B`` with PDF support
+own dependencies. For example, if ``Package-B`` needs ``Package-A`` with PDF support
 installed, it might declare the dependency like this:
 
 .. tab:: pyproject.toml
index ce714fb18b05b49f4431d5a87cf6721b53964087..11e83c9909ebb511bfeff57093aaf9530a7c0823 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = setuptools
-version = 63.4.2
+version = 63.4.3
 author = Python Packaging Authority
 author_email = distutils-sig@python.org
 description = Easily download, build, install, upgrade, and uninstall Python packages
index 35c90942d2251f5a97bc714383c4b973656cbb39..ade80056e9329051171c736103345d1bd7177b94 100644 (file)
@@ -17,7 +17,7 @@ import os
 import subprocess
 import contextlib
 import warnings
-import unittest.mock
+import unittest.mock as mock
 
 with contextlib.suppress(ImportError):
     import winreg
@@ -224,6 +224,18 @@ class MSVCCompiler(CCompiler):
         self.plat_name = None
         self.initialized = False
 
+    @classmethod
+    def _configure(cls, vc_env):
+        """
+        Set class-level include/lib dirs.
+        """
+        cls.include_dirs = cls._parse_path(vc_env.get('include', ''))
+        cls.library_dirs = cls._parse_path(vc_env.get('lib', ''))
+
+    @staticmethod
+    def _parse_path(val):
+        return [dir.rstrip(os.sep) for dir in val.split(os.pathsep) if dir]
+
     def initialize(self, plat_name=None):
         # multi-init means we would need to check platform same each time...
         assert not self.initialized, "don't init multiple times"
@@ -243,6 +255,7 @@ class MSVCCompiler(CCompiler):
             raise DistutilsPlatformError(
                 "Unable to find a compatible " "Visual Studio installation."
             )
+        self._configure(vc_env)
 
         self._paths = vc_env.get('path', '')
         paths = self._paths.split(os.pathsep)
@@ -253,14 +266,6 @@ class MSVCCompiler(CCompiler):
         self.mc = _find_exe("mc.exe", paths)  # message compiler
         self.mt = _find_exe("mt.exe", paths)  # message compiler
 
-        for dir in vc_env.get('include', '').split(os.pathsep):
-            if dir:
-                self.add_include_dir(dir.rstrip(os.sep))
-
-        for dir in vc_env.get('lib', '').split(os.pathsep):
-            if dir:
-                self.add_library_dir(dir.rstrip(os.sep))
-
         self.preprocess_options = None
         # bpo-38597: Always compile with dynamic linking
         # Future releases of Python 3.x will include all past
@@ -554,7 +559,7 @@ class MSVCCompiler(CCompiler):
         else:
             return
         warnings.warn("Fallback spawn triggered. Please update distutils monkeypatch.")
-        with unittest.mock.patch.dict('os.environ', env):
+        with mock.patch.dict('os.environ', env):
             bag.value = super().spawn(cmd)
 
     # -- Miscellaneous methods -----------------------------------------
index 4cb9bf39324daa51148207cf8b23f83885bd075d..5dfe2a16ffbf5dc907aa3ce315757f4f9a055a82 100644 (file)
@@ -121,7 +121,7 @@ def make_tarball(
 
     # compression using `compress`
     if compress == 'compress':
-        warn("'compress' will be deprecated.", PendingDeprecationWarning)
+        warn("'compress' is deprecated.", DeprecationWarning)
         # the option varies depending on the platform
         compressed_name = archive_name + compress_ext[compress]
         if sys.platform == 'win32':
index b4d3d0fbe0b28f2b8687a6abd5aee6afbe0577a8..3cf5761cf2aee4bae021687949df770a15c68fc2 100644 (file)
@@ -91,6 +91,16 @@ class CCompiler:
     }
     language_order = ["c++", "objc", "c"]
 
+    include_dirs = []
+    """
+    include dirs specific to this compiler class
+    """
+
+    library_dirs = []
+    """
+    library dirs specific to this compiler class
+    """
+
     def __init__(self, verbose=0, dry_run=0, force=0):
         self.dry_run = dry_run
         self.force = force
@@ -324,24 +334,7 @@ class CCompiler:
 
     def _setup_compile(self, outdir, macros, incdirs, sources, depends, extra):
         """Process arguments and decide which source files to compile."""
-        if outdir is None:
-            outdir = self.output_dir
-        elif not isinstance(outdir, str):
-            raise TypeError("'output_dir' must be a string or None")
-
-        if macros is None:
-            macros = self.macros
-        elif isinstance(macros, list):
-            macros = macros + (self.macros or [])
-        else:
-            raise TypeError("'macros' (if supplied) must be a list of tuples")
-
-        if incdirs is None:
-            incdirs = self.include_dirs
-        elif isinstance(incdirs, (list, tuple)):
-            incdirs = list(incdirs) + (self.include_dirs or [])
-        else:
-            raise TypeError("'include_dirs' (if supplied) must be a list of strings")
+        outdir, macros, incdirs = self._fix_compile_args(outdir, macros, incdirs)
 
         if extra is None:
             extra = []
@@ -400,6 +393,9 @@ class CCompiler:
         else:
             raise TypeError("'include_dirs' (if supplied) must be a list of strings")
 
+        # add include dirs for class
+        include_dirs += self.__class__.include_dirs
+
         return output_dir, macros, include_dirs
 
     def _prep_compile(self, sources, output_dir, depends=None):
@@ -456,6 +452,9 @@ class CCompiler:
         else:
             raise TypeError("'library_dirs' (if supplied) must be a list of strings")
 
+        # add library dirs for class
+        library_dirs += self.__class__.library_dirs
+
         if runtime_library_dirs is None:
             runtime_library_dirs = self.runtime_library_dirs
         elif isinstance(runtime_library_dirs, (list, tuple)):
index aaf30713fe0cb684d55ed33dae7202aa105920c9..539481c946043c53aa61bd62cfd4b4146934697d 100644 (file)
@@ -2,17 +2,18 @@
 
 Implements the Distutils 'check' command.
 """
+import contextlib
+
 from distutils.core import Command
 from distutils.errors import DistutilsSetupError
 
-try:
-    # docutils is installed
-    from docutils.utils import Reporter
-    from docutils.parsers.rst import Parser
-    from docutils import frontend
-    from docutils import nodes
+with contextlib.suppress(ImportError):
+    import docutils.utils
+    import docutils.parsers.rst
+    import docutils.frontend
+    import docutils.nodes
 
-    class SilentReporter(Reporter):
+    class SilentReporter(docutils.utils.Reporter):
         def __init__(
             self,
             source,
@@ -30,16 +31,10 @@ try:
 
         def system_message(self, level, message, *children, **kwargs):
             self.messages.append((level, message, children, kwargs))
-            return nodes.system_message(
+            return docutils.nodes.system_message(
                 message, level=level, type=self.levels[level], *children, **kwargs
             )
 
-    HAS_DOCUTILS = True
-except Exception:
-    # Catch all exceptions because exceptions besides ImportError probably
-    # indicate that docutils is not ported to Py3k.
-    HAS_DOCUTILS = False
-
 
 class check(Command):
     """This command checks the meta-data of the package."""
@@ -81,8 +76,11 @@ class check(Command):
         if self.metadata:
             self.check_metadata()
         if self.restructuredtext:
-            if HAS_DOCUTILS:
-                self.check_restructuredtext()
+            if 'docutils' in globals():
+                try:
+                    self.check_restructuredtext()
+                except TypeError as exc:
+                    raise DistutilsSetupError(str(exc))
             elif self.strict:
                 raise DistutilsSetupError('The docutils package is needed.')
 
@@ -124,8 +122,10 @@ class check(Command):
         """Returns warnings when the provided data doesn't compile."""
         # the include and csv_table directives need this to be a path
         source_path = self.distribution.script_name or 'setup.py'
-        parser = Parser()
-        settings = frontend.OptionParser(components=(Parser,)).get_default_values()
+        parser = docutils.parsers.rst.Parser()
+        settings = docutils.frontend.OptionParser(
+            components=(docutils.parsers.rst.Parser,)
+        ).get_default_values()
         settings.tab_width = 4
         settings.pep_references = None
         settings.rfc_references = None
@@ -139,7 +139,7 @@ class check(Command):
             error_handler=settings.error_encoding_error_handler,
         )
 
-        document = nodes.document(settings, reporter, source=source_path)
+        document = docutils.nodes.document(settings, reporter, source=source_path)
         document.note_source(source_path, -1)
         try:
             parser.parse(data, document)
index 2c6424725ded09d94e0b6f2fc1617098d82c09f6..c1402650d7f7defdde15741aabafa9f42843dcdf 100644 (file)
@@ -66,9 +66,9 @@ class register(PyPIRCCommand):
     def check_metadata(self):
         """Deprecated API."""
         warn(
-            "distutils.command.register.check_metadata is deprecated, \
-              use the check command instead",
-            PendingDeprecationWarning,
+            "distutils.command.register.check_metadata is deprecated; "
+            "use the check command instead",
+            DeprecationWarning,
         )
         check = self.distribution.get_command_obj('check')
         check.ensure_finalized()
index 96f93a31c6250a928bbb7320857e097788fa736c..35ddbb5bde6dfcc3b690a74109abf7f831258eb4 100644 (file)
@@ -42,5 +42,17 @@ except (ModuleNotFoundError, ImportError):
     )
 
 
+try:
+    from test.support.import_helper import (
+        DirsOnSysPath,
+        CleanImport,
+    )
+except (ModuleNotFoundError, ImportError):
+    from test.support import (
+        DirsOnSysPath,
+        CleanImport,
+    )
+
+
 if sys.version_info < (3, 9):
     requires_zlib = lambda: test.support.requires_zlib
index 1ff4a1268fcfb49fd70e772ba7cda9374efa0873..5203ed19d4ab3b54517ac6c5a7a536ca42a6925e 100644 (file)
@@ -3,7 +3,6 @@ import os
 import sys
 import shutil
 import tempfile
-import unittest
 import sysconfig
 import itertools
 
@@ -31,9 +30,8 @@ class LoggingSilencer:
 
 @pytest.mark.usefixtures('distutils_managed_tempdir')
 class TempdirManager:
-    """Mix-in class that handles temporary directories for test cases.
-
-    This is intended to be used with unittest.TestCase.
+    """
+    Mix-in class that handles temporary directories for test cases.
     """
 
     def mkdtemp(self):
@@ -99,29 +97,12 @@ def copy_xxmodule_c(directory):
     If the source file can be found, it will be copied to *directory*.  If not,
     the test will be skipped.  Errors during copy are not caught.
     """
-    filename = _get_xxmodule_path()
-    if filename is None:
-        raise unittest.SkipTest(
-            'cannot find xxmodule.c (test must run in ' 'the python build dir)'
-        )
-    shutil.copy(filename, directory)
+    shutil.copy(_get_xxmodule_path(), os.path.join(directory, 'xxmodule.c'))
 
 
 def _get_xxmodule_path():
-    srcdir = sysconfig.get_config_var('srcdir')
-    candidates = [
-        # use installed copy if available
-        os.path.join(os.path.dirname(__file__), 'xxmodule.c'),
-        # otherwise try using copy from build directory
-        os.path.join(srcdir, 'Modules', 'xxmodule.c'),
-        # srcdir mysteriously can be $srcdir/Lib/distutils/tests when
-        # this file is run from its parent directory, so walk up the
-        # tree to find the real srcdir
-        os.path.join(srcdir, '..', '..', '..', 'Modules', 'xxmodule.c'),
-    ]
-    for path in candidates:
-        if os.path.exists(path):
-            return path
+    source_name = 'xxmodule.c' if sys.version_info > (3, 9) else 'xxmodule-3.8.c'
+    return os.path.join(os.path.dirname(__file__), source_name)
 
 
 def fixup_build_ext(cmd):
index c8c74032ae39feb119e24d943dc4c6996ba3d9d6..72aa9d7c7bb04ef9372d1180dffb577e5e0a3081 100644 (file)
@@ -1,10 +1,12 @@
 """Tests for distutils.archive_util."""
-import unittest
 import os
 import sys
 import tarfile
 from os.path import splitdrive
 import warnings
+import functools
+import operator
+import pathlib
 
 import pytest
 
@@ -16,7 +18,7 @@ from distutils.archive_util import (
     make_archive,
     ARCHIVE_FORMATS,
 )
-from distutils.spawn import find_executable, spawn
+from distutils.spawn import spawn
 from distutils.tests import support
 from test.support import patch
 from .unix_compat import require_unix_id, require_uid_0, grp, pwd, UID_0_SUPPORT
@@ -25,24 +27,6 @@ from .py38compat import change_cwd
 from .py38compat import check_warnings
 
 
-try:
-    import zipfile
-
-    ZIP_SUPPORT = True
-except ImportError:
-    ZIP_SUPPORT = find_executable('zip')
-
-try:
-    import bz2
-except ImportError:
-    bz2 = None
-
-try:
-    import lzma
-except ImportError:
-    lzma = None
-
-
 def can_fs_encode(filename):
     """
     Return True if the filename can be saved in the file system.
@@ -56,6 +40,14 @@ def can_fs_encode(filename):
     return True
 
 
+def all_equal(values):
+    return functools.reduce(operator.eq, values)
+
+
+def same_drive(*paths):
+    return all_equal(pathlib.Path(path).drive for path in paths)
+
+
 class ArchiveUtilTestCase(support.TempdirManager, support.LoggingSilencer):
     @pytest.mark.usefixtures('needs_zlib')
     def test_make_tarball(self, name='archive'):
@@ -70,28 +62,24 @@ class ArchiveUtilTestCase(support.TempdirManager, support.LoggingSilencer):
         tmpdir = self._create_files()
         self._make_tarball(tmpdir, 'archive', '.tar.gz', compress='gzip')
 
-    @unittest.skipUnless(bz2, 'Need bz2 support to run')
     def test_make_tarball_bzip2(self):
+        pytest.importorskip('bz2')
         tmpdir = self._create_files()
         self._make_tarball(tmpdir, 'archive', '.tar.bz2', compress='bzip2')
 
-    @unittest.skipUnless(lzma, 'Need lzma support to run')
     def test_make_tarball_xz(self):
+        pytest.importorskip('lzma')
         tmpdir = self._create_files()
         self._make_tarball(tmpdir, 'archive', '.tar.xz', compress='xz')
 
-    @unittest.skipUnless(
-        can_fs_encode('årchiv'), 'File system cannot handle this filename'
-    )
+    @pytest.mark.skipif("not can_fs_encode('årchiv')")
     def test_make_tarball_latin1(self):
         """
         Mirror test_make_tarball, except filename contains latin characters.
         """
         self.test_make_tarball('årchiv')  # note this isn't a real word
 
-    @unittest.skipUnless(
-        can_fs_encode('のアーカイブ'), 'File system cannot handle this filename'
-    )
+    @pytest.mark.skipif("not can_fs_encode('のアーカイブ')")
     def test_make_tarball_extended(self):
         """
         Mirror test_make_tarball, except filename contains extended
@@ -101,10 +89,8 @@ class ArchiveUtilTestCase(support.TempdirManager, support.LoggingSilencer):
 
     def _make_tarball(self, tmpdir, target_name, suffix, **kwargs):
         tmpdir2 = self.mkdtemp()
-        unittest.skipUnless(
-            splitdrive(tmpdir)[0] == splitdrive(tmpdir2)[0],
-            "source and target should be on same drive",
-        )
+        if same_drive(tmpdir, tmpdir2):
+            pytest.skip("source and target should be on same drive")
 
         base_name = os.path.join(tmpdir2, target_name)
 
@@ -149,10 +135,7 @@ class ArchiveUtilTestCase(support.TempdirManager, support.LoggingSilencer):
         return tmpdir
 
     @pytest.mark.usefixtures('needs_zlib')
-    @unittest.skipUnless(
-        find_executable('tar') and find_executable('gzip'),
-        'Need the tar and gzip commands to run',
-    )
+    @pytest.mark.skipif("not (find_executable('tar') and find_executable('gzip'))")
     def test_tarfile_vs_tar(self):
         tmpdir = self._create_files()
         tmpdir2 = self.mkdtemp()
@@ -207,14 +190,12 @@ class ArchiveUtilTestCase(support.TempdirManager, support.LoggingSilencer):
         tarball = base_name + '.tar'
         assert os.path.exists(tarball)
 
-    @unittest.skipUnless(
-        find_executable('compress'), 'The compress program is required'
-    )
+    @pytest.mark.skipif("not find_executable('compress')")
     def test_compress_deprecated(self):
         tmpdir = self._create_files()
         base_name = os.path.join(self.mkdtemp(), 'archive')
 
-        # using compress and testing the PendingDeprecationWarning
+        # using compress and testing the DeprecationWarning
         old_dir = os.getcwd()
         os.chdir(tmpdir)
         try:
@@ -241,8 +222,8 @@ class ArchiveUtilTestCase(support.TempdirManager, support.LoggingSilencer):
         assert len(w.warnings) == 1
 
     @pytest.mark.usefixtures('needs_zlib')
-    @unittest.skipUnless(ZIP_SUPPORT, 'Need zip support to run')
     def test_make_zipfile(self):
+        zipfile = pytest.importorskip('zipfile')
         # creating something to tar
         tmpdir = self._create_files()
         base_name = os.path.join(self.mkdtemp(), 'archive')
@@ -255,8 +236,8 @@ class ArchiveUtilTestCase(support.TempdirManager, support.LoggingSilencer):
         with zipfile.ZipFile(tarball) as zf:
             assert sorted(zf.namelist()) == self._zip_created_files
 
-    @unittest.skipUnless(ZIP_SUPPORT, 'Need zip support to run')
     def test_make_zipfile_no_zlib(self):
+        zipfile = pytest.importorskip('zipfile')
         patch(self, archive_util.zipfile, 'zlib', None)  # force zlib ImportError
 
         called = []
@@ -327,8 +308,8 @@ class ArchiveUtilTestCase(support.TempdirManager, support.LoggingSilencer):
         assert os.path.basename(res) == 'archive.tar.gz'
         assert self._tarinfo(res) == self._created_files
 
-    @unittest.skipUnless(bz2, 'Need bz2 support to run')
     def test_make_archive_bztar(self):
+        pytest.importorskip('bz2')
         base_dir = self._create_files()
         base_name = os.path.join(self.mkdtemp(), 'archive')
         res = make_archive(base_name, 'bztar', base_dir, 'dist')
@@ -336,8 +317,8 @@ class ArchiveUtilTestCase(support.TempdirManager, support.LoggingSilencer):
         assert os.path.basename(res) == 'archive.tar.bz2'
         assert self._tarinfo(res) == self._created_files
 
-    @unittest.skipUnless(lzma, 'Need xz support to run')
     def test_make_archive_xztar(self):
+        pytest.importorskip('lzma')
         base_dir = self._create_files()
         base_name = os.path.join(self.mkdtemp(), 'archive')
         res = make_archive(base_name, 'xztar', base_dir, 'dist')
index 411d09ebeaed9023302e4a5d132ae0fd2656e650..2d14bafc9823c24cbf1c9acf7a7a846be3ff107f 100644 (file)
@@ -1,6 +1,5 @@
 """Tests for distutils.command.bdist_rpm."""
 
-import unittest
 import sys
 import os
 
@@ -9,7 +8,7 @@ import pytest
 from distutils.core import Distribution
 from distutils.command.bdist_rpm import bdist_rpm
 from distutils.tests import support
-from distutils.spawn import find_executable
+from distutils.spawn import find_executable  # noqa: F401
 
 from .py38compat import requires_zlib
 
@@ -32,6 +31,12 @@ def sys_executable_encodable():
         pytest.skip("sys.executable is not encodable to UTF-8")
 
 
+mac_woes = pytest.mark.skipif(
+    "not sys.platform.startswith('linux')",
+    reason='spurious sdtout/stderr output under macOS',
+)
+
+
 @pytest.mark.usefixtures('save_env')
 @pytest.mark.usefixtures('save_argv')
 @pytest.mark.usefixtures('save_cwd')
@@ -39,17 +44,10 @@ class TestBuildRpm(
     support.TempdirManager,
     support.LoggingSilencer,
 ):
-
-    # XXX I am unable yet to make this test work without
-    # spurious sdtout/stderr output under Mac OS X
-    @unittest.skipUnless(
-        sys.platform.startswith('linux'), 'spurious sdtout/stderr output under Mac OS X'
-    )
+    @mac_woes
     @requires_zlib()
-    @unittest.skipIf(find_executable('rpm') is None, 'the rpm command is not found')
-    @unittest.skipIf(
-        find_executable('rpmbuild') is None, 'the rpmbuild command is not found'
-    )
+    @pytest.mark.skipif("not find_executable('rpm')")
+    @pytest.mark.skipif("not find_executable('rpmbuild')")
     def test_quiet(self):
         # let's create a package
         tmp_dir = self.mkdtemp()
@@ -90,17 +88,11 @@ class TestBuildRpm(
         assert ('bdist_rpm', 'any', 'dist/foo-0.1-1.src.rpm') in dist.dist_files
         assert ('bdist_rpm', 'any', 'dist/foo-0.1-1.noarch.rpm') in dist.dist_files
 
-    # XXX I am unable yet to make this test work without
-    # spurious sdtout/stderr output under Mac OS X
-    @unittest.skipUnless(
-        sys.platform.startswith('linux'), 'spurious sdtout/stderr output under Mac OS X'
-    )
+    @mac_woes
     @requires_zlib()
     # http://bugs.python.org/issue1533164
-    @unittest.skipIf(find_executable('rpm') is None, 'the rpm command is not found')
-    @unittest.skipIf(
-        find_executable('rpmbuild') is None, 'the rpmbuild command is not found'
-    )
+    @pytest.mark.skipif("not find_executable('rpm')")
+    @pytest.mark.skipif("not find_executable('rpmbuild')")
     def test_no_optimize_flag(self):
         # let's create a package that breaks bdist_rpm
         tmp_dir = self.mkdtemp()
index 8bc217af68da3f96b3905d1dbf8084bc38acd517..c432d24be690cc88f33ac9959cc20bbb6304e409 100644 (file)
@@ -1,7 +1,5 @@
 """Tests for distutils.command.bdist_wininst."""
-import sys
-import platform
-import unittest
+import pytest
 
 from .py38compat import check_warnings
 
@@ -9,14 +7,8 @@ from distutils.command.bdist_wininst import bdist_wininst
 from distutils.tests import support
 
 
-@unittest.skipIf(
-    sys.platform == 'win32' and platform.machine() == 'ARM64',
-    'bdist_wininst is not supported in this install',
-)
-@unittest.skipIf(
-    getattr(bdist_wininst, '_unsupported', False),
-    'bdist_wininst is not supported in this install',
-)
+@pytest.mark.skipif("platform.machine() == 'ARM64'")
+@pytest.mark.skipif("bdist_wininst._unsupported")
 class TestBuildWinInst(support.TempdirManager, support.LoggingSilencer):
     def test_get_exe_bytes(self):
 
index 45bc22f82251e7074b81e1d374ebdf5fa7aba206..80367607f5401384a800a52663707fb5b2643ea1 100644 (file)
@@ -1,5 +1,4 @@
 """Tests for distutils.command.build."""
-import unittest
 import os
 import sys
 
@@ -8,7 +7,7 @@ from distutils.tests import support
 from sysconfig import get_platform
 
 
-class BuildTestCase(support.TempdirManager, support.LoggingSilencer, unittest.TestCase):
+class TestBuild(support.TempdirManager, support.LoggingSilencer):
     def test_finalize_options(self):
         pkg_dir, dist = self.create_dist()
         cmd = build(dist)
index 2048e29a5275caf437f953435292a90e44bc7a4a..c931c06ec5880fb1a39b4ac6eb0edc520ff8d1d3 100644 (file)
@@ -1,14 +1,13 @@
 """Tests for distutils.command.build_clib."""
-import unittest
 import os
-import sys
 
 from test.support import missing_compiler_executable
 
+import pytest
+
 from distutils.command.build_clib import build_clib
 from distutils.errors import DistutilsSetupError
 from distutils.tests import support
-import pytest
 
 
 class TestBuildCLib(support.TempdirManager, support.LoggingSilencer):
@@ -111,7 +110,7 @@ class TestBuildCLib(support.TempdirManager, support.LoggingSilencer):
         with pytest.raises(DistutilsSetupError):
             cmd.finalize_options()
 
-    @unittest.skipIf(sys.platform == 'win32', "can't test on Windows")
+    @pytest.mark.skipif('platform.system() == "Windows"')
     def test_run(self):
         pkg_dir, dist = self.create_dist()
         cmd = build_clib(dist)
index 16d4873886a9c41f9de471231d971b0635551bfe..e60814ff64e191754315d75b94ad21933634fd8f 100644 (file)
@@ -3,6 +3,11 @@ import os
 from io import StringIO
 import textwrap
 import site
+import contextlib
+import platform
+import tempfile
+import importlib
+import shutil
 
 from distutils.core import Distribution
 from distutils.command.build_ext import build_ext
@@ -12,7 +17,6 @@ from distutils.tests.support import (
     LoggingSilencer,
     copy_xxmodule_c,
     fixup_build_ext,
-    combine_markers,
 )
 from distutils.extension import Extension
 from distutils.errors import (
@@ -22,17 +26,12 @@ from distutils.errors import (
     UnknownFileError,
 )
 
-import unittest
 from test import support
 from . import py38compat as os_helper
-from test.support.script_helper import assert_python_ok
+from . import py38compat as import_helper
 import pytest
 import re
 
-# http://bugs.python.org/issue4373
-# Don't load the xx module more than once.
-ALREADY_TESTED = False
-
 
 @pytest.fixture()
 def user_site_dir(request):
@@ -55,6 +54,35 @@ def user_site_dir(request):
     build_ext.USER_BASE = orig_user_base
 
 
+@contextlib.contextmanager
+def safe_extension_import(name, path):
+    with import_helper.CleanImport(name):
+        with extension_redirect(name, path) as new_path:
+            with import_helper.DirsOnSysPath(new_path):
+                yield
+
+
+@contextlib.contextmanager
+def extension_redirect(mod, path):
+    """
+    Tests will fail to tear down an extension module if it's been imported.
+
+    Before importing, copy the file to a temporary directory that won't
+    be cleaned up. Yield the new path.
+    """
+    if platform.system() != "Windows" and sys.platform != "cygwin":
+        yield path
+        return
+    with import_helper.DirsOnSysPath(path):
+        spec = importlib.util.find_spec(mod)
+    filename = os.path.basename(spec.origin)
+    trash_dir = tempfile.mkdtemp(prefix='deleteme')
+    dest = os.path.join(trash_dir, os.path.basename(filename))
+    shutil.copy(spec.origin, dest)
+    yield trash_dir
+    # TODO: can the file be scheduled for deletion?
+
+
 @pytest.mark.usefixtures('user_site_dir')
 class TestBuildExt(TempdirManager, LoggingSilencer):
     def build_ext(self, *args, **kwargs):
@@ -62,9 +90,6 @@ class TestBuildExt(TempdirManager, LoggingSilencer):
 
     def test_build_ext(self):
         cmd = support.missing_compiler_executable()
-        if cmd is not None:
-            self.skipTest('The %r command is not found' % cmd)
-        global ALREADY_TESTED
         copy_xxmodule_c(self.tmp_dir)
         xx_c = os.path.join(self.tmp_dir, 'xxmodule.c')
         xx_ext = Extension('xx', [xx_c])
@@ -85,41 +110,24 @@ class TestBuildExt(TempdirManager, LoggingSilencer):
         finally:
             sys.stdout = old_stdout
 
-        if ALREADY_TESTED:
-            self.skipTest('Already tested in %s' % ALREADY_TESTED)
-        else:
-            ALREADY_TESTED = type(self).__name__
-
-        code = textwrap.dedent(
-            f"""
-            tmp_dir = {self.tmp_dir!r}
-
-            import sys
-            import unittest
-            from test import support
+        with safe_extension_import('xx', self.tmp_dir):
+            self._test_xx()
 
-            sys.path.insert(0, tmp_dir)
-            import xx
+    @staticmethod
+    def _test_xx():
+        import xx
 
-            class Tests(unittest.TestCase):
-                def test_xx(self):
-                    for attr in ('error', 'foo', 'new', 'roj'):
-                        self.assertTrue(hasattr(xx, attr))
+        for attr in ('error', 'foo', 'new', 'roj'):
+            assert hasattr(xx, attr)
 
-                    self.assertEqual(xx.foo(2, 5), 7)
-                    self.assertEqual(xx.foo(13,15), 28)
-                    self.assertEqual(xx.new().demo(), None)
-                    if support.HAVE_DOCSTRINGS:
-                        doc = 'This is a template module just for instruction.'
-                        self.assertEqual(xx.__doc__, doc)
-                    self.assertIsInstance(xx.Null(), xx.Null)
-                    self.assertIsInstance(xx.Str(), xx.Str)
-
-
-            unittest.main()
-        """
-        )
-        assert_python_ok('-c', code)
+        assert xx.foo(2, 5) == 7
+        assert xx.foo(13, 15) == 28
+        assert xx.new().demo() is None
+        if support.HAVE_DOCSTRINGS:
+            doc = 'This is a template module just for instruction.'
+            assert xx.__doc__ == doc
+        assert isinstance(xx.Null(), xx.Null)
+        assert isinstance(xx.Str(), xx.Str)
 
     def test_solaris_enable_shared(self):
         dist = Distribution({'name': 'xx'})
@@ -353,8 +361,6 @@ class TestBuildExt(TempdirManager, LoggingSilencer):
 
     def test_get_outputs(self):
         cmd = support.missing_compiler_executable()
-        if cmd is not None:
-            self.skipTest('The %r command is not found' % cmd)
         tmp_dir = self.mkdtemp()
         c_file = os.path.join(tmp_dir, 'foo.c')
         self.write_file(c_file, 'void PyInit_foo(void) {}\n')
@@ -453,7 +459,7 @@ class TestBuildExt(TempdirManager, LoggingSilencer):
         wanted = os.path.join(curdir, 'twisted', 'runner', 'portmap' + ext)
         assert wanted == path
 
-    @unittest.skipUnless(sys.platform == 'darwin', 'test only relevant for MacOSX')
+    @pytest.mark.skipif('platform.system() != "Darwin"')
     @pytest.mark.usefixtures('save_env')
     def test_deployment_target_default(self):
         # Issue 9516: Test that, in the absence of the environment variable,
@@ -461,7 +467,7 @@ class TestBuildExt(TempdirManager, LoggingSilencer):
         #  the interpreter.
         self._try_compile_deployment_target('==', None)
 
-    @unittest.skipUnless(sys.platform == 'darwin', 'test only relevant for MacOSX')
+    @pytest.mark.skipif('platform.system() != "Darwin"')
     @pytest.mark.usefixtures('save_env')
     def test_deployment_target_too_low(self):
         # Issue 9516: Test that an extension module is not allowed to be
@@ -469,7 +475,7 @@ class TestBuildExt(TempdirManager, LoggingSilencer):
         with pytest.raises(DistutilsPlatformError):
             self._try_compile_deployment_target('>', '10.1')
 
-    @unittest.skipUnless(sys.platform == 'darwin', 'test only relevant for MacOSX')
+    @pytest.mark.skipif('platform.system() != "Darwin"')
     @pytest.mark.usefixtures('save_env')
     def test_deployment_target_higher_ok(self):
         # Issue 9516: Test that an extension module can be compiled with a
index cab5c65b6546aa215848ccec0a12f16bf32cebb2..63543dcaa1a6e236417f61a765c7a09afc8c354d 100644 (file)
@@ -2,12 +2,13 @@
 
 import os
 import sys
-import unittest
+import unittest.mock as mock
+
+import pytest
 
 from distutils.command.build_py import build_py
 from distutils.core import Distribution
 from distutils.errors import DistutilsFileError
-from unittest.mock import patch
 
 from distutils.tests import support
 
@@ -86,7 +87,7 @@ class TestBuildPy(support.TempdirManager, support.LoggingSilencer):
         except DistutilsFileError:
             self.fail("failed package_data test when package_dir is ''")
 
-    @unittest.skipIf(sys.dont_write_bytecode, 'byte-compile disabled')
+    @pytest.mark.skipif('sys.dont_write_bytecode')
     def test_byte_compile(self):
         project_dir, dist = self.create_dist(py_modules=['boiledeggs'])
         os.chdir(project_dir)
@@ -102,7 +103,7 @@ class TestBuildPy(support.TempdirManager, support.LoggingSilencer):
         found = os.listdir(os.path.join(cmd.build_lib, '__pycache__'))
         assert found == ['boiledeggs.%s.pyc' % sys.implementation.cache_tag]
 
-    @unittest.skipIf(sys.dont_write_bytecode, 'byte-compile disabled')
+    @pytest.mark.skipif('sys.dont_write_bytecode')
     def test_byte_compile_optimized(self):
         project_dir, dist = self.create_dist(py_modules=['boiledeggs'])
         os.chdir(project_dir)
@@ -166,7 +167,7 @@ class TestBuildPy(support.TempdirManager, support.LoggingSilencer):
 
         assert 'byte-compiling is disabled' in self.logs[0][1] % self.logs[0][2]
 
-    @patch("distutils.command.build_py.log.warn")
+    @mock.patch("distutils.command.build_py.log.warn")
     def test_namespace_package_does_not_warn(self, log_warn):
         """
         Originally distutils implementation did not account for PEP 420
diff --git a/setuptools/_distutils/tests/test_ccompiler.py b/setuptools/_distutils/tests/test_ccompiler.py
new file mode 100644 (file)
index 0000000..da1879f
--- /dev/null
@@ -0,0 +1,55 @@
+import os
+import sys
+import platform
+import textwrap
+import sysconfig
+
+import pytest
+
+from distutils import ccompiler
+
+
+def _make_strs(paths):
+    """
+    Convert paths to strings for legacy compatibility.
+    """
+    if sys.version_info > (3, 8) and platform.system() != "Windows":
+        return paths
+    return list(map(os.fspath, paths))
+
+
+@pytest.fixture
+def c_file(tmp_path):
+    c_file = tmp_path / 'foo.c'
+    gen_headers = ('Python.h',)
+    is_windows = platform.system() == "Windows"
+    plat_headers = ('windows.h',) * is_windows
+    all_headers = gen_headers + plat_headers
+    headers = '\n'.join(f'#include <{header}>\n' for header in all_headers)
+    payload = (
+        textwrap.dedent(
+            """
+        #headers
+        void PyInit_foo(void) {}
+        """
+        )
+        .lstrip()
+        .replace('#headers', headers)
+    )
+    c_file.write_text(payload)
+    return c_file
+
+
+def test_set_include_dirs(c_file):
+    """
+    Extensions should build even if set_include_dirs is invoked.
+    In particular, compiler-specific paths should not be overridden.
+    """
+    compiler = ccompiler.new_compiler()
+    python = sysconfig.get_paths()['include']
+    compiler.set_include_dirs([python])
+    compiler.compile(_make_strs([c_file]))
+
+    # do it again, setting include dirs after any initialization
+    compiler.set_include_dirs([python])
+    compiler.compile(_make_strs([c_file]))
index 7ad3cdfa8c6b174310304e4dfa0fe1f0910abf86..3e5f6034bfb1c31edaa0768d48566f561867fa2a 100644 (file)
@@ -1,12 +1,12 @@
 """Tests for distutils.command.check."""
 import os
 import textwrap
-import unittest
 
-from distutils.command.check import check, HAS_DOCUTILS
+import pytest
+
+from distutils.command.check import check
 from distutils.tests import support
 from distutils.errors import DistutilsSetupError
-import pytest
 
 try:
     import pygments
@@ -102,8 +102,8 @@ class TestCheck(support.LoggingSilencer, support.TempdirManager):
             cmd = self._run(metadata)
             assert cmd._warnings == 0
 
-    @unittest.skipUnless(HAS_DOCUTILS, "won't test without docutils")
     def test_check_document(self):
+        pytest.importorskip('docutils')
         pkg_info, dist = self.create_dist()
         cmd = check(dist)
 
@@ -117,8 +117,8 @@ class TestCheck(support.LoggingSilencer, support.TempdirManager):
         msgs = cmd._check_rst_data(rest)
         assert len(msgs) == 0
 
-    @unittest.skipUnless(HAS_DOCUTILS, "won't test without docutils")
     def test_check_restructuredtext(self):
+        pytest.importorskip('docutils')
         # let's see if it detects broken rest in long_description
         broken_rest = 'title\n===\n\ntest'
         pkg_info, dist = self.create_dist(long_description=broken_rest)
@@ -148,8 +148,8 @@ class TestCheck(support.LoggingSilencer, support.TempdirManager):
         cmd = self._run(metadata, cwd=HERE, strict=1, restructuredtext=1)
         assert cmd._warnings == 0
 
-    @unittest.skipUnless(HAS_DOCUTILS, "won't test without docutils")
     def test_check_restructuredtext_with_syntax_highlight(self):
+        pytest.importorskip('docutils')
         # Don't fail if there is a `code` or `code-block` directive
 
         example_rst_docs = []
index f5104e1db77d1b55045e90302cfebb009f7edcfa..e4d5bf3c0167b0e1d93c08a38107ac8bc332b8f2 100644 (file)
@@ -1,5 +1,4 @@
 """Tests for distutils.cmd."""
-import unittest
 import os
 from test.support import captured_stdout
 
@@ -15,14 +14,13 @@ class MyCmd(Command):
         pass
 
 
-class TestCommand(unittest.TestCase):
-    def setUp(self):
-        dist = Distribution()
-        self.cmd = MyCmd(dist)
+@pytest.fixture
+def cmd(request):
+    return MyCmd(Distribution())
 
-    def test_ensure_string_list(self):
 
-        cmd = self.cmd
+class TestCommand:
+    def test_ensure_string_list(self, cmd):
         cmd.not_string_list = ['one', 2, 'three']
         cmd.yes_string_list = ['one', 'two', 'three']
         cmd.not_string_list2 = object()
@@ -47,10 +45,7 @@ class TestCommand(unittest.TestCase):
         with pytest.raises(DistutilsOptionError):
             cmd.ensure_string_list('option3')
 
-    def test_make_file(self):
-
-        cmd = self.cmd
-
+    def test_make_file(self, cmd):
         # making sure it raises when infiles is not a string or a list/tuple
         with pytest.raises(TypeError):
             cmd.make_file(infiles=1, outfile='', func='func', args=())
@@ -63,14 +58,13 @@ class TestCommand(unittest.TestCase):
         cmd.execute = _execute
         cmd.make_file(infiles='in', outfile='out', func='func', args=())
 
-    def test_dump_options(self):
+    def test_dump_options(self, cmd):
 
         msgs = []
 
         def _announce(msg, level):
             msgs.append(msg)
 
-        cmd = self.cmd
         cmd.announce = _announce
         cmd.option1 = 1
         cmd.option2 = 1
@@ -80,8 +74,7 @@ class TestCommand(unittest.TestCase):
         wanted = ["command options for 'MyCmd':", '  option1 = 1', '  option2 = 1']
         assert msgs == wanted
 
-    def test_ensure_string(self):
-        cmd = self.cmd
+    def test_ensure_string(self, cmd):
         cmd.option1 = 'ok'
         cmd.ensure_string('option1')
 
@@ -93,24 +86,21 @@ class TestCommand(unittest.TestCase):
         with pytest.raises(DistutilsOptionError):
             cmd.ensure_string('option3')
 
-    def test_ensure_filename(self):
-        cmd = self.cmd
+    def test_ensure_filename(self, cmd):
         cmd.option1 = __file__
         cmd.ensure_filename('option1')
         cmd.option2 = 'xxx'
         with pytest.raises(DistutilsOptionError):
             cmd.ensure_filename('option2')
 
-    def test_ensure_dirname(self):
-        cmd = self.cmd
+    def test_ensure_dirname(self, cmd):
         cmd.option1 = os.path.dirname(__file__) or os.curdir
         cmd.ensure_dirname('option1')
         cmd.option2 = 'xxx'
         with pytest.raises(DistutilsOptionError):
             cmd.ensure_dirname('option2')
 
-    def test_debug_print(self):
-        cmd = self.cmd
+    def test_debug_print(self, cmd):
         with captured_stdout() as stdout:
             cmd.debug_print('xxx')
         stdout.seek(0)
index b088d6007fcc7de7d8e7a26eea852ecb0b095309..43ba6766aef4e13d4a8a195c65ff59cb28be2a61 100644 (file)
@@ -1,14 +1,8 @@
 """Tests for distutils.pypirc.pypirc."""
 import os
-import unittest
 
 import pytest
 
-from distutils.core import PyPIRCCommand
-from distutils.core import Distribution
-from distutils.log import set_threshold
-from distutils.log import WARN
-
 from distutils.tests import support
 
 PYPIRC = """\
@@ -52,37 +46,13 @@ password:xxx
 
 
 @support.combine_markers
-@pytest.mark.usefixtures('save_env')
+@pytest.mark.usefixtures('threshold_warn')
+@pytest.mark.usefixtures('pypirc')
 class BasePyPIRCCommandTestCase(
     support.TempdirManager,
     support.LoggingSilencer,
-    unittest.TestCase,
 ):
-    def setUp(self):
-        """Patches the environment."""
-        super().setUp()
-        self.tmp_dir = self.mkdtemp()
-        os.environ['HOME'] = self.tmp_dir
-        os.environ['USERPROFILE'] = self.tmp_dir
-        self.rc = os.path.join(self.tmp_dir, '.pypirc')
-        self.dist = Distribution()
-
-        class command(PyPIRCCommand):
-            def __init__(self, dist):
-                super().__init__(dist)
-
-            def initialize_options(self):
-                pass
-
-            finalize_options = initialize_options
-
-        self._cmd = command
-        self.old_threshold = set_threshold(WARN)
-
-    def tearDown(self):
-        """Removes the patch."""
-        set_threshold(self.old_threshold)
-        super().tearDown()
+    pass
 
 
 class PyPIRCCommandTestCase(BasePyPIRCCommandTestCase):
index 425cc1ba4894047e1585bf7ca165e89fd4e17f70..65c60f64dd4f7613e990e70c82e7dbe16a782f0a 100644 (file)
@@ -1,32 +1,28 @@
 """Tests for distutils.command.config."""
-import unittest
 import os
 import sys
 from test.support import missing_compiler_executable
 
+import pytest
+
 from distutils.command.config import dump_file, config
 from distutils.tests import support
 from distutils import log
 
 
+@pytest.fixture(autouse=True)
+def info_log(request, monkeypatch):
+    self = request.instance
+    self._logs = []
+    monkeypatch.setattr(log, 'info', self._info)
+
+
 @support.combine_markers
-class ConfigTestCase(
-    support.LoggingSilencer, support.TempdirManager, unittest.TestCase
-):
+class TestConfig(support.LoggingSilencer, support.TempdirManager):
     def _info(self, msg, *args):
         for line in msg.splitlines():
             self._logs.append(line)
 
-    def setUp(self):
-        super().setUp()
-        self._logs = []
-        self.old_log = log.info
-        log.info = self._info
-
-    def tearDown(self):
-        log.info = self.old_log
-        super().tearDown()
-
     def test_dump_file(self):
         this_file = os.path.splitext(__file__)[0] + '.py'
         f = open(this_file)
@@ -38,7 +34,7 @@ class ConfigTestCase(
         dump_file(this_file, 'I am the header')
         assert len(self._logs) == numlines + 1
 
-    @unittest.skipIf(sys.platform == 'win32', "can't test on Windows")
+    @pytest.mark.skipif('platform.system() == "Windows"')
     def test_search_cpp(self):
         cmd = missing_compiler_executable(['preprocessor'])
         if cmd is not None:
index ef085a853082a9f95bbe84dfad09e4d17d18bf5e..86b0040f60e191bd99ec37a5cc9355b0f78be839 100644 (file)
@@ -3,15 +3,12 @@
 import io
 import distutils.core
 import os
-import shutil
 import sys
 from test.support import captured_stdout
 
 import pytest
 
 from . import py38compat as os_helper
-import unittest
-from distutils import log
 from distutils.dist import Distribution
 
 # setup script that uses __file__
@@ -59,27 +56,15 @@ if __name__ == "__main__":
 """
 
 
+@pytest.fixture(autouse=True)
+def save_stdout(monkeypatch):
+    monkeypatch.setattr(sys, 'stdout', sys.stdout)
+
+
 @pytest.mark.usefixtures('save_env')
 @pytest.mark.usefixtures('save_argv')
-class CoreTestCase(unittest.TestCase):
-    def setUp(self):
-        super().setUp()
-        self.old_stdout = sys.stdout
-        self.cleanup_testfn()
-        self.addCleanup(log.set_threshold, log._global_log.threshold)
-
-    def tearDown(self):
-        sys.stdout = self.old_stdout
-        self.cleanup_testfn()
-        super().tearDown()
-
-    def cleanup_testfn(self):
-        path = os_helper.TESTFN
-        if os.path.isfile(path):
-            os.remove(path)
-        elif os.path.isdir(path):
-            shutil.rmtree(path)
-
+@pytest.mark.usefixtures('cleanup_testfn')
+class TestCore:
     def write_setup(self, text, path=os_helper.TESTFN):
         f = open(path, "w")
         try:
index b14ddb40c3fc0a8ab278c754d2d4b03b2ef0d469..ef01ae219901bf0f0f4c8d3578cbb06bf1a91c4a 100644 (file)
@@ -1,8 +1,9 @@
 """Tests for distutils.cygwinccompiler."""
-import unittest
 import sys
 import os
 
+import pytest
+
 from distutils.cygwinccompiler import (
     check_config_h,
     CONFIG_H_OK,
@@ -11,33 +12,23 @@ from distutils.cygwinccompiler import (
     get_msvcr,
 )
 from distutils.tests import support
-import pytest
-
-
-class CygwinCCompilerTestCase(support.TempdirManager, unittest.TestCase):
-    def setUp(self):
-        super().setUp()
-        self.version = sys.version
-        self.python_h = os.path.join(self.mkdtemp(), 'python.h')
-        from distutils import sysconfig
+from distutils import sysconfig
 
-        self.old_get_config_h_filename = sysconfig.get_config_h_filename
-        sysconfig.get_config_h_filename = self._get_config_h_filename
 
-    def tearDown(self):
-        sys.version = self.version
-        from distutils import sysconfig
+@pytest.fixture(autouse=True)
+def stuff(request, monkeypatch, distutils_managed_tempdir):
+    self = request.instance
+    self.python_h = os.path.join(self.mkdtemp(), 'python.h')
+    monkeypatch.setattr(sysconfig, 'get_config_h_filename', self._get_config_h_filename)
+    monkeypatch.setattr(sys, 'version', sys.version)
 
-        sysconfig.get_config_h_filename = self.old_get_config_h_filename
-        super().tearDown()
 
+class TestCygwinCCompiler(support.TempdirManager):
     def _get_config_h_filename(self):
         return self.python_h
 
-    @unittest.skipIf(sys.platform != "cygwin", "Not running on Cygwin")
-    @unittest.skipIf(
-        not os.path.exists("/usr/lib/libbash.dll.a"), "Don't know a linkable library"
-    )
+    @pytest.mark.skipif('sys.platform != "cygwin"')
+    @pytest.mark.skipif('not os.path.exists("/usr/lib/libbash.dll.a")')
     def test_find_library_file(self):
         from distutils.cygwinccompiler import CygwinCCompiler
 
@@ -48,7 +39,7 @@ class CygwinCCompilerTestCase(support.TempdirManager, unittest.TestCase):
         assert os.path.exists(linkable_file)
         assert linkable_file == f"/usr/lib/lib{link_name:s}.dll.a"
 
-    @unittest.skipIf(sys.platform != "cygwin", "Not running on Cygwin")
+    @pytest.mark.skipif('sys.platform != "cygwin"')
     def test_runtime_library_dir_option(self):
         from distutils.cygwinccompiler import CygwinCCompiler
 
index fc32c7fe74e375aa3b6a11b768582371ad95cb39..cd7e018f5e5219d98e6454f0fd1f209205e532e3 100644 (file)
@@ -1,9 +1,7 @@
 """Tests for distutils.dir_util."""
-import unittest
 import os
 import stat
-import sys
-from unittest.mock import patch
+import unittest.mock as mock
 
 from distutils import dir_util, errors
 from distutils.dir_util import (
@@ -19,27 +17,24 @@ from distutils.tests import support
 import pytest
 
 
-class DirUtilTestCase(support.TempdirManager, unittest.TestCase):
+@pytest.fixture(autouse=True)
+def stuff(request, monkeypatch, distutils_managed_tempdir):
+    self = request.instance
+    self._logs = []
+    tmp_dir = self.mkdtemp()
+    self.root_target = os.path.join(tmp_dir, 'deep')
+    self.target = os.path.join(self.root_target, 'here')
+    self.target2 = os.path.join(tmp_dir, 'deep2')
+    monkeypatch.setattr(log, 'info', self._log)
+
+
+class TestDirUtil(support.TempdirManager):
     def _log(self, msg, *args):
         if len(args) > 0:
             self._logs.append(msg % args)
         else:
             self._logs.append(msg)
 
-    def setUp(self):
-        super().setUp()
-        self._logs = []
-        tmp_dir = self.mkdtemp()
-        self.root_target = os.path.join(tmp_dir, 'deep')
-        self.target = os.path.join(self.root_target, 'here')
-        self.target2 = os.path.join(tmp_dir, 'deep2')
-        self.old_log = log.info
-        log.info = self._log
-
-    def tearDown(self):
-        log.info = self.old_log
-        super().tearDown()
-
     def test_mkpath_remove_tree_verbosity(self):
 
         mkpath(self.target, verbose=0)
@@ -56,10 +51,7 @@ class DirUtilTestCase(support.TempdirManager, unittest.TestCase):
         wanted = ["removing '%s' (and everything under it)" % self.root_target]
         assert self._logs == wanted
 
-    @unittest.skipIf(
-        sys.platform.startswith('win'),
-        "This test is only appropriate for POSIX-like systems.",
-    )
+    @pytest.mark.skipif("platform.system() == 'Windows'")
     def test_mkpath_with_custom_mode(self):
         # Get and set the current umask value for testing mode bits.
         umask = os.umask(0o002)
@@ -129,7 +121,7 @@ class DirUtilTestCase(support.TempdirManager, unittest.TestCase):
         """
         An exception in listdir should raise a DistutilsFileError
         """
-        with patch("os.listdir", side_effect=OSError()), pytest.raises(
+        with mock.patch("os.listdir", side_effect=OSError()), pytest.raises(
             errors.DistutilsFileError
         ):
             src = self.tempdirs[-1]
index c962d3f3ac0ab0c0106c785d0cd99fa0aa339521..ddfaf9216772f5db2beba07fc1bba4bb3913a6f2 100644 (file)
@@ -2,12 +2,10 @@
 import os
 import io
 import sys
-import unittest
 import warnings
 import textwrap
 import functools
-
-from unittest import mock
+import unittest.mock as mock
 
 import pytest
 
@@ -89,9 +87,9 @@ class TestDistributionBehavior(
         assert isinstance(cmd, test_dist)
         assert cmd.sample_option == "sometext"
 
-    @unittest.skipIf(
+    @pytest.mark.skipif(
         'distutils' not in Distribution.parse_config_files.__module__,
-        'Cannot test when virtualenv has monkey-patched Distribution.',
+        reason='Cannot test when virtualenv has monkey-patched Distribution',
     )
     def test_venv_install_options(self, request):
         sys.argv.append("install")
index e95535df05aaddebdd1faeff8d418f4e79d3a475..b2e83c52f2981beef8d720066c46855bbe9f99dc 100644 (file)
@@ -1,8 +1,7 @@
 """Tests for distutils.file_util."""
-import unittest
 import os
 import errno
-from unittest.mock import patch
+import unittest.mock as mock
 
 from distutils.file_util import move_file, copy_file
 from distutils import log
@@ -12,27 +11,24 @@ from .py38compat import unlink
 import pytest
 
 
-class FileUtilTestCase(support.TempdirManager, unittest.TestCase):
+@pytest.fixture(autouse=True)
+def stuff(request, monkeypatch, distutils_managed_tempdir):
+    self = request.instance
+    self._logs = []
+    tmp_dir = self.mkdtemp()
+    self.source = os.path.join(tmp_dir, 'f1')
+    self.target = os.path.join(tmp_dir, 'f2')
+    self.target_dir = os.path.join(tmp_dir, 'd1')
+    monkeypatch.setattr(log, 'info', self._log)
+
+
+class TestFileUtil(support.TempdirManager):
     def _log(self, msg, *args):
         if len(args) > 0:
             self._logs.append(msg % args)
         else:
             self._logs.append(msg)
 
-    def setUp(self):
-        super().setUp()
-        self._logs = []
-        self.old_log = log.info
-        log.info = self._log
-        tmp_dir = self.mkdtemp()
-        self.source = os.path.join(tmp_dir, 'f1')
-        self.target = os.path.join(tmp_dir, 'f2')
-        self.target_dir = os.path.join(tmp_dir, 'd1')
-
-    def tearDown(self):
-        log.info = self.old_log
-        super().tearDown()
-
     def test_move_file_verbosity(self):
         f = open(self.source, 'w')
         try:
@@ -63,7 +59,7 @@ class FileUtilTestCase(support.TempdirManager, unittest.TestCase):
 
     def test_move_file_exception_unpacking_rename(self):
         # see issue 22182
-        with patch("os.rename", side_effect=OSError("wrong", 1)), pytest.raises(
+        with mock.patch("os.rename", side_effect=OSError("wrong", 1)), pytest.raises(
             DistutilsFileError
         ):
             with open(self.source, 'w') as fobj:
@@ -72,9 +68,11 @@ class FileUtilTestCase(support.TempdirManager, unittest.TestCase):
 
     def test_move_file_exception_unpacking_unlink(self):
         # see issue 22182
-        with patch("os.rename", side_effect=OSError(errno.EXDEV, "wrong")), patch(
-            "os.unlink", side_effect=OSError("wrong", 1)
-        ), pytest.raises(DistutilsFileError):
+        with mock.patch(
+            "os.rename", side_effect=OSError(errno.EXDEV, "wrong")
+        ), mock.patch("os.unlink", side_effect=OSError("wrong", 1)), pytest.raises(
+            DistutilsFileError
+        ):
             with open(self.source, 'w') as fobj:
                 fobj.write('spam eggs')
             move_file(self.source, self.target, verbose=0)
@@ -106,7 +104,7 @@ class FileUtilTestCase(support.TempdirManager, unittest.TestCase):
         with open(self.source, 'w') as f:
             f.write('some content')
         st = os.stat(self.source)
-        with patch("os.link", side_effect=OSError(0, "linking unsupported")):
+        with mock.patch("os.link", side_effect=OSError(0, "linking unsupported")):
             copy_file(self.source, self.target, link='hard')
         st2 = os.stat(self.source)
         st3 = os.stat(self.target)
index 519227d25eff47a71559287d5700fb7e5f671b7c..32a18b2f2f26b8ac543674c4cc48e7f41328d0ee 100644 (file)
@@ -2,8 +2,8 @@
 
 import os
 import sys
-import unittest
 import site
+import pathlib
 
 from test.support import captured_stdout
 
@@ -28,10 +28,9 @@ def _make_ext_name(modname):
 
 @support.combine_markers
 @pytest.mark.usefixtures('save_env')
-class InstallTestCase(
+class TestInstall(
     support.TempdirManager,
     support.LoggingSilencer,
-    unittest.TestCase,
 ):
     @pytest.mark.xfail(
         'platform.system() == "Windows" and sys.version_info > (3, 11)',
@@ -78,35 +77,23 @@ class InstallTestCase(
         check_path(cmd.install_scripts, os.path.join(destination, "bin"))
         check_path(cmd.install_data, destination)
 
-    def test_user_site(self):
+    def test_user_site(self, monkeypatch):
         # test install with --user
         # preparing the environment for the test
-        self.old_user_base = site.USER_BASE
-        self.old_user_site = site.USER_SITE
         self.tmpdir = self.mkdtemp()
-        self.user_base = os.path.join(self.tmpdir, 'B')
-        self.user_site = os.path.join(self.tmpdir, 'S')
-        site.USER_BASE = self.user_base
-        site.USER_SITE = self.user_site
-        install_module.USER_BASE = self.user_base
-        install_module.USER_SITE = self.user_site
+        orig_site = site.USER_SITE
+        orig_base = site.USER_BASE
+        monkeypatch.setattr(site, 'USER_BASE', os.path.join(self.tmpdir, 'B'))
+        monkeypatch.setattr(site, 'USER_SITE', os.path.join(self.tmpdir, 'S'))
+        monkeypatch.setattr(install_module, 'USER_BASE', site.USER_BASE)
+        monkeypatch.setattr(install_module, 'USER_SITE', site.USER_SITE)
 
         def _expanduser(path):
             if path.startswith('~'):
                 return os.path.normpath(self.tmpdir + path[1:])
             return path
 
-        self.old_expand = os.path.expanduser
-        os.path.expanduser = _expanduser
-
-        def cleanup():
-            site.USER_BASE = self.old_user_base
-            site.USER_SITE = self.old_user_site
-            install_module.USER_BASE = self.old_user_base
-            install_module.USER_SITE = self.old_user_site
-            os.path.expanduser = self.old_expand
-
-        self.addCleanup(cleanup)
+        monkeypatch.setattr(os.path, 'expanduser', _expanduser)
 
         for key in ('nt_user', 'posix_user'):
             assert key in INSTALL_SCHEMES
@@ -122,24 +109,22 @@ class InstallTestCase(
         cmd.user = 1
 
         # user base and site shouldn't be created yet
-        assert not os.path.exists(self.user_base)
-        assert not os.path.exists(self.user_site)
+        assert not os.path.exists(site.USER_BASE)
+        assert not os.path.exists(site.USER_SITE)
 
         # let's run finalize
         cmd.ensure_finalized()
 
         # now they should
-        assert os.path.exists(self.user_base)
-        assert os.path.exists(self.user_site)
+        assert os.path.exists(site.USER_BASE)
+        assert os.path.exists(site.USER_SITE)
 
         assert 'userbase' in cmd.config_vars
         assert 'usersite' in cmd.config_vars
 
-        actual_headers = os.path.relpath(cmd.install_headers, self.user_base)
+        actual_headers = os.path.relpath(cmd.install_headers, site.USER_BASE)
         if os.name == 'nt':
-            site_path = os.path.relpath(
-                os.path.dirname(self.old_user_site), self.old_user_base
-            )
+            site_path = os.path.relpath(os.path.dirname(orig_site), orig_base)
             include = os.path.join(site_path, 'Include')
         else:
             include = sysconfig.get_python_inc(0, '')
@@ -232,7 +217,7 @@ class InstallTestCase(
     def test_record_extensions(self):
         cmd = test_support.missing_compiler_executable()
         if cmd is not None:
-            self.skipTest('The %r command is not found' % cmd)
+            pytest.skip('The %r command is not found' % cmd)
         install_dir = self.mkdtemp()
         project_dir, dist = self.create_dist(
             ext_modules=[Extension('xx', ['xxmodule.c'])]
@@ -252,11 +237,7 @@ class InstallTestCase(
         cmd.ensure_finalized()
         cmd.run()
 
-        f = open(cmd.record)
-        try:
-            content = f.read()
-        finally:
-            f.close()
+        content = pathlib.Path(cmd.record).read_text()
 
         found = [os.path.basename(line) for line in content.splitlines()]
         expected = [
index d8192e06ce8a49d38d4e70269430e22ec65cf536..a654a66a79b6a8a2bb2cdddd7422a6d756690bf3 100644 (file)
@@ -2,7 +2,6 @@
 import sys
 import os
 import importlib.util
-import unittest
 
 import pytest
 
@@ -38,7 +37,7 @@ class TestInstallLib(
         cmd.finalize_options()
         assert cmd.optimize == 2
 
-    @unittest.skipIf(sys.dont_write_bytecode, 'byte-compile disabled')
+    @pytest.mark.skipif('sys.dont_write_bytecode')
     def test_byte_compile(self):
         project_dir, dist = self.create_dist()
         os.chdir(project_dir)
index 614da574ae1b28fe2c918fc083bbceb049265d9a..7aeee4057fdea53e11e98c32e4c282a4ca406928 100644 (file)
@@ -2,50 +2,51 @@
 
 import io
 import sys
-import unittest
 from test.support import swap_attr
 
+import pytest
+
 from distutils import log
 
 
-class TestLog(unittest.TestCase):
-    def test_non_ascii(self):
-        # Issues #8663, #34421: test that non-encodable text is escaped with
-        # backslashreplace error handler and encodable non-ASCII text is
-        # output as is.
-        for errors in (
+class TestLog:
+    @pytest.mark.parametrize(
+        'errors',
+        (
             'strict',
             'backslashreplace',
             'surrogateescape',
             'replace',
             'ignore',
-        ):
-            with self.subTest(errors=errors):
-                stdout = io.TextIOWrapper(io.BytesIO(), encoding='cp437', errors=errors)
-                stderr = io.TextIOWrapper(io.BytesIO(), encoding='cp437', errors=errors)
-                old_threshold = log.set_threshold(log.DEBUG)
-                try:
-                    with swap_attr(sys, 'stdout', stdout), swap_attr(
-                        sys, 'stderr', stderr
-                    ):
-                        log.debug('Dεbug\tMėssãge')
-                        log.fatal('Fαtal\tÈrrōr')
-                finally:
-                    log.set_threshold(old_threshold)
+        ),
+    )
+    def test_non_ascii(self, errors):
+        # Issues #8663, #34421: test that non-encodable text is escaped with
+        # backslashreplace error handler and encodable non-ASCII text is
+        # output as is.
+        stdout = io.TextIOWrapper(io.BytesIO(), encoding='cp437', errors=errors)
+        stderr = io.TextIOWrapper(io.BytesIO(), encoding='cp437', errors=errors)
+        old_threshold = log.set_threshold(log.DEBUG)
+        try:
+            with swap_attr(sys, 'stdout', stdout), swap_attr(sys, 'stderr', stderr):
+                log.debug('Dεbug\tMėssãge')
+                log.fatal('Fαtal\tÈrrōr')
+        finally:
+            log.set_threshold(old_threshold)
 
-                stdout.seek(0)
-                assert stdout.read().rstrip() == (
-                    'Dεbug\tM?ss?ge'
-                    if errors == 'replace'
-                    else 'Dεbug\tMssge'
-                    if errors == 'ignore'
-                    else 'Dεbug\tM\\u0117ss\\xe3ge'
-                )
-                stderr.seek(0)
-                assert stderr.read().rstrip() == (
-                    'Fαtal\t?rr?r'
-                    if errors == 'replace'
-                    else 'Fαtal\trrr'
-                    if errors == 'ignore'
-                    else 'Fαtal\t\\xc8rr\\u014dr'
-                )
+        stdout.seek(0)
+        assert stdout.read().rstrip() == (
+            'Dεbug\tM?ss?ge'
+            if errors == 'replace'
+            else 'Dεbug\tMssge'
+            if errors == 'ignore'
+            else 'Dεbug\tM\\u0117ss\\xe3ge'
+        )
+        stderr.seek(0)
+        assert stderr.read().rstrip() == (
+            'Fαtal\t?rr?r'
+            if errors == 'replace'
+            else 'Fαtal\trrr'
+            if errors == 'ignore'
+            else 'Fαtal\t\\xc8rr\\u014dr'
+        )
index db4694e1b31d0519cf2934f783830b0f396182c9..f63537b8e5b009c72975ad2fd6e8e95a44a582ac 100644 (file)
@@ -1,8 +1,8 @@
 """Tests for distutils._msvccompiler."""
 import sys
-import unittest
 import os
 import threading
+import unittest.mock as mock
 
 import pytest
 
@@ -50,26 +50,17 @@ class Testmsvccompiler(support.TempdirManager):
                 os.environ['DISTUTILS_USE_SDK'] = old_distutils_use_sdk
 
     @needs_winreg
-    def test_get_vc2017(self):
-        # This function cannot be mocked, so pass it if we find VS 2017
-        # and mark it skipped if we do not.
-        version, path = _msvccompiler._find_vc2017()
-        if version:
-            assert version >= 15
-            assert os.path.isdir(path)
-        else:
-            raise unittest.SkipTest("VS 2017 is not installed")
-
-    @needs_winreg
-    def test_get_vc2015(self):
-        # This function cannot be mocked, so pass it if we find VS 2015
-        # and mark it skipped if we do not.
-        version, path = _msvccompiler._find_vc2015()
-        if version:
-            assert version >= 14
-            assert os.path.isdir(path)
-        else:
-            raise unittest.SkipTest("VS 2015 is not installed")
+    @pytest.mark.parametrize('ver', (2015, 2017))
+    def test_get_vc(self, ver):
+        # This function cannot be mocked, so pass if VC is found
+        # and skip otherwise.
+        lookup = getattr(_msvccompiler, f'_find_vc{ver}')
+        expected_version = {2015: 14, 2017: 15}[ver]
+        version, path = lookup()
+        if not version:
+            pytest.skip(f"VS {ver} is not installed")
+        assert version >= expected_version
+        assert os.path.isdir(path)
 
 
 class CheckThread(threading.Thread):
@@ -118,7 +109,7 @@ class TestSpawn:
             "A spawn without an env argument."
             assert os.environ["PATH"] == "expected"
 
-        with unittest.mock.patch.object(ccompiler.CCompiler, 'spawn', CCompiler_spawn):
+        with mock.patch.object(ccompiler.CCompiler, 'spawn', CCompiler_spawn):
             compiler.spawn(["n/a"])
 
         assert os.environ.get("PATH") != "expected"
index 7657f3914f559f911c37fb93630a1f0e1ca912fd..0a5765f1fdc231eecf03e08f97dbb2cb255abede 100644 (file)
@@ -1,12 +1,7 @@
 """Tests for distutils.command.register."""
 import os
-import unittest
 import getpass
 import urllib
-import warnings
-
-
-from .py38compat import check_warnings
 
 from distutils.command import register as register_module
 from distutils.command.register import register
@@ -78,26 +73,20 @@ class FakeOpener:
         }.get(name.lower(), default)
 
 
-class RegisterTestCase(BasePyPIRCCommandTestCase):
-    def setUp(self):
-        super().setUp()
-        # patching the password prompt
-        self._old_getpass = getpass.getpass
+@pytest.fixture(autouse=True)
+def autopass(monkeypatch):
+    monkeypatch.setattr(getpass, 'getpass', lambda prompt: 'password')
 
-        def _getpass(prompt):
-            return 'password'
 
-        getpass.getpass = _getpass
-        urllib.request._opener = None
-        self.old_opener = urllib.request.build_opener
-        self.conn = urllib.request.build_opener = FakeOpener()
+@pytest.fixture(autouse=True)
+def fake_opener(monkeypatch, request):
+    opener = FakeOpener()
+    monkeypatch.setattr(urllib.request, 'build_opener', opener)
+    monkeypatch.setattr(urllib.request, '_opener', None)
+    request.instance.conn = opener
 
-    def tearDown(self):
-        getpass.getpass = self._old_getpass
-        urllib.request._opener = None
-        urllib.request.build_opener = self.old_opener
-        super().tearDown()
 
+class TestRegister(BasePyPIRCCommandTestCase):
     def _get_cmd(self, metadata=None):
         if metadata is None:
             metadata = {
@@ -106,6 +95,7 @@ class RegisterTestCase(BasePyPIRCCommandTestCase):
                 'author_email': 'xxx',
                 'name': 'xxx',
                 'version': 'xxx',
+                'long_description': 'xxx',
             }
         pkg_info, dist = self.create_dist(**metadata)
         return register(dist)
@@ -164,8 +154,8 @@ class RegisterTestCase(BasePyPIRCCommandTestCase):
         req1 = dict(self.conn.reqs[0].headers)
         req2 = dict(self.conn.reqs[1].headers)
 
-        assert req1['Content-length'] == '1359'
-        assert req2['Content-length'] == '1359'
+        assert req1['Content-length'] == '1358'
+        assert req2['Content-length'] == '1358'
         assert b'xxx' in self.conn.reqs[1].data
 
     def test_password_not_in_file(self):
@@ -216,13 +206,14 @@ class RegisterTestCase(BasePyPIRCCommandTestCase):
         assert headers['Content-length'] == '290'
         assert b'tarek' in req.data
 
-    @unittest.skipUnless(docutils is not None, 'needs docutils')
     def test_strict(self):
-        # testing the script option
+        # testing the strict option
         # when on, the register command stops if
         # the metadata is incomplete or if
         # long_description is not reSt compliant
 
+        pytest.importorskip('docutils')
+
         # empty metadata
         cmd = self._get_cmd({})
         cmd.ensure_finalized()
@@ -292,8 +283,8 @@ class RegisterTestCase(BasePyPIRCCommandTestCase):
         finally:
             del register_module.input
 
-    @unittest.skipUnless(docutils is not None, 'needs docutils')
-    def test_register_invalid_long_description(self):
+    def test_register_invalid_long_description(self, monkeypatch):
+        pytest.importorskip('docutils')
         description = ':funkie:`str`'  # mimic Sphinx-specific markup
         metadata = {
             'url': 'xxx',
@@ -307,20 +298,11 @@ class RegisterTestCase(BasePyPIRCCommandTestCase):
         cmd.ensure_finalized()
         cmd.strict = True
         inputs = Inputs('2', 'tarek', 'tarek@ziade.org')
-        register_module.input = inputs
-        self.addCleanup(delattr, register_module, 'input')
+        monkeypatch.setattr(register_module, 'input', inputs, raising=False)
 
         with pytest.raises(DistutilsSetupError):
             cmd.run()
 
-    def test_check_metadata_deprecated(self):
-        # makes sure make_metadata is deprecated
-        cmd = self._get_cmd()
-        with check_warnings() as w:
-            warnings.simplefilter("always")
-            cmd.check_metadata()
-            assert len(w.warnings) == 1
-
     def test_list_classifiers(self):
         cmd = self._get_cmd()
         cmd.list_classifiers = 1
index 24ec9eb608ead364024d12cc1fd3f8acb2e110fe..b11fe7c41ea440929723dd64b16b2ca8dab58075 100644 (file)
@@ -1,7 +1,6 @@
 """Tests for distutils.command.sdist."""
 import os
 import tarfile
-import unittest
 import warnings
 import zipfile
 from os.path import join
@@ -10,6 +9,8 @@ from test.support import captured_stdout
 from .unix_compat import require_unix_id, require_uid_0, pwd, grp
 
 import pytest
+import path
+import jaraco.path
 
 from .py38compat import check_warnings
 
@@ -17,7 +18,7 @@ from distutils.command.sdist import sdist, show_formats
 from distutils.core import Distribution
 from distutils.tests.test_config import BasePyPIRCCommandTestCase
 from distutils.errors import DistutilsOptionError
-from distutils.spawn import find_executable
+from distutils.spawn import find_executable  # noqa: F401
 from distutils.log import WARN
 from distutils.filelist import FileList
 from distutils.archive_util import ARCHIVE_FORMATS
@@ -45,26 +46,24 @@ somecode%(sep)sdoc.txt
 """
 
 
-class SDistTestCase(BasePyPIRCCommandTestCase):
-    def setUp(self):
-        # PyPIRCCommandTestCase creates a temp dir already
-        # and put it in self.tmp_dir
-        super().setUp()
-        # setting up an environment
-        self.old_path = os.getcwd()
-        os.mkdir(join(self.tmp_dir, 'somecode'))
-        os.mkdir(join(self.tmp_dir, 'dist'))
-        # a package, and a README
-        self.write_file((self.tmp_dir, 'README'), 'xxx')
-        self.write_file((self.tmp_dir, 'somecode', '__init__.py'), '#')
-        self.write_file((self.tmp_dir, 'setup.py'), SETUP_PY)
-        os.chdir(self.tmp_dir)
-
-    def tearDown(self):
-        # back to normal
-        os.chdir(self.old_path)
-        super().tearDown()
-
+@pytest.fixture(autouse=True)
+def project_dir(request, pypirc):
+    self = request.instance
+    jaraco.path.build(
+        {
+            'somecode': {
+                '__init__.py': '#',
+            },
+            'README': 'xxx',
+            'setup.py': SETUP_PY,
+        },
+        self.tmp_dir,
+    )
+    with path.Path(self.tmp_dir):
+        yield
+
+
+class TestSDist(BasePyPIRCCommandTestCase):
     def get_cmd(self, metadata=None):
         """Returns a cmd"""
         if metadata is None:
@@ -133,8 +132,8 @@ class SDistTestCase(BasePyPIRCCommandTestCase):
         assert sorted(content) == ['fake-1.0/' + x for x in expected]
 
     @pytest.mark.usefixtures('needs_zlib')
-    @unittest.skipIf(find_executable('tar') is None, "The tar command is not found")
-    @unittest.skipIf(find_executable('gzip') is None, "The gzip command is not found")
+    @pytest.mark.skipif("not find_executable('tar')")
+    @pytest.mark.skipif("not find_executable('gzip')")
     def test_make_distribution(self):
         # now building a sdist
         dist, cmd = self.get_cmd()
@@ -341,7 +340,7 @@ class SDistTestCase(BasePyPIRCCommandTestCase):
         # this manifest command takes one argument
         self._check_template('prune')
 
-    @unittest.skipIf(os.name != 'nt', 'test relevant for Windows only')
+    @pytest.mark.skipif("platform.system() != 'Windows'")
     def test_invalid_template_wrong_path(self):
         # on Windows, trailing slashes are not allowed
         # this used to crash instead of raising a warning: #8286
@@ -466,8 +465,8 @@ class SDistTestCase(BasePyPIRCCommandTestCase):
     @pytest.mark.usefixtures('needs_zlib')
     @require_unix_id
     @require_uid_0
-    @unittest.skipIf(find_executable('tar') is None, "The tar command is not found")
-    @unittest.skipIf(find_executable('gzip') is None, "The gzip command is not found")
+    @pytest.mark.skipif("not find_executable('tar')")
+    @pytest.mark.skipif("not find_executable('gzip')")
     def test_make_distribution_owner_group(self):
         # now building a sdist
         dist, cmd = self.get_cmd()
index c28b8ba594bcd91cb4f9334976b8a633aec2bccb..d2a898ed3f61ed61fb78a20de33cebeaf9088fb4 100644 (file)
@@ -2,7 +2,8 @@
 import os
 import stat
 import sys
-import unittest.mock
+import unittest.mock as mock
+
 from test.support import unix_shell
 
 from . import py38compat as os_helper
@@ -15,7 +16,7 @@ import pytest
 
 
 class TestSpawn(support.TempdirManager, support.LoggingSilencer):
-    @unittest.skipUnless(os.name in ('nt', 'posix'), 'Runs only under posix or nt')
+    @pytest.mark.skipif("os.name not in ('nt', 'posix')")
     def test_spawn(self):
         tmpdir = self.mkdtemp()
 
@@ -78,9 +79,9 @@ class TestSpawn(support.TempdirManager, support.LoggingSilencer):
             # PATH='': no match, except in the current directory
             with os_helper.EnvironmentVarGuard() as env:
                 env['PATH'] = ''
-                with unittest.mock.patch(
+                with mock.patch(
                     'distutils.spawn.os.confstr', return_value=tmp_dir, create=True
-                ), unittest.mock.patch('distutils.spawn.os.defpath', tmp_dir):
+                ), mock.patch('distutils.spawn.os.defpath', tmp_dir):
                     rv = find_executable(program)
                     assert rv is None
 
@@ -92,9 +93,9 @@ class TestSpawn(support.TempdirManager, support.LoggingSilencer):
             # PATH=':': explicitly looks in the current directory
             with os_helper.EnvironmentVarGuard() as env:
                 env['PATH'] = os.pathsep
-                with unittest.mock.patch(
+                with mock.patch(
                     'distutils.spawn.os.confstr', return_value='', create=True
-                ), unittest.mock.patch('distutils.spawn.os.defpath', ''):
+                ), mock.patch('distutils.spawn.os.defpath', ''):
                     rv = find_executable(program)
                     assert rv is None
 
@@ -108,16 +109,16 @@ class TestSpawn(support.TempdirManager, support.LoggingSilencer):
                 env.pop('PATH', None)
 
                 # without confstr
-                with unittest.mock.patch(
+                with mock.patch(
                     'distutils.spawn.os.confstr', side_effect=ValueError, create=True
-                ), unittest.mock.patch('distutils.spawn.os.defpath', tmp_dir):
+                ), mock.patch('distutils.spawn.os.defpath', tmp_dir):
                     rv = find_executable(program)
                     assert rv == filename
 
                 # with confstr
-                with unittest.mock.patch(
+                with mock.patch(
                     'distutils.spawn.os.confstr', return_value=tmp_dir, create=True
-                ), unittest.mock.patch('distutils.spawn.os.defpath', ''):
+                ), mock.patch('distutils.spawn.os.defpath', ''):
                     rv = find_executable(program)
                     assert rv == filename
 
index 39e81f1778db2a4940998b82a87aab52988155ee..f1759839bb99d553532a16dd07c9e528f0ae68e0 100644 (file)
@@ -5,14 +5,13 @@ import shutil
 import subprocess
 import sys
 import textwrap
-import unittest
 
 import pytest
 import jaraco.envs
 
 import distutils
 from distutils import sysconfig
-from distutils.ccompiler import get_default_compiler
+from distutils.ccompiler import get_default_compiler  # noqa: F401
 from distutils.unixccompiler import UnixCCompiler
 from test.support import swap_item
 
@@ -20,17 +19,8 @@ from .py38compat import TESTFN
 
 
 @pytest.mark.usefixtures('save_env')
-class SysconfigTestCase(unittest.TestCase):
-    def setUp(self):
-        super().setUp()
-        self.makefile = None
-
-    def tearDown(self):
-        if self.makefile is not None:
-            os.unlink(self.makefile)
-        self.cleanup_testfn()
-        super().tearDown()
-
+@pytest.mark.usefixtures('cleanup_testfn')
+class TestSysconfig:
     def cleanup_testfn(self):
         if os.path.isfile(TESTFN):
             os.remove(TESTFN)
@@ -41,12 +31,8 @@ class SysconfigTestCase(unittest.TestCase):
         config_h = sysconfig.get_config_h_filename()
         assert os.path.isfile(config_h), config_h
 
-    @unittest.skipIf(
-        sys.platform == 'win32', 'Makefile only exists on Unix like systems'
-    )
-    @unittest.skipIf(
-        sys.implementation.name != 'cpython', 'Makefile only exists in CPython'
-    )
+    @pytest.mark.skipif("platform.system() == 'Windows'")
+    @pytest.mark.skipif("sys.implementation.name != 'cpython'")
     def test_get_makefile_filename(self):
         makefile = sysconfig.get_makefile_filename()
         assert os.path.isfile(makefile), makefile
@@ -62,7 +48,8 @@ class SysconfigTestCase(unittest.TestCase):
         assert isinstance(cvars, dict)
         assert cvars
 
-    @unittest.skip('sysconfig.IS_PYPY')
+    @pytest.mark.skipif('sysconfig.IS_PYPY')
+    @pytest.mark.xfail(reason="broken")
     def test_srcdir(self):
         # See Issues #15322, #15364.
         srcdir = sysconfig.get_config_var('srcdir')
@@ -125,9 +112,7 @@ class SysconfigTestCase(unittest.TestCase):
 
         return comp
 
-    @unittest.skipUnless(
-        get_default_compiler() == 'unix', 'not testing if default compiler is not unix'
-    )
+    @pytest.mark.skipif("get_default_compiler() != 'unix'")
     def test_customize_compiler(self):
         # Make sure that sysconfig._config_vars is initialized
         sysconfig.get_config_vars()
@@ -216,9 +201,7 @@ class SysconfigTestCase(unittest.TestCase):
             'LDFLAGS'
         )
 
-    @unittest.skipIf(
-        sysconfig.get_config_var('CUSTOMIZED_OSX_COMPILER'), 'compiler flags customized'
-    )
+    @pytest.mark.skipif("sysconfig.get_config_var('CUSTOMIZED_OSX_COMPILER')")
     def test_sysconfig_compiler_vars(self):
         # On OS X, binary installers support extension module building on
         # various levels of the operating system with differing Xcode
@@ -237,16 +220,13 @@ class SysconfigTestCase(unittest.TestCase):
         import sysconfig as global_sysconfig
 
         if sysconfig.get_config_var('CUSTOMIZED_OSX_COMPILER'):
-            self.skipTest('compiler flags customized')
+            pytest.skip('compiler flags customized')
         assert global_sysconfig.get_config_var('LDSHARED') == sysconfig.get_config_var(
             'LDSHARED'
         )
         assert global_sysconfig.get_config_var('CC') == sysconfig.get_config_var('CC')
 
-    @unittest.skipIf(
-        sysconfig.get_config_var('EXT_SUFFIX') is None,
-        'EXT_SUFFIX required for this test',
-    )
+    @pytest.mark.skipif("not sysconfig.get_config_var('EXT_SUFFIX')")
     def test_SO_deprecation(self):
         with pytest.warns(DeprecationWarning):
             sysconfig.get_config_var('SO')
@@ -286,21 +266,17 @@ class SysconfigTestCase(unittest.TestCase):
             result = sysconfig.parse_config_h(f)
         assert isinstance(result, dict)
 
-    @unittest.skipUnless(sys.platform == 'win32', 'Testing windows pyd suffix')
-    @unittest.skipUnless(
-        sys.implementation.name == 'cpython', 'Need cpython for this test'
-    )
+    @pytest.mark.skipif("platform.system() != 'Windows'")
+    @pytest.mark.skipif("sys.implementation.name != 'cpython'")
     def test_win_ext_suffix(self):
         assert sysconfig.get_config_var("EXT_SUFFIX").endswith(".pyd")
         assert sysconfig.get_config_var("EXT_SUFFIX") != ".pyd"
 
-    @unittest.skipUnless(sys.platform == 'win32', 'Testing Windows build layout')
-    @unittest.skipUnless(
-        sys.implementation.name == 'cpython', 'Need cpython for this test'
-    )
-    @unittest.skipUnless(
-        '\\PCbuild\\'.casefold() in sys.executable.casefold(),
-        'Need sys.executable to be in a source tree',
+    @pytest.mark.skipif("platform.system() != 'Windows'")
+    @pytest.mark.skipif("sys.implementation.name != 'cpython'")
+    @pytest.mark.skipif(
+        '\\PCbuild\\'.casefold() not in sys.executable.casefold(),
+        reason='Need sys.executable to be in a source tree',
     )
     def test_win_build_venv_from_source_tree(self):
         """Ensure distutils.sysconfig detects venvs from source tree builds."""
index 4be4ff2753b46c236c98a6bf9c1e7674de2abb1e..3978c23952e9c2c4d0848426a8b5745f636b6a1a 100644 (file)
@@ -1,8 +1,7 @@
 """Tests for distutils.unixccompiler."""
 import os
 import sys
-import unittest
-from unittest.mock import patch
+import unittest.mock as mock
 
 from .py38compat import EnvironmentVarGuard
 
@@ -15,26 +14,24 @@ from . import support
 import pytest
 
 
-class UnixCCompilerTestCase(support.TempdirManager, unittest.TestCase):
-    def setUp(self):
-        super().setUp()
-        self._backup_platform = sys.platform
-        self._backup_get_config_var = sysconfig.get_config_var
-        self._backup_get_config_vars = sysconfig.get_config_vars
+@pytest.fixture(autouse=True)
+def save_values(monkeypatch):
+    monkeypatch.setattr(sys, 'platform', sys.platform)
+    monkeypatch.setattr(sysconfig, 'get_config_var', sysconfig.get_config_var)
+    monkeypatch.setattr(sysconfig, 'get_config_vars', sysconfig.get_config_vars)
 
-        class CompilerWrapper(UnixCCompiler):
-            def rpath_foo(self):
-                return self.runtime_library_dir_option('/foo')
 
-        self.cc = CompilerWrapper()
+@pytest.fixture(autouse=True)
+def compiler_wrapper(request):
+    class CompilerWrapper(UnixCCompiler):
+        def rpath_foo(self):
+            return self.runtime_library_dir_option('/foo')
 
-    def tearDown(self):
-        super().tearDown()
-        sys.platform = self._backup_platform
-        sysconfig.get_config_var = self._backup_get_config_var
-        sysconfig.get_config_vars = self._backup_get_config_vars
+    request.instance.cc = CompilerWrapper()
 
-    @unittest.skipIf(sys.platform == 'win32', "can't test on Windows")
+
+class TestUnixCCompiler(support.TempdirManager):
+    @pytest.mark.skipif('platform.system == "Windows"')  # noqa: C901
     def test_runtime_libdir_option(self):  # noqa: C901
         # Issue #5900; GitHub Issue #37
         #
@@ -215,7 +212,7 @@ class UnixCCompilerTestCase(support.TempdirManager, unittest.TestCase):
         sysconfig.get_config_var = gcv
         assert self.cc.rpath_foo() == '-Wl,-R/foo'
 
-    @unittest.skipIf(sys.platform == 'win32', "can't test on Windows")
+    @pytest.mark.skipif('platform.system == "Windows"')
     def test_cc_overrides_ldshared(self):
         # Issue #18080:
         # ensure that setting CC env variable also changes default linker
@@ -237,7 +234,7 @@ class UnixCCompilerTestCase(support.TempdirManager, unittest.TestCase):
             sysconfig.customize_compiler(self.cc)
         assert self.cc.linker_so[0] == 'my_cc'
 
-    @unittest.skipIf(sys.platform == 'win32', "can't test on Windows")
+    @pytest.mark.skipif('platform.system == "Windows"')
     def test_cc_overrides_ldshared_for_cxx_correctly(self):
         """
         Ensure that setting CC env variable also changes default linker
@@ -260,11 +257,11 @@ class UnixCCompilerTestCase(support.TempdirManager, unittest.TestCase):
 
         sysconfig.get_config_var = gcv
         sysconfig.get_config_vars = gcvs
-        with patch.object(
+        with mock.patch.object(
             self.cc, 'spawn', return_value=None
-        ) as mock_spawn, patch.object(
+        ) as mock_spawn, mock.patch.object(
             self.cc, '_need_link', return_value=True
-        ), patch.object(
+        ), mock.patch.object(
             self.cc, 'mkpath', return_value=None
         ), EnvironmentVarGuard() as env:
             env['CC'] = 'ccache my_cc'
@@ -277,7 +274,7 @@ class UnixCCompilerTestCase(support.TempdirManager, unittest.TestCase):
             expected = ['my_cxx', '-bundle', '-undefined', 'dynamic_lookup']
             assert call_args[:4] == expected
 
-    @unittest.skipIf(sys.platform == 'win32', "can't test on Windows")
+    @pytest.mark.skipif('platform.system == "Windows"')
     def test_explicit_ldshared(self):
         # Issue #18080:
         # ensure that setting CC env variable does not change
index e5c6649694087512d6a8cffcd50f859ec1259b15..fb905b641ab819fc58eaac3ebd8540a1709aa154 100644 (file)
@@ -65,19 +65,14 @@ class FakeOpen:
         return self.code
 
 
-class uploadTestCase(BasePyPIRCCommandTestCase):
-    def setUp(self):
-        super().setUp()
-        self.old_open = upload_mod.urlopen
-        upload_mod.urlopen = self._urlopen
-        self.last_open = None
-        self.next_msg = None
-        self.next_code = None
-
-    def tearDown(self):
-        upload_mod.urlopen = self.old_open
-        super().tearDown()
+@pytest.fixture(autouse=True)
+def urlopen(request, monkeypatch):
+    self = request.instance
+    monkeypatch.setattr(upload_mod, 'urlopen', self._urlopen)
+    self.next_msg = self.next_code = None
 
+
+class TestUpload(BasePyPIRCCommandTestCase):
     def _urlopen(self, url):
         self.last_open = FakeOpen(url, msg=self.next_msg, code=self.next_code)
         return self.last_open
@@ -189,7 +184,19 @@ class uploadTestCase(BasePyPIRCCommandTestCase):
         with pytest.raises(DistutilsError):
             self.test_upload()
 
-    def test_wrong_exception_order(self):
+    @pytest.mark.parametrize(
+        'exception,expected,raised_exception',
+        [
+            (OSError('oserror'), 'oserror', OSError),
+            pytest.param(
+                HTTPError('url', 400, 'httperror', {}, None),
+                'Upload failed (400): httperror',
+                DistutilsError,
+                id="HTTP 400",
+            ),
+        ],
+    )
+    def test_wrong_exception_order(self, exception, expected, raised_exception):
         tmp = self.mkdtemp()
         path = os.path.join(tmp, 'xxx')
         self.write_file(path)
@@ -197,24 +204,15 @@ class uploadTestCase(BasePyPIRCCommandTestCase):
         self.write_file(self.rc, PYPIRC_LONG_PASSWORD)
 
         pkg_dir, dist = self.create_dist(dist_files=dist_files)
-        tests = [
-            (OSError('oserror'), 'oserror', OSError),
-            (
-                HTTPError('url', 400, 'httperror', {}, None),
-                'Upload failed (400): httperror',
-                DistutilsError,
-            ),
-        ]
-        for exception, expected, raised_exception in tests:
-            with self.subTest(exception=type(exception).__name__):
-                with mock.patch(
-                    'distutils.command.upload.urlopen',
-                    new=mock.Mock(side_effect=exception),
-                ):
-                    with pytest.raises(raised_exception):
-                        cmd = upload(dist)
-                        cmd.ensure_finalized()
-                        cmd.run()
-                    results = self.get_logs(ERROR)
-                    assert expected in results[-1]
-                    self.clear_logs()
+
+        with mock.patch(
+            'distutils.command.upload.urlopen',
+            new=mock.Mock(side_effect=exception),
+        ):
+            with pytest.raises(raised_exception):
+                cmd = upload(dist)
+                cmd.ensure_finalized()
+                cmd.run()
+            results = self.get_logs(ERROR)
+            assert expected in results[-1]
+            self.clear_logs()
index ac0feead55d60f3059cf20a5af85b5c57c6eb831..605b0d40b725121044298fd8643a91757ebaa4ee 100644 (file)
@@ -1,10 +1,9 @@
 """Tests for distutils.util."""
 import os
 import sys
-import unittest
 import sysconfig as stdlib_sysconfig
+import unittest.mock as mock
 from copy import copy
-from unittest import mock
 
 import pytest
 
@@ -20,78 +19,44 @@ from distutils.util import (
     grok_environment_error,
     get_host_platform,
 )
-from distutils import util  # used to patch _environ_checked
+from distutils import util
 from distutils import sysconfig
 from distutils.errors import DistutilsPlatformError, DistutilsByteCompileError
 
 
-@pytest.mark.usefixtures('save_env')
-class UtilTestCase(unittest.TestCase):
-    def setUp(self):
-        super().setUp()
-        # saving the environment
-        self.name = os.name
-        self.platform = sys.platform
-        self.version = sys.version
-        self.sep = os.sep
-        self.join = os.path.join
-        self.isabs = os.path.isabs
-        self.splitdrive = os.path.splitdrive
-        self._config_vars = copy(sysconfig._config_vars)
-
-        # patching os.uname
-        if hasattr(os, 'uname'):
-            self.uname = os.uname
-            self._uname = os.uname()
-        else:
-            self.uname = None
-            self._uname = None
-
-        os.uname = self._get_uname
-
-    def tearDown(self):
-        # getting back the environment
-        os.name = self.name
-        sys.platform = self.platform
-        sys.version = self.version
-        os.sep = self.sep
-        os.path.join = self.join
-        os.path.isabs = self.isabs
-        os.path.splitdrive = self.splitdrive
-        if self.uname is not None:
-            os.uname = self.uname
-        else:
-            del os.uname
-        sysconfig._config_vars = copy(self._config_vars)
-        super().tearDown()
-
-    def _set_uname(self, uname):
-        self._uname = uname
-
-    def _get_uname(self):
-        return self._uname
+@pytest.fixture(autouse=True)
+def environment(monkeypatch):
+    monkeypatch.setattr(os, 'name', os.name)
+    monkeypatch.setattr(sys, 'platform', sys.platform)
+    monkeypatch.setattr(sys, 'version', sys.version)
+    monkeypatch.setattr(os, 'sep', os.sep)
+    monkeypatch.setattr(os.path, 'join', os.path.join)
+    monkeypatch.setattr(os.path, 'isabs', os.path.isabs)
+    monkeypatch.setattr(os.path, 'splitdrive', os.path.splitdrive)
+    monkeypatch.setattr(sysconfig, '_config_vars', copy(sysconfig._config_vars))
+
 
+@pytest.mark.usefixtures('save_env')
+class TestUtil:
     def test_get_host_platform(self):
-        with unittest.mock.patch('os.name', 'nt'):
-            with unittest.mock.patch('sys.version', '... [... (ARM64)]'):
+        with mock.patch('os.name', 'nt'):
+            with mock.patch('sys.version', '... [... (ARM64)]'):
                 assert get_host_platform() == 'win-arm64'
-            with unittest.mock.patch('sys.version', '... [... (ARM)]'):
+            with mock.patch('sys.version', '... [... (ARM)]'):
                 assert get_host_platform() == 'win-arm32'
 
-        with unittest.mock.patch('sys.version_info', (3, 9, 0, 'final', 0)):
+        with mock.patch('sys.version_info', (3, 9, 0, 'final', 0)):
             assert get_host_platform() == stdlib_sysconfig.get_platform()
 
     def test_get_platform(self):
-        with unittest.mock.patch('os.name', 'nt'):
-            with unittest.mock.patch.dict('os.environ', {'VSCMD_ARG_TGT_ARCH': 'x86'}):
+        with mock.patch('os.name', 'nt'):
+            with mock.patch.dict('os.environ', {'VSCMD_ARG_TGT_ARCH': 'x86'}):
                 assert get_platform() == 'win32'
-            with unittest.mock.patch.dict('os.environ', {'VSCMD_ARG_TGT_ARCH': 'x64'}):
+            with mock.patch.dict('os.environ', {'VSCMD_ARG_TGT_ARCH': 'x64'}):
                 assert get_platform() == 'win-amd64'
-            with unittest.mock.patch.dict('os.environ', {'VSCMD_ARG_TGT_ARCH': 'arm'}):
+            with mock.patch.dict('os.environ', {'VSCMD_ARG_TGT_ARCH': 'arm'}):
                 assert get_platform() == 'win-arm32'
-            with unittest.mock.patch.dict(
-                'os.environ', {'VSCMD_ARG_TGT_ARCH': 'arm64'}
-            ):
+            with mock.patch.dict('os.environ', {'VSCMD_ARG_TGT_ARCH': 'arm64'}):
                 assert get_platform() == 'win-arm64'
 
     def test_convert_path(self):
@@ -179,7 +144,7 @@ class UtilTestCase(unittest.TestCase):
         assert os.environ['PLAT'] == get_platform()
         assert util._environ_checked == 1
 
-    @unittest.skipUnless(os.name == 'posix', 'specific to posix')
+    @pytest.mark.skipif("os.name != 'posix'")
     def test_check_environ_getpwuid(self):
         util._environ_checked = 0
         os.environ.pop('HOME', None)
index 8115faea3b5face82ddc66104d1e85341ef68396..ff52ea468308417479a7d48b5704608267d1d884 100644 (file)
@@ -1,18 +1,18 @@
 """Tests for distutils.version."""
-import unittest
+import pytest
+
 import distutils
 from distutils.version import LooseVersion
 from distutils.version import StrictVersion
 
 
-class VersionTestCase(unittest.TestCase):
-    def setUp(self):
-        self.ctx = distutils.version.suppress_known_deprecation()
-        self.ctx.__enter__()
+@pytest.fixture(autouse=True)
+def suppress_deprecation():
+    with distutils.version.suppress_known_deprecation():
+        yield
 
-    def tearDown(self):
-        self.ctx.__exit__(None, None, None)
 
+class TestVersion:
     def test_prerelease(self):
         version = StrictVersion('1.2.3a1')
         assert version.version == (1, 2, 3)
index 8250b36327850f4a864cf0601b27afc9921c1947..95fc8eebe288657f95bcd92163f0a78faec78bfd 100644 (file)
@@ -1,5 +1,4 @@
 import sys
-import unittest
 
 try:
     import grp
@@ -7,9 +6,13 @@ try:
 except ImportError:
     grp = pwd = None
 
+import pytest
+
 
 UNIX_ID_SUPPORT = grp and pwd
 UID_0_SUPPORT = UNIX_ID_SUPPORT and sys.platform != "cygwin"
 
-require_unix_id = unittest.skipUnless(UNIX_ID_SUPPORT, "Requires grp and pwd support")
-require_uid_0 = unittest.skipUnless(UID_0_SUPPORT, "Requires UID 0 support")
+require_unix_id = pytest.mark.skipif(
+    not UNIX_ID_SUPPORT, reason="Requires grp and pwd support"
+)
+require_uid_0 = pytest.mark.skipif(not UID_0_SUPPORT, reason="Requires UID 0 support")
diff --git a/setuptools/_distutils/tests/xxmodule-3.8.c b/setuptools/_distutils/tests/xxmodule-3.8.c
new file mode 100644 (file)
index 0000000..0250031
--- /dev/null
@@ -0,0 +1,411 @@
+
+/* Use this file as a template to start implementing a module that
+   also declares object types. All occurrences of 'Xxo' should be changed
+   to something reasonable for your objects. After that, all other
+   occurrences of 'xx' should be changed to something reasonable for your
+   module. If your module is named foo your sourcefile should be named
+   foomodule.c.
+
+   You will probably want to delete all references to 'x_attr' and add
+   your own types of attributes instead.  Maybe you want to name your
+   local variables other than 'self'.  If your object type is needed in
+   other files, you'll have to create a file "foobarobject.h"; see
+   floatobject.h for an example. */
+
+/* Xxo objects */
+
+#include "Python.h"
+
+static PyObject *ErrorObject;
+
+typedef struct {
+    PyObject_HEAD
+    PyObject            *x_attr;        /* Attributes dictionary */
+} XxoObject;
+
+static PyTypeObject Xxo_Type;
+
+#define XxoObject_Check(v)      (Py_TYPE(v) == &Xxo_Type)
+
+static XxoObject *
+newXxoObject(PyObject *arg)
+{
+    XxoObject *self;
+    self = PyObject_New(XxoObject, &Xxo_Type);
+    if (self == NULL)
+        return NULL;
+    self->x_attr = NULL;
+    return self;
+}
+
+/* Xxo methods */
+
+static void
+Xxo_dealloc(XxoObject *self)
+{
+    Py_XDECREF(self->x_attr);
+    PyObject_Del(self);
+}
+
+static PyObject *
+Xxo_demo(XxoObject *self, PyObject *args)
+{
+    if (!PyArg_ParseTuple(args, ":demo"))
+        return NULL;
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyMethodDef Xxo_methods[] = {
+    {"demo",            (PyCFunction)Xxo_demo,  METH_VARARGS,
+        PyDoc_STR("demo() -> None")},
+    {NULL,              NULL}           /* sentinel */
+};
+
+static PyObject *
+Xxo_getattro(XxoObject *self, PyObject *name)
+{
+    if (self->x_attr != NULL) {
+        PyObject *v = PyDict_GetItemWithError(self->x_attr, name);
+        if (v != NULL) {
+            Py_INCREF(v);
+            return v;
+        }
+        else if (PyErr_Occurred()) {
+            return NULL;
+        }
+    }
+    return PyObject_GenericGetAttr((PyObject *)self, name);
+}
+
+static int
+Xxo_setattr(XxoObject *self, const char *name, PyObject *v)
+{
+    if (self->x_attr == NULL) {
+        self->x_attr = PyDict_New();
+        if (self->x_attr == NULL)
+            return -1;
+    }
+    if (v == NULL) {
+        int rv = PyDict_DelItemString(self->x_attr, name);
+        if (rv < 0 && PyErr_ExceptionMatches(PyExc_KeyError))
+            PyErr_SetString(PyExc_AttributeError,
+                "delete non-existing Xxo attribute");
+        return rv;
+    }
+    else
+        return PyDict_SetItemString(self->x_attr, name, v);
+}
+
+static PyTypeObject Xxo_Type = {
+    /* The ob_type field must be initialized in the module init function
+     * to be portable to Windows without using C++. */
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "xxmodule.Xxo",             /*tp_name*/
+    sizeof(XxoObject),          /*tp_basicsize*/
+    0,                          /*tp_itemsize*/
+    /* methods */
+    (destructor)Xxo_dealloc,    /*tp_dealloc*/
+    0,                          /*tp_vectorcall_offset*/
+    (getattrfunc)0,             /*tp_getattr*/
+    (setattrfunc)Xxo_setattr,   /*tp_setattr*/
+    0,                          /*tp_as_async*/
+    0,                          /*tp_repr*/
+    0,                          /*tp_as_number*/
+    0,                          /*tp_as_sequence*/
+    0,                          /*tp_as_mapping*/
+    0,                          /*tp_hash*/
+    0,                          /*tp_call*/
+    0,                          /*tp_str*/
+    (getattrofunc)Xxo_getattro, /*tp_getattro*/
+    0,                          /*tp_setattro*/
+    0,                          /*tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT,         /*tp_flags*/
+    0,                          /*tp_doc*/
+    0,                          /*tp_traverse*/
+    0,                          /*tp_clear*/
+    0,                          /*tp_richcompare*/
+    0,                          /*tp_weaklistoffset*/
+    0,                          /*tp_iter*/
+    0,                          /*tp_iternext*/
+    Xxo_methods,                /*tp_methods*/
+    0,                          /*tp_members*/
+    0,                          /*tp_getset*/
+    0,                          /*tp_base*/
+    0,                          /*tp_dict*/
+    0,                          /*tp_descr_get*/
+    0,                          /*tp_descr_set*/
+    0,                          /*tp_dictoffset*/
+    0,                          /*tp_init*/
+    0,                          /*tp_alloc*/
+    0,                          /*tp_new*/
+    0,                          /*tp_free*/
+    0,                          /*tp_is_gc*/
+};
+/* --------------------------------------------------------------------- */
+
+/* Function of two integers returning integer */
+
+PyDoc_STRVAR(xx_foo_doc,
+"foo(i,j)\n\
+\n\
+Return the sum of i and j.");
+
+static PyObject *
+xx_foo(PyObject *self, PyObject *args)
+{
+    long i, j;
+    long res;
+    if (!PyArg_ParseTuple(args, "ll:foo", &i, &j))
+        return NULL;
+    res = i+j; /* XXX Do something here */
+    return PyLong_FromLong(res);
+}
+
+
+/* Function of no arguments returning new Xxo object */
+
+static PyObject *
+xx_new(PyObject *self, PyObject *args)
+{
+    XxoObject *rv;
+
+    if (!PyArg_ParseTuple(args, ":new"))
+        return NULL;
+    rv = newXxoObject(args);
+    if (rv == NULL)
+        return NULL;
+    return (PyObject *)rv;
+}
+
+/* Example with subtle bug from extensions manual ("Thin Ice"). */
+
+static PyObject *
+xx_bug(PyObject *self, PyObject *args)
+{
+    PyObject *list, *item;
+
+    if (!PyArg_ParseTuple(args, "O:bug", &list))
+        return NULL;
+
+    item = PyList_GetItem(list, 0);
+    /* Py_INCREF(item); */
+    PyList_SetItem(list, 1, PyLong_FromLong(0L));
+    PyObject_Print(item, stdout, 0);
+    printf("\n");
+    /* Py_DECREF(item); */
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+/* Test bad format character */
+
+static PyObject *
+xx_roj(PyObject *self, PyObject *args)
+{
+    PyObject *a;
+    long b;
+    if (!PyArg_ParseTuple(args, "O#:roj", &a, &b))
+        return NULL;
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+/* ---------- */
+
+static PyTypeObject Str_Type = {
+    /* The ob_type field must be initialized in the module init function
+     * to be portable to Windows without using C++. */
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "xxmodule.Str",             /*tp_name*/
+    0,                          /*tp_basicsize*/
+    0,                          /*tp_itemsize*/
+    /* methods */
+    0,                          /*tp_dealloc*/
+    0,                          /*tp_vectorcall_offset*/
+    0,                          /*tp_getattr*/
+    0,                          /*tp_setattr*/
+    0,                          /*tp_as_async*/
+    0,                          /*tp_repr*/
+    0,                          /*tp_as_number*/
+    0,                          /*tp_as_sequence*/
+    0,                          /*tp_as_mapping*/
+    0,                          /*tp_hash*/
+    0,                          /*tp_call*/
+    0,                          /*tp_str*/
+    0,                          /*tp_getattro*/
+    0,                          /*tp_setattro*/
+    0,                          /*tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+    0,                          /*tp_doc*/
+    0,                          /*tp_traverse*/
+    0,                          /*tp_clear*/
+    0,                          /*tp_richcompare*/
+    0,                          /*tp_weaklistoffset*/
+    0,                          /*tp_iter*/
+    0,                          /*tp_iternext*/
+    0,                          /*tp_methods*/
+    0,                          /*tp_members*/
+    0,                          /*tp_getset*/
+    0, /* see PyInit_xx */      /*tp_base*/
+    0,                          /*tp_dict*/
+    0,                          /*tp_descr_get*/
+    0,                          /*tp_descr_set*/
+    0,                          /*tp_dictoffset*/
+    0,                          /*tp_init*/
+    0,                          /*tp_alloc*/
+    0,                          /*tp_new*/
+    0,                          /*tp_free*/
+    0,                          /*tp_is_gc*/
+};
+
+/* ---------- */
+
+static PyObject *
+null_richcompare(PyObject *self, PyObject *other, int op)
+{
+    Py_INCREF(Py_NotImplemented);
+    return Py_NotImplemented;
+}
+
+static PyTypeObject Null_Type = {
+    /* The ob_type field must be initialized in the module init function
+     * to be portable to Windows without using C++. */
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "xxmodule.Null",            /*tp_name*/
+    0,                          /*tp_basicsize*/
+    0,                          /*tp_itemsize*/
+    /* methods */
+    0,                          /*tp_dealloc*/
+    0,                          /*tp_vectorcall_offset*/
+    0,                          /*tp_getattr*/
+    0,                          /*tp_setattr*/
+    0,                          /*tp_as_async*/
+    0,                          /*tp_repr*/
+    0,                          /*tp_as_number*/
+    0,                          /*tp_as_sequence*/
+    0,                          /*tp_as_mapping*/
+    0,                          /*tp_hash*/
+    0,                          /*tp_call*/
+    0,                          /*tp_str*/
+    0,                          /*tp_getattro*/
+    0,                          /*tp_setattro*/
+    0,                          /*tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+    0,                          /*tp_doc*/
+    0,                          /*tp_traverse*/
+    0,                          /*tp_clear*/
+    null_richcompare,           /*tp_richcompare*/
+    0,                          /*tp_weaklistoffset*/
+    0,                          /*tp_iter*/
+    0,                          /*tp_iternext*/
+    0,                          /*tp_methods*/
+    0,                          /*tp_members*/
+    0,                          /*tp_getset*/
+    0, /* see PyInit_xx */      /*tp_base*/
+    0,                          /*tp_dict*/
+    0,                          /*tp_descr_get*/
+    0,                          /*tp_descr_set*/
+    0,                          /*tp_dictoffset*/
+    0,                          /*tp_init*/
+    0,                          /*tp_alloc*/
+    PyType_GenericNew,          /*tp_new*/
+    0,                          /*tp_free*/
+    0,                          /*tp_is_gc*/
+};
+
+
+/* ---------- */
+
+
+/* List of functions defined in the module */
+
+static PyMethodDef xx_methods[] = {
+    {"roj",             xx_roj,         METH_VARARGS,
+        PyDoc_STR("roj(a,b) -> None")},
+    {"foo",             xx_foo,         METH_VARARGS,
+        xx_foo_doc},
+    {"new",             xx_new,         METH_VARARGS,
+        PyDoc_STR("new() -> new Xx object")},
+    {"bug",             xx_bug,         METH_VARARGS,
+        PyDoc_STR("bug(o) -> None")},
+    {NULL,              NULL}           /* sentinel */
+};
+
+PyDoc_STRVAR(module_doc,
+"This is a template module just for instruction.");
+
+
+static int
+xx_exec(PyObject *m)
+{
+    /* Slot initialization is subject to the rules of initializing globals.
+       C99 requires the initializers to be "address constants".  Function
+       designators like 'PyType_GenericNew', with implicit conversion to
+       a pointer, are valid C99 address constants.
+
+       However, the unary '&' operator applied to a non-static variable
+       like 'PyBaseObject_Type' is not required to produce an address
+       constant.  Compilers may support this (gcc does), MSVC does not.
+
+       Both compilers are strictly standard conforming in this particular
+       behavior.
+    */
+    Null_Type.tp_base = &PyBaseObject_Type;
+    Str_Type.tp_base = &PyUnicode_Type;
+
+    /* Finalize the type object including setting type of the new type
+     * object; doing it here is required for portability, too. */
+    if (PyType_Ready(&Xxo_Type) < 0)
+        goto fail;
+
+    /* Add some symbolic constants to the module */
+    if (ErrorObject == NULL) {
+        ErrorObject = PyErr_NewException("xx.error", NULL, NULL);
+        if (ErrorObject == NULL)
+            goto fail;
+    }
+    Py_INCREF(ErrorObject);
+    PyModule_AddObject(m, "error", ErrorObject);
+
+    /* Add Str */
+    if (PyType_Ready(&Str_Type) < 0)
+        goto fail;
+    PyModule_AddObject(m, "Str", (PyObject *)&Str_Type);
+
+    /* Add Null */
+    if (PyType_Ready(&Null_Type) < 0)
+        goto fail;
+    PyModule_AddObject(m, "Null", (PyObject *)&Null_Type);
+    return 0;
+ fail:
+    Py_XDECREF(m);
+    return -1;
+}
+
+static struct PyModuleDef_Slot xx_slots[] = {
+    {Py_mod_exec, xx_exec},
+    {0, NULL},
+};
+
+static struct PyModuleDef xxmodule = {
+    PyModuleDef_HEAD_INIT,
+    "xx",
+    module_doc,
+    0,
+    xx_methods,
+    xx_slots,
+    NULL,
+    NULL,
+    NULL
+};
+
+/* Export function for the module (*must* be called PyInit_xx) */
+
+PyMODINIT_FUNC
+PyInit_xx(void)
+{
+    return PyModuleDef_Init(&xxmodule);
+}
diff --git a/setuptools/_distutils/tests/xxmodule.c b/setuptools/_distutils/tests/xxmodule.c
new file mode 100644 (file)
index 0000000..a6e5071
--- /dev/null
@@ -0,0 +1,412 @@
+
+/* Use this file as a template to start implementing a module that
+   also declares object types. All occurrences of 'Xxo' should be changed
+   to something reasonable for your objects. After that, all other
+   occurrences of 'xx' should be changed to something reasonable for your
+   module. If your module is named foo your sourcefile should be named
+   foomodule.c.
+
+   You will probably want to delete all references to 'x_attr' and add
+   your own types of attributes instead.  Maybe you want to name your
+   local variables other than 'self'.  If your object type is needed in
+   other files, you'll have to create a file "foobarobject.h"; see
+   floatobject.h for an example. */
+
+/* Xxo objects */
+
+#include "Python.h"
+
+static PyObject *ErrorObject;
+
+typedef struct {
+    PyObject_HEAD
+    PyObject            *x_attr;        /* Attributes dictionary */
+} XxoObject;
+
+static PyTypeObject Xxo_Type;
+
+#define XxoObject_Check(v)      Py_IS_TYPE(v, &Xxo_Type)
+
+static XxoObject *
+newXxoObject(PyObject *arg)
+{
+    XxoObject *self;
+    self = PyObject_New(XxoObject, &Xxo_Type);
+    if (self == NULL)
+        return NULL;
+    self->x_attr = NULL;
+    return self;
+}
+
+/* Xxo methods */
+
+static void
+Xxo_dealloc(XxoObject *self)
+{
+    Py_XDECREF(self->x_attr);
+    PyObject_Free(self);
+}
+
+static PyObject *
+Xxo_demo(XxoObject *self, PyObject *args)
+{
+    if (!PyArg_ParseTuple(args, ":demo"))
+        return NULL;
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyMethodDef Xxo_methods[] = {
+    {"demo",            (PyCFunction)Xxo_demo,  METH_VARARGS,
+        PyDoc_STR("demo() -> None")},
+    {NULL,              NULL}           /* sentinel */
+};
+
+static PyObject *
+Xxo_getattro(XxoObject *self, PyObject *name)
+{
+    if (self->x_attr != NULL) {
+        PyObject *v = PyDict_GetItemWithError(self->x_attr, name);
+        if (v != NULL) {
+            Py_INCREF(v);
+            return v;
+        }
+        else if (PyErr_Occurred()) {
+            return NULL;
+        }
+    }
+    return PyObject_GenericGetAttr((PyObject *)self, name);
+}
+
+static int
+Xxo_setattr(XxoObject *self, const char *name, PyObject *v)
+{
+    if (self->x_attr == NULL) {
+        self->x_attr = PyDict_New();
+        if (self->x_attr == NULL)
+            return -1;
+    }
+    if (v == NULL) {
+        int rv = PyDict_DelItemString(self->x_attr, name);
+        if (rv < 0 && PyErr_ExceptionMatches(PyExc_KeyError))
+            PyErr_SetString(PyExc_AttributeError,
+                "delete non-existing Xxo attribute");
+        return rv;
+    }
+    else
+        return PyDict_SetItemString(self->x_attr, name, v);
+}
+
+static PyTypeObject Xxo_Type = {
+    /* The ob_type field must be initialized in the module init function
+     * to be portable to Windows without using C++. */
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "xxmodule.Xxo",             /*tp_name*/
+    sizeof(XxoObject),          /*tp_basicsize*/
+    0,                          /*tp_itemsize*/
+    /* methods */
+    (destructor)Xxo_dealloc,    /*tp_dealloc*/
+    0,                          /*tp_vectorcall_offset*/
+    (getattrfunc)0,             /*tp_getattr*/
+    (setattrfunc)Xxo_setattr,   /*tp_setattr*/
+    0,                          /*tp_as_async*/
+    0,                          /*tp_repr*/
+    0,                          /*tp_as_number*/
+    0,                          /*tp_as_sequence*/
+    0,                          /*tp_as_mapping*/
+    0,                          /*tp_hash*/
+    0,                          /*tp_call*/
+    0,                          /*tp_str*/
+    (getattrofunc)Xxo_getattro, /*tp_getattro*/
+    0,                          /*tp_setattro*/
+    0,                          /*tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT,         /*tp_flags*/
+    0,                          /*tp_doc*/
+    0,                          /*tp_traverse*/
+    0,                          /*tp_clear*/
+    0,                          /*tp_richcompare*/
+    0,                          /*tp_weaklistoffset*/
+    0,                          /*tp_iter*/
+    0,                          /*tp_iternext*/
+    Xxo_methods,                /*tp_methods*/
+    0,                          /*tp_members*/
+    0,                          /*tp_getset*/
+    0,                          /*tp_base*/
+    0,                          /*tp_dict*/
+    0,                          /*tp_descr_get*/
+    0,                          /*tp_descr_set*/
+    0,                          /*tp_dictoffset*/
+    0,                          /*tp_init*/
+    0,                          /*tp_alloc*/
+    0,                          /*tp_new*/
+    0,                          /*tp_free*/
+    0,                          /*tp_is_gc*/
+};
+/* --------------------------------------------------------------------- */
+
+/* Function of two integers returning integer */
+
+PyDoc_STRVAR(xx_foo_doc,
+"foo(i,j)\n\
+\n\
+Return the sum of i and j.");
+
+static PyObject *
+xx_foo(PyObject *self, PyObject *args)
+{
+    long i, j;
+    long res;
+    if (!PyArg_ParseTuple(args, "ll:foo", &i, &j))
+        return NULL;
+    res = i+j; /* XXX Do something here */
+    return PyLong_FromLong(res);
+}
+
+
+/* Function of no arguments returning new Xxo object */
+
+static PyObject *
+xx_new(PyObject *self, PyObject *args)
+{
+    XxoObject *rv;
+
+    if (!PyArg_ParseTuple(args, ":new"))
+        return NULL;
+    rv = newXxoObject(args);
+    if (rv == NULL)
+        return NULL;
+    return (PyObject *)rv;
+}
+
+/* Example with subtle bug from extensions manual ("Thin Ice"). */
+
+static PyObject *
+xx_bug(PyObject *self, PyObject *args)
+{
+    PyObject *list, *item;
+
+    if (!PyArg_ParseTuple(args, "O:bug", &list))
+        return NULL;
+
+    item = PyList_GetItem(list, 0);
+    /* Py_INCREF(item); */
+    PyList_SetItem(list, 1, PyLong_FromLong(0L));
+    PyObject_Print(item, stdout, 0);
+    printf("\n");
+    /* Py_DECREF(item); */
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+/* Test bad format character */
+
+static PyObject *
+xx_roj(PyObject *self, PyObject *args)
+{
+    PyObject *a;
+    long b;
+    if (!PyArg_ParseTuple(args, "O#:roj", &a, &b))
+        return NULL;
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+/* ---------- */
+
+static PyTypeObject Str_Type = {
+    /* The ob_type field must be initialized in the module init function
+     * to be portable to Windows without using C++. */
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "xxmodule.Str",             /*tp_name*/
+    0,                          /*tp_basicsize*/
+    0,                          /*tp_itemsize*/
+    /* methods */
+    0,                          /*tp_dealloc*/
+    0,                          /*tp_vectorcall_offset*/
+    0,                          /*tp_getattr*/
+    0,                          /*tp_setattr*/
+    0,                          /*tp_as_async*/
+    0,                          /*tp_repr*/
+    0,                          /*tp_as_number*/
+    0,                          /*tp_as_sequence*/
+    0,                          /*tp_as_mapping*/
+    0,                          /*tp_hash*/
+    0,                          /*tp_call*/
+    0,                          /*tp_str*/
+    0,                          /*tp_getattro*/
+    0,                          /*tp_setattro*/
+    0,                          /*tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+    0,                          /*tp_doc*/
+    0,                          /*tp_traverse*/
+    0,                          /*tp_clear*/
+    0,                          /*tp_richcompare*/
+    0,                          /*tp_weaklistoffset*/
+    0,                          /*tp_iter*/
+    0,                          /*tp_iternext*/
+    0,                          /*tp_methods*/
+    0,                          /*tp_members*/
+    0,                          /*tp_getset*/
+    0, /* see PyInit_xx */      /*tp_base*/
+    0,                          /*tp_dict*/
+    0,                          /*tp_descr_get*/
+    0,                          /*tp_descr_set*/
+    0,                          /*tp_dictoffset*/
+    0,                          /*tp_init*/
+    0,                          /*tp_alloc*/
+    0,                          /*tp_new*/
+    0,                          /*tp_free*/
+    0,                          /*tp_is_gc*/
+};
+
+/* ---------- */
+
+static PyObject *
+null_richcompare(PyObject *self, PyObject *other, int op)
+{
+    Py_INCREF(Py_NotImplemented);
+    return Py_NotImplemented;
+}
+
+static PyTypeObject Null_Type = {
+    /* The ob_type field must be initialized in the module init function
+     * to be portable to Windows without using C++. */
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "xxmodule.Null",            /*tp_name*/
+    0,                          /*tp_basicsize*/
+    0,                          /*tp_itemsize*/
+    /* methods */
+    0,                          /*tp_dealloc*/
+    0,                          /*tp_vectorcall_offset*/
+    0,                          /*tp_getattr*/
+    0,                          /*tp_setattr*/
+    0,                          /*tp_as_async*/
+    0,                          /*tp_repr*/
+    0,                          /*tp_as_number*/
+    0,                          /*tp_as_sequence*/
+    0,                          /*tp_as_mapping*/
+    0,                          /*tp_hash*/
+    0,                          /*tp_call*/
+    0,                          /*tp_str*/
+    0,                          /*tp_getattro*/
+    0,                          /*tp_setattro*/
+    0,                          /*tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+    0,                          /*tp_doc*/
+    0,                          /*tp_traverse*/
+    0,                          /*tp_clear*/
+    null_richcompare,           /*tp_richcompare*/
+    0,                          /*tp_weaklistoffset*/
+    0,                          /*tp_iter*/
+    0,                          /*tp_iternext*/
+    0,                          /*tp_methods*/
+    0,                          /*tp_members*/
+    0,                          /*tp_getset*/
+    0, /* see PyInit_xx */      /*tp_base*/
+    0,                          /*tp_dict*/
+    0,                          /*tp_descr_get*/
+    0,                          /*tp_descr_set*/
+    0,                          /*tp_dictoffset*/
+    0,                          /*tp_init*/
+    0,                          /*tp_alloc*/
+    PyType_GenericNew,          /*tp_new*/
+    0,                          /*tp_free*/
+    0,                          /*tp_is_gc*/
+};
+
+
+/* ---------- */
+
+
+/* List of functions defined in the module */
+
+static PyMethodDef xx_methods[] = {
+    {"roj",             xx_roj,         METH_VARARGS,
+        PyDoc_STR("roj(a,b) -> None")},
+    {"foo",             xx_foo,         METH_VARARGS,
+        xx_foo_doc},
+    {"new",             xx_new,         METH_VARARGS,
+        PyDoc_STR("new() -> new Xx object")},
+    {"bug",             xx_bug,         METH_VARARGS,
+        PyDoc_STR("bug(o) -> None")},
+    {NULL,              NULL}           /* sentinel */
+};
+
+PyDoc_STRVAR(module_doc,
+"This is a template module just for instruction.");
+
+
+static int
+xx_exec(PyObject *m)
+{
+    /* Slot initialization is subject to the rules of initializing globals.
+       C99 requires the initializers to be "address constants".  Function
+       designators like 'PyType_GenericNew', with implicit conversion to
+       a pointer, are valid C99 address constants.
+
+       However, the unary '&' operator applied to a non-static variable
+       like 'PyBaseObject_Type' is not required to produce an address
+       constant.  Compilers may support this (gcc does), MSVC does not.
+
+       Both compilers are strictly standard conforming in this particular
+       behavior.
+    */
+    Null_Type.tp_base = &PyBaseObject_Type;
+    Str_Type.tp_base = &PyUnicode_Type;
+
+    /* Finalize the type object including setting type of the new type
+     * object; doing it here is required for portability, too. */
+    if (PyType_Ready(&Xxo_Type) < 0) {
+        return -1;
+    }
+
+    /* Add some symbolic constants to the module */
+    if (ErrorObject == NULL) {
+        ErrorObject = PyErr_NewException("xx.error", NULL, NULL);
+        if (ErrorObject == NULL) {
+            return -1;
+        }
+    }
+    int rc = PyModule_AddType(m, (PyTypeObject *)ErrorObject);
+    Py_DECREF(ErrorObject);
+    if (rc < 0) {
+        return -1;
+    }
+
+    /* Add Str and Null types */
+    if (PyModule_AddType(m, &Str_Type) < 0) {
+        return -1;
+    }
+    if (PyModule_AddType(m, &Null_Type) < 0) {
+        return -1;
+    }
+
+    return 0;
+}
+
+static struct PyModuleDef_Slot xx_slots[] = {
+    {Py_mod_exec, xx_exec},
+    {0, NULL},
+};
+
+static struct PyModuleDef xxmodule = {
+    PyModuleDef_HEAD_INIT,
+    "xx",
+    module_doc,
+    0,
+    xx_methods,
+    xx_slots,
+    NULL,
+    NULL,
+    NULL
+};
+
+/* Export function for the module (*must* be called PyInit_xx) */
+
+PyMODINIT_FUNC
+PyInit_xx(void)
+{
+    return PyModuleDef_Init(&xxmodule);
+}