diff --git a/changelog.d/3496.misc.rst b/changelog.d/3496.misc.rst new file mode 100644 index 0000000000..5d7542febd --- /dev/null +++ b/changelog.d/3496.misc.rst @@ -0,0 +1 @@ +Update to pypa/distutils@b65aa40 including more robust support for library/include dir handling in msvccompiler (pypa/distutils#153) and test suite improvements. diff --git a/setuptools/_distutils/_msvccompiler.py b/setuptools/_distutils/_msvccompiler.py index 35c90942d2..ade80056e9 100644 --- a/setuptools/_distutils/_msvccompiler.py +++ b/setuptools/_distutils/_msvccompiler.py @@ -17,7 +17,7 @@ import subprocess import contextlib import warnings -import unittest.mock +import unittest.mock as mock with contextlib.suppress(ImportError): import winreg @@ -224,6 +224,18 @@ def __init__(self, verbose=0, dry_run=0, force=0): 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 @@ def initialize(self, plat_name=None): 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 @@ def initialize(self, plat_name=None): 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 @@ def _fallback_spawn(self, cmd, env): 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 ----------------------------------------- diff --git a/setuptools/_distutils/archive_util.py b/setuptools/_distutils/archive_util.py index 4cb9bf3932..5dfe2a16ff 100644 --- a/setuptools/_distutils/archive_util.py +++ b/setuptools/_distutils/archive_util.py @@ -121,7 +121,7 @@ def _set_uid_gid(tarinfo): # 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': diff --git a/setuptools/_distutils/ccompiler.py b/setuptools/_distutils/ccompiler.py index b4d3d0fbe0..3cf5761cf2 100644 --- a/setuptools/_distutils/ccompiler.py +++ b/setuptools/_distutils/ccompiler.py @@ -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 @@ def set_link_objects(self, objects): 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 @@ def _fix_compile_args(self, output_dir, macros, include_dirs): 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 @@ def _fix_lib_args(self, libraries, library_dirs, runtime_library_dirs): 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)): diff --git a/setuptools/_distutils/command/check.py b/setuptools/_distutils/command/check.py index aaf30713fe..539481c946 100644 --- a/setuptools/_distutils/command/check.py +++ b/setuptools/_distutils/command/check.py @@ -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 @@ def __init__( 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 @@ def run(self): 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 @@ def _check_rst_data(self, data): """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 @@ def _check_rst_data(self, data): 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) diff --git a/setuptools/_distutils/command/register.py b/setuptools/_distutils/command/register.py index 2c6424725d..c1402650d7 100644 --- a/setuptools/_distutils/command/register.py +++ b/setuptools/_distutils/command/register.py @@ -66,9 +66,9 @@ def run(self): 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() diff --git a/setuptools/_distutils/tests/py38compat.py b/setuptools/_distutils/tests/py38compat.py index 96f93a31c6..35ddbb5bde 100644 --- a/setuptools/_distutils/tests/py38compat.py +++ b/setuptools/_distutils/tests/py38compat.py @@ -42,5 +42,17 @@ ) +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 diff --git a/setuptools/_distutils/tests/support.py b/setuptools/_distutils/tests/support.py index 1ff4a1268f..5203ed19d4 100644 --- a/setuptools/_distutils/tests/support.py +++ b/setuptools/_distutils/tests/support.py @@ -3,7 +3,6 @@ import sys import shutil import tempfile -import unittest import sysconfig import itertools @@ -31,9 +30,8 @@ def clear_logs(self): @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 test_compile(self): 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): diff --git a/setuptools/_distutils/tests/test_archive_util.py b/setuptools/_distutils/tests/test_archive_util.py index c8c74032ae..72aa9d7c7b 100644 --- a/setuptools/_distutils/tests/test_archive_util.py +++ b/setuptools/_distutils/tests/test_archive_util.py @@ -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 @@ 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 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 @@ def test_make_tarball_gzip(self): 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 @@ def test_make_tarball_extended(self): 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 @@ def _create_files(self): 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 @@ def test_tarfile_vs_tar(self): 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 @@ def test_compress_deprecated(self): 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 @@ def test_make_zipfile(self): 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 @@ def test_make_archive_gztar(self): 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 @@ def test_make_archive_bztar(self): 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') diff --git a/setuptools/_distutils/tests/test_bdist_rpm.py b/setuptools/_distutils/tests/test_bdist_rpm.py index 411d09ebea..2d14bafc98 100644 --- a/setuptools/_distutils/tests/test_bdist_rpm.py +++ b/setuptools/_distutils/tests/test_bdist_rpm.py @@ -1,6 +1,5 @@ """Tests for distutils.command.bdist_rpm.""" -import unittest import sys import os @@ -9,7 +8,7 @@ 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 @@ def test_quiet(self): 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() diff --git a/setuptools/_distutils/tests/test_bdist_wininst.py b/setuptools/_distutils/tests/test_bdist_wininst.py index 8bc217af68..c432d24be6 100644 --- a/setuptools/_distutils/tests/test_bdist_wininst.py +++ b/setuptools/_distutils/tests/test_bdist_wininst.py @@ -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.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): diff --git a/setuptools/_distutils/tests/test_build.py b/setuptools/_distutils/tests/test_build.py index 45bc22f822..80367607f5 100644 --- a/setuptools/_distutils/tests/test_build.py +++ b/setuptools/_distutils/tests/test_build.py @@ -1,5 +1,4 @@ """Tests for distutils.command.build.""" -import unittest import os import sys @@ -8,7 +7,7 @@ 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) diff --git a/setuptools/_distutils/tests/test_build_clib.py b/setuptools/_distutils/tests/test_build_clib.py index 2048e29a52..c931c06ec5 100644 --- a/setuptools/_distutils/tests/test_build_clib.py +++ b/setuptools/_distutils/tests/test_build_clib.py @@ -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 @@ def test_finalize_options(self): 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) diff --git a/setuptools/_distutils/tests/test_build_ext.py b/setuptools/_distutils/tests/test_build_ext.py index 16d4873886..e60814ff64 100644 --- a/setuptools/_distutils/tests/test_build_ext.py +++ b/setuptools/_distutils/tests/test_build_ext.py @@ -3,6 +3,11 @@ 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 @@ LoggingSilencer, copy_xxmodule_c, fixup_build_ext, - combine_markers, ) from distutils.extension import Extension from distutils.errors import ( @@ -22,17 +26,12 @@ 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 @@ def build_ext(self, *args, **kwargs): 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 @@ def test_build_ext(self): 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 @@ def test_compiler_option(self): 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 @@ def test_ext_fullpath(self): 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 @@ def test_deployment_target_default(self): # 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 @@ def test_deployment_target_too_low(self): 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 diff --git a/setuptools/_distutils/tests/test_build_py.py b/setuptools/_distutils/tests/test_build_py.py index cab5c65b65..63543dcaa1 100644 --- a/setuptools/_distutils/tests/test_build_py.py +++ b/setuptools/_distutils/tests/test_build_py.py @@ -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 @@ def test_empty_package_dir(self): 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 @@ def test_byte_compile(self): 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 @@ def test_dont_write_bytecode(self): 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 index 0000000000..da1879f237 --- /dev/null +++ b/setuptools/_distutils/tests/test_ccompiler.py @@ -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])) diff --git a/setuptools/_distutils/tests/test_check.py b/setuptools/_distutils/tests/test_check.py index 7ad3cdfa8c..3e5f6034bf 100644 --- a/setuptools/_distutils/tests/test_check.py +++ b/setuptools/_distutils/tests/test_check.py @@ -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 @@ def test_check_author_maintainer(self): 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 @@ def test_check_document(self): 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 @@ def test_check_restructuredtext(self): 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 = [] diff --git a/setuptools/_distutils/tests/test_cmd.py b/setuptools/_distutils/tests/test_cmd.py index f5104e1db7..e4d5bf3c01 100644 --- a/setuptools/_distutils/tests/test_cmd.py +++ b/setuptools/_distutils/tests/test_cmd.py @@ -1,5 +1,4 @@ """Tests for distutils.cmd.""" -import unittest import os from test.support import captured_stdout @@ -15,14 +14,13 @@ def initialize_options(self): 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 @@ def test_ensure_string_list(self): 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 @@ def _execute(func, args, exec_msg, level): 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 @@ def _announce(msg, level): 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 @@ def test_ensure_string(self): 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) diff --git a/setuptools/_distutils/tests/test_config.py b/setuptools/_distutils/tests/test_config.py index b088d6007f..43ba6766ae 100644 --- a/setuptools/_distutils/tests/test_config.py +++ b/setuptools/_distutils/tests/test_config.py @@ -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 @@ @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): diff --git a/setuptools/_distutils/tests/test_config_cmd.py b/setuptools/_distutils/tests/test_config_cmd.py index 425cc1ba48..65c60f64dd 100644 --- a/setuptools/_distutils/tests/test_config_cmd.py +++ b/setuptools/_distutils/tests/test_config_cmd.py @@ -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 @@ def test_dump_file(self): 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: diff --git a/setuptools/_distutils/tests/test_core.py b/setuptools/_distutils/tests/test_core.py index ef085a8530..86b0040f60 100644 --- a/setuptools/_distutils/tests/test_core.py +++ b/setuptools/_distutils/tests/test_core.py @@ -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 @@ def 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: diff --git a/setuptools/_distutils/tests/test_cygwinccompiler.py b/setuptools/_distutils/tests/test_cygwinccompiler.py index b14ddb40c3..ef01ae2199 100644 --- a/setuptools/_distutils/tests/test_cygwinccompiler.py +++ b/setuptools/_distutils/tests/test_cygwinccompiler.py @@ -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 @@ 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 @@ def test_find_library_file(self): 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 diff --git a/setuptools/_distutils/tests/test_dir_util.py b/setuptools/_distutils/tests/test_dir_util.py index fc32c7fe74..cd7e018f5e 100644 --- a/setuptools/_distutils/tests/test_dir_util.py +++ b/setuptools/_distutils/tests/test_dir_util.py @@ -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 @@ 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 @@ def test_mkpath_remove_tree_verbosity(self): 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 @@ def test_copy_tree_exception_in_listdir(self): """ 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] diff --git a/setuptools/_distutils/tests/test_dist.py b/setuptools/_distutils/tests/test_dist.py index c962d3f3ac..ddfaf92167 100644 --- a/setuptools/_distutils/tests/test_dist.py +++ b/setuptools/_distutils/tests/test_dist.py @@ -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 @@ def test_command_packages_cmdline(self, clear_argv): 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") diff --git a/setuptools/_distutils/tests/test_file_util.py b/setuptools/_distutils/tests/test_file_util.py index e95535df05..b2e83c52f2 100644 --- a/setuptools/_distutils/tests/test_file_util.py +++ b/setuptools/_distutils/tests/test_file_util.py @@ -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 @@ 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 @@ def test_move_file_verbosity(self): 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 @@ def test_move_file_exception_unpacking_rename(self): 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 @@ def test_copy_file_hard_link_failure(self): 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) diff --git a/setuptools/_distutils/tests/test_install.py b/setuptools/_distutils/tests/test_install.py index 519227d25e..32a18b2f2f 100644 --- a/setuptools/_distutils/tests/test_install.py +++ b/setuptools/_distutils/tests/test_install.py @@ -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 @@ def check_path(got, expected): 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 @@ def cleanup(): 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 @@ def test_record(self): 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 @@ def test_record_extensions(self): 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 = [ diff --git a/setuptools/_distutils/tests/test_install_lib.py b/setuptools/_distutils/tests/test_install_lib.py index d8192e06ce..a654a66a79 100644 --- a/setuptools/_distutils/tests/test_install_lib.py +++ b/setuptools/_distutils/tests/test_install_lib.py @@ -2,7 +2,6 @@ import sys import os import importlib.util -import unittest import pytest @@ -38,7 +37,7 @@ def test_finalize_options(self): 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) diff --git a/setuptools/_distutils/tests/test_log.py b/setuptools/_distutils/tests/test_log.py index 614da574ae..7aeee4057f 100644 --- a/setuptools/_distutils/tests/test_log.py +++ b/setuptools/_distutils/tests/test_log.py @@ -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' + ) diff --git a/setuptools/_distutils/tests/test_msvccompiler.py b/setuptools/_distutils/tests/test_msvccompiler.py index db4694e1b3..f63537b8e5 100644 --- a/setuptools/_distutils/tests/test_msvccompiler.py +++ b/setuptools/_distutils/tests/test_msvccompiler.py @@ -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 @@ def test_get_vc_env_unicode(self): 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 @@ def CCompiler_spawn(self, cmd): "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" diff --git a/setuptools/_distutils/tests/test_register.py b/setuptools/_distutils/tests/test_register.py index 7657f3914f..0a5765f1fd 100644 --- a/setuptools/_distutils/tests/test_register.py +++ b/setuptools/_distutils/tests/test_register.py @@ -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 @@ def getheader(self, name, default=None): }.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 @@ def _get_cmd(self, metadata=None): 'author_email': 'xxx', 'name': 'xxx', 'version': 'xxx', + 'long_description': 'xxx', } pkg_info, dist = self.create_dist(**metadata) return register(dist) @@ -164,8 +154,8 @@ def _no_way(prompt=''): 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 @@ def test_password_reset(self): 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 @@ def test_strict(self): 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 @@ def test_register_invalid_long_description(self): 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 diff --git a/setuptools/_distutils/tests/test_sdist.py b/setuptools/_distutils/tests/test_sdist.py index 24ec9eb608..b11fe7c41e 100644 --- a/setuptools/_distutils/tests/test_sdist.py +++ b/setuptools/_distutils/tests/test_sdist.py @@ -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 .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.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 @@ """ -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 @@ def test_prune_file_list(self): 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 @@ def test_invalid_template_wrong_arguments(self): # 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 @@ def test_manual_manifest(self): @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() diff --git a/setuptools/_distutils/tests/test_spawn.py b/setuptools/_distutils/tests/test_spawn.py index c28b8ba594..d2a898ed3f 100644 --- a/setuptools/_distutils/tests/test_spawn.py +++ b/setuptools/_distutils/tests/test_spawn.py @@ -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 @@ 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 @@ def test_find_executable(self): # 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 @@ def test_find_executable(self): # 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 @@ def test_find_executable(self): 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 diff --git a/setuptools/_distutils/tests/test_sysconfig.py b/setuptools/_distutils/tests/test_sysconfig.py index 39e81f1778..f1759839bb 100644 --- a/setuptools/_distutils/tests/test_sysconfig.py +++ b/setuptools/_distutils/tests/test_sysconfig.py @@ -5,14 +5,13 @@ 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 @@ @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 @@ def test_get_config_h_filename(self): 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 @@ def test_get_config_vars(self): 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 @@ def set_executables(self, **kw): 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 @@ def test_sysconfig_module(self): '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 @@ def test_sysconfig_compiler_vars(self): 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 @@ def test_parse_config_h(self): 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.""" diff --git a/setuptools/_distutils/tests/test_unixccompiler.py b/setuptools/_distutils/tests/test_unixccompiler.py index 4be4ff2753..3978c23952 100644 --- a/setuptools/_distutils/tests/test_unixccompiler.py +++ b/setuptools/_distutils/tests/test_unixccompiler.py @@ -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 @@ 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 @@ def gcv(v): 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 @@ def gcvs(*args, _orig=sysconfig.get_config_vars): 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 @@ def gcvs(*args, _orig=sysconfig.get_config_vars): 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 @@ def gcvs(*args, _orig=sysconfig.get_config_vars): 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 diff --git a/setuptools/_distutils/tests/test_upload.py b/setuptools/_distutils/tests/test_upload.py index e5c6649694..fb905b641a 100644 --- a/setuptools/_distutils/tests/test_upload.py +++ b/setuptools/_distutils/tests/test_upload.py @@ -65,19 +65,14 @@ def getcode(self): 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 @@ def test_upload_fails(self): 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 @@ def test_wrong_exception_order(self): 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() diff --git a/setuptools/_distutils/tests/test_util.py b/setuptools/_distutils/tests/test_util.py index ac0feead55..605b0d40b7 100644 --- a/setuptools/_distutils/tests/test_util.py +++ b/setuptools/_distutils/tests/test_util.py @@ -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 @@ 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 @@ def test_check_environ(self): 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) diff --git a/setuptools/_distutils/tests/test_version.py b/setuptools/_distutils/tests/test_version.py index 8115faea3b..ff52ea4683 100644 --- a/setuptools/_distutils/tests/test_version.py +++ b/setuptools/_distutils/tests/test_version.py @@ -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) diff --git a/setuptools/_distutils/tests/unix_compat.py b/setuptools/_distutils/tests/unix_compat.py index 8250b36327..95fc8eebe2 100644 --- a/setuptools/_distutils/tests/unix_compat.py +++ b/setuptools/_distutils/tests/unix_compat.py @@ -1,5 +1,4 @@ import sys -import unittest try: import grp @@ -7,9 +6,13 @@ 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 index 0000000000..0250031d72 --- /dev/null +++ b/setuptools/_distutils/tests/xxmodule-3.8.c @@ -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 index 0000000000..a6e5071d1d --- /dev/null +++ b/setuptools/_distutils/tests/xxmodule.c @@ -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); +}