diff options
| author | Jason R. Coombs <jaraco@jaraco.com> | 2022-08-13 13:16:37 -0400 |
|---|---|---|
| committer | Jason R. Coombs <jaraco@jaraco.com> | 2022-08-13 13:16:37 -0400 |
| commit | c5cba301167d6351ef37a3b9982a6e9e5360b3d7 (patch) | |
| tree | a074b603e06d3d9914f95cc854bfc655a443b6c9 | |
| parent | 1c6d72b5f0680ba3708fecd3d86c2b38b1d80bc0 (diff) | |
| parent | e6062a16d91983d49ad3e54c6eabb7e4b32d1bc0 (diff) | |
| download | python-setuptools-git-c5cba301167d6351ef37a3b9982a6e9e5360b3d7.tar.gz | |
Merge branch 'main' into debt/remove-bdist_msi
82 files changed, 2612 insertions, 3977 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e244014d..62f6fcef 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,6 +20,10 @@ jobs: - ubuntu-latest - macos-latest - windows-latest + exclude: + # macOS is failing to build pyobjc (#165) + - platform: macos-latest + python: ~3.11.0-0 runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v2 diff --git a/conftest.py b/conftest.py index 70999c9e..feac1b60 100644 --- a/conftest.py +++ b/conftest.py @@ -1,5 +1,7 @@ import os +import sys import platform +import shutil import pytest @@ -31,3 +33,126 @@ def save_env(): @pytest.fixture def needs_zlib(): pytest.importorskip('zlib') + + +@pytest.fixture +def distutils_logging_silencer(request): + from distutils import log + + self = request.instance + self.threshold = log.set_threshold(log.FATAL) + # catching warnings + # when log will be replaced by logging + # we won't need such monkey-patch anymore + self._old_log = log.Log._log + log.Log._log = self._log + self.logs = [] + + try: + yield + finally: + log.set_threshold(self.threshold) + log.Log._log = self._old_log + + +@pytest.fixture +def distutils_managed_tempdir(request): + from distutils.tests import py38compat as os_helper + + self = request.instance + self.old_cwd = os.getcwd() + self.tempdirs = [] + try: + yield + finally: + # Restore working dir, for Solaris and derivatives, where rmdir() + # on the current directory fails. + os.chdir(self.old_cwd) + while self.tempdirs: + tmpdir = self.tempdirs.pop() + os_helper.rmtree(tmpdir) + + +@pytest.fixture +def save_argv(): + orig = sys.argv[:] + try: + yield + finally: + sys.argv[:] = orig + + +@pytest.fixture +def save_cwd(): + orig = os.getcwd() + try: + yield + finally: + os.chdir(orig) + + +@pytest.fixture +def threshold_warn(): + from distutils.log import set_threshold, WARN + + orig = set_threshold(WARN) + yield + set_threshold(orig) + + +@pytest.fixture +def pypirc(request, save_env, distutils_managed_tempdir): + from distutils.core import PyPIRCCommand + from distutils.core import Distribution + + self = request.instance + 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 + + +@pytest.fixture +def cleanup_testfn(): + from distutils.tests import py38compat as os_helper + + yield + path = os_helper.TESTFN + if os.path.isfile(path): + os.remove(path) + elif os.path.isdir(path): + shutil.rmtree(path) + + +# from pytest-dev/pytest#363 +@pytest.fixture(scope="session") +def monkeysession(request): + from _pytest.monkeypatch import MonkeyPatch + + mpatch = MonkeyPatch() + yield mpatch + mpatch.undo() + + +@pytest.fixture(autouse=True, scope="session") +def suppress_path_mangle(monkeysession): + """ + Disable the path mangling in CCompiler. Workaround for #169. + """ + from distutils import ccompiler + + monkeysession.setattr( + ccompiler.CCompiler, '_mangle_base', staticmethod(lambda x: x) + ) diff --git a/distutils/_msvccompiler.py b/distutils/_msvccompiler.py index aa0ceccb..ade80056 100644 --- a/distutils/_msvccompiler.py +++ b/distutils/_msvccompiler.py @@ -17,7 +17,7 @@ import os import subprocess import contextlib import warnings -import unittest.mock +import unittest.mock as mock with contextlib.suppress(ImportError): import winreg @@ -144,12 +144,12 @@ def _get_vc_env(plat_spec): try: out = subprocess.check_output( - 'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec), + f'cmd /u /c "{vcvarsall}" {plat_spec} && set', stderr=subprocess.STDOUT, ).decode('utf-16le', errors='replace') except subprocess.CalledProcessError as exc: log.error(exc.output) - raise DistutilsPlatformError("Error executing {}".format(exc.cmd)) + raise DistutilsPlatformError(f"Error executing {exc.cmd}") env = { key.lower(): value @@ -224,6 +224,18 @@ class MSVCCompiler(CCompiler): self.plat_name = None self.initialized = False + @classmethod + def _configure(cls, vc_env): + """ + Set class-level include/lib dirs. + """ + cls.include_dirs = cls._parse_path(vc_env.get('include', '')) + cls.library_dirs = cls._parse_path(vc_env.get('lib', '')) + + @staticmethod + def _parse_path(val): + return [dir.rstrip(os.sep) for dir in val.split(os.pathsep) if dir] + def initialize(self, plat_name=None): # multi-init means we would need to check platform same each time... assert not self.initialized, "don't init multiple times" @@ -232,7 +244,7 @@ class MSVCCompiler(CCompiler): # sanity check for platforms to prevent obscure errors later. if plat_name not in PLAT_TO_VCVARS: raise DistutilsPlatformError( - "--plat-name must be one of {}".format(tuple(PLAT_TO_VCVARS)) + f"--plat-name must be one of {tuple(PLAT_TO_VCVARS)}" ) # Get the vcvarsall.bat spec for the requested platform. @@ -243,6 +255,7 @@ class MSVCCompiler(CCompiler): raise DistutilsPlatformError( "Unable to find a compatible " "Visual Studio installation." ) + self._configure(vc_env) self._paths = vc_env.get('path', '') paths = self._paths.split(os.pathsep) @@ -253,14 +266,6 @@ class MSVCCompiler(CCompiler): self.mc = _find_exe("mc.exe", paths) # message compiler self.mt = _find_exe("mt.exe", paths) # message compiler - for dir in vc_env.get('include', '').split(os.pathsep): - if dir: - self.add_include_dir(dir.rstrip(os.sep)) - - for dir in vc_env.get('lib', '').split(os.pathsep): - if dir: - self.add_library_dir(dir.rstrip(os.sep)) - self.preprocess_options = None # bpo-38597: Always compile with dynamic linking # Future releases of Python 3.x will include all past @@ -341,7 +346,7 @@ class MSVCCompiler(CCompiler): # Better to raise an exception instead of silently continuing # and later complain about sources and targets having # different lengths - raise CompileError("Don't know how to compile {}".format(p)) + raise CompileError(f"Don't know how to compile {p}") return list(map(make_out_path, source_filenames)) @@ -425,9 +430,7 @@ class MSVCCompiler(CCompiler): continue else: # how to handle this file? - raise CompileError( - "Don't know how to compile {} to {}".format(src, obj) - ) + raise CompileError(f"Don't know how to compile {src} to {obj}") args = [self.cc] + compile_opts + pp_opts if add_cpp_opts: @@ -556,7 +559,7 @@ class MSVCCompiler(CCompiler): else: return warnings.warn("Fallback spawn triggered. Please update distutils monkeypatch.") - with unittest.mock.patch.dict('os.environ', env): + with mock.patch.dict('os.environ', env): bag.value = super().spawn(cmd) # -- Miscellaneous methods ----------------------------------------- diff --git a/distutils/archive_util.py b/distutils/archive_util.py index 4cb9bf39..5dfe2a16 100644 --- a/distutils/archive_util.py +++ b/distutils/archive_util.py @@ -121,7 +121,7 @@ def make_tarball( # compression using `compress` if compress == 'compress': - warn("'compress' will be deprecated.", PendingDeprecationWarning) + warn("'compress' is deprecated.", DeprecationWarning) # the option varies depending on the platform compressed_name = archive_name + compress_ext[compress] if sys.platform == 'win32': diff --git a/distutils/bcppcompiler.py b/distutils/bcppcompiler.py index 7a6f951f..ee033ed9 100644 --- a/distutils/bcppcompiler.py +++ b/distutils/bcppcompiler.py @@ -234,7 +234,7 @@ class BCPPCompiler(CCompiler): def_file = os.path.join(temp_dir, '%s.def' % modname) contents = ['EXPORTS'] for sym in export_symbols or []: - contents.append(' %s=_%s' % (sym, sym)) + contents.append(' {}=_{}'.format(sym, sym)) self.execute(write_file, (def_file, contents), "writing %s" % def_file) # Borland C++ has problems with '/' in paths @@ -346,7 +346,7 @@ class BCPPCompiler(CCompiler): (base, ext) = os.path.splitext(os.path.normcase(src_name)) if ext not in (self.src_extensions + ['.rc', '.res']): raise UnknownFileError( - "unknown file type '%s' (from '%s')" % (ext, src_name) + "unknown file type '{}' (from '{}')".format(ext, src_name) ) if strip_dir: base = os.path.basename(base) diff --git a/distutils/ccompiler.py b/distutils/ccompiler.py index c1761d02..c8d3b24b 100644 --- a/distutils/ccompiler.py +++ b/distutils/ccompiler.py @@ -6,6 +6,8 @@ for the Distutils compiler abstraction model.""" import sys import os import re +import warnings + from distutils.errors import ( CompileError, LinkError, @@ -91,6 +93,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 +336,7 @@ class CCompiler: def _setup_compile(self, outdir, macros, incdirs, sources, depends, extra): """Process arguments and decide which source files to compile.""" - if outdir is None: - outdir = self.output_dir - elif not isinstance(outdir, str): - raise TypeError("'output_dir' must be a string or None") - - if macros is None: - macros = self.macros - elif isinstance(macros, list): - macros = macros + (self.macros or []) - else: - raise TypeError("'macros' (if supplied) must be a list of tuples") - - if incdirs is None: - incdirs = self.include_dirs - elif isinstance(incdirs, (list, tuple)): - incdirs = list(incdirs) + (self.include_dirs or []) - else: - raise TypeError("'include_dirs' (if supplied) must be a list of strings") + outdir, macros, incdirs = self._fix_compile_args(outdir, macros, incdirs) if extra is None: extra = [] @@ -400,6 +395,9 @@ class CCompiler: else: raise TypeError("'include_dirs' (if supplied) must be a list of strings") + # add include dirs for class + include_dirs += self.__class__.include_dirs + return output_dir, macros, include_dirs def _prep_compile(self, sources, output_dir, depends=None): @@ -456,6 +454,9 @@ class CCompiler: else: raise TypeError("'library_dirs' (if supplied) must be a list of strings") + # add library dirs for class + library_dirs += self.__class__.library_dirs + if runtime_library_dirs is None: runtime_library_dirs = self.runtime_library_dirs elif isinstance(runtime_library_dirs, (list, tuple)): @@ -926,17 +927,35 @@ int main (int argc, char **argv) { obj_names = [] for src_name in source_filenames: base, ext = os.path.splitext(src_name) - base = os.path.splitdrive(base)[1] # Chop off the drive - base = base[os.path.isabs(base) :] # If abs, chop off leading / + base = self._mangle_base(base) if ext not in self.src_extensions: raise UnknownFileError( - "unknown file type '%s' (from '%s')" % (ext, src_name) + "unknown file type '{}' (from '{}')".format(ext, src_name) ) if strip_dir: base = os.path.basename(base) obj_names.append(os.path.join(output_dir, base + self.obj_extension)) return obj_names + @staticmethod + def _mangle_base(base): + """ + For unknown reasons, absolute paths are mangled. + """ + # Chop off the drive + no_drive = os.path.splitdrive(base)[1] + # If abs, chop off leading / + rel = no_drive[os.path.isabs(no_drive) :] + if rel != base: + msg = ( + f"Absolute path {base!r} is being replaced with a " + f"relative path {rel!r} for outputs. This behavior is " + "deprecated. If this behavior is desired, please " + "comment in pypa/distutils#169." + ) + warnings.warn(msg, DeprecationWarning) + return rel + def shared_object_filename(self, basename, strip_dir=0, output_dir=''): assert output_dir is not None if strip_dir: diff --git a/distutils/cmd.py b/distutils/cmd.py index 6f68801d..68a9267c 100644 --- a/distutils/cmd.py +++ b/distutils/cmd.py @@ -163,7 +163,7 @@ class Command: if option[-1] == "=": option = option[:-1] value = getattr(self, option) - self.announce(indent + "%s = %s" % (option, value), level=log.INFO) + self.announce(indent + "{} = {}".format(option, value), level=log.INFO) def run(self): """A command's raison d'etre: carry out the action it exists to @@ -215,7 +215,7 @@ class Command: return default elif not isinstance(val, str): raise DistutilsOptionError( - "'%s' must be a %s (got `%s`)" % (option, what, val) + "'{}' must be a {} (got `{}`)".format(option, what, val) ) return val @@ -243,7 +243,7 @@ class Command: ok = False if not ok: raise DistutilsOptionError( - "'%s' must be a list of strings (got %r)" % (option, val) + "'{}' must be a list of strings (got {!r})".format(option, val) ) def _ensure_tested_string(self, option, tester, what, error_fmt, default=None): @@ -424,7 +424,7 @@ class Command: raise TypeError("'infiles' must be a string, or a list or tuple of strings") if exec_msg is None: - exec_msg = "generating %s from %s" % (outfile, ', '.join(infiles)) + exec_msg = "generating {} from {}".format(outfile, ', '.join(infiles)) # If 'outfile' must be regenerated (either because it doesn't # exist, is out-of-date, or the 'force' flag is true) then diff --git a/distutils/command/bdist.py b/distutils/command/bdist.py index 6a701731..de37dae0 100644 --- a/distutils/command/bdist.py +++ b/distutils/command/bdist.py @@ -4,6 +4,8 @@ Implements the Distutils 'bdist' command (create a built [binary] distribution).""" import os +import warnings + from distutils.core import Command from distutils.errors import DistutilsPlatformError, DistutilsOptionError from distutils.util import get_platform @@ -15,11 +17,21 @@ def show_formats(): formats = [] for format in bdist.format_commands: - formats.append(("formats=" + format, None, bdist.format_command[format][1])) + formats.append(("formats=" + format, None, bdist.format_commands[format][1])) pretty_printer = FancyGetopt(formats) pretty_printer.print_help("List of available distribution formats:") +class ListCompat(dict): + # adapter to allow for Setuptools compatibility in format_commands + def append(self, item): + warnings.warn( + """format_commands is now a dict. append is deprecated.""", + DeprecationWarning, + stacklevel=2, + ) + + class bdist(Command): description = "create a built (binary) distribution" @@ -64,29 +76,21 @@ class bdist(Command): # Debian-ish Linux, Solaris, FreeBSD, ..., Windows, Mac OS. default_format = {'posix': 'gztar', 'nt': 'zip'} - # Establish the preferred order (for the --help-formats option). - format_commands = [ - 'rpm', - 'gztar', - 'bztar', - 'xztar', - 'ztar', - 'tar', - 'wininst', - 'zip', - ] - - # And the real information. - format_command = { - 'rpm': ('bdist_rpm', "RPM distribution"), - 'gztar': ('bdist_dumb', "gzip'ed tar file"), - 'bztar': ('bdist_dumb', "bzip2'ed tar file"), - 'xztar': ('bdist_dumb', "xz'ed tar file"), - 'ztar': ('bdist_dumb', "compressed tar file"), - 'tar': ('bdist_dumb', "tar file"), - 'wininst': ('bdist_wininst', "Windows executable installer"), - 'zip': ('bdist_dumb', "ZIP file"), - } + # Define commands in preferred order for the --help-formats option + format_commands = ListCompat( + { + 'rpm': ('bdist_rpm', "RPM distribution"), + 'gztar': ('bdist_dumb', "gzip'ed tar file"), + 'bztar': ('bdist_dumb', "bzip2'ed tar file"), + 'xztar': ('bdist_dumb', "xz'ed tar file"), + 'ztar': ('bdist_dumb', "compressed tar file"), + 'tar': ('bdist_dumb', "tar file"), + 'zip': ('bdist_dumb', "ZIP file"), + } + ) + + # for compatibility until consumers only reference format_commands + format_command = format_commands def initialize_options(self): self.bdist_base = None @@ -130,7 +134,7 @@ class bdist(Command): commands = [] for format in self.formats: try: - commands.append(self.format_command[format][0]) + commands.append(self.format_commands[format][0]) except KeyError: raise DistutilsOptionError("invalid format '%s'" % format) diff --git a/distutils/command/bdist_dumb.py b/distutils/command/bdist_dumb.py index d3f519e0..0f52330f 100644 --- a/distutils/command/bdist_dumb.py +++ b/distutils/command/bdist_dumb.py @@ -105,7 +105,9 @@ class bdist_dumb(Command): # And make an archive relative to the root of the # pseudo-installation tree. - archive_basename = "%s.%s" % (self.distribution.get_fullname(), self.plat_name) + archive_basename = "{}.{}".format( + self.distribution.get_fullname(), self.plat_name + ) pseudoinstall_root = os.path.join(self.dist_dir, archive_basename) if not self.relative: diff --git a/distutils/command/bdist_rpm.py b/distutils/command/bdist_rpm.py index fcfd7cd8..6a50ef34 100644 --- a/distutils/command/bdist_rpm.py +++ b/distutils/command/bdist_rpm.py @@ -353,7 +353,7 @@ class bdist_rpm(Command): nvr_string = "%{name}-%{version}-%{release}" src_rpm = nvr_string + ".src.rpm" non_src_rpm = "%{arch}/" + nvr_string + ".%{arch}.rpm" - q_cmd = r"rpm -q --qf '%s %s\n' --specfile '%s'" % ( + q_cmd = r"rpm -q --qf '{} {}\n' --specfile '{}'".format( src_rpm, non_src_rpm, spec_path, @@ -488,9 +488,9 @@ class bdist_rpm(Command): ): val = getattr(self, field.lower()) if isinstance(val, list): - spec_file.append('%s: %s' % (field, ' '.join(val))) + spec_file.append('{}: {}'.format(field, ' '.join(val))) elif val is not None: - spec_file.append('%s: %s' % (field, val)) + spec_file.append('{}: {}'.format(field, val)) if self.distribution.get_url(): spec_file.append('Url: ' + self.distribution.get_url()) @@ -527,7 +527,7 @@ class bdist_rpm(Command): # rpm scripts # figure out default build script - def_setup_call = "%s %s" % (self.python, os.path.basename(sys.argv[0])) + def_setup_call = "{} {}".format(self.python, os.path.basename(sys.argv[0])) def_build = "%s build" % def_setup_call if self.use_rpm_opt_flags: def_build = 'env CFLAGS="$RPM_OPT_FLAGS" ' + def_build diff --git a/distutils/command/bdist_wininst.py b/distutils/command/bdist_wininst.py deleted file mode 100644 index 7e9a64a5..00000000 --- a/distutils/command/bdist_wininst.py +++ /dev/null @@ -1,418 +0,0 @@ -"""distutils.command.bdist_wininst - -Implements the Distutils 'bdist_wininst' command: create a windows installer -exe-program.""" - -import os -import sys -import warnings -from distutils.core import Command -from distutils.util import get_platform -from distutils.dir_util import remove_tree -from distutils.errors import DistutilsOptionError, DistutilsPlatformError -from distutils.sysconfig import get_python_version -from distutils import log - - -class bdist_wininst(Command): - - description = "create an executable installer for MS Windows" - - user_options = [ - ('bdist-dir=', None, "temporary directory for creating the distribution"), - ( - 'plat-name=', - 'p', - "platform name to embed in generated filenames " - "(default: %s)" % get_platform(), - ), - ( - 'keep-temp', - 'k', - "keep the pseudo-installation tree around after " - + "creating the distribution archive", - ), - ( - 'target-version=', - None, - "require a specific python version" + " on the target system", - ), - ('no-target-compile', 'c', "do not compile .py to .pyc on the target system"), - ( - 'no-target-optimize', - 'o', - "do not compile .py to .pyo (optimized) " "on the target system", - ), - ('dist-dir=', 'd', "directory to put final built distributions in"), - ( - 'bitmap=', - 'b', - "bitmap to use for the installer instead of python-powered logo", - ), - ( - 'title=', - 't', - "title to display on the installer background instead of default", - ), - ('skip-build', None, "skip rebuilding everything (for testing/debugging)"), - ( - 'install-script=', - None, - "basename of installation script to be run after " - "installation or before deinstallation", - ), - ( - 'pre-install-script=', - None, - "Fully qualified filename of a script to be run before " - "any files are installed. This script need not be in the " - "distribution", - ), - ( - 'user-access-control=', - None, - "specify Vista's UAC handling - 'none'/default=no " - "handling, 'auto'=use UAC if target Python installed for " - "all users, 'force'=always use UAC", - ), - ] - - boolean_options = [ - 'keep-temp', - 'no-target-compile', - 'no-target-optimize', - 'skip-build', - ] - - # bpo-10945: bdist_wininst requires mbcs encoding only available on Windows - _unsupported = sys.platform != "win32" - - def __init__(self, *args, **kw): - super().__init__(*args, **kw) - warnings.warn( - "bdist_wininst command is deprecated since Python 3.8, " - "use bdist_wheel (wheel packages) instead", - DeprecationWarning, - 2, - ) - - def initialize_options(self): - self.bdist_dir = None - self.plat_name = None - self.keep_temp = 0 - self.no_target_compile = 0 - self.no_target_optimize = 0 - self.target_version = None - self.dist_dir = None - self.bitmap = None - self.title = None - self.skip_build = None - self.install_script = None - self.pre_install_script = None - self.user_access_control = None - - def finalize_options(self): - self.set_undefined_options('bdist', ('skip_build', 'skip_build')) - - if self.bdist_dir is None: - if self.skip_build and self.plat_name: - # If build is skipped and plat_name is overridden, bdist will - # not see the correct 'plat_name' - so set that up manually. - bdist = self.distribution.get_command_obj('bdist') - bdist.plat_name = self.plat_name - # next the command will be initialized using that name - bdist_base = self.get_finalized_command('bdist').bdist_base - self.bdist_dir = os.path.join(bdist_base, 'wininst') - - if not self.target_version: - self.target_version = "" - - if not self.skip_build and self.distribution.has_ext_modules(): - short_version = get_python_version() - if self.target_version and self.target_version != short_version: - raise DistutilsOptionError( - "target version can only be %s, or the '--skip-build'" - " option must be specified" % (short_version,) - ) - self.target_version = short_version - - self.set_undefined_options( - 'bdist', - ('dist_dir', 'dist_dir'), - ('plat_name', 'plat_name'), - ) - - if self.install_script: - for script in self.distribution.scripts: - if self.install_script == os.path.basename(script): - break - else: - raise DistutilsOptionError( - "install_script '%s' not found in scripts" % self.install_script - ) - - def run(self): - if sys.platform != "win32" and ( - self.distribution.has_ext_modules() or self.distribution.has_c_libraries() - ): - raise DistutilsPlatformError( - "distribution contains extensions and/or C libraries; " - "must be compiled on a Windows 32 platform" - ) - - if not self.skip_build: - self.run_command('build') - - install = self.reinitialize_command('install', reinit_subcommands=1) - install.root = self.bdist_dir - install.skip_build = self.skip_build - install.warn_dir = 0 - install.plat_name = self.plat_name - - install_lib = self.reinitialize_command('install_lib') - # we do not want to include pyc or pyo files - install_lib.compile = 0 - install_lib.optimize = 0 - - if self.distribution.has_ext_modules(): - # If we are building an installer for a Python version other - # than the one we are currently running, then we need to ensure - # our build_lib reflects the other Python version rather than ours. - # Note that for target_version!=sys.version, we must have skipped the - # build step, so there is no issue with enforcing the build of this - # version. - target_version = self.target_version - if not target_version: - assert self.skip_build, "Should have already checked this" - target_version = '%d.%d' % sys.version_info[:2] - plat_specifier = ".%s-%s" % (self.plat_name, target_version) - build = self.get_finalized_command('build') - build.build_lib = os.path.join(build.build_base, 'lib' + plat_specifier) - - # Use a custom scheme for the zip-file, because we have to decide - # at installation time which scheme to use. - for key in ('purelib', 'platlib', 'headers', 'scripts', 'data'): - value = key.upper() - if key == 'headers': - value = value + '/Include/$dist_name' - setattr(install, 'install_' + key, value) - - log.info("installing to %s", self.bdist_dir) - install.ensure_finalized() - - # avoid warning of 'install_lib' about installing - # into a directory not in sys.path - sys.path.insert(0, os.path.join(self.bdist_dir, 'PURELIB')) - - install.run() - - del sys.path[0] - - # And make an archive relative to the root of the - # pseudo-installation tree. - from tempfile import mktemp - - archive_basename = mktemp() - fullname = self.distribution.get_fullname() - arcname = self.make_archive(archive_basename, "zip", root_dir=self.bdist_dir) - # create an exe containing the zip-file - self.create_exe(arcname, fullname, self.bitmap) - if self.distribution.has_ext_modules(): - pyversion = get_python_version() - else: - pyversion = 'any' - self.distribution.dist_files.append( - ('bdist_wininst', pyversion, self.get_installer_filename(fullname)) - ) - # remove the zip-file again - log.debug("removing temporary file '%s'", arcname) - os.remove(arcname) - - if not self.keep_temp: - remove_tree(self.bdist_dir, dry_run=self.dry_run) - - def get_inidata(self): - # Return data describing the installation. - lines = [] - metadata = self.distribution.metadata - - # Write the [metadata] section. - lines.append("[metadata]") - - # 'info' will be displayed in the installer's dialog box, - # describing the items to be installed. - info = (metadata.long_description or '') + '\n' - - # Escape newline characters - def escape(s): - return s.replace("\n", "\\n") - - for name in [ - "author", - "author_email", - "description", - "maintainer", - "maintainer_email", - "name", - "url", - "version", - ]: - data = getattr(metadata, name, "") - if data: - info = info + ("\n %s: %s" % (name.capitalize(), escape(data))) - lines.append("%s=%s" % (name, escape(data))) - - # The [setup] section contains entries controlling - # the installer runtime. - lines.append("\n[Setup]") - if self.install_script: - lines.append("install_script=%s" % self.install_script) - lines.append("info=%s" % escape(info)) - lines.append("target_compile=%d" % (not self.no_target_compile)) - lines.append("target_optimize=%d" % (not self.no_target_optimize)) - if self.target_version: - lines.append("target_version=%s" % self.target_version) - if self.user_access_control: - lines.append("user_access_control=%s" % self.user_access_control) - - title = self.title or self.distribution.get_fullname() - lines.append("title=%s" % escape(title)) - import time - import distutils - - build_info = "Built %s with distutils-%s" % ( - time.ctime(time.time()), - distutils.__version__, - ) - lines.append("build_info=%s" % build_info) - return "\n".join(lines) - - def create_exe(self, arcname, fullname, bitmap=None): - import struct - - self.mkpath(self.dist_dir) - - cfgdata = self.get_inidata() - - installer_name = self.get_installer_filename(fullname) - self.announce("creating %s" % installer_name) - - if bitmap: - with open(bitmap, "rb") as f: - bitmapdata = f.read() - bitmaplen = len(bitmapdata) - else: - bitmaplen = 0 - - with open(installer_name, "wb") as file: - file.write(self.get_exe_bytes()) - if bitmap: - file.write(bitmapdata) - - # Convert cfgdata from unicode to ascii, mbcs encoded - if isinstance(cfgdata, str): - cfgdata = cfgdata.encode("mbcs") - - # Append the pre-install script - cfgdata = cfgdata + b"\0" - if self.pre_install_script: - # We need to normalize newlines, so we open in text mode and - # convert back to bytes. "latin-1" simply avoids any possible - # failures. - with open(self.pre_install_script, "r", encoding="latin-1") as script: - script_data = script.read().encode("latin-1") - cfgdata = cfgdata + script_data + b"\n\0" - else: - # empty pre-install script - cfgdata = cfgdata + b"\0" - file.write(cfgdata) - - # The 'magic number' 0x1234567B is used to make sure that the - # binary layout of 'cfgdata' is what the wininst.exe binary - # expects. If the layout changes, increment that number, make - # the corresponding changes to the wininst.exe sources, and - # recompile them. - header = struct.pack( - "<iii", - 0x1234567B, # tag - len(cfgdata), # length - bitmaplen, # number of bytes in bitmap - ) - file.write(header) - with open(arcname, "rb") as f: - file.write(f.read()) - - def get_installer_filename(self, fullname): - # Factored out to allow overriding in subclasses - if self.target_version: - # if we create an installer for a specific python version, - # it's better to include this in the name - installer_name = os.path.join( - self.dist_dir, - "%s.%s-py%s.exe" % (fullname, self.plat_name, self.target_version), - ) - else: - installer_name = os.path.join( - self.dist_dir, "%s.%s.exe" % (fullname, self.plat_name) - ) - return installer_name - - def get_exe_bytes(self): # noqa: C901 - # If a target-version other than the current version has been - # specified, then using the MSVC version from *this* build is no good. - # Without actually finding and executing the target version and parsing - # its sys.version, we just hard-code our knowledge of old versions. - # NOTE: Possible alternative is to allow "--target-version" to - # specify a Python executable rather than a simple version string. - # We can then execute this program to obtain any info we need, such - # as the real sys.version string for the build. - cur_version = get_python_version() - - # If the target version is *later* than us, then we assume they - # use what we use - # string compares seem wrong, but are what sysconfig.py itself uses - if self.target_version and self.target_version < cur_version: - if self.target_version < "2.4": - bv = '6.0' - elif self.target_version == "2.4": - bv = '7.1' - elif self.target_version == "2.5": - bv = '8.0' - elif self.target_version <= "3.2": - bv = '9.0' - elif self.target_version <= "3.4": - bv = '10.0' - else: - bv = '14.0' - else: - # for current version - use authoritative check. - try: - from msvcrt import CRT_ASSEMBLY_VERSION - except ImportError: - # cross-building, so assume the latest version - bv = '14.0' - else: - # as far as we know, CRT is binary compatible based on - # the first field, so assume 'x.0' until proven otherwise - major = CRT_ASSEMBLY_VERSION.partition('.')[0] - bv = major + '.0' - - # wininst-x.y.exe is in the same directory as this file - directory = os.path.dirname(__file__) - # we must use a wininst-x.y.exe built with the same C compiler - # used for python. XXX What about mingw, borland, and so on? - - # if plat_name starts with "win" but is not "win32" - # we want to strip "win" and leave the rest (e.g. -amd64) - # for all other cases, we don't want any suffix - if self.plat_name != 'win32' and self.plat_name[:3] == 'win': - sfix = self.plat_name[3:] - else: - sfix = '' - - filename = os.path.join(directory, "wininst-%s%s.exe" % (bv, sfix)) - f = open(filename, "rb") - try: - return f.read() - finally: - f.close() diff --git a/distutils/command/build.py b/distutils/command/build.py index e4b06425..6d453419 100644 --- a/distutils/command/build.py +++ b/distutils/command/build.py @@ -79,7 +79,7 @@ class build(Command): "using './configure --help' on your platform)" ) - plat_specifier = ".%s-%s" % (self.plat_name, sys.implementation.cache_tag) + plat_specifier = ".{}-{}".format(self.plat_name, sys.implementation.cache_tag) # Make it so Python 2.x and Python 2.x with --with-pydebug don't # share the same build directories. Doing so confuses the build diff --git a/distutils/command/build_ext.py b/distutils/command/build_ext.py index 153a0b6d..3c6cee7e 100644 --- a/distutils/command/build_ext.py +++ b/distutils/command/build_ext.py @@ -498,7 +498,7 @@ class build_ext(Command): except (CCompilerError, DistutilsError, CompileError) as e: if not ext.optional: raise - self.warn('building extension "%s" failed: %s' % (ext.name, e)) + self.warn('building extension "{}" failed: {}'.format(ext.name, e)) def build_extension(self, ext): sources = ext.sources diff --git a/distutils/command/check.py b/distutils/command/check.py index 9c3523a8..539481c9 100644 --- a/distutils/command/check.py +++ b/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 @@ try: def system_message(self, level, message, *children, **kwargs): self.messages.append((level, message, children, kwargs)) - return nodes.system_message( + return docutils.nodes.system_message( message, level=level, type=self.levels[level], *children, **kwargs ) - HAS_DOCUTILS = True -except Exception: - # Catch all exceptions because exceptions besides ImportError probably - # indicate that docutils is not ported to Py3k. - HAS_DOCUTILS = False - class check(Command): """This command checks the meta-data of the package.""" @@ -81,8 +76,11 @@ class check(Command): if self.metadata: self.check_metadata() if self.restructuredtext: - if HAS_DOCUTILS: - self.check_restructuredtext() + if 'docutils' in globals(): + try: + self.check_restructuredtext() + except TypeError as exc: + raise DistutilsSetupError(str(exc)) elif self.strict: raise DistutilsSetupError('The docutils package is needed.') @@ -117,15 +115,17 @@ class check(Command): if line is None: warning = warning[1] else: - warning = '%s (line %s)' % (warning[1], line) + warning = '{} (line {})'.format(warning[1], line) self.warn(warning) 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 @@ class check(Command): error_handler=settings.error_encoding_error_handler, ) - document = nodes.document(settings, reporter, source=source_path) + document = docutils.nodes.document(settings, reporter, source=source_path) document.note_source(source_path, -1) try: parser.parse(data, document) diff --git a/distutils/command/register.py b/distutils/command/register.py index d2351ab8..c1402650 100644 --- a/distutils/command/register.py +++ b/distutils/command/register.py @@ -66,9 +66,9 @@ class register(PyPIRCCommand): def check_metadata(self): """Deprecated API.""" warn( - "distutils.command.register.check_metadata is deprecated, \ - use the check command instead", - PendingDeprecationWarning, + "distutils.command.register.check_metadata is deprecated; " + "use the check command instead", + DeprecationWarning, ) check = self.distribution.get_command_obj('check') check.ensure_finalized() @@ -174,7 +174,7 @@ Your selection [default 1]: ''', auth.add_password(self.realm, host, username, password) # send the info to the server and report the result code, result = self.post_to_server(self.build_post_data('submit'), auth) - self.announce('Server response (%s): %s' % (code, result), log.INFO) + self.announce('Server response ({}): {}'.format(code, result), log.INFO) # possibly save the login if code == 200: @@ -224,7 +224,7 @@ Your selection [default 1]: ''', log.info('Server response (%s): %s', code, result) else: log.info('You will receive an email shortly.') - log.info(('Follow the instructions in it to ' 'complete registration.')) + log.info('Follow the instructions in it to ' 'complete registration.') elif choice == '3': data = {':action': 'password_reset'} data['email'] = '' @@ -265,7 +265,7 @@ Your selection [default 1]: ''', '''Post a query to the server, and return a string response.''' if 'name' in data: self.announce( - 'Registering %s to %s' % (data['name'], self.repository), log.INFO + 'Registering {} to {}'.format(data['name'], self.repository), log.INFO ) # Build up the MIME payload for the urllib2 POST data boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' diff --git a/distutils/command/sdist.py b/distutils/command/sdist.py index ec3c97ac..d6e9489d 100644 --- a/distutils/command/sdist.py +++ b/distutils/command/sdist.py @@ -402,7 +402,7 @@ class sdist(Command): seps = '/' vcs_dirs = ['RCS', 'CVS', r'\.svn', r'\.hg', r'\.git', r'\.bzr', '_darcs'] - vcs_ptrn = r'(^|%s)(%s)(%s).*' % (seps, '|'.join(vcs_dirs), seps) + vcs_ptrn = r'(^|{})({})({}).*'.format(seps, '|'.join(vcs_dirs), seps) self.filelist.exclude_pattern(vcs_ptrn, is_regex=1) def write_manifest(self): diff --git a/distutils/command/upload.py b/distutils/command/upload.py index f2a8118e..6af53943 100644 --- a/distutils/command/upload.py +++ b/distutils/command/upload.py @@ -170,7 +170,7 @@ class upload(PyPIRCCommand): body.write(end_boundary) body = body.getvalue() - msg = "Submitting %s to %s" % (filename, self.repository) + msg = "Submitting {} to {}".format(filename, self.repository) self.announce(msg, log.INFO) # build the Request @@ -194,12 +194,12 @@ class upload(PyPIRCCommand): raise if status == 200: - self.announce('Server response (%s): %s' % (status, reason), log.INFO) + self.announce('Server response ({}): {}'.format(status, reason), log.INFO) if self.show_response: text = self._read_pypi_response(result) msg = '\n'.join(('-' * 75, text, '-' * 75)) self.announce(msg, log.INFO) else: - msg = 'Upload failed (%s): %s' % (status, reason) + msg = 'Upload failed ({}): {}'.format(status, reason) self.announce(msg, log.ERROR) raise DistutilsError(msg) diff --git a/distutils/core.py b/distutils/core.py index 333596ac..de13978f 100644 --- a/distutils/core.py +++ b/distutils/core.py @@ -149,7 +149,7 @@ def setup(**attrs): # noqa: C901 if 'name' not in attrs: raise SystemExit("error in setup command: %s" % msg) else: - raise SystemExit("error in %s setup command: %s" % (attrs['name'], msg)) + raise SystemExit("error in {} setup command: {}".format(attrs['name'], msg)) if _setup_stop_after == "init": return dist @@ -203,10 +203,10 @@ def run_commands(dist): raise SystemExit("interrupted") except OSError as exc: if DEBUG: - sys.stderr.write("error: %s\n" % (exc,)) + sys.stderr.write("error: {}\n".format(exc)) raise else: - raise SystemExit("error: %s" % (exc,)) + raise SystemExit("error: {}".format(exc)) except (DistutilsError, CCompilerError) as msg: if DEBUG: @@ -249,7 +249,7 @@ def run_setup(script_name, script_args=None, stop_after="run"): used to drive the Distutils. """ if stop_after not in ('init', 'config', 'commandline', 'run'): - raise ValueError("invalid value for 'stop_after': %r" % (stop_after,)) + raise ValueError("invalid value for 'stop_after': {!r}".format(stop_after)) global _setup_stop_after, _setup_distribution _setup_stop_after = stop_after diff --git a/distutils/cygwinccompiler.py b/distutils/cygwinccompiler.py index 5c23e988..63910f2a 100644 --- a/distutils/cygwinccompiler.py +++ b/distutils/cygwinccompiler.py @@ -6,47 +6,6 @@ the Mingw32CCompiler class which handles the mingw32 port of GCC (same as cygwin in no-cygwin mode). """ -# problems: -# -# * if you use a msvc compiled python version (1.5.2) -# 1. you have to insert a __GNUC__ section in its config.h -# 2. you have to generate an import library for its dll -# - create a def-file for python??.dll -# - create an import library using -# dlltool --dllname python15.dll --def python15.def \ -# --output-lib libpython15.a -# -# see also http://starship.python.net/crew/kernr/mingw32/Notes.html -# -# * We put export_symbols in a def-file, and don't use -# --export-all-symbols because it doesn't worked reliable in some -# tested configurations. And because other windows compilers also -# need their symbols specified this no serious problem. -# -# tested configurations: -# -# * cygwin gcc 2.91.57/ld 2.9.4/dllwrap 0.2.4 works -# (after patching python's config.h and for C++ some other include files) -# see also http://starship.python.net/crew/kernr/mingw32/Notes.html -# * mingw32 gcc 2.95.2/ld 2.9.4/dllwrap 0.2.4 works -# (ld doesn't support -shared, so we use dllwrap) -# * cygwin gcc 2.95.2/ld 2.10.90/dllwrap 2.10.90 works now -# - its dllwrap doesn't work, there is a bug in binutils 2.10.90 -# see also http://sources.redhat.com/ml/cygwin/2000-06/msg01274.html -# - using gcc -mdll instead dllwrap doesn't work without -static because -# it tries to link against dlls instead their import libraries. (If -# it finds the dll first.) -# By specifying -static we force ld to link against the import libraries, -# this is windows standard and there are normally not the necessary symbols -# in the dlls. -# *** only the version of June 2000 shows these problems -# * cygwin gcc 3.2/ld 2.13.90 works -# (ld supports -shared) -# * mingw gcc 3.2/ld 2.13 works -# (ld supports -shared) -# * llvm-mingw with Clang 11 works -# (lld supports -shared) - import os import sys import copy @@ -101,6 +60,12 @@ def get_msvcr(): raise ValueError("Unknown MS Compiler version %s " % msc_ver) +_runtime_library_dirs_msg = ( + "Unable to set runtime library search path on Windows, " + "usually indicated by `runtime_library_dirs` parameter to Extension" +) + + class CygwinCCompiler(UnixCCompiler): """Handles the Cygwin port of the GNU C compiler to Windows.""" @@ -119,7 +84,9 @@ class CygwinCCompiler(UnixCCompiler): super().__init__(verbose, dry_run, force) status, details = check_config_h() - self.debug_print("Python's GCC status: %s (details: %s)" % (status, details)) + self.debug_print( + "Python's GCC status: {} (details: {})".format(status, details) + ) if status is not CONFIG_H_OK: self.warn( "Python's pyconfig.h doesn't seem to support your compiler. " @@ -138,7 +105,7 @@ class CygwinCCompiler(UnixCCompiler): compiler_so='%s -mcygwin -mdll -O -Wall' % self.cc, compiler_cxx='%s -mcygwin -O -Wall' % self.cxx, linker_exe='%s -mcygwin' % self.cc, - linker_so=('%s -mcygwin %s' % (self.linker_dll, shared_option)), + linker_so=('{} -mcygwin {}'.format(self.linker_dll, shared_option)), ) # Include the appropriate MSVC runtime library if Python was built @@ -199,10 +166,7 @@ class CygwinCCompiler(UnixCCompiler): objects = copy.copy(objects or []) if runtime_library_dirs: - self.warn( - "I don't know what to do with 'runtime_library_dirs': " - + str(runtime_library_dirs) - ) + self.warn(_runtime_library_dirs_msg) # Additional libraries libraries.extend(self.dll_libraries) @@ -273,7 +237,7 @@ class CygwinCCompiler(UnixCCompiler): # cygwin doesn't support rpath. While in theory we could error # out like MSVC does, code might expect it to work like on Unix, so # just warn and hope for the best. - self.warn("don't know how to set runtime library search path on Windows") + self.warn(_runtime_library_dirs_msg) return [] # -- Miscellaneous methods ----------------------------------------- @@ -288,7 +252,7 @@ class CygwinCCompiler(UnixCCompiler): base, ext = os.path.splitext(os.path.normcase(src_name)) if ext not in (self.src_extensions + ['.rc', '.res']): raise UnknownFileError( - "unknown file type '%s' (from '%s')" % (ext, src_name) + "unknown file type '{}' (from '{}')".format(ext, src_name) ) if strip_dir: base = os.path.basename(base) @@ -322,7 +286,7 @@ class Mingw32CCompiler(CygwinCCompiler): compiler_so='%s -mdll -O -Wall' % self.cc, compiler_cxx='%s -O -Wall' % self.cxx, linker_exe='%s' % self.cc, - linker_so='%s %s' % (self.linker_dll, shared_option), + linker_so='{} {}'.format(self.linker_dll, shared_option), ) # Maybe we should also append -mthreads, but then the finished @@ -337,9 +301,7 @@ class Mingw32CCompiler(CygwinCCompiler): self.dll_libraries = get_msvcr() def runtime_library_dir_option(self, dir): - raise DistutilsPlatformError( - "don't know how to set runtime library search path on Windows" - ) + raise DistutilsPlatformError(_runtime_library_dirs_msg) # Because these compilers aren't configured in Python's pyconfig.h file by @@ -395,7 +357,7 @@ def check_config_h(): finally: config_h.close() except OSError as exc: - return (CONFIG_H_UNCERTAIN, "couldn't read '%s': %s" % (fn, exc.strerror)) + return (CONFIG_H_UNCERTAIN, "couldn't read '{}': {}".format(fn, exc.strerror)) def is_cygwincc(cc): diff --git a/distutils/dir_util.py b/distutils/dir_util.py index 7a132e31..6f0bb8ad 100644 --- a/distutils/dir_util.py +++ b/distutils/dir_util.py @@ -34,7 +34,7 @@ def mkpath(name, mode=0o777, verbose=1, dry_run=0): # noqa: C901 # Detect a common bug -- name is None if not isinstance(name, str): raise DistutilsInternalError( - "mkpath: 'name' must be a string (got %r)" % (name,) + "mkpath: 'name' must be a string (got {!r})".format(name) ) # XXX what's the better way to handle verbosity? print as we create @@ -76,7 +76,7 @@ def mkpath(name, mode=0o777, verbose=1, dry_run=0): # noqa: C901 except OSError as exc: if not (exc.errno == errno.EEXIST and os.path.isdir(head)): raise DistutilsFileError( - "could not create '%s': %s" % (head, exc.args[-1]) + "could not create '{}': {}".format(head, exc.args[-1]) ) created_dirs.append(head) @@ -144,7 +144,7 @@ def copy_tree( # noqa: C901 names = [] else: raise DistutilsFileError( - "error listing files in '%s': %s" % (src, e.strerror) + "error listing files in '{}': {}".format(src, e.strerror) ) if not dry_run: diff --git a/distutils/dist.py b/distutils/dist.py index b4535eb7..0406ab19 100644 --- a/distutils/dist.py +++ b/distutils/dist.py @@ -825,7 +825,7 @@ Common commands: (see '--help-commands' for more) return klass for pkgname in self.get_command_packages(): - module_name = "%s.%s" % (pkgname, command) + module_name = "{}.{}".format(pkgname, command) klass_name = command try: @@ -893,7 +893,7 @@ Common commands: (see '--help-commands' for more) self.announce(" setting options for '%s' command:" % command_name) for (option, (source, value)) in option_dict.items(): if DEBUG: - self.announce(" %s = %s (from %s)" % (option, value, source)) + self.announce(" {} = {} (from {})".format(option, value, source)) try: bool_opts = [translate_longopt(o) for o in command_obj.boolean_options] except AttributeError: @@ -1159,7 +1159,7 @@ class DistributionMetadata: def maybe_write(header, val): if val: - file.write("{}: {}\n".format(header, val)) + file.write(f"{header}: {val}\n") # optional fields maybe_write("Summary", self.get_description()) @@ -1182,7 +1182,7 @@ class DistributionMetadata: def _write_list(self, file, name, values): values = values or [] for value in values: - file.write('%s: %s\n' % (name, value)) + file.write('{}: {}\n'.format(name, value)) # -- Metadata query methods ---------------------------------------- @@ -1193,7 +1193,7 @@ class DistributionMetadata: return self.version or "0.0.0" def get_fullname(self): - return "%s-%s" % (self.get_name(), self.get_version()) + return "{}-{}".format(self.get_name(), self.get_version()) def get_author(self): return self.author diff --git a/distutils/extension.py b/distutils/extension.py index dff2be9e..6b8575de 100644 --- a/distutils/extension.py +++ b/distutils/extension.py @@ -134,7 +134,7 @@ class Extension: warnings.warn(msg) def __repr__(self): - return '<%s.%s(%r) at %#x>' % ( + return '<{}.{}({!r}) at {:#x}>'.format( self.__class__.__module__, self.__class__.__qualname__, self.name, diff --git a/distutils/fancy_getopt.py b/distutils/fancy_getopt.py index 9ee06420..830f047e 100644 --- a/distutils/fancy_getopt.py +++ b/distutils/fancy_getopt.py @@ -22,7 +22,7 @@ longopt_pat = r'[a-zA-Z](?:[a-zA-Z0-9-]*)' longopt_re = re.compile(r'^%s$' % longopt_pat) # For recognizing "negative alias" options, eg. "quiet=!verbose" -neg_alias_re = re.compile("^(%s)=!(%s)$" % (longopt_pat, longopt_pat)) +neg_alias_re = re.compile("^({})=!({})$".format(longopt_pat, longopt_pat)) # This is used to translate long options to legitimate Python identifiers # (for use as attributes of some object). @@ -157,7 +157,7 @@ class FancyGetopt: else: # the option table is part of the code, so simply # assert that it is correct - raise ValueError("invalid option tuple: %r" % (option,)) + raise ValueError("invalid option tuple: {!r}".format(option)) # Type- and value-check the option names if not isinstance(long, str) or len(long) < 2: @@ -359,7 +359,7 @@ class FancyGetopt: # Case 2: we have a short option, so we have to include it # just after the long option else: - opt_names = "%s (-%s)" % (long, short) + opt_names = "{} (-{})".format(long, short) if text: lines.append(" --%-*s %s" % (max_opt, opt_names, text[0])) else: diff --git a/distutils/file_util.py b/distutils/file_util.py index 0662fe40..1f1e444b 100644 --- a/distutils/file_util.py +++ b/distutils/file_util.py @@ -26,27 +26,29 @@ def _copy_file_contents(src, dst, buffer_size=16 * 1024): # noqa: C901 try: fsrc = open(src, 'rb') except OSError as e: - raise DistutilsFileError("could not open '%s': %s" % (src, e.strerror)) + raise DistutilsFileError("could not open '{}': {}".format(src, e.strerror)) if os.path.exists(dst): try: os.unlink(dst) except OSError as e: raise DistutilsFileError( - "could not delete '%s': %s" % (dst, e.strerror) + "could not delete '{}': {}".format(dst, e.strerror) ) try: fdst = open(dst, 'wb') except OSError as e: - raise DistutilsFileError("could not create '%s': %s" % (dst, e.strerror)) + raise DistutilsFileError( + "could not create '{}': {}".format(dst, e.strerror) + ) while True: try: buf = fsrc.read(buffer_size) except OSError as e: raise DistutilsFileError( - "could not read from '%s': %s" % (src, e.strerror) + "could not read from '{}': {}".format(src, e.strerror) ) if not buf: @@ -56,7 +58,7 @@ def _copy_file_contents(src, dst, buffer_size=16 * 1024): # noqa: C901 fdst.write(buf) except OSError as e: raise DistutilsFileError( - "could not write to '%s': %s" % (dst, e.strerror) + "could not write to '{}': {}".format(dst, e.strerror) ) finally: if fdst: @@ -198,12 +200,12 @@ def move_file(src, dst, verbose=1, dry_run=0): # noqa: C901 dst = os.path.join(dst, basename(src)) elif exists(dst): raise DistutilsFileError( - "can't move '%s': destination '%s' already exists" % (src, dst) + "can't move '{}': destination '{}' already exists".format(src, dst) ) if not isdir(dirname(dst)): raise DistutilsFileError( - "can't move '%s': destination '%s' not a valid path" % (src, dst) + "can't move '{}': destination '{}' not a valid path".format(src, dst) ) copy_it = False @@ -214,7 +216,9 @@ def move_file(src, dst, verbose=1, dry_run=0): # noqa: C901 if num == errno.EXDEV: copy_it = True else: - raise DistutilsFileError("couldn't move '%s' to '%s': %s" % (src, dst, msg)) + raise DistutilsFileError( + "couldn't move '{}' to '{}': {}".format(src, dst, msg) + ) if copy_it: copy_file(src, dst, verbose=verbose) diff --git a/distutils/filelist.py b/distutils/filelist.py index 4396d9de..987931a9 100644 --- a/distutils/filelist.py +++ b/distutils/filelist.py @@ -159,7 +159,7 @@ class FileList: ) elif action == 'recursive-include': - self.debug_print("recursive-include %s %s" % (dir, ' '.join(patterns))) + self.debug_print("recursive-include {} {}".format(dir, ' '.join(patterns))) for pattern in patterns: if not self.include_pattern(pattern, prefix=dir): msg = ( @@ -168,7 +168,7 @@ class FileList: log.warn(msg, pattern, dir) elif action == 'recursive-exclude': - self.debug_print("recursive-exclude %s %s" % (dir, ' '.join(patterns))) + self.debug_print("recursive-exclude {} {}".format(dir, ' '.join(patterns))) for pattern in patterns: if not self.exclude_pattern(pattern, prefix=dir): log.warn( @@ -363,9 +363,9 @@ def translate_pattern(pattern, anchor=1, prefix=None, is_regex=0): if os.sep == '\\': sep = r'\\' pattern_re = pattern_re[len(start) : len(pattern_re) - len(end)] - pattern_re = r'%s\A%s%s.*%s%s' % (start, prefix_re, sep, pattern_re, end) + pattern_re = r'{}\A{}{}.*{}{}'.format(start, prefix_re, sep, pattern_re, end) else: # no prefix -- respect anchor flag if anchor: - pattern_re = r'%s\A%s' % (start, pattern_re[len(start) :]) + pattern_re = r'{}\A{}'.format(start, pattern_re[len(start) :]) return re.compile(pattern_re) diff --git a/distutils/msvc9compiler.py b/distutils/msvc9compiler.py deleted file mode 100644 index 276e1379..00000000 --- a/distutils/msvc9compiler.py +++ /dev/null @@ -1,820 +0,0 @@ -"""distutils.msvc9compiler - -Contains MSVCCompiler, an implementation of the abstract CCompiler class -for the Microsoft Visual Studio 2008. - -The module is compatible with VS 2005 and VS 2008. You can find legacy support -for older versions of VS in distutils.msvccompiler. -""" - -# Written by Perry Stoll -# hacked by Robin Becker and Thomas Heller to do a better job of -# finding DevStudio (through the registry) -# ported to VS2005 and VS 2008 by Christian Heimes - -import os -import subprocess -import sys -import re - -from distutils.errors import ( - DistutilsExecError, - DistutilsPlatformError, - CompileError, - LibError, - LinkError, -) -from distutils.ccompiler import CCompiler, gen_lib_options -from distutils import log -from distutils.util import get_platform - -import winreg - -RegOpenKeyEx = winreg.OpenKeyEx -RegEnumKey = winreg.EnumKey -RegEnumValue = winreg.EnumValue -RegError = winreg.error - -HKEYS = ( - winreg.HKEY_USERS, - winreg.HKEY_CURRENT_USER, - winreg.HKEY_LOCAL_MACHINE, - winreg.HKEY_CLASSES_ROOT, -) - -NATIVE_WIN64 = sys.platform == 'win32' and sys.maxsize > 2**32 -if NATIVE_WIN64: - # Visual C++ is a 32-bit application, so we need to look in - # the corresponding registry branch, if we're running a - # 64-bit Python on Win64 - VS_BASE = r"Software\Wow6432Node\Microsoft\VisualStudio\%0.1f" - WINSDK_BASE = r"Software\Wow6432Node\Microsoft\Microsoft SDKs\Windows" - NET_BASE = r"Software\Wow6432Node\Microsoft\.NETFramework" -else: - VS_BASE = r"Software\Microsoft\VisualStudio\%0.1f" - WINSDK_BASE = r"Software\Microsoft\Microsoft SDKs\Windows" - NET_BASE = r"Software\Microsoft\.NETFramework" - -# A map keyed by get_platform() return values to values accepted by -# 'vcvarsall.bat'. Note a cross-compile may combine these (eg, 'x86_amd64' is -# the param to cross-compile on x86 targeting amd64.) -PLAT_TO_VCVARS = { - 'win32': 'x86', - 'win-amd64': 'amd64', -} - - -class Reg: - """Helper class to read values from the registry""" - - def get_value(cls, path, key): - for base in HKEYS: - d = cls.read_values(base, path) - if d and key in d: - return d[key] - raise KeyError(key) - - get_value = classmethod(get_value) - - def read_keys(cls, base, key): - """Return list of registry keys.""" - try: - handle = RegOpenKeyEx(base, key) - except RegError: - return None - L = [] - i = 0 - while True: - try: - k = RegEnumKey(handle, i) - except RegError: - break - L.append(k) - i += 1 - return L - - read_keys = classmethod(read_keys) - - def read_values(cls, base, key): - """Return dict of registry keys and values. - - All names are converted to lowercase. - """ - try: - handle = RegOpenKeyEx(base, key) - except RegError: - return None - d = {} - i = 0 - while True: - try: - name, value, type = RegEnumValue(handle, i) - except RegError: - break - name = name.lower() - d[cls.convert_mbcs(name)] = cls.convert_mbcs(value) - i += 1 - return d - - read_values = classmethod(read_values) - - def convert_mbcs(s): - dec = getattr(s, "decode", None) - if dec is not None: - try: - s = dec("mbcs") - except UnicodeError: - pass - return s - - convert_mbcs = staticmethod(convert_mbcs) - - -class MacroExpander: - def __init__(self, version): - self.macros = {} - self.vsbase = VS_BASE % version - self.load_macros(version) - - def set_macro(self, macro, path, key): - self.macros["$(%s)" % macro] = Reg.get_value(path, key) - - def load_macros(self, version): - self.set_macro("VCInstallDir", self.vsbase + r"\Setup\VC", "productdir") - self.set_macro("VSInstallDir", self.vsbase + r"\Setup\VS", "productdir") - self.set_macro("FrameworkDir", NET_BASE, "installroot") - try: - if version >= 8.0: - self.set_macro("FrameworkSDKDir", NET_BASE, "sdkinstallrootv2.0") - else: - raise KeyError("sdkinstallrootv2.0") - except KeyError: - raise DistutilsPlatformError( - """Python was built with Visual Studio 2008; -extensions must be built with a compiler than can generate compatible binaries. -Visual Studio 2008 was not found on this system. If you have Cygwin installed, -you can try compiling with MingW32, by passing "-c mingw32" to setup.py.""" - ) - - if version >= 9.0: - self.set_macro("FrameworkVersion", self.vsbase, "clr version") - self.set_macro("WindowsSdkDir", WINSDK_BASE, "currentinstallfolder") - else: - p = r"Software\Microsoft\NET Framework Setup\Product" - for base in HKEYS: - try: - h = RegOpenKeyEx(base, p) - except RegError: - continue - key = RegEnumKey(h, 0) - d = Reg.get_value(base, r"%s\%s" % (p, key)) - self.macros["$(FrameworkVersion)"] = d["version"] - - def sub(self, s): - for k, v in self.macros.items(): - s = s.replace(k, v) - return s - - -def get_build_version(): - """Return the version of MSVC that was used to build Python. - - For Python 2.3 and up, the version number is included in - sys.version. For earlier versions, assume the compiler is MSVC 6. - """ - prefix = "MSC v." - i = sys.version.find(prefix) - if i == -1: - return 6 - i = i + len(prefix) - s, rest = sys.version[i:].split(" ", 1) - majorVersion = int(s[:-2]) - 6 - if majorVersion >= 13: - # v13 was skipped and should be v14 - majorVersion += 1 - minorVersion = int(s[2:3]) / 10.0 - # I don't think paths are affected by minor version in version 6 - if majorVersion == 6: - minorVersion = 0 - if majorVersion >= 6: - return majorVersion + minorVersion - # else we don't know what version of the compiler this is - return None - - -def normalize_and_reduce_paths(paths): - """Return a list of normalized paths with duplicates removed. - - The current order of paths is maintained. - """ - # Paths are normalized so things like: /a and /a/ aren't both preserved. - reduced_paths = [] - for p in paths: - np = os.path.normpath(p) - # XXX(nnorwitz): O(n**2), if reduced_paths gets long perhaps use a set. - if np not in reduced_paths: - reduced_paths.append(np) - return reduced_paths - - -def removeDuplicates(variable): - """Remove duplicate values of an environment variable.""" - oldList = variable.split(os.pathsep) - newList = [] - for i in oldList: - if i not in newList: - newList.append(i) - newVariable = os.pathsep.join(newList) - return newVariable - - -def find_vcvarsall(version): - """Find the vcvarsall.bat file - - At first it tries to find the productdir of VS 2008 in the registry. If - that fails it falls back to the VS90COMNTOOLS env var. - """ - vsbase = VS_BASE % version - try: - productdir = Reg.get_value(r"%s\Setup\VC" % vsbase, "productdir") - except KeyError: - log.debug("Unable to find productdir in registry") - productdir = None - - if not productdir or not os.path.isdir(productdir): - toolskey = "VS%0.f0COMNTOOLS" % version - toolsdir = os.environ.get(toolskey, None) - - if toolsdir and os.path.isdir(toolsdir): - productdir = os.path.join(toolsdir, os.pardir, os.pardir, "VC") - productdir = os.path.abspath(productdir) - if not os.path.isdir(productdir): - log.debug("%s is not a valid directory" % productdir) - return None - else: - log.debug("Env var %s is not set or invalid" % toolskey) - if not productdir: - log.debug("No productdir found") - return None - vcvarsall = os.path.join(productdir, "vcvarsall.bat") - if os.path.isfile(vcvarsall): - return vcvarsall - log.debug("Unable to find vcvarsall.bat") - return None - - -def query_vcvarsall(version, arch="x86"): - """Launch vcvarsall.bat and read the settings from its environment""" - vcvarsall = find_vcvarsall(version) - interesting = {"include", "lib", "libpath", "path"} - result = {} - - if vcvarsall is None: - raise DistutilsPlatformError("Unable to find vcvarsall.bat") - log.debug("Calling 'vcvarsall.bat %s' (version=%s)", arch, version) - popen = subprocess.Popen( - '"%s" %s & set' % (vcvarsall, arch), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - try: - stdout, stderr = popen.communicate() - if popen.wait() != 0: - raise DistutilsPlatformError(stderr.decode("mbcs")) - - stdout = stdout.decode("mbcs") - for line in stdout.split("\n"): - line = Reg.convert_mbcs(line) - if '=' not in line: - continue - line = line.strip() - key, value = line.split('=', 1) - key = key.lower() - if key in interesting: - if value.endswith(os.pathsep): - value = value[:-1] - result[key] = removeDuplicates(value) - - finally: - popen.stdout.close() - popen.stderr.close() - - if len(result) != len(interesting): - raise ValueError(str(list(result.keys()))) - - return result - - -# More globals -VERSION = get_build_version() -# MACROS = MacroExpander(VERSION) - - -class MSVCCompiler(CCompiler): - """Concrete class that implements an interface to Microsoft Visual C++, - as defined by the CCompiler abstract class.""" - - compiler_type = 'msvc' - - # Just set this so CCompiler's constructor doesn't barf. We currently - # don't use the 'set_executables()' bureaucracy provided by CCompiler, - # as it really isn't necessary for this sort of single-compiler class. - # Would be nice to have a consistent interface with UnixCCompiler, - # though, so it's worth thinking about. - executables = {} - - # Private class data (need to distinguish C from C++ source for compiler) - _c_extensions = ['.c'] - _cpp_extensions = ['.cc', '.cpp', '.cxx'] - _rc_extensions = ['.rc'] - _mc_extensions = ['.mc'] - - # Needed for the filename generation methods provided by the - # base class, CCompiler. - src_extensions = _c_extensions + _cpp_extensions + _rc_extensions + _mc_extensions - res_extension = '.res' - obj_extension = '.obj' - static_lib_extension = '.lib' - shared_lib_extension = '.dll' - static_lib_format = shared_lib_format = '%s%s' - exe_extension = '.exe' - - def __init__(self, verbose=0, dry_run=0, force=0): - super().__init__(verbose, dry_run, force) - self.__version = VERSION - self.__root = r"Software\Microsoft\VisualStudio" - # self.__macros = MACROS - self.__paths = [] - # target platform (.plat_name is consistent with 'bdist') - self.plat_name = None - self.__arch = None # deprecated name - self.initialized = False - - def initialize(self, plat_name=None): # noqa: C901 - # multi-init means we would need to check platform same each time... - assert not self.initialized, "don't init multiple times" - if self.__version < 8.0: - raise DistutilsPlatformError( - "VC %0.1f is not supported by this module" % self.__version - ) - if plat_name is None: - plat_name = get_platform() - # sanity check for platforms to prevent obscure errors later. - ok_plats = 'win32', 'win-amd64' - if plat_name not in ok_plats: - raise DistutilsPlatformError("--plat-name must be one of %s" % (ok_plats,)) - - if ( - "DISTUTILS_USE_SDK" in os.environ - and "MSSdk" in os.environ - and self.find_exe("cl.exe") - ): - # Assume that the SDK set up everything alright; don't try to be - # smarter - self.cc = "cl.exe" - self.linker = "link.exe" - self.lib = "lib.exe" - self.rc = "rc.exe" - self.mc = "mc.exe" - else: - # On x86, 'vcvars32.bat amd64' creates an env that doesn't work; - # to cross compile, you use 'x86_amd64'. - # On AMD64, 'vcvars32.bat amd64' is a native build env; to cross - # compile use 'x86' (ie, it runs the x86 compiler directly) - if plat_name == get_platform() or plat_name == 'win32': - # native build or cross-compile to win32 - plat_spec = PLAT_TO_VCVARS[plat_name] - else: - # cross compile from win32 -> some 64bit - plat_spec = ( - PLAT_TO_VCVARS[get_platform()] + '_' + PLAT_TO_VCVARS[plat_name] - ) - - vc_env = query_vcvarsall(VERSION, plat_spec) - - self.__paths = vc_env['path'].split(os.pathsep) - os.environ['lib'] = vc_env['lib'] - os.environ['include'] = vc_env['include'] - - if len(self.__paths) == 0: - raise DistutilsPlatformError( - "Python was built with %s, " - "and extensions need to be built with the same " - "version of the compiler, but it isn't installed." % self.__product - ) - - self.cc = self.find_exe("cl.exe") - self.linker = self.find_exe("link.exe") - self.lib = self.find_exe("lib.exe") - self.rc = self.find_exe("rc.exe") # resource compiler - self.mc = self.find_exe("mc.exe") # message compiler - # self.set_path_env_var('lib') - # self.set_path_env_var('include') - - # extend the MSVC path with the current path - try: - for p in os.environ['path'].split(';'): - self.__paths.append(p) - except KeyError: - pass - self.__paths = normalize_and_reduce_paths(self.__paths) - os.environ['path'] = ";".join(self.__paths) - - self.preprocess_options = None - if self.__arch == "x86": - self.compile_options = ['/nologo', '/O2', '/MD', '/W3', '/DNDEBUG'] - self.compile_options_debug = [ - '/nologo', - '/Od', - '/MDd', - '/W3', - '/Z7', - '/D_DEBUG', - ] - else: - # Win64 - self.compile_options = ['/nologo', '/O2', '/MD', '/W3', '/GS-', '/DNDEBUG'] - self.compile_options_debug = [ - '/nologo', - '/Od', - '/MDd', - '/W3', - '/GS-', - '/Z7', - '/D_DEBUG', - ] - - self.ldflags_shared = ['/DLL', '/nologo', '/INCREMENTAL:NO'] - if self.__version >= 7: - self.ldflags_shared_debug = ['/DLL', '/nologo', '/INCREMENTAL:no', '/DEBUG'] - self.ldflags_static = ['/nologo'] - - self.initialized = True - - # -- Worker methods ------------------------------------------------ - - def object_filenames(self, source_filenames, strip_dir=0, output_dir=''): - # Copied from ccompiler.py, extended to return .res as 'object'-file - # for .rc input file - if output_dir is None: - output_dir = '' - obj_names = [] - for src_name in source_filenames: - (base, ext) = os.path.splitext(src_name) - base = os.path.splitdrive(base)[1] # Chop off the drive - base = base[os.path.isabs(base) :] # If abs, chop off leading / - if ext not in self.src_extensions: - # Better to raise an exception instead of silently continuing - # and later complain about sources and targets having - # different lengths - raise CompileError("Don't know how to compile %s" % src_name) - if strip_dir: - base = os.path.basename(base) - if ext in self._rc_extensions: - obj_names.append(os.path.join(output_dir, base + self.res_extension)) - elif ext in self._mc_extensions: - obj_names.append(os.path.join(output_dir, base + self.res_extension)) - else: - obj_names.append(os.path.join(output_dir, base + self.obj_extension)) - return obj_names - - def compile( # noqa: C901 - self, - sources, - output_dir=None, - macros=None, - include_dirs=None, - debug=0, - extra_preargs=None, - extra_postargs=None, - depends=None, - ): - - if not self.initialized: - self.initialize() - compile_info = self._setup_compile( - output_dir, macros, include_dirs, sources, depends, extra_postargs - ) - macros, objects, extra_postargs, pp_opts, build = compile_info - - compile_opts = extra_preargs or [] - compile_opts.append('/c') - if debug: - compile_opts.extend(self.compile_options_debug) - else: - compile_opts.extend(self.compile_options) - - for obj in objects: - try: - src, ext = build[obj] - except KeyError: - continue - if debug: - # pass the full pathname to MSVC in debug mode, - # this allows the debugger to find the source file - # without asking the user to browse for it - src = os.path.abspath(src) - - if ext in self._c_extensions: - input_opt = "/Tc" + src - elif ext in self._cpp_extensions: - input_opt = "/Tp" + src - elif ext in self._rc_extensions: - # compile .RC to .RES file - input_opt = src - output_opt = "/fo" + obj - try: - self.spawn([self.rc] + pp_opts + [output_opt] + [input_opt]) - except DistutilsExecError as msg: - raise CompileError(msg) - continue - elif ext in self._mc_extensions: - # Compile .MC to .RC file to .RES file. - # * '-h dir' specifies the directory for the - # generated include file - # * '-r dir' specifies the target directory of the - # generated RC file and the binary message resource - # it includes - # - # For now (since there are no options to change this), - # we use the source-directory for the include file and - # the build directory for the RC file and message - # resources. This works at least for win32all. - h_dir = os.path.dirname(src) - rc_dir = os.path.dirname(obj) - try: - # first compile .MC to .RC and .H file - self.spawn([self.mc] + ['-h', h_dir, '-r', rc_dir] + [src]) - base, _ = os.path.splitext(os.path.basename(src)) - rc_file = os.path.join(rc_dir, base + '.rc') - # then compile .RC to .RES file - self.spawn([self.rc] + ["/fo" + obj] + [rc_file]) - - except DistutilsExecError as msg: - raise CompileError(msg) - continue - else: - # how to handle this file? - raise CompileError("Don't know how to compile %s to %s" % (src, obj)) - - output_opt = "/Fo" + obj - try: - self.spawn( - [self.cc] - + compile_opts - + pp_opts - + [input_opt, output_opt] - + extra_postargs - ) - except DistutilsExecError as msg: - raise CompileError(msg) - - return objects - - def create_static_lib( - self, objects, output_libname, output_dir=None, debug=0, target_lang=None - ): - - if not self.initialized: - self.initialize() - (objects, output_dir) = self._fix_object_args(objects, output_dir) - output_filename = self.library_filename(output_libname, output_dir=output_dir) - - if self._need_link(objects, output_filename): - lib_args = objects + ['/OUT:' + output_filename] - if debug: - pass # XXX what goes here? - try: - self.spawn([self.lib] + lib_args) - except DistutilsExecError as msg: - raise LibError(msg) - else: - log.debug("skipping %s (up-to-date)", output_filename) - - def link( # noqa: C901 - self, - target_desc, - objects, - output_filename, - output_dir=None, - libraries=None, - library_dirs=None, - runtime_library_dirs=None, - export_symbols=None, - debug=0, - extra_preargs=None, - extra_postargs=None, - build_temp=None, - target_lang=None, - ): - - if not self.initialized: - self.initialize() - (objects, output_dir) = self._fix_object_args(objects, output_dir) - fixed_args = self._fix_lib_args(libraries, library_dirs, runtime_library_dirs) - (libraries, library_dirs, runtime_library_dirs) = fixed_args - - if runtime_library_dirs: - self.warn( - "I don't know what to do with 'runtime_library_dirs': " - + str(runtime_library_dirs) - ) - - lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, libraries) - if output_dir is not None: - output_filename = os.path.join(output_dir, output_filename) - - if self._need_link(objects, output_filename): - if target_desc == CCompiler.EXECUTABLE: - if debug: - ldflags = self.ldflags_shared_debug[1:] - else: - ldflags = self.ldflags_shared[1:] - else: - if debug: - ldflags = self.ldflags_shared_debug - else: - ldflags = self.ldflags_shared - - export_opts = [] - for sym in export_symbols or []: - export_opts.append("/EXPORT:" + sym) - - ld_args = ( - ldflags + lib_opts + export_opts + objects + ['/OUT:' + output_filename] - ) - - # The MSVC linker generates .lib and .exp files, which cannot be - # suppressed by any linker switches. The .lib files may even be - # needed! Make sure they are generated in the temporary build - # directory. Since they have different names for debug and release - # builds, they can go into the same directory. - build_temp = os.path.dirname(objects[0]) - if export_symbols is not None: - (dll_name, dll_ext) = os.path.splitext( - os.path.basename(output_filename) - ) - implib_file = os.path.join(build_temp, self.library_filename(dll_name)) - ld_args.append('/IMPLIB:' + implib_file) - - self.manifest_setup_ldargs(output_filename, build_temp, ld_args) - - if extra_preargs: - ld_args[:0] = extra_preargs - if extra_postargs: - ld_args.extend(extra_postargs) - - self.mkpath(os.path.dirname(output_filename)) - try: - self.spawn([self.linker] + ld_args) - except DistutilsExecError as msg: - raise LinkError(msg) - - # embed the manifest - # XXX - this is somewhat fragile - if mt.exe fails, distutils - # will still consider the DLL up-to-date, but it will not have a - # manifest. Maybe we should link to a temp file? OTOH, that - # implies a build environment error that shouldn't go undetected. - mfinfo = self.manifest_get_embed_info(target_desc, ld_args) - if mfinfo is not None: - mffilename, mfid = mfinfo - out_arg = '-outputresource:%s;%s' % (output_filename, mfid) - try: - self.spawn(['mt.exe', '-nologo', '-manifest', mffilename, out_arg]) - except DistutilsExecError as msg: - raise LinkError(msg) - else: - log.debug("skipping %s (up-to-date)", output_filename) - - def manifest_setup_ldargs(self, output_filename, build_temp, ld_args): - # If we need a manifest at all, an embedded manifest is recommended. - # See MSDN article titled - # "How to: Embed a Manifest Inside a C/C++ Application" - # (currently at http://msdn2.microsoft.com/en-us/library/ms235591(VS.80).aspx) - # Ask the linker to generate the manifest in the temp dir, so - # we can check it, and possibly embed it, later. - temp_manifest = os.path.join( - build_temp, os.path.basename(output_filename) + ".manifest" - ) - ld_args.append('/MANIFESTFILE:' + temp_manifest) - - def manifest_get_embed_info(self, target_desc, ld_args): - # If a manifest should be embedded, return a tuple of - # (manifest_filename, resource_id). Returns None if no manifest - # should be embedded. See http://bugs.python.org/issue7833 for why - # we want to avoid any manifest for extension modules if we can) - for arg in ld_args: - if arg.startswith("/MANIFESTFILE:"): - temp_manifest = arg.split(":", 1)[1] - break - else: - # no /MANIFESTFILE so nothing to do. - return None - if target_desc == CCompiler.EXECUTABLE: - # by default, executables always get the manifest with the - # CRT referenced. - mfid = 1 - else: - # Extension modules try and avoid any manifest if possible. - mfid = 2 - temp_manifest = self._remove_visual_c_ref(temp_manifest) - if temp_manifest is None: - return None - return temp_manifest, mfid - - def _remove_visual_c_ref(self, manifest_file): - try: - # Remove references to the Visual C runtime, so they will - # fall through to the Visual C dependency of Python.exe. - # This way, when installed for a restricted user (e.g. - # runtimes are not in WinSxS folder, but in Python's own - # folder), the runtimes do not need to be in every folder - # with .pyd's. - # Returns either the filename of the modified manifest or - # None if no manifest should be embedded. - manifest_f = open(manifest_file) - try: - manifest_buf = manifest_f.read() - finally: - manifest_f.close() - pattern = re.compile( - r"""<assemblyIdentity.*?name=("|')Microsoft\.""" - r"""VC\d{2}\.CRT("|').*?(/>|</assemblyIdentity>)""", - re.DOTALL, - ) - manifest_buf = re.sub(pattern, "", manifest_buf) - pattern = r"<dependentAssembly>\s*</dependentAssembly>" - manifest_buf = re.sub(pattern, "", manifest_buf) - # Now see if any other assemblies are referenced - if not, we - # don't want a manifest embedded. - pattern = re.compile( - r"""<assemblyIdentity.*?name=(?:"|')(.+?)(?:"|')""" - r""".*?(?:/>|</assemblyIdentity>)""", - re.DOTALL, - ) - if re.search(pattern, manifest_buf) is None: - return None - - manifest_f = open(manifest_file, 'w') - try: - manifest_f.write(manifest_buf) - return manifest_file - finally: - manifest_f.close() - except OSError: - pass - - # -- Miscellaneous methods ----------------------------------------- - # These are all used by the 'gen_lib_options() function, in - # ccompiler.py. - - def library_dir_option(self, dir): - return "/LIBPATH:" + dir - - def runtime_library_dir_option(self, dir): - raise DistutilsPlatformError( - "don't know how to set runtime library search path for MSVC++" - ) - - def library_option(self, lib): - return self.library_filename(lib) - - def find_library_file(self, dirs, lib, debug=0): - # Prefer a debugging library if found (and requested), but deal - # with it if we don't have one. - if debug: - try_names = [lib + "_d", lib] - else: - try_names = [lib] - for dir in dirs: - for name in try_names: - libfile = os.path.join(dir, self.library_filename(name)) - if os.path.exists(libfile): - return libfile - else: - # Oops, didn't find it in *any* of 'dirs' - return None - - # Helper methods for using the MSVC registry settings - - def find_exe(self, exe): - """Return path to an MSVC executable program. - - Tries to find the program in several places: first, one of the - MSVC program search paths from the registry; next, the directories - in the PATH environment variable. If any of those work, return an - absolute path that is known to exist. If none of them work, just - return the original program name, 'exe'. - """ - for p in self.__paths: - fn = os.path.join(os.path.abspath(p), exe) - if os.path.isfile(fn): - return fn - - # didn't find it; try existing path - for p in os.environ['Path'].split(';'): - fn = os.path.join(os.path.abspath(p), exe) - if os.path.isfile(fn): - return fn - - return exe diff --git a/distutils/msvccompiler.py b/distutils/msvccompiler.py deleted file mode 100644 index 8e509ac5..00000000 --- a/distutils/msvccompiler.py +++ /dev/null @@ -1,684 +0,0 @@ -"""distutils.msvccompiler - -Contains MSVCCompiler, an implementation of the abstract CCompiler class -for the Microsoft Visual Studio. -""" - -# Written by Perry Stoll -# hacked by Robin Becker and Thomas Heller to do a better job of -# finding DevStudio (through the registry) - -import sys -import os -from distutils.errors import ( - DistutilsExecError, - DistutilsPlatformError, - CompileError, - LibError, - LinkError, -) -from distutils.ccompiler import CCompiler, gen_lib_options -from distutils import log - -_can_read_reg = False -try: - import winreg - - _can_read_reg = True - hkey_mod = winreg - - RegOpenKeyEx = winreg.OpenKeyEx - RegEnumKey = winreg.EnumKey - RegEnumValue = winreg.EnumValue - RegError = winreg.error - -except ImportError: - try: - import win32api - import win32con - - _can_read_reg = True - hkey_mod = win32con - - RegOpenKeyEx = win32api.RegOpenKeyEx - RegEnumKey = win32api.RegEnumKey - RegEnumValue = win32api.RegEnumValue - RegError = win32api.error - except ImportError: - log.info( - "Warning: Can't read registry to find the " - "necessary compiler setting\n" - "Make sure that Python modules winreg, " - "win32api or win32con are installed." - ) - pass - -if _can_read_reg: - HKEYS = ( - hkey_mod.HKEY_USERS, - hkey_mod.HKEY_CURRENT_USER, - hkey_mod.HKEY_LOCAL_MACHINE, - hkey_mod.HKEY_CLASSES_ROOT, - ) - - -def read_keys(base, key): - """Return list of registry keys.""" - try: - handle = RegOpenKeyEx(base, key) - except RegError: - return None - L = [] - i = 0 - while True: - try: - k = RegEnumKey(handle, i) - except RegError: - break - L.append(k) - i += 1 - return L - - -def read_values(base, key): - """Return dict of registry keys and values. - - All names are converted to lowercase. - """ - try: - handle = RegOpenKeyEx(base, key) - except RegError: - return None - d = {} - i = 0 - while True: - try: - name, value, type = RegEnumValue(handle, i) - except RegError: - break - name = name.lower() - d[convert_mbcs(name)] = convert_mbcs(value) - i += 1 - return d - - -def convert_mbcs(s): - dec = getattr(s, "decode", None) - if dec is not None: - try: - s = dec("mbcs") - except UnicodeError: - pass - return s - - -class MacroExpander: - def __init__(self, version): - self.macros = {} - self.load_macros(version) - - def set_macro(self, macro, path, key): - for base in HKEYS: - d = read_values(base, path) - if d: - self.macros["$(%s)" % macro] = d[key] - break - - def load_macros(self, version): - vsbase = r"Software\Microsoft\VisualStudio\%0.1f" % version - self.set_macro("VCInstallDir", vsbase + r"\Setup\VC", "productdir") - self.set_macro("VSInstallDir", vsbase + r"\Setup\VS", "productdir") - net = r"Software\Microsoft\.NETFramework" - self.set_macro("FrameworkDir", net, "installroot") - try: - if version > 7.0: - self.set_macro("FrameworkSDKDir", net, "sdkinstallrootv1.1") - else: - self.set_macro("FrameworkSDKDir", net, "sdkinstallroot") - except KeyError: - raise DistutilsPlatformError( - """Python was built with Visual Studio 2003; -extensions must be built with a compiler than can generate compatible binaries. -Visual Studio 2003 was not found on this system. If you have Cygwin installed, -you can try compiling with MingW32, by passing "-c mingw32" to setup.py.""" - ) - - p = r"Software\Microsoft\NET Framework Setup\Product" - for base in HKEYS: - try: - h = RegOpenKeyEx(base, p) - except RegError: - continue - key = RegEnumKey(h, 0) - d = read_values(base, r"%s\%s" % (p, key)) - self.macros["$(FrameworkVersion)"] = d["version"] - - def sub(self, s): - for k, v in self.macros.items(): - s = s.replace(k, v) - return s - - -def get_build_version(): - """Return the version of MSVC that was used to build Python. - - For Python 2.3 and up, the version number is included in - sys.version. For earlier versions, assume the compiler is MSVC 6. - """ - prefix = "MSC v." - i = sys.version.find(prefix) - if i == -1: - return 6 - i = i + len(prefix) - s, rest = sys.version[i:].split(" ", 1) - majorVersion = int(s[:-2]) - 6 - if majorVersion >= 13: - # v13 was skipped and should be v14 - majorVersion += 1 - minorVersion = int(s[2:3]) / 10.0 - # I don't think paths are affected by minor version in version 6 - if majorVersion == 6: - minorVersion = 0 - if majorVersion >= 6: - return majorVersion + minorVersion - # else we don't know what version of the compiler this is - return None - - -def get_build_architecture(): - """Return the processor architecture. - - Possible results are "Intel" or "AMD64". - """ - - prefix = " bit (" - i = sys.version.find(prefix) - if i == -1: - return "Intel" - j = sys.version.find(")", i) - return sys.version[i + len(prefix) : j] - - -def normalize_and_reduce_paths(paths): - """Return a list of normalized paths with duplicates removed. - - The current order of paths is maintained. - """ - # Paths are normalized so things like: /a and /a/ aren't both preserved. - reduced_paths = [] - for p in paths: - np = os.path.normpath(p) - # XXX(nnorwitz): O(n**2), if reduced_paths gets long perhaps use a set. - if np not in reduced_paths: - reduced_paths.append(np) - return reduced_paths - - -class MSVCCompiler(CCompiler): - """Concrete class that implements an interface to Microsoft Visual C++, - as defined by the CCompiler abstract class.""" - - compiler_type = 'msvc' - - # Just set this so CCompiler's constructor doesn't barf. We currently - # don't use the 'set_executables()' bureaucracy provided by CCompiler, - # as it really isn't necessary for this sort of single-compiler class. - # Would be nice to have a consistent interface with UnixCCompiler, - # though, so it's worth thinking about. - executables = {} - - # Private class data (need to distinguish C from C++ source for compiler) - _c_extensions = ['.c'] - _cpp_extensions = ['.cc', '.cpp', '.cxx'] - _rc_extensions = ['.rc'] - _mc_extensions = ['.mc'] - - # Needed for the filename generation methods provided by the - # base class, CCompiler. - src_extensions = _c_extensions + _cpp_extensions + _rc_extensions + _mc_extensions - res_extension = '.res' - obj_extension = '.obj' - static_lib_extension = '.lib' - shared_lib_extension = '.dll' - static_lib_format = shared_lib_format = '%s%s' - exe_extension = '.exe' - - def __init__(self, verbose=0, dry_run=0, force=0): - super().__init__(verbose, dry_run, force) - self.__version = get_build_version() - self.__arch = get_build_architecture() - if self.__arch == "Intel": - # x86 - if self.__version >= 7: - self.__root = r"Software\Microsoft\VisualStudio" - self.__macros = MacroExpander(self.__version) - else: - self.__root = r"Software\Microsoft\Devstudio" - self.__product = "Visual Studio version %s" % self.__version - else: - # Win64. Assume this was built with the platform SDK - self.__product = "Microsoft SDK compiler %s" % (self.__version + 6) - - self.initialized = False - - def initialize(self): - self.__paths = [] - if ( - "DISTUTILS_USE_SDK" in os.environ - and "MSSdk" in os.environ - and self.find_exe("cl.exe") - ): - # Assume that the SDK set up everything alright; don't try to be - # smarter - self.cc = "cl.exe" - self.linker = "link.exe" - self.lib = "lib.exe" - self.rc = "rc.exe" - self.mc = "mc.exe" - else: - self.__paths = self.get_msvc_paths("path") - - if len(self.__paths) == 0: - raise DistutilsPlatformError( - "Python was built with %s, " - "and extensions need to be built with the same " - "version of the compiler, but it isn't installed." % self.__product - ) - - self.cc = self.find_exe("cl.exe") - self.linker = self.find_exe("link.exe") - self.lib = self.find_exe("lib.exe") - self.rc = self.find_exe("rc.exe") # resource compiler - self.mc = self.find_exe("mc.exe") # message compiler - self.set_path_env_var('lib') - self.set_path_env_var('include') - - # extend the MSVC path with the current path - try: - for p in os.environ['path'].split(';'): - self.__paths.append(p) - except KeyError: - pass - self.__paths = normalize_and_reduce_paths(self.__paths) - os.environ['path'] = ";".join(self.__paths) - - self.preprocess_options = None - if self.__arch == "Intel": - self.compile_options = ['/nologo', '/O2', '/MD', '/W3', '/GX', '/DNDEBUG'] - self.compile_options_debug = [ - '/nologo', - '/Od', - '/MDd', - '/W3', - '/GX', - '/Z7', - '/D_DEBUG', - ] - else: - # Win64 - self.compile_options = ['/nologo', '/O2', '/MD', '/W3', '/GS-', '/DNDEBUG'] - self.compile_options_debug = [ - '/nologo', - '/Od', - '/MDd', - '/W3', - '/GS-', - '/Z7', - '/D_DEBUG', - ] - - self.ldflags_shared = ['/DLL', '/nologo', '/INCREMENTAL:NO'] - if self.__version >= 7: - self.ldflags_shared_debug = ['/DLL', '/nologo', '/INCREMENTAL:no', '/DEBUG'] - else: - self.ldflags_shared_debug = [ - '/DLL', - '/nologo', - '/INCREMENTAL:no', - '/pdb:None', - '/DEBUG', - ] - self.ldflags_static = ['/nologo'] - - self.initialized = True - - # -- Worker methods ------------------------------------------------ - - def object_filenames(self, source_filenames, strip_dir=0, output_dir=''): - # Copied from ccompiler.py, extended to return .res as 'object'-file - # for .rc input file - if output_dir is None: - output_dir = '' - obj_names = [] - for src_name in source_filenames: - (base, ext) = os.path.splitext(src_name) - base = os.path.splitdrive(base)[1] # Chop off the drive - base = base[os.path.isabs(base) :] # If abs, chop off leading / - if ext not in self.src_extensions: - # Better to raise an exception instead of silently continuing - # and later complain about sources and targets having - # different lengths - raise CompileError("Don't know how to compile %s" % src_name) - if strip_dir: - base = os.path.basename(base) - if ext in self._rc_extensions: - obj_names.append(os.path.join(output_dir, base + self.res_extension)) - elif ext in self._mc_extensions: - obj_names.append(os.path.join(output_dir, base + self.res_extension)) - else: - obj_names.append(os.path.join(output_dir, base + self.obj_extension)) - return obj_names - - def compile( # noqa: C901 - self, - sources, - output_dir=None, - macros=None, - include_dirs=None, - debug=0, - extra_preargs=None, - extra_postargs=None, - depends=None, - ): - - if not self.initialized: - self.initialize() - compile_info = self._setup_compile( - output_dir, macros, include_dirs, sources, depends, extra_postargs - ) - macros, objects, extra_postargs, pp_opts, build = compile_info - - compile_opts = extra_preargs or [] - compile_opts.append('/c') - if debug: - compile_opts.extend(self.compile_options_debug) - else: - compile_opts.extend(self.compile_options) - - for obj in objects: - try: - src, ext = build[obj] - except KeyError: - continue - if debug: - # pass the full pathname to MSVC in debug mode, - # this allows the debugger to find the source file - # without asking the user to browse for it - src = os.path.abspath(src) - - if ext in self._c_extensions: - input_opt = "/Tc" + src - elif ext in self._cpp_extensions: - input_opt = "/Tp" + src - elif ext in self._rc_extensions: - # compile .RC to .RES file - input_opt = src - output_opt = "/fo" + obj - try: - self.spawn([self.rc] + pp_opts + [output_opt] + [input_opt]) - except DistutilsExecError as msg: - raise CompileError(msg) - continue - elif ext in self._mc_extensions: - # Compile .MC to .RC file to .RES file. - # * '-h dir' specifies the directory for the - # generated include file - # * '-r dir' specifies the target directory of the - # generated RC file and the binary message resource - # it includes - # - # For now (since there are no options to change this), - # we use the source-directory for the include file and - # the build directory for the RC file and message - # resources. This works at least for win32all. - h_dir = os.path.dirname(src) - rc_dir = os.path.dirname(obj) - try: - # first compile .MC to .RC and .H file - self.spawn([self.mc] + ['-h', h_dir, '-r', rc_dir] + [src]) - base, _ = os.path.splitext(os.path.basename(src)) - rc_file = os.path.join(rc_dir, base + '.rc') - # then compile .RC to .RES file - self.spawn([self.rc] + ["/fo" + obj] + [rc_file]) - - except DistutilsExecError as msg: - raise CompileError(msg) - continue - else: - # how to handle this file? - raise CompileError("Don't know how to compile %s to %s" % (src, obj)) - - output_opt = "/Fo" + obj - try: - self.spawn( - [self.cc] - + compile_opts - + pp_opts - + [input_opt, output_opt] - + extra_postargs - ) - except DistutilsExecError as msg: - raise CompileError(msg) - - return objects - - def create_static_lib( - self, objects, output_libname, output_dir=None, debug=0, target_lang=None - ): - - if not self.initialized: - self.initialize() - (objects, output_dir) = self._fix_object_args(objects, output_dir) - output_filename = self.library_filename(output_libname, output_dir=output_dir) - - if self._need_link(objects, output_filename): - lib_args = objects + ['/OUT:' + output_filename] - if debug: - pass # XXX what goes here? - try: - self.spawn([self.lib] + lib_args) - except DistutilsExecError as msg: - raise LibError(msg) - else: - log.debug("skipping %s (up-to-date)", output_filename) - - def link( # noqa: C901 - self, - target_desc, - objects, - output_filename, - output_dir=None, - libraries=None, - library_dirs=None, - runtime_library_dirs=None, - export_symbols=None, - debug=0, - extra_preargs=None, - extra_postargs=None, - build_temp=None, - target_lang=None, - ): - - if not self.initialized: - self.initialize() - (objects, output_dir) = self._fix_object_args(objects, output_dir) - fixed_args = self._fix_lib_args(libraries, library_dirs, runtime_library_dirs) - (libraries, library_dirs, runtime_library_dirs) = fixed_args - - if runtime_library_dirs: - self.warn( - "I don't know what to do with 'runtime_library_dirs': " - + str(runtime_library_dirs) - ) - - lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, libraries) - if output_dir is not None: - output_filename = os.path.join(output_dir, output_filename) - - if self._need_link(objects, output_filename): - if target_desc == CCompiler.EXECUTABLE: - if debug: - ldflags = self.ldflags_shared_debug[1:] - else: - ldflags = self.ldflags_shared[1:] - else: - if debug: - ldflags = self.ldflags_shared_debug - else: - ldflags = self.ldflags_shared - - export_opts = [] - for sym in export_symbols or []: - export_opts.append("/EXPORT:" + sym) - - ld_args = ( - ldflags + lib_opts + export_opts + objects + ['/OUT:' + output_filename] - ) - - # The MSVC linker generates .lib and .exp files, which cannot be - # suppressed by any linker switches. The .lib files may even be - # needed! Make sure they are generated in the temporary build - # directory. Since they have different names for debug and release - # builds, they can go into the same directory. - if export_symbols is not None: - (dll_name, dll_ext) = os.path.splitext( - os.path.basename(output_filename) - ) - implib_file = os.path.join( - os.path.dirname(objects[0]), self.library_filename(dll_name) - ) - ld_args.append('/IMPLIB:' + implib_file) - - if extra_preargs: - ld_args[:0] = extra_preargs - if extra_postargs: - ld_args.extend(extra_postargs) - - self.mkpath(os.path.dirname(output_filename)) - try: - self.spawn([self.linker] + ld_args) - except DistutilsExecError as msg: - raise LinkError(msg) - - else: - log.debug("skipping %s (up-to-date)", output_filename) - - # -- Miscellaneous methods ----------------------------------------- - # These are all used by the 'gen_lib_options() function, in - # ccompiler.py. - - def library_dir_option(self, dir): - return "/LIBPATH:" + dir - - def runtime_library_dir_option(self, dir): - raise DistutilsPlatformError( - "don't know how to set runtime library search path for MSVC++" - ) - - def library_option(self, lib): - return self.library_filename(lib) - - def find_library_file(self, dirs, lib, debug=0): - # Prefer a debugging library if found (and requested), but deal - # with it if we don't have one. - if debug: - try_names = [lib + "_d", lib] - else: - try_names = [lib] - for dir in dirs: - for name in try_names: - libfile = os.path.join(dir, self.library_filename(name)) - if os.path.exists(libfile): - return libfile - else: - # Oops, didn't find it in *any* of 'dirs' - return None - - # Helper methods for using the MSVC registry settings - - def find_exe(self, exe): - """Return path to an MSVC executable program. - - Tries to find the program in several places: first, one of the - MSVC program search paths from the registry; next, the directories - in the PATH environment variable. If any of those work, return an - absolute path that is known to exist. If none of them work, just - return the original program name, 'exe'. - """ - for p in self.__paths: - fn = os.path.join(os.path.abspath(p), exe) - if os.path.isfile(fn): - return fn - - # didn't find it; try existing path - for p in os.environ['Path'].split(';'): - fn = os.path.join(os.path.abspath(p), exe) - if os.path.isfile(fn): - return fn - - return exe - - def get_msvc_paths(self, path, platform='x86'): - """Get a list of devstudio directories (include, lib or path). - - Return a list of strings. The list will be empty if unable to - access the registry or appropriate registry keys not found. - """ - if not _can_read_reg: - return [] - - path = path + " dirs" - if self.__version >= 7: - key = r"%s\%0.1f\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories" % ( - self.__root, - self.__version, - ) - else: - key = ( - r"%s\6.0\Build System\Components\Platforms" - r"\Win32 (%s)\Directories" % (self.__root, platform) - ) - - for base in HKEYS: - d = read_values(base, key) - if d: - if self.__version >= 7: - return self.__macros.sub(d[path]).split(";") - else: - return d[path].split(";") - # MSVC 6 seems to create the registry entries we need only when - # the GUI is run. - if self.__version == 6: - for base in HKEYS: - if read_values(base, r"%s\6.0" % self.__root) is not None: - self.warn( - "It seems you have Visual Studio 6 installed, " - "but the expected registry settings are not present.\n" - "You must at least run the Visual Studio GUI once " - "so that these entries are created." - ) - break - return [] - - def set_path_env_var(self, name): - """Set environment variable 'name' to an MSVC path type value. - - This is equivalent to a SET command prior to execution of spawned - commands. - """ - - if name == "lib": - p = self.get_msvc_paths("library") - else: - p = self.get_msvc_paths(name) - if p: - os.environ[name] = ';'.join(p) - - -if get_build_version() >= 8.0: - log.debug("Importing new compiler from distutils.msvc9compiler") - OldMSVCCompiler = MSVCCompiler - from distutils.msvc9compiler import MSVCCompiler - - # get_build_architecture not really relevant now we support cross-compile - from distutils.msvc9compiler import MacroExpander # noqa: F811 diff --git a/distutils/py38compat.py b/distutils/py38compat.py index e556b69e..59224e71 100644 --- a/distutils/py38compat.py +++ b/distutils/py38compat.py @@ -5,4 +5,4 @@ def aix_platform(osname, version, release): return _aix_support.aix_platform() except ImportError: pass - return "%s-%s.%s" % (osname, version, release) + return "{}-{}.{}".format(osname, version, release) diff --git a/distutils/spawn.py b/distutils/spawn.py index db9f08ee..b18ba9db 100644 --- a/distutils/spawn.py +++ b/distutils/spawn.py @@ -60,13 +60,15 @@ def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None): # noqa: C901 except OSError as exc: if not DEBUG: cmd = cmd[0] - raise DistutilsExecError("command %r failed: %s" % (cmd, exc.args[-1])) from exc + raise DistutilsExecError( + "command {!r} failed: {}".format(cmd, exc.args[-1]) + ) from exc if exitcode: if not DEBUG: cmd = cmd[0] raise DistutilsExecError( - "command %r failed with exit code %s" % (cmd, exitcode) + "command {!r} failed with exit code {}".format(cmd, exitcode) ) diff --git a/distutils/tests/py38compat.py b/distutils/tests/py38compat.py index 96f93a31..35ddbb5b 100644 --- a/distutils/tests/py38compat.py +++ b/distutils/tests/py38compat.py @@ -42,5 +42,17 @@ except (ModuleNotFoundError, ImportError): ) +try: + from test.support.import_helper import ( + DirsOnSysPath, + CleanImport, + ) +except (ModuleNotFoundError, ImportError): + from test.support import ( + DirsOnSysPath, + CleanImport, + ) + + if sys.version_info < (3, 9): requires_zlib = lambda: test.support.requires_zlib diff --git a/distutils/tests/support.py b/distutils/tests/support.py index 2e9d66b7..5203ed19 100644 --- a/distutils/tests/support.py +++ b/distutils/tests/support.py @@ -3,32 +3,17 @@ import os import sys import shutil import tempfile -import unittest import sysconfig +import itertools -from . import py38compat as os_helper +import pytest -from distutils import log from distutils.log import DEBUG, INFO, WARN, ERROR, FATAL from distutils.core import Distribution -class LoggingSilencer(object): - def setUp(self): - super().setUp() - self.threshold = log.set_threshold(log.FATAL) - # catching warnings - # when log will be replaced by logging - # we won't need such monkey-patch anymore - self._old_log = log.Log._log - log.Log._log = self._log - self.logs = [] - - def tearDown(self): - log.set_threshold(self.threshold) - log.Log._log = self._old_log - super().tearDown() - +@pytest.mark.usefixtures('distutils_logging_silencer') +class LoggingSilencer: def _log(self, level, msg, args): if level not in (DEBUG, INFO, WARN, ERROR, FATAL): raise ValueError('%s wrong log level' % str(level)) @@ -43,25 +28,11 @@ class LoggingSilencer(object): self.logs = [] -class TempdirManager(object): - """Mix-in class that handles temporary directories for test cases. - - This is intended to be used with unittest.TestCase. +@pytest.mark.usefixtures('distutils_managed_tempdir') +class TempdirManager: + """ + Mix-in class that handles temporary directories for test cases. """ - - def setUp(self): - super().setUp() - self.old_cwd = os.getcwd() - self.tempdirs = [] - - def tearDown(self): - # Restore working dir, for Solaris and derivatives, where rmdir() - # on the current directory fails. - os.chdir(self.old_cwd) - super().tearDown() - while self.tempdirs: - tmpdir = self.tempdirs.pop() - os_helper.rmtree(tmpdir) def mkdtemp(self): """Create a temporary directory that will be cleaned up. @@ -108,8 +79,7 @@ class DummyCommand: """Class to store options for retrieval via set_undefined_options().""" def __init__(self, **kwargs): - for kw, val in kwargs.items(): - setattr(self, kw, val) + vars(self).update(kwargs) def ensure_finalized(self): pass @@ -127,29 +97,12 @@ def copy_xxmodule_c(directory): If the source file can be found, it will be copied to *directory*. If not, the test will be skipped. Errors during copy are not caught. """ - filename = _get_xxmodule_path() - if filename is None: - raise unittest.SkipTest( - 'cannot find xxmodule.c (test must run in ' 'the python build dir)' - ) - shutil.copy(filename, directory) + shutil.copy(_get_xxmodule_path(), os.path.join(directory, 'xxmodule.c')) def _get_xxmodule_path(): - srcdir = sysconfig.get_config_var('srcdir') - candidates = [ - # use installed copy if available - os.path.join(os.path.dirname(__file__), 'xxmodule.c'), - # otherwise try using copy from build directory - os.path.join(srcdir, 'Modules', 'xxmodule.c'), - # srcdir mysteriously can be $srcdir/Lib/distutils/tests when - # this file is run from its parent directory, so walk up the - # tree to find the real srcdir - os.path.join(srcdir, '..', '..', '..', 'Modules', 'xxmodule.c'), - ] - for path in candidates: - if os.path.exists(path): - return path + source_name = 'xxmodule.c' if sys.version_info > (3, 9) else 'xxmodule-3.8.c' + return os.path.join(os.path.dirname(__file__), source_name) def fixup_build_ext(cmd): @@ -187,3 +140,17 @@ def fixup_build_ext(cmd): else: name, equals, value = runshared.partition('=') cmd.library_dirs = [d for d in value.split(os.pathsep) if d] + + +def combine_markers(cls): + """ + pytest will honor markers as found on the class, but when + markers are on multiple subclasses, only one appears. Use + this decorator to combine those markers. + """ + cls.pytestmark = [ + mark + for base in itertools.chain([cls], cls.__bases__) + for mark in getattr(base, 'pytestmark', []) + ] + return cls diff --git a/distutils/tests/test_archive_util.py b/distutils/tests/test_archive_util.py index 7a324c45..72aa9d7c 100644 --- a/distutils/tests/test_archive_util.py +++ b/distutils/tests/test_archive_util.py @@ -1,11 +1,12 @@ -# -*- coding: utf-8 -*- """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 @@ -17,7 +18,7 @@ from distutils.archive_util import ( make_archive, ARCHIVE_FORMATS, ) -from distutils.spawn import find_executable, spawn +from distutils.spawn import spawn from distutils.tests import support from test.support import patch from .unix_compat import require_unix_id, require_uid_0, grp, pwd, UID_0_SUPPORT @@ -26,24 +27,6 @@ from .py38compat import change_cwd from .py38compat import check_warnings -try: - import zipfile - - ZIP_SUPPORT = True -except ImportError: - ZIP_SUPPORT = find_executable('zip') - -try: - import bz2 -except ImportError: - bz2 = None - -try: - import lzma -except ImportError: - lzma = None - - def can_fs_encode(filename): """ Return True if the filename can be saved in the file system. @@ -57,9 +40,15 @@ def can_fs_encode(filename): return True -class ArchiveUtilTestCase( - support.TempdirManager, support.LoggingSilencer, unittest.TestCase -): +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'): # creating something to tar @@ -73,28 +62,24 @@ class ArchiveUtilTestCase( 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 @@ -104,10 +89,8 @@ class ArchiveUtilTestCase( 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) @@ -117,8 +100,8 @@ class ArchiveUtilTestCase( # check if the compressed tarball was created tarball = base_name + suffix - self.assertTrue(os.path.exists(tarball)) - self.assertEqual(self._tarinfo(tarball), self._created_files) + assert os.path.exists(tarball) + assert self._tarinfo(tarball) == self._created_files def _tarinfo(self, path): tar = tarfile.open(path) @@ -152,10 +135,7 @@ class ArchiveUtilTestCase( 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() @@ -169,7 +149,7 @@ class ArchiveUtilTestCase( # check if the compressed tarball was created tarball = base_name + '.tar.gz' - self.assertTrue(os.path.exists(tarball)) + assert os.path.exists(tarball) # now create another tarball using `tar` tarball2 = os.path.join(tmpdir, 'archive2.tar.gz') @@ -183,10 +163,10 @@ class ArchiveUtilTestCase( finally: os.chdir(old_dir) - self.assertTrue(os.path.exists(tarball2)) + assert os.path.exists(tarball2) # let's compare both tarballs - self.assertEqual(self._tarinfo(tarball), self._created_files) - self.assertEqual(self._tarinfo(tarball2), self._created_files) + assert self._tarinfo(tarball) == self._created_files + assert self._tarinfo(tarball2) == self._created_files # trying an uncompressed one base_name = os.path.join(tmpdir2, 'archive') @@ -197,7 +177,7 @@ class ArchiveUtilTestCase( finally: os.chdir(old_dir) tarball = base_name + '.tar' - self.assertTrue(os.path.exists(tarball)) + assert os.path.exists(tarball) # now for a dry_run base_name = os.path.join(tmpdir2, 'archive') @@ -208,16 +188,14 @@ class ArchiveUtilTestCase( finally: os.chdir(old_dir) tarball = base_name + '.tar' - self.assertTrue(os.path.exists(tarball)) + 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: @@ -227,8 +205,8 @@ class ArchiveUtilTestCase( finally: os.chdir(old_dir) tarball = base_name + '.tar.Z' - self.assertTrue(os.path.exists(tarball)) - self.assertEqual(len(w.warnings), 1) + assert os.path.exists(tarball) + assert len(w.warnings) == 1 # same test with dry_run os.remove(tarball) @@ -240,12 +218,12 @@ class ArchiveUtilTestCase( make_tarball(base_name, 'dist', compress='compress', dry_run=True) finally: os.chdir(old_dir) - self.assertFalse(os.path.exists(tarball)) - self.assertEqual(len(w.warnings), 1) + assert not os.path.exists(tarball) + 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') @@ -254,12 +232,12 @@ class ArchiveUtilTestCase( # check if the compressed tarball was created tarball = base_name + '.zip' - self.assertTrue(os.path.exists(tarball)) + assert os.path.exists(tarball) with zipfile.ZipFile(tarball) as zf: - self.assertEqual(sorted(zf.namelist()), self._zip_created_files) + 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 = [] @@ -279,23 +257,23 @@ class ArchiveUtilTestCase( make_zipfile(base_name, 'dist') tarball = base_name + '.zip' - self.assertEqual( - called, [((tarball, "w"), {'compression': zipfile.ZIP_STORED})] - ) - self.assertTrue(os.path.exists(tarball)) + assert called == [((tarball, "w"), {'compression': zipfile.ZIP_STORED})] + assert os.path.exists(tarball) with zipfile.ZipFile(tarball) as zf: - self.assertEqual(sorted(zf.namelist()), self._zip_created_files) + assert sorted(zf.namelist()) == self._zip_created_files def test_check_archive_formats(self): - self.assertEqual(check_archive_formats(['gztar', 'xxx', 'zip']), 'xxx') - self.assertIsNone( + assert check_archive_formats(['gztar', 'xxx', 'zip']) == 'xxx' + assert ( check_archive_formats(['gztar', 'bztar', 'xztar', 'ztar', 'tar', 'zip']) + is None ) def test_make_archive(self): tmpdir = self.mkdtemp() base_name = os.path.join(tmpdir, 'archive') - self.assertRaises(ValueError, make_archive, base_name, 'xxx') + with pytest.raises(ValueError): + make_archive(base_name, 'xxx') def test_make_archive_cwd(self): current_dir = os.getcwd() @@ -309,7 +287,7 @@ class ArchiveUtilTestCase( make_archive('xxx', 'xxx', root_dir=self.mkdtemp()) except Exception: pass - self.assertEqual(os.getcwd(), current_dir) + assert os.getcwd() == current_dir finally: del ARCHIVE_FORMATS['xxx'] @@ -317,36 +295,36 @@ class ArchiveUtilTestCase( base_dir = self._create_files() base_name = os.path.join(self.mkdtemp(), 'archive') res = make_archive(base_name, 'tar', base_dir, 'dist') - self.assertTrue(os.path.exists(res)) - self.assertEqual(os.path.basename(res), 'archive.tar') - self.assertEqual(self._tarinfo(res), self._created_files) + assert os.path.exists(res) + assert os.path.basename(res) == 'archive.tar' + assert self._tarinfo(res) == self._created_files @pytest.mark.usefixtures('needs_zlib') def test_make_archive_gztar(self): base_dir = self._create_files() base_name = os.path.join(self.mkdtemp(), 'archive') res = make_archive(base_name, 'gztar', base_dir, 'dist') - self.assertTrue(os.path.exists(res)) - self.assertEqual(os.path.basename(res), 'archive.tar.gz') - self.assertEqual(self._tarinfo(res), self._created_files) + assert os.path.exists(res) + 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') - self.assertTrue(os.path.exists(res)) - self.assertEqual(os.path.basename(res), 'archive.tar.bz2') - self.assertEqual(self._tarinfo(res), self._created_files) + assert os.path.exists(res) + 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') - self.assertTrue(os.path.exists(res)) - self.assertEqual(os.path.basename(res), 'archive.tar.xz') - self.assertEqual(self._tarinfo(res), self._created_files) + assert os.path.exists(res) + assert os.path.basename(res) == 'archive.tar.xz' + assert self._tarinfo(res) == self._created_files def test_make_archive_owner_group(self): # testing make_archive with owner and group, with various combinations @@ -363,20 +341,20 @@ class ArchiveUtilTestCase( res = make_archive( base_name, 'zip', root_dir, base_dir, owner=owner, group=group ) - self.assertTrue(os.path.exists(res)) + assert os.path.exists(res) res = make_archive(base_name, 'zip', root_dir, base_dir) - self.assertTrue(os.path.exists(res)) + assert os.path.exists(res) res = make_archive( base_name, 'tar', root_dir, base_dir, owner=owner, group=group ) - self.assertTrue(os.path.exists(res)) + assert os.path.exists(res) res = make_archive( base_name, 'tar', root_dir, base_dir, owner='kjhkjhkjg', group='oihohoh' ) - self.assertTrue(os.path.exists(res)) + assert os.path.exists(res) @pytest.mark.usefixtures('needs_zlib') @require_unix_id @@ -396,13 +374,13 @@ class ArchiveUtilTestCase( os.chdir(old_dir) # check if the compressed tarball was created - self.assertTrue(os.path.exists(archive_name)) + assert os.path.exists(archive_name) # now checks the rights archive = tarfile.open(archive_name) try: for member in archive.getmembers(): - self.assertEqual(member.uid, 0) - self.assertEqual(member.gid, 0) + assert member.uid == 0 + assert member.gid == 0 finally: archive.close() diff --git a/distutils/tests/test_bdist.py b/distutils/tests/test_bdist.py index 56976c23..8db72efc 100644 --- a/distutils/tests/test_bdist.py +++ b/distutils/tests/test_bdist.py @@ -1,13 +1,12 @@ """Tests for distutils.command.bdist.""" import os -import unittest import warnings from distutils.command.bdist import bdist from distutils.tests import support -class BuildTestCase(support.TempdirManager, unittest.TestCase): +class TestBuild(support.TempdirManager): def test_formats(self): # let's create a command and make sure # we can set the format @@ -15,7 +14,7 @@ class BuildTestCase(support.TempdirManager, unittest.TestCase): cmd = bdist(dist) cmd.formats = ['msi'] cmd.ensure_finalized() - self.assertEqual(cmd.formats, ['msi']) + assert cmd.formats == ['msi'] # what formats does bdist offer? formats = [ @@ -23,13 +22,12 @@ class BuildTestCase(support.TempdirManager, unittest.TestCase): 'gztar', 'rpm', 'tar', - 'wininst', 'xztar', 'zip', 'ztar', ] - found = sorted(cmd.format_command) - self.assertEqual(found, formats) + found = sorted(cmd.format_commands) + assert found == formats def test_skip_build(self): # bug #10946: bdist --skip-build should trickle down to subcommands @@ -41,7 +39,6 @@ class BuildTestCase(support.TempdirManager, unittest.TestCase): names = [ 'bdist_dumb', - 'bdist_wininst', ] # bdist_rpm does not support --skip-build for name in names: @@ -53,6 +50,4 @@ class BuildTestCase(support.TempdirManager, unittest.TestCase): if getattr(subcmd, '_unsupported', False): # command is not supported on this build continue - self.assertTrue( - subcmd.skip_build, '%s should take --skip-build from bdist' % name - ) + assert subcmd.skip_build, '%s should take --skip-build from bdist' % name diff --git a/distutils/tests/test_bdist_dumb.py b/distutils/tests/test_bdist_dumb.py index 7c4d5964..8624a429 100644 --- a/distutils/tests/test_bdist_dumb.py +++ b/distutils/tests/test_bdist_dumb.py @@ -3,7 +3,6 @@ import os import sys import zipfile -import unittest import pytest @@ -21,23 +20,14 @@ setup(name='foo', version='0.1', py_modules=['foo'], """ +@support.combine_markers @pytest.mark.usefixtures('save_env') -class BuildDumbTestCase( +@pytest.mark.usefixtures('save_argv') +@pytest.mark.usefixtures('save_cwd') +class TestBuildDumb( support.TempdirManager, support.LoggingSilencer, - unittest.TestCase, ): - def setUp(self): - super(BuildDumbTestCase, self).setUp() - self.old_location = os.getcwd() - self.old_sys_argv = sys.argv, sys.argv[:] - - def tearDown(self): - os.chdir(self.old_location) - sys.argv = self.old_sys_argv[0] - sys.argv[:] = self.old_sys_argv[1] - super(BuildDumbTestCase, self).tearDown() - @pytest.mark.usefixtures('needs_zlib') def test_simple_built(self): @@ -75,9 +65,9 @@ class BuildDumbTestCase( # see what we have dist_created = os.listdir(os.path.join(pkg_dir, 'dist')) - base = "%s.%s.zip" % (dist.get_fullname(), cmd.plat_name) + base = "{}.{}.zip".format(dist.get_fullname(), cmd.plat_name) - self.assertEqual(dist_created, [base]) + assert dist_created == [base] # now let's check what we have in the zip file fp = zipfile.ZipFile(os.path.join('dist', base)) @@ -90,4 +80,4 @@ class BuildDumbTestCase( wanted = ['foo-0.1-py%s.%s.egg-info' % sys.version_info[:2], 'foo.py'] if not sys.dont_write_bytecode: wanted.append('foo.%s.pyc' % sys.implementation.cache_tag) - self.assertEqual(contents, sorted(wanted)) + assert contents == sorted(wanted) diff --git a/distutils/tests/test_bdist_rpm.py b/distutils/tests/test_bdist_rpm.py index ba809392..2d14bafc 100644 --- a/distutils/tests/test_bdist_rpm.py +++ b/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 @@ import pytest from distutils.core import Distribution from distutils.command.bdist_rpm import bdist_rpm from distutils.tests import support -from distutils.spawn import find_executable +from distutils.spawn import find_executable # noqa: F401 from .py38compat import requires_zlib @@ -24,38 +23,31 @@ setup(name='foo', version='0.1', py_modules=['foo'], """ +@pytest.fixture(autouse=True) +def sys_executable_encodable(): + try: + sys.executable.encode('UTF-8') + except UnicodeEncodeError: + 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') -class BuildRpmTestCase( +@pytest.mark.usefixtures('save_argv') +@pytest.mark.usefixtures('save_cwd') +class TestBuildRpm( support.TempdirManager, support.LoggingSilencer, - unittest.TestCase, ): - def setUp(self): - try: - sys.executable.encode("UTF-8") - except UnicodeEncodeError: - raise unittest.SkipTest("sys.executable is not encodable to UTF-8") - - super(BuildRpmTestCase, self).setUp() - self.old_location = os.getcwd() - self.old_sys_argv = sys.argv, sys.argv[:] - - def tearDown(self): - os.chdir(self.old_location) - sys.argv = self.old_sys_argv[0] - sys.argv[:] = self.old_sys_argv[1] - super(BuildRpmTestCase, self).tearDown() - - # 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,25 +82,17 @@ class BuildRpmTestCase( cmd.run() dist_created = os.listdir(os.path.join(pkg_dir, 'dist')) - self.assertIn('foo-0.1-1.noarch.rpm', dist_created) + assert 'foo-0.1-1.noarch.rpm' in dist_created # bug #2945: upload ignores bdist_rpm files - self.assertIn(('bdist_rpm', 'any', 'dist/foo-0.1-1.src.rpm'), dist.dist_files) - self.assertIn( - ('bdist_rpm', 'any', 'dist/foo-0.1-1.noarch.rpm'), dist.dist_files - ) + 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() @@ -142,12 +126,10 @@ class BuildRpmTestCase( cmd.run() dist_created = os.listdir(os.path.join(pkg_dir, 'dist')) - self.assertIn('foo-0.1-1.noarch.rpm', dist_created) + assert 'foo-0.1-1.noarch.rpm' in dist_created # bug #2945: upload ignores bdist_rpm files - self.assertIn(('bdist_rpm', 'any', 'dist/foo-0.1-1.src.rpm'), dist.dist_files) - self.assertIn( - ('bdist_rpm', 'any', 'dist/foo-0.1-1.noarch.rpm'), dist.dist_files - ) + 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 os.remove(os.path.join(pkg_dir, 'dist', 'foo-0.1-1.noarch.rpm')) diff --git a/distutils/tests/test_bdist_wininst.py b/distutils/tests/test_bdist_wininst.py deleted file mode 100644 index 4e4fcc5b..00000000 --- a/distutils/tests/test_bdist_wininst.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Tests for distutils.command.bdist_wininst.""" -import sys -import platform -import unittest - -from .py38compat import check_warnings - -from distutils.command.bdist_wininst import bdist_wininst -from distutils.tests import support - - -@unittest.skipIf( - sys.platform == 'win32' and platform.machine() == 'ARM64', - 'bdist_wininst is not supported in this install', -) -@unittest.skipIf( - getattr(bdist_wininst, '_unsupported', False), - 'bdist_wininst is not supported in this install', -) -class BuildWinInstTestCase( - support.TempdirManager, support.LoggingSilencer, unittest.TestCase -): - def test_get_exe_bytes(self): - - # issue5731: command was broken on non-windows platforms - # this test makes sure it works now for every platform - # let's create a command - pkg_pth, dist = self.create_dist() - with check_warnings(("", DeprecationWarning)): - cmd = bdist_wininst(dist) - cmd.ensure_finalized() - - # let's run the code that finds the right wininst*.exe file - # and make sure it finds it and returns its content - # no matter what platform we have - exe_file = cmd.get_exe_bytes() - self.assertGreater(len(exe_file), 10) diff --git a/distutils/tests/test_build.py b/distutils/tests/test_build.py index 712b0d53..80367607 100644 --- a/distutils/tests/test_build.py +++ b/distutils/tests/test_build.py @@ -1,5 +1,4 @@ """Tests for distutils.command.build.""" -import unittest import os import sys @@ -8,39 +7,39 @@ from distutils.tests import support from sysconfig import get_platform -class BuildTestCase(support.TempdirManager, support.LoggingSilencer, unittest.TestCase): +class TestBuild(support.TempdirManager, support.LoggingSilencer): def test_finalize_options(self): pkg_dir, dist = self.create_dist() cmd = build(dist) cmd.finalize_options() # if not specified, plat_name gets the current platform - self.assertEqual(cmd.plat_name, get_platform()) + assert cmd.plat_name == get_platform() # build_purelib is build + lib wanted = os.path.join(cmd.build_base, 'lib') - self.assertEqual(cmd.build_purelib, wanted) + assert cmd.build_purelib == wanted # build_platlib is 'build/lib.platform-cache_tag[-pydebug]' # examples: # build/lib.macosx-10.3-i386-cpython39 - plat_spec = '.%s-%s' % (cmd.plat_name, sys.implementation.cache_tag) + plat_spec = '.{}-{}'.format(cmd.plat_name, sys.implementation.cache_tag) if hasattr(sys, 'gettotalrefcount'): - self.assertTrue(cmd.build_platlib.endswith('-pydebug')) + assert cmd.build_platlib.endswith('-pydebug') plat_spec += '-pydebug' wanted = os.path.join(cmd.build_base, 'lib' + plat_spec) - self.assertEqual(cmd.build_platlib, wanted) + assert cmd.build_platlib == wanted # by default, build_lib = build_purelib - self.assertEqual(cmd.build_lib, cmd.build_purelib) + assert cmd.build_lib == cmd.build_purelib # build_temp is build/temp.<plat> wanted = os.path.join(cmd.build_base, 'temp' + plat_spec) - self.assertEqual(cmd.build_temp, wanted) + assert cmd.build_temp == wanted # build_scripts is build/scripts-x.x wanted = os.path.join(cmd.build_base, 'scripts-%d.%d' % sys.version_info[:2]) - self.assertEqual(cmd.build_scripts, wanted) + assert cmd.build_scripts == wanted # executable is os.path.normpath(sys.executable) - self.assertEqual(cmd.executable, os.path.normpath(sys.executable)) + assert cmd.executable == os.path.normpath(sys.executable) diff --git a/distutils/tests/test_build_clib.py b/distutils/tests/test_build_clib.py index c8fbb5c2..c931c06e 100644 --- a/distutils/tests/test_build_clib.py +++ b/distutils/tests/test_build_clib.py @@ -1,47 +1,44 @@ """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 -class BuildCLibTestCase( - support.TempdirManager, support.LoggingSilencer, unittest.TestCase -): +class TestBuildCLib(support.TempdirManager, support.LoggingSilencer): def test_check_library_dist(self): pkg_dir, dist = self.create_dist() cmd = build_clib(dist) # 'libraries' option must be a list - self.assertRaises(DistutilsSetupError, cmd.check_library_list, 'foo') + with pytest.raises(DistutilsSetupError): + cmd.check_library_list('foo') # each element of 'libraries' must a 2-tuple - self.assertRaises(DistutilsSetupError, cmd.check_library_list, ['foo1', 'foo2']) + with pytest.raises(DistutilsSetupError): + cmd.check_library_list(['foo1', 'foo2']) # first element of each tuple in 'libraries' # must be a string (the library name) - self.assertRaises( - DistutilsSetupError, cmd.check_library_list, [(1, 'foo1'), ('name', 'foo2')] - ) + with pytest.raises(DistutilsSetupError): + cmd.check_library_list([(1, 'foo1'), ('name', 'foo2')]) # library name may not contain directory separators - self.assertRaises( - DistutilsSetupError, - cmd.check_library_list, - [('name', 'foo1'), ('another/name', 'foo2')], - ) + with pytest.raises(DistutilsSetupError): + cmd.check_library_list( + [('name', 'foo1'), ('another/name', 'foo2')], + ) # second element of each tuple must be a dictionary (build info) - self.assertRaises( - DistutilsSetupError, - cmd.check_library_list, - [('name', {}), ('another', 'foo2')], - ) + with pytest.raises(DistutilsSetupError): + cmd.check_library_list( + [('name', {}), ('another', 'foo2')], + ) # those work libs = [('name', {}), ('name', {'ok': 'good'})] @@ -54,22 +51,24 @@ class BuildCLibTestCase( # "in 'libraries' option 'sources' must be present and must be # a list of source filenames cmd.libraries = [('name', {})] - self.assertRaises(DistutilsSetupError, cmd.get_source_files) + with pytest.raises(DistutilsSetupError): + cmd.get_source_files() cmd.libraries = [('name', {'sources': 1})] - self.assertRaises(DistutilsSetupError, cmd.get_source_files) + with pytest.raises(DistutilsSetupError): + cmd.get_source_files() cmd.libraries = [('name', {'sources': ['a', 'b']})] - self.assertEqual(cmd.get_source_files(), ['a', 'b']) + assert cmd.get_source_files() == ['a', 'b'] cmd.libraries = [('name', {'sources': ('a', 'b')})] - self.assertEqual(cmd.get_source_files(), ['a', 'b']) + assert cmd.get_source_files() == ['a', 'b'] cmd.libraries = [ ('name', {'sources': ('a', 'b')}), ('name2', {'sources': ['c', 'd']}), ] - self.assertEqual(cmd.get_source_files(), ['a', 'b', 'c', 'd']) + assert cmd.get_source_files() == ['a', 'b', 'c', 'd'] def test_build_libraries(self): @@ -86,7 +85,8 @@ class BuildCLibTestCase( # build_libraries is also doing a bit of typo checking lib = [('name', {'sources': 'notvalid'})] - self.assertRaises(DistutilsSetupError, cmd.build_libraries, lib) + with pytest.raises(DistutilsSetupError): + cmd.build_libraries(lib) lib = [('name', {'sources': list()})] cmd.build_libraries(lib) @@ -100,16 +100,17 @@ class BuildCLibTestCase( cmd.include_dirs = 'one-dir' cmd.finalize_options() - self.assertEqual(cmd.include_dirs, ['one-dir']) + assert cmd.include_dirs == ['one-dir'] cmd.include_dirs = None cmd.finalize_options() - self.assertEqual(cmd.include_dirs, []) + assert cmd.include_dirs == [] cmd.distribution.libraries = 'WONTWORK' - self.assertRaises(DistutilsSetupError, cmd.finalize_options) + 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) @@ -133,4 +134,4 @@ class BuildCLibTestCase( cmd.run() # let's check the result - self.assertIn('libfoo.a', os.listdir(build_temp)) + assert 'libfoo.a' in os.listdir(build_temp) diff --git a/distutils/tests/test_build_ext.py b/distutils/tests/test_build_ext.py index 39d7920a..e60814ff 100644 --- a/distutils/tests/test_build_ext.py +++ b/distutils/tests/test_build_ext.py @@ -2,6 +2,12 @@ import sys import os from io import StringIO import textwrap +import site +import contextlib +import platform +import tempfile +import importlib +import shutil from distutils.core import Distribution from distutils.command.build_ext import build_ext @@ -20,53 +26,70 @@ from distutils.errors import ( UnknownFileError, ) -import unittest from test import support from . import py38compat as os_helper -from test.support.script_helper import assert_python_ok - -# http://bugs.python.org/issue4373 -# Don't load the xx module more than once. -ALREADY_TESTED = False - - -class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): - def setUp(self): - # Create a simple test environment - super(BuildExtTestCase, self).setUp() - self.tmp_dir = self.mkdtemp() - import site - - self.old_user_base = site.USER_BASE - site.USER_BASE = self.mkdtemp() - from distutils.command import build_ext - - build_ext.USER_BASE = site.USER_BASE - - # bpo-30132: On Windows, a .pdb file may be created in the current - # working directory. Create a temporary working directory to cleanup - # everything at the end of the test. - change_cwd = os_helper.change_cwd(self.tmp_dir) - change_cwd.__enter__() - self.addCleanup(change_cwd.__exit__, None, None, None) - - def tearDown(self): - import site - - site.USER_BASE = self.old_user_base - from distutils.command import build_ext - - build_ext.USER_BASE = self.old_user_base - super(BuildExtTestCase, self).tearDown() - +from . import py38compat as import_helper +import pytest +import re + + +@pytest.fixture() +def user_site_dir(request): + self = request.instance + self.tmp_dir = self.mkdtemp() + from distutils.command import build_ext + + orig_user_base = site.USER_BASE + + site.USER_BASE = self.mkdtemp() + build_ext.USER_BASE = site.USER_BASE + + # bpo-30132: On Windows, a .pdb file may be created in the current + # working directory. Create a temporary working directory to cleanup + # everything at the end of the test. + with os_helper.change_cwd(self.tmp_dir): + yield + + site.USER_BASE = orig_user_base + 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): return build_ext(*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]) @@ -87,43 +110,24 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): 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( - """ - tmp_dir = {self.tmp_dir!r} - - import sys - import unittest - from test import support - - sys.path.insert(0, tmp_dir) - import xx + with safe_extension_import('xx', self.tmp_dir): + self._test_xx() - class Tests(unittest.TestCase): - def test_xx(self): - for attr in ('error', 'foo', 'new', 'roj'): - self.assertTrue(hasattr(xx, attr)) + @staticmethod + def _test_xx(): + import xx - 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) + for attr in ('error', 'foo', 'new', 'roj'): + assert hasattr(xx, attr) - - unittest.main() - """.format( - **locals() - ) - ) - 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'}) @@ -145,7 +149,7 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): _config_vars['Py_ENABLE_SHARED'] = old_var # make sure we get some library dirs under solaris - self.assertGreater(len(cmd.library_dirs), 0) + assert len(cmd.library_dirs) > 0 def test_user_site(self): import site @@ -155,7 +159,7 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): # making sure the user option is there options = [name for name, short, lable in cmd.user_options] - self.assertIn('user', options) + assert 'user' in options # setting a value cmd.user = 1 @@ -171,9 +175,9 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): # see if include_dirs and library_dirs # were set - self.assertIn(lib, cmd.library_dirs) - self.assertIn(lib, cmd.rpath) - self.assertIn(incl, cmd.include_dirs) + assert lib in cmd.library_dirs + assert lib in cmd.rpath + assert incl in cmd.include_dirs def test_optional_extension(self): @@ -183,9 +187,8 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): dist = Distribution({'name': 'xx', 'ext_modules': modules}) cmd = self.build_ext(dist) cmd.ensure_finalized() - self.assertRaises( - (UnknownFileError, CompileError), cmd.run - ) # should raise an error + with pytest.raises((UnknownFileError, CompileError)): + cmd.run() # should raise an error modules = [Extension('foo', ['xxx'], optional=True)] dist = Distribution({'name': 'xx', 'ext_modules': modules}) @@ -203,40 +206,40 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): py_include = sysconfig.get_python_inc() for p in py_include.split(os.path.pathsep): - self.assertIn(p, cmd.include_dirs) + assert p in cmd.include_dirs plat_py_include = sysconfig.get_python_inc(plat_specific=1) for p in plat_py_include.split(os.path.pathsep): - self.assertIn(p, cmd.include_dirs) + assert p in cmd.include_dirs # make sure cmd.libraries is turned into a list # if it's a string cmd = self.build_ext(dist) cmd.libraries = 'my_lib, other_lib lastlib' cmd.finalize_options() - self.assertEqual(cmd.libraries, ['my_lib', 'other_lib', 'lastlib']) + assert cmd.libraries == ['my_lib', 'other_lib', 'lastlib'] # make sure cmd.library_dirs is turned into a list # if it's a string cmd = self.build_ext(dist) cmd.library_dirs = 'my_lib_dir%sother_lib_dir' % os.pathsep cmd.finalize_options() - self.assertIn('my_lib_dir', cmd.library_dirs) - self.assertIn('other_lib_dir', cmd.library_dirs) + assert 'my_lib_dir' in cmd.library_dirs + assert 'other_lib_dir' in cmd.library_dirs # make sure rpath is turned into a list # if it's a string cmd = self.build_ext(dist) cmd.rpath = 'one%stwo' % os.pathsep cmd.finalize_options() - self.assertEqual(cmd.rpath, ['one', 'two']) + assert cmd.rpath == ['one', 'two'] # make sure cmd.link_objects is turned into a list # if it's a string cmd = build_ext(dist) cmd.link_objects = 'one two,three' cmd.finalize_options() - self.assertEqual(cmd.link_objects, ['one', 'two', 'three']) + assert cmd.link_objects == ['one', 'two', 'three'] # XXX more tests to perform for win32 @@ -245,25 +248,25 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): cmd = self.build_ext(dist) cmd.define = 'one,two' cmd.finalize_options() - self.assertEqual(cmd.define, [('one', '1'), ('two', '1')]) + assert cmd.define == [('one', '1'), ('two', '1')] # make sure undef is turned into a list of # strings if they are ','-separated strings cmd = self.build_ext(dist) cmd.undef = 'one,two' cmd.finalize_options() - self.assertEqual(cmd.undef, ['one', 'two']) + assert cmd.undef == ['one', 'two'] # make sure swig_opts is turned into a list cmd = self.build_ext(dist) cmd.swig_opts = None cmd.finalize_options() - self.assertEqual(cmd.swig_opts, []) + assert cmd.swig_opts == [] cmd = self.build_ext(dist) cmd.swig_opts = '1 2' cmd.finalize_options() - self.assertEqual(cmd.swig_opts, ['1', '2']) + assert cmd.swig_opts == ['1', '2'] def test_check_extensions_list(self): dist = Distribution() @@ -271,35 +274,39 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): cmd.finalize_options() # 'extensions' option must be a list of Extension instances - self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, 'foo') + with pytest.raises(DistutilsSetupError): + cmd.check_extensions_list('foo') # each element of 'ext_modules' option must be an # Extension instance or 2-tuple exts = [('bar', 'foo', 'bar'), 'foo'] - self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts) + with pytest.raises(DistutilsSetupError): + cmd.check_extensions_list(exts) # first element of each tuple in 'ext_modules' # must be the extension name (a string) and match # a python dotted-separated name exts = [('foo-bar', '')] - self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts) + with pytest.raises(DistutilsSetupError): + cmd.check_extensions_list(exts) # second element of each tuple in 'ext_modules' # must be a dictionary (build info) exts = [('foo.bar', '')] - self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts) + with pytest.raises(DistutilsSetupError): + cmd.check_extensions_list(exts) # ok this one should pass exts = [('foo.bar', {'sources': [''], 'libraries': 'foo', 'some': 'bar'})] cmd.check_extensions_list(exts) ext = exts[0] - self.assertIsInstance(ext, Extension) + assert isinstance(ext, Extension) # check_extensions_list adds in ext the values passed # when they are in ('include_dirs', 'library_dirs', 'libraries' # 'extra_objects', 'extra_compile_args', 'extra_link_args') - self.assertEqual(ext.libraries, 'foo') - self.assertFalse(hasattr(ext, 'some')) + assert ext.libraries == 'foo' + assert not hasattr(ext, 'some') # 'macros' element of build info dict must be 1- or 2-tuple exts = [ @@ -313,19 +320,20 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): }, ) ] - self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts) + with pytest.raises(DistutilsSetupError): + cmd.check_extensions_list(exts) exts[0][1]['macros'] = [('1', '2'), ('3',)] cmd.check_extensions_list(exts) - self.assertEqual(exts[0].undef_macros, ['3']) - self.assertEqual(exts[0].define_macros, [('1', '2')]) + assert exts[0].undef_macros == ['3'] + assert exts[0].define_macros == [('1', '2')] def test_get_source_files(self): modules = [Extension('foo', ['xxx'], optional=False)] dist = Distribution({'name': 'xx', 'ext_modules': modules}) cmd = self.build_ext(dist) cmd.ensure_finalized() - self.assertEqual(cmd.get_source_files(), ['xxx']) + assert cmd.get_source_files() == ['xxx'] def test_unicode_module_names(self): modules = [ @@ -335,10 +343,10 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): dist = Distribution({'name': 'xx', 'ext_modules': modules}) cmd = self.build_ext(dist) cmd.ensure_finalized() - self.assertRegex(cmd.get_ext_filename(modules[0].name), r'foo(_d)?\..*') - self.assertRegex(cmd.get_ext_filename(modules[1].name), r'föö(_d)?\..*') - self.assertEqual(cmd.get_export_symbols(modules[0]), ['PyInit_foo']) - self.assertEqual(cmd.get_export_symbols(modules[1]), ['PyInitU_f_1gaa']) + assert re.search(r'foo(_d)?\..*', cmd.get_ext_filename(modules[0].name)) + assert re.search(r'föö(_d)?\..*', cmd.get_ext_filename(modules[1].name)) + assert cmd.get_export_symbols(modules[0]) == ['PyInit_foo'] + assert cmd.get_export_symbols(modules[1]) == ['PyInitU_f_1gaa'] def test_compiler_option(self): # cmd.compiler is an option and @@ -349,12 +357,10 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): cmd.compiler = 'unix' cmd.ensure_finalized() cmd.run() - self.assertEqual(cmd.compiler, 'unix') + assert cmd.compiler == 'unix' 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') @@ -363,7 +369,7 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): cmd = self.build_ext(dist) fixup_build_ext(cmd) cmd.ensure_finalized() - self.assertEqual(len(cmd.get_outputs()), 1) + assert len(cmd.get_outputs()) == 1 cmd.build_lib = os.path.join(self.tmp_dir, 'build') cmd.build_temp = os.path.join(self.tmp_dir, 'tempt') @@ -379,20 +385,20 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): so_file = cmd.get_outputs()[0] finally: os.chdir(old_wd) - self.assertTrue(os.path.exists(so_file)) + assert os.path.exists(so_file) ext_suffix = sysconfig.get_config_var('EXT_SUFFIX') - self.assertTrue(so_file.endswith(ext_suffix)) + assert so_file.endswith(ext_suffix) so_dir = os.path.dirname(so_file) - self.assertEqual(so_dir, other_tmp_dir) + assert so_dir == other_tmp_dir cmd.inplace = 0 cmd.compiler = None cmd.run() so_file = cmd.get_outputs()[0] - self.assertTrue(os.path.exists(so_file)) - self.assertTrue(so_file.endswith(ext_suffix)) + assert os.path.exists(so_file) + assert so_file.endswith(ext_suffix) so_dir = os.path.dirname(so_file) - self.assertEqual(so_dir, cmd.build_lib) + assert so_dir == cmd.build_lib # inplace = 0, cmd.package = 'bar' build_py = cmd.get_finalized_command('build_py') @@ -400,7 +406,7 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): path = cmd.get_ext_fullpath('foo') # checking that the last directory is the build_dir path = os.path.split(path)[0] - self.assertEqual(path, cmd.build_lib) + assert path == cmd.build_lib # inplace = 1, cmd.package = 'bar' cmd.inplace = 1 @@ -414,7 +420,7 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): # checking that the last directory is bar path = os.path.split(path)[0] lastdir = os.path.split(path)[-1] - self.assertEqual(lastdir, 'bar') + assert lastdir == 'bar' def test_ext_fullpath(self): ext = sysconfig.get_config_var('EXT_SUFFIX') @@ -430,14 +436,14 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): curdir = os.getcwd() wanted = os.path.join(curdir, 'src', 'lxml', 'etree' + ext) path = cmd.get_ext_fullpath('lxml.etree') - self.assertEqual(wanted, path) + assert wanted == path # building lxml.etree not inplace cmd.inplace = 0 cmd.build_lib = os.path.join(curdir, 'tmpdir') wanted = os.path.join(curdir, 'tmpdir', 'lxml', 'etree' + ext) path = cmd.get_ext_fullpath('lxml.etree') - self.assertEqual(wanted, path) + assert wanted == path # building twisted.runner.portmap not inplace build_py = cmd.get_finalized_command('build_py') @@ -445,30 +451,32 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): cmd.distribution.packages = ['twisted', 'twisted.runner.portmap'] path = cmd.get_ext_fullpath('twisted.runner.portmap') wanted = os.path.join(curdir, 'tmpdir', 'twisted', 'runner', 'portmap' + ext) - self.assertEqual(wanted, path) + assert wanted == path # building twisted.runner.portmap inplace cmd.inplace = 1 path = cmd.get_ext_fullpath('twisted.runner.portmap') wanted = os.path.join(curdir, 'twisted', 'runner', 'portmap' + ext) - self.assertEqual(wanted, path) + 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, # an extension module is compiled with the same deployment target as # 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 # compiled with a deployment target less than that of the interpreter. - self.assertRaises( - DistutilsPlatformError, self._try_compile_deployment_target, '>', '10.1' - ) + 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 # deployment target higher than that of the interpreter: the ext @@ -482,10 +490,6 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): self._try_compile_deployment_target('<', deptarget) def _try_compile_deployment_target(self, operator, target): - orig_environ = os.environ - os.environ = orig_environ.copy() - self.addCleanup(setattr, os, 'environ', orig_environ) - if target is None: if os.environ.get('MACOSX_DEPLOYMENT_TARGET'): del os.environ['MACOSX_DEPLOYMENT_TARGET'] @@ -531,7 +535,7 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): deptarget_ext = Extension( 'deptarget', [deptarget_c], - extra_compile_args=['-DTARGET=%s' % (target,)], + extra_compile_args=['-DTARGET={}'.format(target)], ) dist = Distribution({'name': 'deptarget', 'ext_modules': [deptarget_ext]}) dist.package_dir = self.tmp_dir @@ -554,7 +558,7 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): self.fail("Wrong deployment target during compilation") -class ParallelBuildExtTestCase(BuildExtTestCase): +class TestParallelBuildExt(TestBuildExt): def build_ext(self, *args, **kwargs): build_ext = super().build_ext(*args, **kwargs) build_ext.parallel = True diff --git a/distutils/tests/test_build_py.py b/distutils/tests/test_build_py.py index 4a8582e4..63543dca 100644 --- a/distutils/tests/test_build_py.py +++ b/distutils/tests/test_build_py.py @@ -2,19 +2,19 @@ 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 -class BuildPyTestCase( - support.TempdirManager, support.LoggingSilencer, unittest.TestCase -): +@support.combine_markers +class TestBuildPy(support.TempdirManager, support.LoggingSilencer): def test_package_data(self): sources = self.mkdtemp() f = open(os.path.join(sources, "__init__.py"), "w") @@ -41,24 +41,24 @@ class BuildPyTestCase( cmd = build_py(dist) cmd.compile = 1 cmd.ensure_finalized() - self.assertEqual(cmd.package_data, dist.package_data) + assert cmd.package_data == dist.package_data cmd.run() # This makes sure the list of outputs includes byte-compiled # files for Python modules but not for package data files # (there shouldn't *be* byte-code files for those!). - self.assertEqual(len(cmd.get_outputs()), 3) + assert len(cmd.get_outputs()) == 3 pkgdest = os.path.join(destination, "pkg") files = os.listdir(pkgdest) pycache_dir = os.path.join(pkgdest, "__pycache__") - self.assertIn("__init__.py", files) - self.assertIn("README.txt", files) + assert "__init__.py" in files + assert "README.txt" in files if sys.dont_write_bytecode: - self.assertFalse(os.path.exists(pycache_dir)) + assert not os.path.exists(pycache_dir) else: pyc_files = os.listdir(pycache_dir) - self.assertIn("__init__.%s.pyc" % sys.implementation.cache_tag, pyc_files) + assert "__init__.%s.pyc" % sys.implementation.cache_tag in pyc_files def test_empty_package_dir(self): # See bugs #1668596/#1720897 @@ -87,7 +87,7 @@ class BuildPyTestCase( 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) @@ -99,11 +99,11 @@ class BuildPyTestCase( cmd.run() found = os.listdir(cmd.build_lib) - self.assertEqual(sorted(found), ['__pycache__', 'boiledeggs.py']) + assert sorted(found) == ['__pycache__', 'boiledeggs.py'] found = os.listdir(os.path.join(cmd.build_lib, '__pycache__')) - self.assertEqual(found, ['boiledeggs.%s.pyc' % sys.implementation.cache_tag]) + 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) @@ -116,10 +116,10 @@ class BuildPyTestCase( cmd.run() found = os.listdir(cmd.build_lib) - self.assertEqual(sorted(found), ['__pycache__', 'boiledeggs.py']) + assert sorted(found) == ['__pycache__', 'boiledeggs.py'] found = os.listdir(os.path.join(cmd.build_lib, '__pycache__')) - expect = 'boiledeggs.{}.opt-1.pyc'.format(sys.implementation.cache_tag) - self.assertEqual(sorted(found), [expect]) + expect = f'boiledeggs.{sys.implementation.cache_tag}.opt-1.pyc' + assert sorted(found) == [expect] def test_dir_in_package_data(self): """ @@ -165,9 +165,9 @@ class BuildPyTestCase( finally: sys.dont_write_bytecode = old_dont_write_bytecode - self.assertIn('byte-compiling is disabled', self.logs[0][1] % self.logs[0][2]) + 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/distutils/tests/test_build_scripts.py b/distutils/tests/test_build_scripts.py index 6ef9dd61..00d7fc59 100644 --- a/distutils/tests/test_build_scripts.py +++ b/distutils/tests/test_build_scripts.py @@ -1,7 +1,6 @@ """Tests for distutils.command.build_scripts.""" import os -import unittest from distutils.command.build_scripts import build_scripts from distutils.core import Distribution @@ -10,18 +9,16 @@ from distutils import sysconfig from distutils.tests import support -class BuildScriptsTestCase( - support.TempdirManager, support.LoggingSilencer, unittest.TestCase -): +class TestBuildScripts(support.TempdirManager, support.LoggingSilencer): def test_default_settings(self): cmd = self.get_build_scripts_cmd("/foo/bar", []) - self.assertFalse(cmd.force) - self.assertIsNone(cmd.build_dir) + assert not cmd.force + assert cmd.build_dir is None cmd.finalize_options() - self.assertTrue(cmd.force) - self.assertEqual(cmd.build_dir, "/foo/bar") + assert cmd.force + assert cmd.build_dir == "/foo/bar" def test_build(self): source = self.mkdtemp() @@ -36,7 +33,7 @@ class BuildScriptsTestCase( built = os.listdir(target) for name in expected: - self.assertIn(name, built) + assert name in built def get_build_scripts_cmd(self, target, scripts): import sys @@ -106,4 +103,4 @@ class BuildScriptsTestCase( built = os.listdir(target) for name in expected: - self.assertIn(name, built) + assert name in built diff --git a/distutils/tests/test_ccompiler.py b/distutils/tests/test_ccompiler.py new file mode 100644 index 00000000..da1879f2 --- /dev/null +++ b/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/distutils/tests/test_check.py b/distutils/tests/test_check.py index 21035f5d..3e5f6034 100644 --- a/distutils/tests/test_check.py +++ b/distutils/tests/test_check.py @@ -1,9 +1,10 @@ """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 @@ -16,7 +17,8 @@ except ImportError: HERE = os.path.dirname(__file__) -class CheckTestCase(support.LoggingSilencer, support.TempdirManager, unittest.TestCase): +@support.combine_markers +class TestCheck(support.LoggingSilencer, support.TempdirManager): def _run(self, metadata=None, cwd=None, **options): if metadata is None: metadata = {} @@ -39,7 +41,7 @@ class CheckTestCase(support.LoggingSilencer, support.TempdirManager, unittest.Te # by default, check is checking the metadata # should have some warnings cmd = self._run() - self.assertEqual(cmd._warnings, 1) + assert cmd._warnings == 1 # now let's add the required fields # and run it again, to make sure we don't get @@ -52,15 +54,16 @@ class CheckTestCase(support.LoggingSilencer, support.TempdirManager, unittest.Te 'version': 'xxx', } cmd = self._run(metadata) - self.assertEqual(cmd._warnings, 0) + assert cmd._warnings == 0 # now with the strict mode, we should # get an error if there are missing metadata - self.assertRaises(DistutilsSetupError, self._run, {}, **{'strict': 1}) + with pytest.raises(DistutilsSetupError): + self._run({}, **{'strict': 1}) # and of course, no error when all metadata are present cmd = self._run(metadata, strict=1) - self.assertEqual(cmd._warnings, 0) + assert cmd._warnings == 0 # now a test with non-ASCII characters metadata = { @@ -73,7 +76,7 @@ class CheckTestCase(support.LoggingSilencer, support.TempdirManager, unittest.Te 'long_description': 'More things about esszet \u00df', } cmd = self._run(metadata) - self.assertEqual(cmd._warnings, 0) + assert cmd._warnings == 0 def test_check_author_maintainer(self): for kind in ("author", "maintainer"): @@ -86,42 +89,42 @@ class CheckTestCase(support.LoggingSilencer, support.TempdirManager, unittest.Te 'version': 'xxx', } cmd = self._run(metadata) - self.assertEqual(cmd._warnings, 0) + assert cmd._warnings == 0 # the check should not warn if only email is given metadata[kind + '_email'] = 'name@email.com' cmd = self._run(metadata) - self.assertEqual(cmd._warnings, 0) + assert cmd._warnings == 0 # the check should not warn if only the name is given metadata[kind] = "Name" del metadata[kind + '_email'] cmd = self._run(metadata) - self.assertEqual(cmd._warnings, 0) + 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) # let's see if it detects broken rest broken_rest = 'title\n===\n\ntest' msgs = cmd._check_rst_data(broken_rest) - self.assertEqual(len(msgs), 1) + assert len(msgs) == 1 # and non-broken rest rest = 'title\n=====\n\ntest' msgs = cmd._check_rst_data(rest) - self.assertEqual(len(msgs), 0) + 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) cmd = check(dist) cmd.check_restructuredtext() - self.assertEqual(cmd._warnings, 1) + assert cmd._warnings == 1 # let's see if we have an error with strict=1 metadata = { @@ -132,25 +135,21 @@ class CheckTestCase(support.LoggingSilencer, support.TempdirManager, unittest.Te 'version': 'xxx', 'long_description': broken_rest, } - self.assertRaises( - DistutilsSetupError, - self._run, - metadata, - **{'strict': 1, 'restructuredtext': 1} - ) + with pytest.raises(DistutilsSetupError): + self._run(metadata, **{'strict': 1, 'restructuredtext': 1}) # and non-broken rest, including a non-ASCII character to test #12114 metadata['long_description'] = 'title\n=====\n\ntest \u00df' cmd = self._run(metadata, strict=1, restructuredtext=1) - self.assertEqual(cmd._warnings, 0) + assert cmd._warnings == 0 # check that includes work to test #31292 metadata['long_description'] = 'title\n=====\n\n.. include:: includetest.rst' cmd = self._run(metadata, cwd=HERE, strict=1, restructuredtext=1) - self.assertEqual(cmd._warnings, 0) + 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 = [] @@ -185,14 +184,14 @@ class CheckTestCase(support.LoggingSilencer, support.TempdirManager, unittest.Te cmd.check_restructuredtext() msgs = cmd._check_rst_data(rest_with_code) if pygments is not None: - self.assertEqual(len(msgs), 0) + assert len(msgs) == 0 else: - self.assertEqual(len(msgs), 1) - self.assertEqual( - str(msgs[0][1]), 'Cannot analyze code. Pygments package not found.' + assert len(msgs) == 1 + assert ( + str(msgs[0][1]) + == 'Cannot analyze code. Pygments package not found.' ) def test_check_all(self): - self.assertRaises( - DistutilsSetupError, self._run, {}, **{'strict': 1, 'restructuredtext': 1} - ) + with pytest.raises(DistutilsSetupError): + self._run({}, **{'strict': 1, 'restructuredtext': 1}) diff --git a/distutils/tests/test_clean.py b/distutils/tests/test_clean.py index 796ca0fc..4166bb7e 100644 --- a/distutils/tests/test_clean.py +++ b/distutils/tests/test_clean.py @@ -1,12 +1,11 @@ """Tests for distutils.command.clean.""" import os -import unittest from distutils.command.clean import clean from distutils.tests import support -class cleanTestCase(support.TempdirManager, support.LoggingSilencer, unittest.TestCase): +class TestClean(support.TempdirManager, support.LoggingSilencer): def test_simple_run(self): pkg_dir, dist = self.create_dist() cmd = clean(dist) @@ -38,7 +37,7 @@ class cleanTestCase(support.TempdirManager, support.LoggingSilencer, unittest.Te # make sure the files where removed for name, path in dirs: - self.assertFalse(os.path.exists(path), '%s was not removed' % path) + assert not os.path.exists(path), '%s was not removed' % path # let's run the command again (should spit warnings but succeed) cmd.all = 1 diff --git a/distutils/tests/test_cmd.py b/distutils/tests/test_cmd.py index 6a771a11..e4d5bf3c 100644 --- a/distutils/tests/test_cmd.py +++ b/distutils/tests/test_cmd.py @@ -1,5 +1,4 @@ """Tests for distutils.cmd.""" -import unittest import os from test.support import captured_stdout @@ -7,6 +6,7 @@ from distutils.cmd import Command from distutils.dist import Distribution from distutils.errors import DistutilsOptionError from distutils import debug +import pytest class MyCmd(Command): @@ -14,14 +14,13 @@ class MyCmd(Command): pass -class CommandTestCase(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() @@ -29,49 +28,43 @@ class CommandTestCase(unittest.TestCase): cmd.ensure_string_list('yes_string_list') cmd.ensure_string_list('yes_string_list2') - self.assertRaises( - DistutilsOptionError, cmd.ensure_string_list, 'not_string_list' - ) + with pytest.raises(DistutilsOptionError): + cmd.ensure_string_list('not_string_list') - self.assertRaises( - DistutilsOptionError, cmd.ensure_string_list, 'not_string_list2' - ) + with pytest.raises(DistutilsOptionError): + cmd.ensure_string_list('not_string_list2') cmd.option1 = 'ok,dok' cmd.ensure_string_list('option1') - self.assertEqual(cmd.option1, ['ok', 'dok']) + assert cmd.option1 == ['ok', 'dok'] cmd.option2 = ['xxx', 'www'] cmd.ensure_string_list('option2') cmd.option3 = ['ok', 2] - self.assertRaises(DistutilsOptionError, cmd.ensure_string_list, 'option3') - - def test_make_file(self): - - cmd = self.cmd + with pytest.raises(DistutilsOptionError): + cmd.ensure_string_list('option3') + def test_make_file(self, cmd): # making sure it raises when infiles is not a string or a list/tuple - self.assertRaises( - TypeError, cmd.make_file, infiles=1, outfile='', func='func', args=() - ) + with pytest.raises(TypeError): + cmd.make_file(infiles=1, outfile='', func='func', args=()) # making sure execute gets called properly def _execute(func, args, exec_msg, level): - self.assertEqual(exec_msg, 'generating out from in') + assert exec_msg == 'generating out from in' cmd.force = True 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 @@ -79,46 +72,45 @@ class CommandTestCase(unittest.TestCase): cmd.dump_options() wanted = ["command options for 'MyCmd':", ' option1 = 1', ' option2 = 1'] - self.assertEqual(msgs, wanted) + assert msgs == wanted - def test_ensure_string(self): - cmd = self.cmd + def test_ensure_string(self, cmd): cmd.option1 = 'ok' cmd.ensure_string('option1') cmd.option2 = None cmd.ensure_string('option2', 'xxx') - self.assertTrue(hasattr(cmd, 'option2')) + assert hasattr(cmd, 'option2') cmd.option3 = 1 - self.assertRaises(DistutilsOptionError, cmd.ensure_string, 'option3') + 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' - self.assertRaises(DistutilsOptionError, cmd.ensure_filename, 'option2') + 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' - self.assertRaises(DistutilsOptionError, cmd.ensure_dirname, 'option2') + 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) - self.assertEqual(stdout.read(), '') + assert stdout.read() == '' debug.DEBUG = True try: with captured_stdout() as stdout: cmd.debug_print('xxx') stdout.seek(0) - self.assertEqual(stdout.read(), 'xxx\n') + assert stdout.read() == 'xxx\n' finally: debug.DEBUG = False diff --git a/distutils/tests/test_config.py b/distutils/tests/test_config.py index 5bca4da8..43ba6766 100644 --- a/distutils/tests/test_config.py +++ b/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 = """\ @@ -51,37 +45,14 @@ password:xxx """ -@pytest.mark.usefixtures('save_env') +@support.combine_markers +@pytest.mark.usefixtures('threshold_warn') +@pytest.mark.usefixtures('pypirc') class BasePyPIRCCommandTestCase( support.TempdirManager, support.LoggingSilencer, - unittest.TestCase, ): - def setUp(self): - """Patches the environment.""" - super(BasePyPIRCCommandTestCase, self).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(BasePyPIRCCommandTestCase, self).tearDown() + pass class PyPIRCCommandTestCase(BasePyPIRCCommandTestCase): @@ -103,7 +74,7 @@ class PyPIRCCommandTestCase(BasePyPIRCCommandTestCase): ('server', 'server1'), ('username', 'me'), ] - self.assertEqual(config, waited) + assert config == waited # old format self.write_file(self.rc, PYPIRC_OLD) @@ -116,18 +87,18 @@ class PyPIRCCommandTestCase(BasePyPIRCCommandTestCase): ('server', 'server-login'), ('username', 'tarek'), ] - self.assertEqual(config, waited) + assert config == waited def test_server_empty_registration(self): cmd = self._cmd(self.dist) rc = cmd._get_rc_file() - self.assertFalse(os.path.exists(rc)) + assert not os.path.exists(rc) cmd._store_pypirc('tarek', 'xxx') - self.assertTrue(os.path.exists(rc)) + assert os.path.exists(rc) f = open(rc) try: content = f.read() - self.assertEqual(content, WANTED) + assert content == WANTED finally: f.close() @@ -146,4 +117,4 @@ class PyPIRCCommandTestCase(BasePyPIRCCommandTestCase): ('server', 'server3'), ('username', 'cbiggles'), ] - self.assertEqual(config, waited) + assert config == waited diff --git a/distutils/tests/test_config_cmd.py b/distutils/tests/test_config_cmd.py index 3c0879b5..65c60f64 100644 --- a/distutils/tests/test_config_cmd.py +++ b/distutils/tests/test_config_cmd.py @@ -1,31 +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 -class ConfigTestCase( - support.LoggingSilencer, support.TempdirManager, unittest.TestCase -): +@pytest.fixture(autouse=True) +def info_log(request, monkeypatch): + self = request.instance + self._logs = [] + monkeypatch.setattr(log, 'info', self._info) + + +@support.combine_markers +class TestConfig(support.LoggingSilencer, support.TempdirManager): def _info(self, msg, *args): for line in msg.splitlines(): self._logs.append(line) - def setUp(self): - super(ConfigTestCase, self).setUp() - self._logs = [] - self.old_log = log.info - log.info = self._info - - def tearDown(self): - log.info = self.old_log - super(ConfigTestCase, self).tearDown() - def test_dump_file(self): this_file = os.path.splitext(__file__)[0] + '.py' f = open(this_file) @@ -35,9 +32,9 @@ class ConfigTestCase( f.close() dump_file(this_file, 'I am the header') - self.assertEqual(len(self._logs), numlines + 1) + 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: @@ -53,10 +50,10 @@ class ConfigTestCase( # simple pattern searches match = cmd.search_cpp(pattern='xxx', body='/* xxx */') - self.assertEqual(match, 0) + assert match == 0 match = cmd.search_cpp(pattern='_configtest', body='/* xxx */') - self.assertEqual(match, 1) + assert match == 1 def test_finalize_options(self): # finalize_options does a bit of transformation @@ -68,9 +65,9 @@ class ConfigTestCase( cmd.library_dirs = 'three%sfour' % os.pathsep cmd.ensure_finalized() - self.assertEqual(cmd.include_dirs, ['one', 'two']) - self.assertEqual(cmd.libraries, ['one']) - self.assertEqual(cmd.library_dirs, ['three', 'four']) + assert cmd.include_dirs == ['one', 'two'] + assert cmd.libraries == ['one'] + assert cmd.library_dirs == ['three', 'four'] def test_clean(self): # _clean removes files @@ -82,11 +79,11 @@ class ConfigTestCase( self.write_file(f2, 'xxx') for f in (f1, f2): - self.assertTrue(os.path.exists(f)) + assert os.path.exists(f) pkg_dir, dist = self.create_dist() cmd = config(dist) cmd._clean(f1, f2) for f in (f1, f2): - self.assertFalse(os.path.exists(f)) + assert not os.path.exists(f) diff --git a/distutils/tests/test_core.py b/distutils/tests/test_core.py index c3943866..86b0040f 100644 --- a/distutils/tests/test_core.py +++ b/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,29 +56,15 @@ if __name__ == "__main__": """ -@pytest.mark.usefixtures('save_env') -class CoreTestCase(unittest.TestCase): - def setUp(self): - super(CoreTestCase, self).setUp() - self.old_stdout = sys.stdout - self.cleanup_testfn() - self.old_argv = sys.argv, sys.argv[:] - self.addCleanup(log.set_threshold, log._global_log.threshold) - - def tearDown(self): - sys.stdout = self.old_stdout - self.cleanup_testfn() - sys.argv = self.old_argv[0] - sys.argv[:] = self.old_argv[1] - super(CoreTestCase, self).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.fixture(autouse=True) +def save_stdout(monkeypatch): + monkeypatch.setattr(sys, 'stdout', sys.stdout) + +@pytest.mark.usefixtures('save_env') +@pytest.mark.usefixtures('save_argv') +@pytest.mark.usefixtures('cleanup_testfn') +class TestCore: def write_setup(self, text, path=os_helper.TESTFN): f = open(path, "w") try: @@ -99,14 +82,14 @@ class CoreTestCase(unittest.TestCase): # Make sure run_setup does not clobber sys.argv argv_copy = sys.argv.copy() distutils.core.run_setup(self.write_setup(setup_does_nothing)) - self.assertEqual(sys.argv, argv_copy) + assert sys.argv == argv_copy def test_run_setup_defines_subclass(self): # Make sure the script can use __file__; if that's missing, the test # setup.py script will raise NameError. dist = distutils.core.run_setup(self.write_setup(setup_defines_subclass)) install = dist.get_command_obj('install') - self.assertIn('cmd', install.sub_commands) + assert 'cmd' in install.sub_commands def test_run_setup_uses_current_dir(self): # This tests that the setup script is run with the current directory @@ -123,23 +106,23 @@ class CoreTestCase(unittest.TestCase): output = sys.stdout.getvalue() if output.endswith("\n"): output = output[:-1] - self.assertEqual(cwd, output) + assert cwd == output def test_run_setup_within_if_main(self): dist = distutils.core.run_setup( self.write_setup(setup_within_if_main), stop_after="config" ) - self.assertIsInstance(dist, Distribution) - self.assertEqual(dist.get_name(), "setup_within_if_main") + assert isinstance(dist, Distribution) + assert dist.get_name() == "setup_within_if_main" def test_run_commands(self): sys.argv = ['setup.py', 'build'] dist = distutils.core.run_setup( self.write_setup(setup_within_if_main), stop_after="commandline" ) - self.assertNotIn('build', dist.have_run) + assert 'build' not in dist.have_run distutils.core.run_commands(dist) - self.assertIn('build', dist.have_run) + assert 'build' in dist.have_run def test_debug_mode(self): # this covers the code called when DEBUG is set @@ -147,7 +130,7 @@ class CoreTestCase(unittest.TestCase): with captured_stdout() as stdout: distutils.core.setup(name='bar') stdout.seek(0) - self.assertEqual(stdout.read(), 'bar\n') + assert stdout.read() == 'bar\n' distutils.core.DEBUG = True try: @@ -157,4 +140,4 @@ class CoreTestCase(unittest.TestCase): distutils.core.DEBUG = False stdout.seek(0) wanted = "options (after parsing config files):\n" - self.assertEqual(stdout.readlines()[0], wanted) + assert stdout.readlines()[0] == wanted diff --git a/distutils/tests/test_cygwinccompiler.py b/distutils/tests/test_cygwinccompiler.py index da73adc6..ef01ae21 100644 --- a/distutils/tests/test_cygwinccompiler.py +++ b/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,48 +12,39 @@ from distutils.cygwinccompiler import ( get_msvcr, ) from distutils.tests import support +from distutils import sysconfig -class CygwinCCompilerTestCase(support.TempdirManager, unittest.TestCase): - def setUp(self): - super(CygwinCCompilerTestCase, self).setUp() - self.version = sys.version - self.python_h = os.path.join(self.mkdtemp(), 'python.h') - 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(CygwinCCompilerTestCase, self).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 compiler = CygwinCCompiler() link_name = "bash" linkable_file = compiler.find_library_file(["/usr/lib"], link_name) - self.assertIsNotNone(linkable_file) - self.assertTrue(os.path.exists(linkable_file)) - self.assertEquals(linkable_file, "/usr/lib/lib{:s}.dll.a".format(link_name)) + assert linkable_file is not None + 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 compiler = CygwinCCompiler() - self.assertEqual(compiler.runtime_library_dir_option('/foo'), []) + assert compiler.runtime_library_dir_option('/foo') == [] def test_check_config_h(self): @@ -63,21 +55,21 @@ class CygwinCCompilerTestCase(support.TempdirManager, unittest.TestCase): '4.0.1 (Apple Computer, Inc. build 5370)]' ) - self.assertEqual(check_config_h()[0], CONFIG_H_OK) + assert check_config_h()[0] == CONFIG_H_OK # then it tries to see if it can find "__GNUC__" in pyconfig.h sys.version = 'something without the *CC word' # if the file doesn't exist it returns CONFIG_H_UNCERTAIN - self.assertEqual(check_config_h()[0], CONFIG_H_UNCERTAIN) + assert check_config_h()[0] == CONFIG_H_UNCERTAIN # if it exists but does not contain __GNUC__, it returns CONFIG_H_NOTOK self.write_file(self.python_h, 'xxx') - self.assertEqual(check_config_h()[0], CONFIG_H_NOTOK) + assert check_config_h()[0] == CONFIG_H_NOTOK # and CONFIG_H_OK if __GNUC__ is found self.write_file(self.python_h, 'xxx __GNUC__ xxx') - self.assertEqual(check_config_h()[0], CONFIG_H_OK) + assert check_config_h()[0] == CONFIG_H_OK def test_get_msvcr(self): @@ -86,40 +78,41 @@ class CygwinCCompilerTestCase(support.TempdirManager, unittest.TestCase): '2.6.1 (r261:67515, Dec 6 2008, 16:42:21) ' '\n[GCC 4.0.1 (Apple Computer, Inc. build 5370)]' ) - self.assertEqual(get_msvcr(), None) + assert get_msvcr() is None # MSVC 7.0 sys.version = ( '2.5.1 (r251:54863, Apr 18 2007, 08:51:08) ' '[MSC v.1300 32 bits (Intel)]' ) - self.assertEqual(get_msvcr(), ['msvcr70']) + assert get_msvcr() == ['msvcr70'] # MSVC 7.1 sys.version = ( '2.5.1 (r251:54863, Apr 18 2007, 08:51:08) ' '[MSC v.1310 32 bits (Intel)]' ) - self.assertEqual(get_msvcr(), ['msvcr71']) + assert get_msvcr() == ['msvcr71'] # VS2005 / MSVC 8.0 sys.version = ( '2.5.1 (r251:54863, Apr 18 2007, 08:51:08) ' '[MSC v.1400 32 bits (Intel)]' ) - self.assertEqual(get_msvcr(), ['msvcr80']) + assert get_msvcr() == ['msvcr80'] # VS2008 / MSVC 9.0 sys.version = ( '2.5.1 (r251:54863, Apr 18 2007, 08:51:08) ' '[MSC v.1500 32 bits (Intel)]' ) - self.assertEqual(get_msvcr(), ['msvcr90']) + assert get_msvcr() == ['msvcr90'] sys.version = ( '3.10.0 (tags/v3.10.0:b494f59, Oct 4 2021, 18:46:30) ' '[MSC v.1929 32 bit (Intel)]' ) - self.assertEqual(get_msvcr(), ['ucrt', 'vcruntime140']) + assert get_msvcr() == ['ucrt', 'vcruntime140'] # unknown sys.version = ( '2.5.1 (r251:54863, Apr 18 2007, 08:51:08) ' '[MSC v.2000 32 bits (Intel)]' ) - self.assertRaises(ValueError, get_msvcr) + with pytest.raises(ValueError): + get_msvcr() diff --git a/distutils/tests/test_dep_util.py b/distutils/tests/test_dep_util.py index fb170c6f..2dcce1dd 100644 --- a/distutils/tests/test_dep_util.py +++ b/distutils/tests/test_dep_util.py @@ -1,13 +1,13 @@ """Tests for distutils.dep_util.""" -import unittest import os from distutils.dep_util import newer, newer_pairwise, newer_group from distutils.errors import DistutilsFileError from distutils.tests import support +import pytest -class DepUtilTestCase(support.TempdirManager, unittest.TestCase): +class TestDepUtil(support.TempdirManager): def test_newer(self): tmpdir = self.mkdtemp() @@ -15,17 +15,18 @@ class DepUtilTestCase(support.TempdirManager, unittest.TestCase): old_file = os.path.abspath(__file__) # Raise DistutilsFileError if 'new_file' does not exist. - self.assertRaises(DistutilsFileError, newer, new_file, old_file) + with pytest.raises(DistutilsFileError): + newer(new_file, old_file) # Return true if 'new_file' exists and is more recently modified than # 'old_file', or if 'new_file' exists and 'old_file' doesn't. self.write_file(new_file) - self.assertTrue(newer(new_file, 'I_dont_exist')) - self.assertTrue(newer(new_file, old_file)) + assert newer(new_file, 'I_dont_exist') + assert newer(new_file, old_file) # Return false if both exist and 'old_file' is the same age or younger # than 'new_file'. - self.assertFalse(newer(old_file, new_file)) + assert not newer(old_file, new_file) def test_newer_pairwise(self): tmpdir = self.mkdtemp() @@ -41,7 +42,7 @@ class DepUtilTestCase(support.TempdirManager, unittest.TestCase): self.write_file(two) self.write_file(four) - self.assertEqual(newer_pairwise([one, two], [three, four]), ([one], [three])) + assert newer_pairwise([one, two], [three, four]) == ([one], [three]) def test_newer_group(self): tmpdir = self.mkdtemp() @@ -57,13 +58,14 @@ class DepUtilTestCase(support.TempdirManager, unittest.TestCase): self.write_file(one) self.write_file(two) self.write_file(three) - self.assertTrue(newer_group([one, two, three], old_file)) - self.assertFalse(newer_group([one, two, old_file], three)) + assert newer_group([one, two, three], old_file) + assert not newer_group([one, two, old_file], three) # missing handling os.remove(one) - self.assertRaises(OSError, newer_group, [one, two, old_file], three) + with pytest.raises(OSError): + newer_group([one, two, old_file], three) - self.assertFalse(newer_group([one, two, old_file], three, missing='ignore')) + assert not newer_group([one, two, old_file], three, missing='ignore') - self.assertTrue(newer_group([one, two, old_file], three, missing='newer')) + assert newer_group([one, two, old_file], three, missing='newer') diff --git a/distutils/tests/test_dir_util.py b/distutils/tests/test_dir_util.py index 8231df69..cd7e018f 100644 --- a/distutils/tests/test_dir_util.py +++ b/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 ( @@ -16,67 +14,62 @@ from distutils.dir_util import ( from distutils import log from distutils.tests import support +import pytest -class DirUtilTestCase(support.TempdirManager, unittest.TestCase): +@pytest.fixture(autouse=True) +def stuff(request, monkeypatch, distutils_managed_tempdir): + self = request.instance + self._logs = [] + tmp_dir = self.mkdtemp() + self.root_target = os.path.join(tmp_dir, 'deep') + self.target = os.path.join(self.root_target, 'here') + self.target2 = os.path.join(tmp_dir, 'deep2') + monkeypatch.setattr(log, 'info', self._log) + + +class TestDirUtil(support.TempdirManager): def _log(self, msg, *args): if len(args) > 0: self._logs.append(msg % args) else: self._logs.append(msg) - def setUp(self): - super(DirUtilTestCase, self).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(DirUtilTestCase, self).tearDown() - def test_mkpath_remove_tree_verbosity(self): mkpath(self.target, verbose=0) wanted = [] - self.assertEqual(self._logs, wanted) + assert self._logs == wanted remove_tree(self.root_target, verbose=0) mkpath(self.target, verbose=1) wanted = ['creating %s' % self.root_target, 'creating %s' % self.target] - self.assertEqual(self._logs, wanted) + assert self._logs == wanted self._logs = [] remove_tree(self.root_target, verbose=1) wanted = ["removing '%s' (and everything under it)" % self.root_target] - self.assertEqual(self._logs, wanted) + 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) os.umask(umask) mkpath(self.target, 0o700) - self.assertEqual(stat.S_IMODE(os.stat(self.target).st_mode), 0o700 & ~umask) + assert stat.S_IMODE(os.stat(self.target).st_mode) == 0o700 & ~umask mkpath(self.target2, 0o555) - self.assertEqual(stat.S_IMODE(os.stat(self.target2).st_mode), 0o555 & ~umask) + assert stat.S_IMODE(os.stat(self.target2).st_mode) == 0o555 & ~umask def test_create_tree_verbosity(self): create_tree(self.root_target, ['one', 'two', 'three'], verbose=0) - self.assertEqual(self._logs, []) + assert self._logs == [] remove_tree(self.root_target, verbose=0) wanted = ['creating %s' % self.root_target] create_tree(self.root_target, ['one', 'two', 'three'], verbose=1) - self.assertEqual(self._logs, wanted) + assert self._logs == wanted remove_tree(self.root_target, verbose=0) @@ -85,7 +78,7 @@ class DirUtilTestCase(support.TempdirManager, unittest.TestCase): mkpath(self.target, verbose=0) copy_tree(self.target, self.target2, verbose=0) - self.assertEqual(self._logs, []) + assert self._logs == [] remove_tree(self.root_target, verbose=0) @@ -94,9 +87,9 @@ class DirUtilTestCase(support.TempdirManager, unittest.TestCase): with open(a_file, 'w') as f: f.write('some content') - wanted = ['copying %s -> %s' % (a_file, self.target2)] + wanted = ['copying {} -> {}'.format(a_file, self.target2)] copy_tree(self.target, self.target2, verbose=1) - self.assertEqual(self._logs, wanted) + assert self._logs == wanted remove_tree(self.root_target, verbose=0) remove_tree(self.target2, verbose=0) @@ -111,24 +104,24 @@ class DirUtilTestCase(support.TempdirManager, unittest.TestCase): fh.write('some content') copy_tree(self.target, self.target2) - self.assertEqual(os.listdir(self.target2), ['ok.txt']) + assert os.listdir(self.target2) == ['ok.txt'] remove_tree(self.root_target, verbose=0) remove_tree(self.target2, verbose=0) def test_ensure_relative(self): if os.sep == '/': - self.assertEqual(ensure_relative('/home/foo'), 'home/foo') - self.assertEqual(ensure_relative('some/path'), 'some/path') + assert ensure_relative('/home/foo') == 'home/foo' + assert ensure_relative('some/path') == 'some/path' else: # \\ - self.assertEqual(ensure_relative('c:\\home\\foo'), 'c:home\\foo') - self.assertEqual(ensure_relative('home\\foo'), 'home\\foo') + assert ensure_relative('c:\\home\\foo') == 'c:home\\foo' + assert ensure_relative('home\\foo') == 'home\\foo' def test_copy_tree_exception_in_listdir(self): """ An exception in listdir should raise a DistutilsFileError """ - with patch("os.listdir", side_effect=OSError()), self.assertRaises( + with mock.patch("os.listdir", side_effect=OSError()), pytest.raises( errors.DistutilsFileError ): src = self.tempdirs[-1] diff --git a/distutils/tests/test_dist.py b/distutils/tests/test_dist.py index 59d165a4..ddfaf921 100644 --- a/distutils/tests/test_dist.py +++ b/distutils/tests/test_dist.py @@ -2,11 +2,10 @@ import os import io import sys -import unittest import warnings import textwrap - -from unittest import mock +import functools +import unittest.mock as mock import pytest @@ -42,22 +41,18 @@ class TestDistribution(Distribution): return self._config_files +@pytest.fixture +def clear_argv(): + del sys.argv[1:] + + +@support.combine_markers @pytest.mark.usefixtures('save_env') -class DistributionTestCase( +@pytest.mark.usefixtures('save_argv') +class TestDistributionBehavior( support.LoggingSilencer, support.TempdirManager, - unittest.TestCase, ): - def setUp(self): - super(DistributionTestCase, self).setUp() - self.argv = sys.argv, sys.argv[:] - del sys.argv[1:] - - def tearDown(self): - sys.argv = self.argv[0] - sys.argv[:] = self.argv[1] - super(DistributionTestCase, self).tearDown() - def create_distribution(self, configfiles=()): d = TestDistribution() d._config_files = configfiles @@ -65,12 +60,12 @@ class DistributionTestCase( d.parse_command_line() return d - def test_command_packages_unspecified(self): + def test_command_packages_unspecified(self, clear_argv): sys.argv.append("build") d = self.create_distribution() - self.assertEqual(d.get_command_packages(), ["distutils.command"]) + assert d.get_command_packages() == ["distutils.command"] - def test_command_packages_cmdline(self): + def test_command_packages_cmdline(self, clear_argv): from distutils.tests.test_dist import test_dist sys.argv.extend( @@ -83,21 +78,22 @@ class DistributionTestCase( ) d = self.create_distribution() # let's actually try to load our test command: - self.assertEqual( - d.get_command_packages(), - ["distutils.command", "foo.bar", "distutils.tests"], - ) + assert d.get_command_packages() == [ + "distutils.command", + "foo.bar", + "distutils.tests", + ] cmd = d.get_command_obj("test_dist") - self.assertIsInstance(cmd, test_dist) - self.assertEqual(cmd.sample_option, "sometext") + 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): + def test_venv_install_options(self, request): sys.argv.append("install") - self.addCleanup(os.unlink, TESTFN) + request.addfinalizer(functools.partial(os.unlink, TESTFN)) fakepath = '/somedir' @@ -144,23 +140,23 @@ class DistributionTestCase( 'root': option_tuple, } - self.assertEqual( - sorted(d.command_options.get('install').keys()), sorted(result_dict.keys()) + assert sorted(d.command_options.get('install').keys()) == sorted( + result_dict.keys() ) for (key, value) in d.command_options.get('install').items(): - self.assertEqual(value, result_dict[key]) + assert value == result_dict[key] # Test case: In a Virtual Environment with mock.patch.multiple(sys, prefix='/a', base_prefix='/b'): d = self.create_distribution([TESTFN]) for key in result_dict.keys(): - self.assertNotIn(key, d.command_options.get('install', {})) + assert key not in d.command_options.get('install', {}) - def test_command_packages_configfile(self): + def test_command_packages_configfile(self, request, clear_argv): sys.argv.append("build") - self.addCleanup(os.unlink, TESTFN) + request.addfinalizer(functools.partial(os.unlink, TESTFN)) f = open(TESTFN, "w") try: print("[global]", file=f) @@ -169,22 +165,20 @@ class DistributionTestCase( f.close() d = self.create_distribution([TESTFN]) - self.assertEqual( - d.get_command_packages(), ["distutils.command", "foo.bar", "splat"] - ) + assert d.get_command_packages() == ["distutils.command", "foo.bar", "splat"] # ensure command line overrides config: sys.argv[1:] = ["--command-packages", "spork", "build"] d = self.create_distribution([TESTFN]) - self.assertEqual(d.get_command_packages(), ["distutils.command", "spork"]) + assert d.get_command_packages() == ["distutils.command", "spork"] # Setting --command-packages to '' should cause the default to # be used even if a config file specified something else: sys.argv[1:] = ["--command-packages", "", "build"] d = self.create_distribution([TESTFN]) - self.assertEqual(d.get_command_packages(), ["distutils.command"]) + assert d.get_command_packages() == ["distutils.command"] - def test_empty_options(self): + def test_empty_options(self, request): # an empty options dictionary should not stay in the # list of attributes @@ -194,7 +188,9 @@ class DistributionTestCase( def _warn(msg): warns.append(msg) - self.addCleanup(setattr, warnings, 'warn', warnings.warn) + request.addfinalizer( + functools.partial(setattr, warnings, 'warn', warnings.warn) + ) warnings.warn = _warn dist = Distribution( attrs={ @@ -206,8 +202,8 @@ class DistributionTestCase( } ) - self.assertEqual(len(warns), 0) - self.assertNotIn('options', dir(dist)) + assert len(warns) == 0 + assert 'options' not in dir(dist) def test_finalize_options(self): attrs = {'keywords': 'one,two', 'platforms': 'one,two'} @@ -216,32 +212,33 @@ class DistributionTestCase( dist.finalize_options() # finalize_option splits platforms and keywords - self.assertEqual(dist.metadata.platforms, ['one', 'two']) - self.assertEqual(dist.metadata.keywords, ['one', 'two']) + assert dist.metadata.platforms == ['one', 'two'] + assert dist.metadata.keywords == ['one', 'two'] attrs = {'keywords': 'foo bar', 'platforms': 'foo bar'} dist = Distribution(attrs=attrs) dist.finalize_options() - self.assertEqual(dist.metadata.platforms, ['foo bar']) - self.assertEqual(dist.metadata.keywords, ['foo bar']) + assert dist.metadata.platforms == ['foo bar'] + assert dist.metadata.keywords == ['foo bar'] def test_get_command_packages(self): dist = Distribution() - self.assertEqual(dist.command_packages, None) + assert dist.command_packages is None cmds = dist.get_command_packages() - self.assertEqual(cmds, ['distutils.command']) - self.assertEqual(dist.command_packages, ['distutils.command']) + assert cmds == ['distutils.command'] + assert dist.command_packages == ['distutils.command'] dist.command_packages = 'one,two' cmds = dist.get_command_packages() - self.assertEqual(cmds, ['distutils.command', 'one', 'two']) + assert cmds == ['distutils.command', 'one', 'two'] def test_announce(self): # make sure the level is known dist = Distribution() args = ('ok',) kwargs = {'level': 'ok2'} - self.assertRaises(ValueError, dist.announce, args, kwargs) + with pytest.raises(ValueError): + dist.announce(args, kwargs) def test_find_config_files_disable(self): # Ticket #1180: Allow user to disable their home config file. @@ -269,20 +266,12 @@ class DistributionTestCase( os.path.expanduser = old_expander # make sure --no-user-cfg disables the user cfg file - self.assertEqual(len(all_files) - 1, len(files)) + assert len(all_files) - 1 == len(files) @pytest.mark.usefixtures('save_env') -class MetadataTestCase(support.TempdirManager, unittest.TestCase): - def setUp(self): - super(MetadataTestCase, self).setUp() - self.argv = sys.argv, sys.argv[:] - - def tearDown(self): - sys.argv = self.argv[0] - sys.argv[:] = self.argv[1] - super(MetadataTestCase, self).tearDown() - +@pytest.mark.usefixtures('save_argv') +class MetadataTestCase(support.TempdirManager): def format_metadata(self, dist): sio = io.StringIO() dist.metadata.write_pkg_file(sio) @@ -292,10 +281,10 @@ class MetadataTestCase(support.TempdirManager, unittest.TestCase): attrs = {"name": "package", "version": "1.0"} dist = Distribution(attrs) meta = self.format_metadata(dist) - self.assertIn("Metadata-Version: 1.0", meta) - self.assertNotIn("provides:", meta.lower()) - self.assertNotIn("requires:", meta.lower()) - self.assertNotIn("obsoletes:", meta.lower()) + assert "Metadata-Version: 1.0" in meta + assert "provides:" not in meta.lower() + assert "requires:" not in meta.lower() + assert "obsoletes:" not in meta.lower() def test_provides(self): attrs = { @@ -304,19 +293,18 @@ class MetadataTestCase(support.TempdirManager, unittest.TestCase): "provides": ["package", "package.sub"], } dist = Distribution(attrs) - self.assertEqual(dist.metadata.get_provides(), ["package", "package.sub"]) - self.assertEqual(dist.get_provides(), ["package", "package.sub"]) + assert dist.metadata.get_provides() == ["package", "package.sub"] + assert dist.get_provides() == ["package", "package.sub"] meta = self.format_metadata(dist) - self.assertIn("Metadata-Version: 1.1", meta) - self.assertNotIn("requires:", meta.lower()) - self.assertNotIn("obsoletes:", meta.lower()) + assert "Metadata-Version: 1.1" in meta + assert "requires:" not in meta.lower() + assert "obsoletes:" not in meta.lower() def test_provides_illegal(self): - self.assertRaises( - ValueError, - Distribution, - {"name": "package", "version": "1.0", "provides": ["my.pkg (splat)"]}, - ) + with pytest.raises(ValueError): + Distribution( + {"name": "package", "version": "1.0", "provides": ["my.pkg (splat)"]}, + ) def test_requires(self): attrs = { @@ -325,26 +313,25 @@ class MetadataTestCase(support.TempdirManager, unittest.TestCase): "requires": ["other", "another (==1.0)"], } dist = Distribution(attrs) - self.assertEqual(dist.metadata.get_requires(), ["other", "another (==1.0)"]) - self.assertEqual(dist.get_requires(), ["other", "another (==1.0)"]) + assert dist.metadata.get_requires() == ["other", "another (==1.0)"] + assert dist.get_requires() == ["other", "another (==1.0)"] meta = self.format_metadata(dist) - self.assertIn("Metadata-Version: 1.1", meta) - self.assertNotIn("provides:", meta.lower()) - self.assertIn("Requires: other", meta) - self.assertIn("Requires: another (==1.0)", meta) - self.assertNotIn("obsoletes:", meta.lower()) + assert "Metadata-Version: 1.1" in meta + assert "provides:" not in meta.lower() + assert "Requires: other" in meta + assert "Requires: another (==1.0)" in meta + assert "obsoletes:" not in meta.lower() def test_requires_illegal(self): - self.assertRaises( - ValueError, - Distribution, - {"name": "package", "version": "1.0", "requires": ["my.pkg (splat)"]}, - ) + with pytest.raises(ValueError): + Distribution( + {"name": "package", "version": "1.0", "requires": ["my.pkg (splat)"]}, + ) def test_requires_to_list(self): attrs = {"name": "package", "requires": iter(["other"])} dist = Distribution(attrs) - self.assertIsInstance(dist.metadata.requires, list) + assert isinstance(dist.metadata.requires, list) def test_obsoletes(self): attrs = { @@ -353,26 +340,25 @@ class MetadataTestCase(support.TempdirManager, unittest.TestCase): "obsoletes": ["other", "another (<1.0)"], } dist = Distribution(attrs) - self.assertEqual(dist.metadata.get_obsoletes(), ["other", "another (<1.0)"]) - self.assertEqual(dist.get_obsoletes(), ["other", "another (<1.0)"]) + assert dist.metadata.get_obsoletes() == ["other", "another (<1.0)"] + assert dist.get_obsoletes() == ["other", "another (<1.0)"] meta = self.format_metadata(dist) - self.assertIn("Metadata-Version: 1.1", meta) - self.assertNotIn("provides:", meta.lower()) - self.assertNotIn("requires:", meta.lower()) - self.assertIn("Obsoletes: other", meta) - self.assertIn("Obsoletes: another (<1.0)", meta) + assert "Metadata-Version: 1.1" in meta + assert "provides:" not in meta.lower() + assert "requires:" not in meta.lower() + assert "Obsoletes: other" in meta + assert "Obsoletes: another (<1.0)" in meta def test_obsoletes_illegal(self): - self.assertRaises( - ValueError, - Distribution, - {"name": "package", "version": "1.0", "obsoletes": ["my.pkg (splat)"]}, - ) + with pytest.raises(ValueError): + Distribution( + {"name": "package", "version": "1.0", "obsoletes": ["my.pkg (splat)"]}, + ) def test_obsoletes_to_list(self): attrs = {"name": "package", "obsoletes": iter(["other"])} dist = Distribution(attrs) - self.assertIsInstance(dist.metadata.obsoletes, list) + assert isinstance(dist.metadata.obsoletes, list) def test_classifier(self): attrs = { @@ -381,11 +367,9 @@ class MetadataTestCase(support.TempdirManager, unittest.TestCase): 'classifiers': ['Programming Language :: Python :: 3'], } dist = Distribution(attrs) - self.assertEqual( - dist.get_classifiers(), ['Programming Language :: Python :: 3'] - ) + assert dist.get_classifiers() == ['Programming Language :: Python :: 3'] meta = self.format_metadata(dist) - self.assertIn('Metadata-Version: 1.1', meta) + assert 'Metadata-Version: 1.1' in meta def test_classifier_invalid_type(self): attrs = { @@ -396,10 +380,10 @@ class MetadataTestCase(support.TempdirManager, unittest.TestCase): with captured_stderr() as error: d = Distribution(attrs) # should have warning about passing a non-list - self.assertIn('should be a list', error.getvalue()) + assert 'should be a list' in error.getvalue() # should be converted to a list - self.assertIsInstance(d.metadata.classifiers, list) - self.assertEqual(d.metadata.classifiers, list(attrs['classifiers'])) + assert isinstance(d.metadata.classifiers, list) + assert d.metadata.classifiers == list(attrs['classifiers']) def test_keywords(self): attrs = { @@ -408,7 +392,7 @@ class MetadataTestCase(support.TempdirManager, unittest.TestCase): 'keywords': ['spam', 'eggs', 'life of brian'], } dist = Distribution(attrs) - self.assertEqual(dist.get_keywords(), ['spam', 'eggs', 'life of brian']) + assert dist.get_keywords() == ['spam', 'eggs', 'life of brian'] def test_keywords_invalid_type(self): attrs = { @@ -419,10 +403,10 @@ class MetadataTestCase(support.TempdirManager, unittest.TestCase): with captured_stderr() as error: d = Distribution(attrs) # should have warning about passing a non-list - self.assertIn('should be a list', error.getvalue()) + assert 'should be a list' in error.getvalue() # should be converted to a list - self.assertIsInstance(d.metadata.keywords, list) - self.assertEqual(d.metadata.keywords, list(attrs['keywords'])) + assert isinstance(d.metadata.keywords, list) + assert d.metadata.keywords == list(attrs['keywords']) def test_platforms(self): attrs = { @@ -431,7 +415,7 @@ class MetadataTestCase(support.TempdirManager, unittest.TestCase): 'platforms': ['GNU/Linux', 'Some Evil Platform'], } dist = Distribution(attrs) - self.assertEqual(dist.get_platforms(), ['GNU/Linux', 'Some Evil Platform']) + assert dist.get_platforms() == ['GNU/Linux', 'Some Evil Platform'] def test_platforms_invalid_types(self): attrs = { @@ -442,10 +426,10 @@ class MetadataTestCase(support.TempdirManager, unittest.TestCase): with captured_stderr() as error: d = Distribution(attrs) # should have warning about passing a non-list - self.assertIn('should be a list', error.getvalue()) + assert 'should be a list' in error.getvalue() # should be converted to a list - self.assertIsInstance(d.metadata.platforms, list) - self.assertEqual(d.metadata.platforms, list(attrs['platforms'])) + assert isinstance(d.metadata.platforms, list) + assert d.metadata.platforms == list(attrs['platforms']) def test_download_url(self): attrs = { @@ -455,7 +439,7 @@ class MetadataTestCase(support.TempdirManager, unittest.TestCase): } dist = Distribution(attrs) meta = self.format_metadata(dist) - self.assertIn('Metadata-Version: 1.1', meta) + assert 'Metadata-Version: 1.1' in meta def test_long_description(self): long_desc = textwrap.dedent( @@ -470,7 +454,7 @@ class MetadataTestCase(support.TempdirManager, unittest.TestCase): dist = Distribution(attrs) meta = self.format_metadata(dist) meta = meta.replace('\n' + 8 * ' ', '\n') - self.assertIn(long_desc, meta) + assert long_desc in meta def test_custom_pydistutils(self): # fixes #2166 @@ -495,15 +479,15 @@ class MetadataTestCase(support.TempdirManager, unittest.TestCase): if sys.platform in ('linux', 'darwin'): os.environ['HOME'] = temp_dir files = dist.find_config_files() - self.assertIn(user_filename, files) + assert user_filename in files # win32-style if sys.platform == 'win32': # home drive should be found os.environ['USERPROFILE'] = temp_dir files = dist.find_config_files() - self.assertIn( - user_filename, files, '%r not found in %r' % (user_filename, files) + assert user_filename in files, '{!r} not found in {!r}'.format( + user_filename, files ) finally: os.remove(user_filename) @@ -511,8 +495,8 @@ class MetadataTestCase(support.TempdirManager, unittest.TestCase): def test_fix_help_options(self): help_tuples = [('a', 'b', 'c', 'd'), (1, 2, 3, 4)] fancy_options = fix_help_options(help_tuples) - self.assertEqual(fancy_options[0], ('a', 'b', 'c')) - self.assertEqual(fancy_options[1], (1, 2, 3)) + assert fancy_options[0] == ('a', 'b', 'c') + assert fancy_options[1] == (1, 2, 3) def test_show_help(self): # smoke test, just makes sure some help is displayed @@ -525,7 +509,7 @@ class MetadataTestCase(support.TempdirManager, unittest.TestCase): dist.parse_command_line() output = [line for line in s.getvalue().split('\n') if line.strip() != ''] - self.assertTrue(output) + assert output def test_read_metadata(self): attrs = { @@ -547,11 +531,11 @@ class MetadataTestCase(support.TempdirManager, unittest.TestCase): PKG_INFO.seek(0) metadata.read_pkg_file(PKG_INFO) - self.assertEqual(metadata.name, "package") - self.assertEqual(metadata.version, "1.0") - self.assertEqual(metadata.description, "xxx") - self.assertEqual(metadata.download_url, 'http://example.com') - self.assertEqual(metadata.keywords, ['one', 'two']) - self.assertEqual(metadata.platforms, None) - self.assertEqual(metadata.obsoletes, None) - self.assertEqual(metadata.requires, ['foo']) + assert metadata.name == "package" + assert metadata.version == "1.0" + assert metadata.description == "xxx" + assert metadata.download_url == 'http://example.com' + assert metadata.keywords == ['one', 'two'] + assert metadata.platforms is None + assert metadata.obsoletes is None + assert metadata.requires == ['foo'] diff --git a/distutils/tests/test_extension.py b/distutils/tests/test_extension.py index bf573930..f86af073 100644 --- a/distutils/tests/test_extension.py +++ b/distutils/tests/test_extension.py @@ -1,14 +1,14 @@ """Tests for distutils.extension.""" -import unittest import os import warnings from distutils.extension import read_setup_file, Extension from .py38compat import check_warnings +import pytest -class ExtensionTestCase(unittest.TestCase): +class TestExtension: def test_read_setup_file(self): # trying to read a Setup file # (sample extracted from the PyGame project) @@ -57,20 +57,23 @@ class ExtensionTestCase(unittest.TestCase): 'transform', ] - self.assertEqual(names, wanted) + assert names == wanted def test_extension_init(self): # the first argument, which is the name, must be a string - self.assertRaises(AssertionError, Extension, 1, []) + with pytest.raises(AssertionError): + Extension(1, []) ext = Extension('name', []) - self.assertEqual(ext.name, 'name') + assert ext.name == 'name' # the second argument, which is the list of files, must # be a list of strings - self.assertRaises(AssertionError, Extension, 'name', 'file') - self.assertRaises(AssertionError, Extension, 'name', ['file', 1]) + with pytest.raises(AssertionError): + Extension('name', 'file') + with pytest.raises(AssertionError): + Extension('name', ['file', 1]) ext = Extension('name', ['file1', 'file2']) - self.assertEqual(ext.sources, ['file1', 'file2']) + assert ext.sources == ['file1', 'file2'] # others arguments have defaults for attr in ( @@ -87,17 +90,15 @@ class ExtensionTestCase(unittest.TestCase): 'swig_opts', 'depends', ): - self.assertEqual(getattr(ext, attr), []) + assert getattr(ext, attr) == [] - self.assertEqual(ext.language, None) - self.assertEqual(ext.optional, None) + assert ext.language is None + assert ext.optional is None # if there are unknown keyword options, warn about them with check_warnings() as w: warnings.simplefilter('always') ext = Extension('name', ['file1', 'file2'], chic=True) - self.assertEqual(len(w.warnings), 1) - self.assertEqual( - str(w.warnings[0].message), "Unknown Extension options: 'chic'" - ) + assert len(w.warnings) == 1 + assert str(w.warnings[0].message) == "Unknown Extension options: 'chic'" diff --git a/distutils/tests/test_file_util.py b/distutils/tests/test_file_util.py index 6b333d5e..b2e83c52 100644 --- a/distutils/tests/test_file_util.py +++ b/distutils/tests/test_file_util.py @@ -1,37 +1,34 @@ """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 from distutils.tests import support from distutils.errors import DistutilsFileError from .py38compat import unlink +import pytest -class FileUtilTestCase(support.TempdirManager, unittest.TestCase): +@pytest.fixture(autouse=True) +def stuff(request, monkeypatch, distutils_managed_tempdir): + self = request.instance + self._logs = [] + tmp_dir = self.mkdtemp() + self.source = os.path.join(tmp_dir, 'f1') + self.target = os.path.join(tmp_dir, 'f2') + self.target_dir = os.path.join(tmp_dir, 'd1') + monkeypatch.setattr(log, 'info', self._log) + + +class TestFileUtil(support.TempdirManager): def _log(self, msg, *args): if len(args) > 0: self._logs.append(msg % args) else: self._logs.append(msg) - def setUp(self): - super(FileUtilTestCase, self).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(FileUtilTestCase, self).tearDown() - def test_move_file_verbosity(self): f = open(self.source, 'w') try: @@ -41,14 +38,14 @@ class FileUtilTestCase(support.TempdirManager, unittest.TestCase): move_file(self.source, self.target, verbose=0) wanted = [] - self.assertEqual(self._logs, wanted) + assert self._logs == wanted # back to original state move_file(self.target, self.source, verbose=0) move_file(self.source, self.target, verbose=1) - wanted = ['moving %s -> %s' % (self.source, self.target)] - self.assertEqual(self._logs, wanted) + wanted = ['moving {} -> {}'.format(self.source, self.target)] + assert self._logs == wanted # back to original state move_file(self.target, self.source, verbose=0) @@ -57,12 +54,12 @@ class FileUtilTestCase(support.TempdirManager, unittest.TestCase): # now the target is a dir os.mkdir(self.target_dir) move_file(self.source, self.target_dir, verbose=1) - wanted = ['moving %s -> %s' % (self.source, self.target_dir)] - self.assertEqual(self._logs, wanted) + wanted = ['moving {} -> {}'.format(self.source, self.target_dir)] + assert self._logs == wanted def test_move_file_exception_unpacking_rename(self): # see issue 22182 - with patch("os.rename", side_effect=OSError("wrong", 1)), self.assertRaises( + with mock.patch("os.rename", side_effect=OSError("wrong", 1)), pytest.raises( DistutilsFileError ): with open(self.source, 'w') as fobj: @@ -71,9 +68,11 @@ class FileUtilTestCase(support.TempdirManager, unittest.TestCase): def test_move_file_exception_unpacking_unlink(self): # see issue 22182 - with patch("os.rename", side_effect=OSError(errno.EXDEV, "wrong")), patch( - "os.unlink", side_effect=OSError("wrong", 1) - ), self.assertRaises(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) @@ -93,10 +92,10 @@ class FileUtilTestCase(support.TempdirManager, unittest.TestCase): copy_file(self.source, self.target, link='hard') st2 = os.stat(self.source) st3 = os.stat(self.target) - self.assertTrue(os.path.samestat(st, st2), (st, st2)) - self.assertTrue(os.path.samestat(st2, st3), (st2, st3)) - with open(self.source, 'r') as f: - self.assertEqual(f.read(), 'some content') + assert os.path.samestat(st, st2), (st, st2) + assert os.path.samestat(st2, st3), (st2, st3) + with open(self.source) as f: + assert f.read() == 'some content' def test_copy_file_hard_link_failure(self): # If hard linking fails, copy_file() falls back on copying file @@ -105,12 +104,12 @@ class FileUtilTestCase(support.TempdirManager, unittest.TestCase): with open(self.source, 'w') as f: f.write('some content') st = os.stat(self.source) - with patch("os.link", side_effect=OSError(0, "linking unsupported")): + with mock.patch("os.link", side_effect=OSError(0, "linking unsupported")): copy_file(self.source, self.target, link='hard') st2 = os.stat(self.source) st3 = os.stat(self.target) - self.assertTrue(os.path.samestat(st, st2), (st, st2)) - self.assertFalse(os.path.samestat(st2, st3), (st2, st3)) + assert os.path.samestat(st, st2), (st, st2) + assert not os.path.samestat(st2, st3), (st2, st3) for fn in (self.source, self.target): - with open(fn, 'r') as f: - self.assertEqual(f.read(), 'some content') + with open(fn) as f: + assert f.read() == 'some content' diff --git a/distutils/tests/test_filelist.py b/distutils/tests/test_filelist.py index 0673139e..26071820 100644 --- a/distutils/tests/test_filelist.py +++ b/distutils/tests/test_filelist.py @@ -1,7 +1,6 @@ """Tests for distutils.filelist.""" import os import re -import unittest from distutils import debug from distutils.log import WARN from distutils.errors import DistutilsTemplateError @@ -12,6 +11,7 @@ from test.support import captured_stdout from distutils.tests import support from . import py38compat as os_helper +import pytest MANIFEST_IN = """\ @@ -35,13 +35,13 @@ def make_local_path(s): return s.replace('/', os.sep) -class FileListTestCase(support.LoggingSilencer, unittest.TestCase): +class TestFileList(support.LoggingSilencer): def assertNoWarnings(self): - self.assertEqual(self.get_logs(WARN), []) + assert self.get_logs(WARN) == [] self.clear_logs() def assertWarnings(self): - self.assertGreater(len(self.get_logs(WARN)), 0) + assert len(self.get_logs(WARN)) > 0 self.clear_logs() def test_glob_to_re(self): @@ -61,7 +61,7 @@ class FileListTestCase(support.LoggingSilencer, unittest.TestCase): (r'foo\\??', r'(?s:foo\\\\[^%(sep)s][^%(sep)s])\Z'), ): regex = regex % {'sep': sep} - self.assertEqual(glob_to_re(glob), regex) + assert glob_to_re(glob) == regex def test_process_template_line(self): # testing all MANIFEST.in template patterns @@ -106,19 +106,19 @@ class FileListTestCase(support.LoggingSilencer, unittest.TestCase): mlp('dir/dir2/graft2'), ] - self.assertEqual(file_list.files, wanted) + assert file_list.files == wanted def test_debug_print(self): file_list = FileList() with captured_stdout() as stdout: file_list.debug_print('xxx') - self.assertEqual(stdout.getvalue(), '') + assert stdout.getvalue() == '' debug.DEBUG = True try: with captured_stdout() as stdout: file_list.debug_print('xxx') - self.assertEqual(stdout.getvalue(), 'xxx\n') + assert stdout.getvalue() == 'xxx\n' finally: debug.DEBUG = False @@ -126,7 +126,7 @@ class FileListTestCase(support.LoggingSilencer, unittest.TestCase): file_list = FileList() files = ['a', 'b', 'c'] file_list.set_allfiles(files) - self.assertEqual(file_list.allfiles, files) + assert file_list.allfiles == files def test_remove_duplicates(self): file_list = FileList() @@ -134,61 +134,57 @@ class FileListTestCase(support.LoggingSilencer, unittest.TestCase): # files must be sorted beforehand (sdist does it) file_list.sort() file_list.remove_duplicates() - self.assertEqual(file_list.files, ['a', 'b', 'c', 'g']) + assert file_list.files == ['a', 'b', 'c', 'g'] def test_translate_pattern(self): # not regex - self.assertTrue( - hasattr(translate_pattern('a', anchor=True, is_regex=False), 'search') - ) + assert hasattr(translate_pattern('a', anchor=True, is_regex=False), 'search') # is a regex regex = re.compile('a') - self.assertEqual(translate_pattern(regex, anchor=True, is_regex=True), regex) + assert translate_pattern(regex, anchor=True, is_regex=True) == regex # plain string flagged as regex - self.assertTrue( - hasattr(translate_pattern('a', anchor=True, is_regex=True), 'search') - ) + assert hasattr(translate_pattern('a', anchor=True, is_regex=True), 'search') # glob support - self.assertTrue( - translate_pattern('*.py', anchor=True, is_regex=False).search('filelist.py') + assert translate_pattern('*.py', anchor=True, is_regex=False).search( + 'filelist.py' ) def test_exclude_pattern(self): # return False if no match file_list = FileList() - self.assertFalse(file_list.exclude_pattern('*.py')) + assert not file_list.exclude_pattern('*.py') # return True if files match file_list = FileList() file_list.files = ['a.py', 'b.py'] - self.assertTrue(file_list.exclude_pattern('*.py')) + assert file_list.exclude_pattern('*.py') # test excludes file_list = FileList() file_list.files = ['a.py', 'a.txt'] file_list.exclude_pattern('*.py') - self.assertEqual(file_list.files, ['a.txt']) + assert file_list.files == ['a.txt'] def test_include_pattern(self): # return False if no match file_list = FileList() file_list.set_allfiles([]) - self.assertFalse(file_list.include_pattern('*.py')) + assert not file_list.include_pattern('*.py') # return True if files match file_list = FileList() file_list.set_allfiles(['a.py', 'b.txt']) - self.assertTrue(file_list.include_pattern('*.py')) + assert file_list.include_pattern('*.py') # test * matches all files file_list = FileList() - self.assertIsNone(file_list.allfiles) + assert file_list.allfiles is None file_list.set_allfiles(['a.py', 'b.txt']) file_list.include_pattern('*') - self.assertEqual(file_list.allfiles, ['a.py', 'b.txt']) + assert file_list.allfiles == ['a.py', 'b.txt'] def test_process_template(self): mlp = make_local_path @@ -205,20 +201,19 @@ class FileListTestCase(support.LoggingSilencer, unittest.TestCase): 'prune', 'blarg', ): - self.assertRaises( - DistutilsTemplateError, file_list.process_template_line, action - ) + with pytest.raises(DistutilsTemplateError): + file_list.process_template_line(action) # include file_list = FileList() file_list.set_allfiles(['a.py', 'b.txt', mlp('d/c.py')]) file_list.process_template_line('include *.py') - self.assertEqual(file_list.files, ['a.py']) + assert file_list.files == ['a.py'] self.assertNoWarnings() file_list.process_template_line('include *.rb') - self.assertEqual(file_list.files, ['a.py']) + assert file_list.files == ['a.py'] self.assertWarnings() # exclude @@ -226,11 +221,11 @@ class FileListTestCase(support.LoggingSilencer, unittest.TestCase): file_list.files = ['a.py', 'b.txt', mlp('d/c.py')] file_list.process_template_line('exclude *.py') - self.assertEqual(file_list.files, ['b.txt', mlp('d/c.py')]) + assert file_list.files == ['b.txt', mlp('d/c.py')] self.assertNoWarnings() file_list.process_template_line('exclude *.rb') - self.assertEqual(file_list.files, ['b.txt', mlp('d/c.py')]) + assert file_list.files == ['b.txt', mlp('d/c.py')] self.assertWarnings() # global-include @@ -238,11 +233,11 @@ class FileListTestCase(support.LoggingSilencer, unittest.TestCase): file_list.set_allfiles(['a.py', 'b.txt', mlp('d/c.py')]) file_list.process_template_line('global-include *.py') - self.assertEqual(file_list.files, ['a.py', mlp('d/c.py')]) + assert file_list.files == ['a.py', mlp('d/c.py')] self.assertNoWarnings() file_list.process_template_line('global-include *.rb') - self.assertEqual(file_list.files, ['a.py', mlp('d/c.py')]) + assert file_list.files == ['a.py', mlp('d/c.py')] self.assertWarnings() # global-exclude @@ -250,11 +245,11 @@ class FileListTestCase(support.LoggingSilencer, unittest.TestCase): file_list.files = ['a.py', 'b.txt', mlp('d/c.py')] file_list.process_template_line('global-exclude *.py') - self.assertEqual(file_list.files, ['b.txt']) + assert file_list.files == ['b.txt'] self.assertNoWarnings() file_list.process_template_line('global-exclude *.rb') - self.assertEqual(file_list.files, ['b.txt']) + assert file_list.files == ['b.txt'] self.assertWarnings() # recursive-include @@ -262,11 +257,11 @@ class FileListTestCase(support.LoggingSilencer, unittest.TestCase): file_list.set_allfiles(['a.py', mlp('d/b.py'), mlp('d/c.txt'), mlp('d/d/e.py')]) file_list.process_template_line('recursive-include d *.py') - self.assertEqual(file_list.files, [mlp('d/b.py'), mlp('d/d/e.py')]) + assert file_list.files == [mlp('d/b.py'), mlp('d/d/e.py')] self.assertNoWarnings() file_list.process_template_line('recursive-include e *.py') - self.assertEqual(file_list.files, [mlp('d/b.py'), mlp('d/d/e.py')]) + assert file_list.files == [mlp('d/b.py'), mlp('d/d/e.py')] self.assertWarnings() # recursive-exclude @@ -274,11 +269,11 @@ class FileListTestCase(support.LoggingSilencer, unittest.TestCase): file_list.files = ['a.py', mlp('d/b.py'), mlp('d/c.txt'), mlp('d/d/e.py')] file_list.process_template_line('recursive-exclude d *.py') - self.assertEqual(file_list.files, ['a.py', mlp('d/c.txt')]) + assert file_list.files == ['a.py', mlp('d/c.txt')] self.assertNoWarnings() file_list.process_template_line('recursive-exclude e *.py') - self.assertEqual(file_list.files, ['a.py', mlp('d/c.txt')]) + assert file_list.files == ['a.py', mlp('d/c.txt')] self.assertWarnings() # graft @@ -286,11 +281,11 @@ class FileListTestCase(support.LoggingSilencer, unittest.TestCase): file_list.set_allfiles(['a.py', mlp('d/b.py'), mlp('d/d/e.py'), mlp('f/f.py')]) file_list.process_template_line('graft d') - self.assertEqual(file_list.files, [mlp('d/b.py'), mlp('d/d/e.py')]) + assert file_list.files == [mlp('d/b.py'), mlp('d/d/e.py')] self.assertNoWarnings() file_list.process_template_line('graft e') - self.assertEqual(file_list.files, [mlp('d/b.py'), mlp('d/d/e.py')]) + assert file_list.files == [mlp('d/b.py'), mlp('d/d/e.py')] self.assertWarnings() # prune @@ -298,20 +293,20 @@ class FileListTestCase(support.LoggingSilencer, unittest.TestCase): file_list.files = ['a.py', mlp('d/b.py'), mlp('d/d/e.py'), mlp('f/f.py')] file_list.process_template_line('prune d') - self.assertEqual(file_list.files, ['a.py', mlp('f/f.py')]) + assert file_list.files == ['a.py', mlp('f/f.py')] self.assertNoWarnings() file_list.process_template_line('prune e') - self.assertEqual(file_list.files, ['a.py', mlp('f/f.py')]) + assert file_list.files == ['a.py', mlp('f/f.py')] self.assertWarnings() -class FindAllTestCase(unittest.TestCase): +class TestFindAll: @os_helper.skip_unless_symlink def test_missing_symlink(self): with os_helper.temp_cwd(): os.symlink('foo', 'bar') - self.assertEqual(filelist.findall(), []) + assert filelist.findall() == [] def test_basic_discovery(self): """ @@ -327,7 +322,7 @@ class FindAllTestCase(unittest.TestCase): file2 = os.path.join('bar', 'file2.txt') os_helper.create_empty_file(file2) expected = [file2, file1] - self.assertEqual(sorted(filelist.findall()), expected) + assert sorted(filelist.findall()) == expected def test_non_local_discovery(self): """ @@ -338,7 +333,7 @@ class FindAllTestCase(unittest.TestCase): file1 = os.path.join(temp_dir, 'file1.txt') os_helper.create_empty_file(file1) expected = [file1] - self.assertEqual(filelist.findall(temp_dir), expected) + assert filelist.findall(temp_dir) == expected @os_helper.skip_unless_symlink def test_symlink_loop(self): diff --git a/distutils/tests/test_install.py b/distutils/tests/test_install.py index 32ee02c7..32a18b2f 100644 --- a/distutils/tests/test_install.py +++ b/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 @@ -26,11 +26,11 @@ def _make_ext_name(modname): return modname + sysconfig.get_config_var('EXT_SUFFIX') +@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)', @@ -55,13 +55,13 @@ class InstallTestCase( cmd.home = destination cmd.ensure_finalized() - self.assertEqual(cmd.install_base, destination) - self.assertEqual(cmd.install_platbase, destination) + assert cmd.install_base == destination + assert cmd.install_platbase == destination def check_path(got, expected): got = os.path.normpath(got) expected = os.path.normpath(expected) - self.assertEqual(got, expected) + assert got == expected impl_name = sys.implementation.name.replace("cpython", "python") libdir = os.path.join(destination, "lib", impl_name) @@ -77,76 +77,60 @@ class InstallTestCase( check_path(cmd.install_scripts, os.path.join(destination, "bin")) check_path(cmd.install_data, destination) - def test_user_site(self): + def test_user_site(self, monkeypatch): # test install with --user # preparing the environment for the test - self.old_user_base = site.USER_BASE - self.old_user_site = site.USER_SITE self.tmpdir = self.mkdtemp() - self.user_base = os.path.join(self.tmpdir, 'B') - self.user_site = os.path.join(self.tmpdir, 'S') - site.USER_BASE = self.user_base - site.USER_SITE = self.user_site - install_module.USER_BASE = self.user_base - install_module.USER_SITE = self.user_site + orig_site = site.USER_SITE + orig_base = site.USER_BASE + monkeypatch.setattr(site, 'USER_BASE', os.path.join(self.tmpdir, 'B')) + monkeypatch.setattr(site, 'USER_SITE', os.path.join(self.tmpdir, 'S')) + monkeypatch.setattr(install_module, 'USER_BASE', site.USER_BASE) + monkeypatch.setattr(install_module, 'USER_SITE', site.USER_SITE) def _expanduser(path): if path.startswith('~'): return os.path.normpath(self.tmpdir + path[1:]) return path - self.old_expand = os.path.expanduser - os.path.expanduser = _expanduser - - def cleanup(): - site.USER_BASE = self.old_user_base - site.USER_SITE = self.old_user_site - install_module.USER_BASE = self.old_user_base - install_module.USER_SITE = self.old_user_site - os.path.expanduser = self.old_expand - - self.addCleanup(cleanup) + monkeypatch.setattr(os.path, 'expanduser', _expanduser) for key in ('nt_user', 'posix_user'): - self.assertIn(key, INSTALL_SCHEMES) + assert key in INSTALL_SCHEMES dist = Distribution({'name': 'xx'}) cmd = install(dist) # making sure the user option is there options = [name for name, short, lable in cmd.user_options] - self.assertIn('user', options) + assert 'user' in options # setting a value cmd.user = 1 # user base and site shouldn't be created yet - self.assertFalse(os.path.exists(self.user_base)) - self.assertFalse(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 - self.assertTrue(os.path.exists(self.user_base)) - self.assertTrue(os.path.exists(self.user_site)) + assert os.path.exists(site.USER_BASE) + assert os.path.exists(site.USER_SITE) - self.assertIn('userbase', cmd.config_vars) - self.assertIn('usersite', cmd.config_vars) + 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, '') expect_headers = os.path.join(include, 'xx') - self.assertEqual( - os.path.normcase(actual_headers), os.path.normcase(expect_headers) - ) + assert os.path.normcase(actual_headers) == os.path.normcase(expect_headers) def test_handle_extra_path(self): dist = Distribution({'name': 'xx', 'extra_path': 'path,dirs'}) @@ -154,27 +138,28 @@ class InstallTestCase( # two elements cmd.handle_extra_path() - self.assertEqual(cmd.extra_path, ['path', 'dirs']) - self.assertEqual(cmd.extra_dirs, 'dirs') - self.assertEqual(cmd.path_file, 'path') + assert cmd.extra_path == ['path', 'dirs'] + assert cmd.extra_dirs == 'dirs' + assert cmd.path_file == 'path' # one element cmd.extra_path = ['path'] cmd.handle_extra_path() - self.assertEqual(cmd.extra_path, ['path']) - self.assertEqual(cmd.extra_dirs, 'path') - self.assertEqual(cmd.path_file, 'path') + assert cmd.extra_path == ['path'] + assert cmd.extra_dirs == 'path' + assert cmd.path_file == 'path' # none dist.extra_path = cmd.extra_path = None cmd.handle_extra_path() - self.assertEqual(cmd.extra_path, None) - self.assertEqual(cmd.extra_dirs, '') - self.assertEqual(cmd.path_file, None) + assert cmd.extra_path is None + assert cmd.extra_dirs == '' + assert cmd.path_file is None # three elements (no way !) cmd.extra_path = 'path,dirs,again' - self.assertRaises(DistutilsOptionError, cmd.handle_extra_path) + with pytest.raises(DistutilsOptionError): + cmd.handle_extra_path() def test_finalize_options(self): dist = Distribution({'name': 'xx'}) @@ -184,18 +169,21 @@ class InstallTestCase( # install-base/install-platbase -- not both cmd.prefix = 'prefix' cmd.install_base = 'base' - self.assertRaises(DistutilsOptionError, cmd.finalize_options) + with pytest.raises(DistutilsOptionError): + cmd.finalize_options() # must supply either home or prefix/exec-prefix -- not both cmd.install_base = None cmd.home = 'home' - self.assertRaises(DistutilsOptionError, cmd.finalize_options) + with pytest.raises(DistutilsOptionError): + cmd.finalize_options() # can't combine user with prefix/exec_prefix/home or # install_(plat)base cmd.prefix = None cmd.user = 'user' - self.assertRaises(DistutilsOptionError, cmd.finalize_options) + with pytest.raises(DistutilsOptionError): + cmd.finalize_options() def test_record(self): install_dir = self.mkdtemp() @@ -224,12 +212,12 @@ class InstallTestCase( 'sayhi', 'UNKNOWN-0.0.0-py%s.%s.egg-info' % sys.version_info[:2], ] - self.assertEqual(found, expected) + assert found == expected 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'])] @@ -249,18 +237,14 @@ class InstallTestCase( cmd.ensure_finalized() cmd.run() - f = open(cmd.record) - try: - content = f.read() - finally: - f.close() + content = pathlib.Path(cmd.record).read_text() found = [os.path.basename(line) for line in content.splitlines()] expected = [ _make_ext_name('xx'), 'UNKNOWN-0.0.0-py%s.%s.egg-info' % sys.version_info[:2], ] - self.assertEqual(found, expected) + assert found == expected def test_debug_mode(self): # this covers the code called when DEBUG is set @@ -271,4 +255,4 @@ class InstallTestCase( self.test_record() finally: install_module.DEBUG = False - self.assertGreater(len(self.logs), old_logs_len) + assert len(self.logs) > old_logs_len diff --git a/distutils/tests/test_install_data.py b/distutils/tests/test_install_data.py index a66e5d40..f77c790f 100644 --- a/distutils/tests/test_install_data.py +++ b/distutils/tests/test_install_data.py @@ -1,6 +1,5 @@ """Tests for distutils.command.install_data.""" import os -import unittest import pytest @@ -9,10 +8,9 @@ from distutils.tests import support @pytest.mark.usefixtures('save_env') -class InstallDataTestCase( +class TestInstallData( support.TempdirManager, support.LoggingSilencer, - unittest.TestCase, ): def test_simple_run(self): pkg_dir, dist = self.create_dist() @@ -29,18 +27,18 @@ class InstallDataTestCase( self.write_file(two, 'xxx') cmd.data_files = [one, (inst2, [two])] - self.assertEqual(cmd.get_inputs(), [one, (inst2, [two])]) + assert cmd.get_inputs() == [one, (inst2, [two])] # let's run the command cmd.ensure_finalized() cmd.run() # let's check the result - self.assertEqual(len(cmd.get_outputs()), 2) + assert len(cmd.get_outputs()) == 2 rtwo = os.path.split(two)[-1] - self.assertTrue(os.path.exists(os.path.join(inst2, rtwo))) + assert os.path.exists(os.path.join(inst2, rtwo)) rone = os.path.split(one)[-1] - self.assertTrue(os.path.exists(os.path.join(inst, rone))) + assert os.path.exists(os.path.join(inst, rone)) cmd.outfiles = [] # let's try with warn_dir one @@ -49,9 +47,9 @@ class InstallDataTestCase( cmd.run() # let's check the result - self.assertEqual(len(cmd.get_outputs()), 2) - self.assertTrue(os.path.exists(os.path.join(inst2, rtwo))) - self.assertTrue(os.path.exists(os.path.join(inst, rone))) + assert len(cmd.get_outputs()) == 2 + assert os.path.exists(os.path.join(inst2, rtwo)) + assert os.path.exists(os.path.join(inst, rone)) cmd.outfiles = [] # now using root and empty dir @@ -64,6 +62,6 @@ class InstallDataTestCase( cmd.run() # let's check the result - self.assertEqual(len(cmd.get_outputs()), 4) - self.assertTrue(os.path.exists(os.path.join(inst2, rtwo))) - self.assertTrue(os.path.exists(os.path.join(inst, rone))) + assert len(cmd.get_outputs()) == 4 + assert os.path.exists(os.path.join(inst2, rtwo)) + assert os.path.exists(os.path.join(inst, rone)) diff --git a/distutils/tests/test_install_headers.py b/distutils/tests/test_install_headers.py index 9029f7e4..7594f5af 100644 --- a/distutils/tests/test_install_headers.py +++ b/distutils/tests/test_install_headers.py @@ -1,6 +1,5 @@ """Tests for distutils.command.install_headers.""" import os -import unittest import pytest @@ -9,10 +8,9 @@ from distutils.tests import support @pytest.mark.usefixtures('save_env') -class InstallHeadersTestCase( +class TestInstallHeaders( support.TempdirManager, support.LoggingSilencer, - unittest.TestCase, ): def test_simple_run(self): # we have two headers @@ -25,7 +23,7 @@ class InstallHeadersTestCase( pkg_dir, dist = self.create_dist(headers=headers) cmd = install_headers(dist) - self.assertEqual(cmd.get_inputs(), headers) + assert cmd.get_inputs() == headers # let's run the command cmd.install_dir = os.path.join(pkg_dir, 'inst') @@ -33,4 +31,4 @@ class InstallHeadersTestCase( cmd.run() # let's check the results - self.assertEqual(len(cmd.get_outputs()), 2) + assert len(cmd.get_outputs()) == 2 diff --git a/distutils/tests/test_install_lib.py b/distutils/tests/test_install_lib.py index cebc88e7..a654a66a 100644 --- a/distutils/tests/test_install_lib.py +++ b/distutils/tests/test_install_lib.py @@ -2,7 +2,6 @@ import sys import os import importlib.util -import unittest import pytest @@ -12,31 +11,33 @@ from distutils.tests import support from distutils.errors import DistutilsOptionError +@support.combine_markers @pytest.mark.usefixtures('save_env') -class InstallLibTestCase( +class TestInstallLib( support.TempdirManager, support.LoggingSilencer, - unittest.TestCase, ): def test_finalize_options(self): dist = self.create_dist()[1] cmd = install_lib(dist) cmd.finalize_options() - self.assertEqual(cmd.compile, 1) - self.assertEqual(cmd.optimize, 0) + assert cmd.compile == 1 + assert cmd.optimize == 0 # optimize must be 0, 1, or 2 cmd.optimize = 'foo' - self.assertRaises(DistutilsOptionError, cmd.finalize_options) + with pytest.raises(DistutilsOptionError): + cmd.finalize_options() cmd.optimize = '4' - self.assertRaises(DistutilsOptionError, cmd.finalize_options) + with pytest.raises(DistutilsOptionError): + cmd.finalize_options() cmd.optimize = '2' cmd.finalize_options() - self.assertEqual(cmd.optimize, 2) + 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) @@ -50,8 +51,8 @@ class InstallLibTestCase( pyc_opt_file = importlib.util.cache_from_source( 'foo.py', optimization=cmd.optimize ) - self.assertTrue(os.path.exists(pyc_file)) - self.assertTrue(os.path.exists(pyc_opt_file)) + assert os.path.exists(pyc_file) + assert os.path.exists(pyc_opt_file) def test_get_outputs(self): project_dir, dist = self.create_dist() @@ -71,7 +72,7 @@ class InstallLibTestCase( # get_outputs should return 4 elements: spam/__init__.py and .pyc, # foo.import-tag-abiflags.so / foo.pyd outputs = cmd.get_outputs() - self.assertEqual(len(outputs), 4, outputs) + assert len(outputs) == 4, outputs def test_get_inputs(self): project_dir, dist = self.create_dist() @@ -91,7 +92,7 @@ class InstallLibTestCase( # get_inputs should return 2 elements: spam/__init__.py and # foo.import-tag-abiflags.so / foo.pyd inputs = cmd.get_inputs() - self.assertEqual(len(inputs), 2, inputs) + assert len(inputs) == 2, inputs def test_dont_write_bytecode(self): # makes sure byte_compile is not used @@ -107,4 +108,4 @@ class InstallLibTestCase( finally: sys.dont_write_bytecode = old_dont_write_bytecode - self.assertIn('byte-compiling is disabled', self.logs[0][1] % self.logs[0][2]) + assert 'byte-compiling is disabled' in self.logs[0][1] % self.logs[0][2] diff --git a/distutils/tests/test_install_scripts.py b/distutils/tests/test_install_scripts.py index 2b19d736..0d17f11b 100644 --- a/distutils/tests/test_install_scripts.py +++ b/distutils/tests/test_install_scripts.py @@ -1,7 +1,6 @@ """Tests for distutils.command.install_scripts.""" import os -import unittest from distutils.command.install_scripts import install_scripts from distutils.core import Distribution @@ -9,9 +8,7 @@ from distutils.core import Distribution from distutils.tests import support -class InstallScriptsTestCase( - support.TempdirManager, support.LoggingSilencer, unittest.TestCase -): +class TestInstallScripts(support.TempdirManager, support.LoggingSilencer): def test_default_settings(self): dist = Distribution() dist.command_obj["build"] = support.DummyCommand(build_scripts="/foo/bar") @@ -21,17 +18,17 @@ class InstallScriptsTestCase( skip_build=1, ) cmd = install_scripts(dist) - self.assertFalse(cmd.force) - self.assertFalse(cmd.skip_build) - self.assertIsNone(cmd.build_dir) - self.assertIsNone(cmd.install_dir) + assert not cmd.force + assert not cmd.skip_build + assert cmd.build_dir is None + assert cmd.install_dir is None cmd.finalize_options() - self.assertTrue(cmd.force) - self.assertTrue(cmd.skip_build) - self.assertEqual(cmd.build_dir, "/foo/bar") - self.assertEqual(cmd.install_dir, "/splat/funk") + assert cmd.force + assert cmd.skip_build + assert cmd.build_dir == "/foo/bar" + assert cmd.install_dir == "/splat/funk" def test_installation(self): source = self.mkdtemp() @@ -75,4 +72,4 @@ class InstallScriptsTestCase( installed = os.listdir(target) for name in expected: - self.assertIn(name, installed) + assert name in installed diff --git a/distutils/tests/test_log.py b/distutils/tests/test_log.py index 4a5c5a05..7aeee405 100644 --- a/distutils/tests/test_log.py +++ b/distutils/tests/test_log.py @@ -2,52 +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) - self.assertEqual( - 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) - self.assertEqual( - 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/distutils/tests/test_msvc9compiler.py b/distutils/tests/test_msvc9compiler.py deleted file mode 100644 index 11a45557..00000000 --- a/distutils/tests/test_msvc9compiler.py +++ /dev/null @@ -1,183 +0,0 @@ -"""Tests for distutils.msvc9compiler.""" -import sys -import unittest -import os - -from distutils.errors import DistutilsPlatformError -from distutils.tests import support - -# A manifest with the only assembly reference being the msvcrt assembly, so -# should have the assembly completely stripped. Note that although the -# assembly has a <security> reference the assembly is removed - that is -# currently a "feature", not a bug :) -_MANIFEST_WITH_ONLY_MSVC_REFERENCE = """\ -<?xml version="1.0" encoding="UTF-8" standalone="yes"?> -<assembly xmlns="urn:schemas-microsoft-com:asm.v1" - manifestVersion="1.0"> - <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> - <security> - <requestedPrivileges> - <requestedExecutionLevel level="asInvoker" uiAccess="false"> - </requestedExecutionLevel> - </requestedPrivileges> - </security> - </trustInfo> - <dependency> - <dependentAssembly> - <assemblyIdentity type="win32" name="Microsoft.VC90.CRT" - version="9.0.21022.8" processorArchitecture="x86" - publicKeyToken="XXXX"> - </assemblyIdentity> - </dependentAssembly> - </dependency> -</assembly> -""" - -# A manifest with references to assemblies other than msvcrt. When processed, -# this assembly should be returned with just the msvcrt part removed. -_MANIFEST_WITH_MULTIPLE_REFERENCES = """\ -<?xml version="1.0" encoding="UTF-8" standalone="yes"?> -<assembly xmlns="urn:schemas-microsoft-com:asm.v1" - manifestVersion="1.0"> - <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> - <security> - <requestedPrivileges> - <requestedExecutionLevel level="asInvoker" uiAccess="false"> - </requestedExecutionLevel> - </requestedPrivileges> - </security> - </trustInfo> - <dependency> - <dependentAssembly> - <assemblyIdentity type="win32" name="Microsoft.VC90.CRT" - version="9.0.21022.8" processorArchitecture="x86" - publicKeyToken="XXXX"> - </assemblyIdentity> - </dependentAssembly> - </dependency> - <dependency> - <dependentAssembly> - <assemblyIdentity type="win32" name="Microsoft.VC90.MFC" - version="9.0.21022.8" processorArchitecture="x86" - publicKeyToken="XXXX"></assemblyIdentity> - </dependentAssembly> - </dependency> -</assembly> -""" - -_CLEANED_MANIFEST = """\ -<?xml version="1.0" encoding="UTF-8" standalone="yes"?> -<assembly xmlns="urn:schemas-microsoft-com:asm.v1" - manifestVersion="1.0"> - <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> - <security> - <requestedPrivileges> - <requestedExecutionLevel level="asInvoker" uiAccess="false"> - </requestedExecutionLevel> - </requestedPrivileges> - </security> - </trustInfo> - <dependency> - - </dependency> - <dependency> - <dependentAssembly> - <assemblyIdentity type="win32" name="Microsoft.VC90.MFC" - version="9.0.21022.8" processorArchitecture="x86" - publicKeyToken="XXXX"></assemblyIdentity> - </dependentAssembly> - </dependency> -</assembly>""" - -if sys.platform == "win32": - from distutils.msvccompiler import get_build_version - - if get_build_version() >= 8.0: - SKIP_MESSAGE = None - else: - SKIP_MESSAGE = "These tests are only for MSVC8.0 or above" -else: - SKIP_MESSAGE = "These tests are only for win32" - - -@unittest.skipUnless(SKIP_MESSAGE is None, SKIP_MESSAGE) -class msvc9compilerTestCase(support.TempdirManager, unittest.TestCase): - def test_no_compiler(self): - # makes sure query_vcvarsall raises - # a DistutilsPlatformError if the compiler - # is not found - from distutils.msvc9compiler import query_vcvarsall - - def _find_vcvarsall(version): - return None - - from distutils import msvc9compiler - - old_find_vcvarsall = msvc9compiler.find_vcvarsall - msvc9compiler.find_vcvarsall = _find_vcvarsall - try: - self.assertRaises( - DistutilsPlatformError, query_vcvarsall, 'wont find this version' - ) - finally: - msvc9compiler.find_vcvarsall = old_find_vcvarsall - - def test_reg_class(self): - from distutils.msvc9compiler import Reg - - self.assertRaises(KeyError, Reg.get_value, 'xxx', 'xxx') - - # looking for values that should exist on all - # windows registry versions. - path = r'Control Panel\Desktop' - v = Reg.get_value(path, 'dragfullwindows') - self.assertIn(v, ('0', '1', '2')) - - import winreg - - HKCU = winreg.HKEY_CURRENT_USER - keys = Reg.read_keys(HKCU, 'xxxx') - self.assertEqual(keys, None) - - keys = Reg.read_keys(HKCU, r'Control Panel') - self.assertIn('Desktop', keys) - - def test_remove_visual_c_ref(self): - from distutils.msvc9compiler import MSVCCompiler - - tempdir = self.mkdtemp() - manifest = os.path.join(tempdir, 'manifest') - f = open(manifest, 'w') - try: - f.write(_MANIFEST_WITH_MULTIPLE_REFERENCES) - finally: - f.close() - - compiler = MSVCCompiler() - compiler._remove_visual_c_ref(manifest) - - # see what we got - f = open(manifest) - try: - # removing trailing spaces - content = '\n'.join([line.rstrip() for line in f.readlines()]) - finally: - f.close() - - # makes sure the manifest was properly cleaned - self.assertEqual(content, _CLEANED_MANIFEST) - - def test_remove_entire_manifest(self): - from distutils.msvc9compiler import MSVCCompiler - - tempdir = self.mkdtemp() - manifest = os.path.join(tempdir, 'manifest') - f = open(manifest, 'w') - try: - f.write(_MANIFEST_WITH_ONLY_MSVC_REFERENCE) - finally: - f.close() - - compiler = MSVCCompiler() - got = compiler._remove_visual_c_ref(manifest) - self.assertIsNone(got) diff --git a/distutils/tests/test_msvccompiler.py b/distutils/tests/test_msvccompiler.py index 9357a223..f63537b8 100644 --- a/distutils/tests/test_msvccompiler.py +++ b/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 @@ -14,7 +14,7 @@ from distutils import _msvccompiler needs_winreg = pytest.mark.skipif('not hasattr(_msvccompiler, "winreg")') -class msvccompilerTestCase(support.TempdirManager, unittest.TestCase): +class Testmsvccompiler(support.TempdirManager): def test_no_compiler(self): # makes sure query_vcvarsall raises # a DistutilsPlatformError if the compiler @@ -25,11 +25,10 @@ class msvccompilerTestCase(support.TempdirManager, unittest.TestCase): old_find_vcvarsall = _msvccompiler._find_vcvarsall _msvccompiler._find_vcvarsall = _find_vcvarsall try: - self.assertRaises( - DistutilsPlatformError, - _msvccompiler._get_vc_env, - 'wont find this version', - ) + with pytest.raises(DistutilsPlatformError): + _msvccompiler._get_vc_env( + 'wont find this version', + ) finally: _msvccompiler._find_vcvarsall = old_find_vcvarsall @@ -43,34 +42,25 @@ class msvccompilerTestCase(support.TempdirManager, unittest.TestCase): os.environ[test_var] = test_value try: env = _msvccompiler._get_vc_env('x86') - self.assertIn(test_var.lower(), env) - self.assertEqual(test_value, env[test_var.lower()]) + assert test_var.lower() in env + assert test_value == env[test_var.lower()] finally: os.environ.pop(test_var) if old_distutils_use_sdk: 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: - self.assertGreaterEqual(version, 15) - self.assertTrue(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: - self.assertGreaterEqual(version, 14) - self.assertTrue(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): @@ -86,7 +76,7 @@ class CheckThread(threading.Thread): return not self.exc_info -class TestSpawn(unittest.TestCase): +class TestSpawn: def test_concurrent_safe(self): """ Concurrent calls to spawn should have consistent results. @@ -119,7 +109,7 @@ class TestSpawn(unittest.TestCase): "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/distutils/tests/test_register.py b/distutils/tests/test_register.py index 76fec685..0a5765f1 100644 --- a/distutils/tests/test_register.py +++ b/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 @@ -14,6 +9,7 @@ from distutils.errors import DistutilsSetupError from distutils.log import INFO from distutils.tests.test_config import BasePyPIRCCommandTestCase +import pytest try: import docutils @@ -41,7 +37,7 @@ password:password """ -class Inputs(object): +class Inputs: """Fakes user inputs.""" def __init__(self, *answers): @@ -55,7 +51,7 @@ class Inputs(object): self.index += 1 -class FakeOpener(object): +class FakeOpener: """Fakes a PyPI server""" def __init__(self): @@ -77,26 +73,20 @@ class FakeOpener(object): }.get(name.lower(), default) -class RegisterTestCase(BasePyPIRCCommandTestCase): - def setUp(self): - super(RegisterTestCase, self).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(RegisterTestCase, self).tearDown() +class TestRegister(BasePyPIRCCommandTestCase): def _get_cmd(self, metadata=None): if metadata is None: metadata = { @@ -105,6 +95,7 @@ class RegisterTestCase(BasePyPIRCCommandTestCase): 'author_email': 'xxx', 'name': 'xxx', 'version': 'xxx', + 'long_description': 'xxx', } pkg_info, dist = self.create_dist(**metadata) return register(dist) @@ -117,7 +108,7 @@ class RegisterTestCase(BasePyPIRCCommandTestCase): cmd = self._get_cmd() # we shouldn't have a .pypirc file yet - self.assertFalse(os.path.exists(self.rc)) + assert not os.path.exists(self.rc) # patching input and getpass.getpass # so register gets happy @@ -136,13 +127,13 @@ class RegisterTestCase(BasePyPIRCCommandTestCase): del register_module.input # we should have a brand new .pypirc file - self.assertTrue(os.path.exists(self.rc)) + assert os.path.exists(self.rc) # with the content similar to WANTED_PYPIRC f = open(self.rc) try: content = f.read() - self.assertEqual(content, WANTED_PYPIRC) + assert content == WANTED_PYPIRC finally: f.close() @@ -159,13 +150,13 @@ class RegisterTestCase(BasePyPIRCCommandTestCase): # let's see what the server received : we should # have 2 similar requests - self.assertEqual(len(self.conn.reqs), 2) + assert len(self.conn.reqs) == 2 req1 = dict(self.conn.reqs[0].headers) req2 = dict(self.conn.reqs[1].headers) - self.assertEqual(req1['Content-length'], '1359') - self.assertEqual(req2['Content-length'], '1359') - self.assertIn(b'xxx', self.conn.reqs[1].data) + 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): @@ -177,7 +168,7 @@ class RegisterTestCase(BasePyPIRCCommandTestCase): # dist.password should be set # therefore used afterwards by other commands - self.assertEqual(cmd.distribution.password, 'password') + assert cmd.distribution.password == 'password' def test_registering(self): # this test runs choice 2 @@ -191,11 +182,11 @@ class RegisterTestCase(BasePyPIRCCommandTestCase): del register_module.input # we should have send a request - self.assertEqual(len(self.conn.reqs), 1) + assert len(self.conn.reqs) == 1 req = self.conn.reqs[0] headers = dict(req.headers) - self.assertEqual(headers['Content-length'], '608') - self.assertIn(b'tarek', req.data) + assert headers['Content-length'] == '608' + assert b'tarek' in req.data def test_password_reset(self): # this test runs choice 3 @@ -209,24 +200,26 @@ class RegisterTestCase(BasePyPIRCCommandTestCase): del register_module.input # we should have send a request - self.assertEqual(len(self.conn.reqs), 1) + assert len(self.conn.reqs) == 1 req = self.conn.reqs[0] headers = dict(req.headers) - self.assertEqual(headers['Content-length'], '290') - self.assertIn(b'tarek', req.data) + 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() cmd.strict = 1 - self.assertRaises(DistutilsSetupError, cmd.run) + with pytest.raises(DistutilsSetupError): + cmd.run() # metadata are OK but long_description is broken metadata = { @@ -241,7 +234,8 @@ class RegisterTestCase(BasePyPIRCCommandTestCase): cmd = self._get_cmd(metadata) cmd.ensure_finalized() cmd.strict = 1 - self.assertRaises(DistutilsSetupError, cmd.run) + with pytest.raises(DistutilsSetupError): + cmd.run() # now something that works metadata['long_description'] = 'title\n=====\n\ntext' @@ -289,8 +283,8 @@ class RegisterTestCase(BasePyPIRCCommandTestCase): finally: del register_module.input - @unittest.skipUnless(docutils is not None, 'needs docutils') - def test_register_invalid_long_description(self): + def test_register_invalid_long_description(self, monkeypatch): + pytest.importorskip('docutils') description = ':funkie:`str`' # mimic Sphinx-specific markup metadata = { 'url': 'xxx', @@ -304,25 +298,17 @@ class RegisterTestCase(BasePyPIRCCommandTestCase): cmd.ensure_finalized() cmd.strict = True inputs = Inputs('2', 'tarek', 'tarek@ziade.org') - register_module.input = inputs - self.addCleanup(delattr, register_module, 'input') - - self.assertRaises(DistutilsSetupError, cmd.run) + monkeypatch.setattr(register_module, 'input', inputs, raising=False) - 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() - self.assertEqual(len(w.warnings), 1) + with pytest.raises(DistutilsSetupError): + cmd.run() def test_list_classifiers(self): cmd = self._get_cmd() cmd.list_classifiers = 1 cmd.run() results = self.get_logs(INFO) - self.assertEqual(results, ['running check', 'xxx']) + assert results == ['running check', 'xxx'] def test_show_response(self): # test that the --show-response option return a well formatted response @@ -336,4 +322,4 @@ class RegisterTestCase(BasePyPIRCCommandTestCase): del register_module.input results = self.get_logs(INFO) - self.assertEqual(results[3], 75 * '-' + '\nxxx\n' + 75 * '-') + assert results[3] == 75 * '-' + '\nxxx\n' + 75 * '-' diff --git a/distutils/tests/test_sdist.py b/distutils/tests/test_sdist.py index f0ca8295..b11fe7c4 100644 --- a/distutils/tests/test_sdist.py +++ b/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 test.support import captured_stdout from .unix_compat import require_unix_id, require_uid_0, pwd, grp import pytest +import path +import jaraco.path from .py38compat import check_warnings @@ -17,7 +18,7 @@ from distutils.command.sdist import sdist, show_formats from distutils.core import Distribution from distutils.tests.test_config import BasePyPIRCCommandTestCase from distutils.errors import DistutilsOptionError -from distutils.spawn import find_executable +from distutils.spawn import find_executable # noqa: F401 from distutils.log import WARN from distutils.filelist import FileList from distutils.archive_util import ARCHIVE_FORMATS @@ -45,26 +46,24 @@ somecode%(sep)sdoc.txt """ -class SDistTestCase(BasePyPIRCCommandTestCase): - def setUp(self): - # PyPIRCCommandTestCase creates a temp dir already - # and put it in self.tmp_dir - super(SDistTestCase, self).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(SDistTestCase, self).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: @@ -113,7 +112,7 @@ class SDistTestCase(BasePyPIRCCommandTestCase): # now let's check what we have dist_folder = join(self.tmp_dir, 'dist') files = os.listdir(dist_folder) - self.assertEqual(files, ['fake-1.0.zip']) + assert files == ['fake-1.0.zip'] zip_file = zipfile.ZipFile(join(dist_folder, 'fake-1.0.zip')) try: @@ -130,11 +129,11 @@ class SDistTestCase(BasePyPIRCCommandTestCase): 'somecode/', 'somecode/__init__.py', ] - self.assertEqual(sorted(content), ['fake-1.0/' + x for x in expected]) + 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() @@ -148,7 +147,7 @@ class SDistTestCase(BasePyPIRCCommandTestCase): dist_folder = join(self.tmp_dir, 'dist') result = os.listdir(dist_folder) result.sort() - self.assertEqual(result, ['fake-1.0.tar', 'fake-1.0.tar.gz']) + assert result == ['fake-1.0.tar', 'fake-1.0.tar.gz'] os.remove(join(dist_folder, 'fake-1.0.tar')) os.remove(join(dist_folder, 'fake-1.0.tar.gz')) @@ -161,7 +160,7 @@ class SDistTestCase(BasePyPIRCCommandTestCase): result = os.listdir(dist_folder) result.sort() - self.assertEqual(result, ['fake-1.0.tar', 'fake-1.0.tar.gz']) + assert result == ['fake-1.0.tar', 'fake-1.0.tar.gz'] @pytest.mark.usefixtures('needs_zlib') def test_add_defaults(self): @@ -215,7 +214,7 @@ class SDistTestCase(BasePyPIRCCommandTestCase): # now let's check what we have dist_folder = join(self.tmp_dir, 'dist') files = os.listdir(dist_folder) - self.assertEqual(files, ['fake-1.0.zip']) + assert files == ['fake-1.0.zip'] zip_file = zipfile.ZipFile(join(dist_folder, 'fake-1.0.zip')) try: @@ -243,7 +242,7 @@ class SDistTestCase(BasePyPIRCCommandTestCase): 'somecode/doc.dat', 'somecode/doc.txt', ] - self.assertEqual(sorted(content), ['fake-1.0/' + x for x in expected]) + assert sorted(content) == ['fake-1.0/' + x for x in expected] # checking the MANIFEST f = open(join(self.tmp_dir, 'MANIFEST')) @@ -251,7 +250,7 @@ class SDistTestCase(BasePyPIRCCommandTestCase): manifest = f.read() finally: f.close() - self.assertEqual(manifest, MANIFEST % {'sep': os.sep}) + assert manifest == MANIFEST % {'sep': os.sep} @pytest.mark.usefixtures('needs_zlib') def test_metadata_check_option(self): @@ -265,7 +264,7 @@ class SDistTestCase(BasePyPIRCCommandTestCase): warnings = [ msg for msg in self.get_logs(WARN) if msg.startswith('warning: check:') ] - self.assertEqual(len(warnings), 1) + assert len(warnings) == 1 # trying with a complete set of metadata self.clear_logs() @@ -276,7 +275,7 @@ class SDistTestCase(BasePyPIRCCommandTestCase): warnings = [ msg for msg in self.get_logs(WARN) if msg.startswith('warning: check:') ] - self.assertEqual(len(warnings), 0) + assert len(warnings) == 0 def test_check_metadata_deprecated(self): # makes sure make_metadata is deprecated @@ -284,7 +283,7 @@ class SDistTestCase(BasePyPIRCCommandTestCase): with check_warnings() as w: warnings.simplefilter("always") cmd.check_metadata() - self.assertEqual(len(w.warnings), 1) + assert len(w.warnings) == 1 def test_show_formats(self): with captured_stdout() as stdout: @@ -297,27 +296,29 @@ class SDistTestCase(BasePyPIRCCommandTestCase): for line in stdout.getvalue().split('\n') if line.strip().startswith('--formats=') ] - self.assertEqual(len(output), num_formats) + assert len(output) == num_formats def test_finalize_options(self): dist, cmd = self.get_cmd() cmd.finalize_options() # default options set by finalize - self.assertEqual(cmd.manifest, 'MANIFEST') - self.assertEqual(cmd.template, 'MANIFEST.in') - self.assertEqual(cmd.dist_dir, 'dist') + assert cmd.manifest == 'MANIFEST' + assert cmd.template == 'MANIFEST.in' + assert cmd.dist_dir == 'dist' # formats has to be a string splitable on (' ', ',') or # a stringlist cmd.formats = 1 - self.assertRaises(DistutilsOptionError, cmd.finalize_options) + with pytest.raises(DistutilsOptionError): + cmd.finalize_options() cmd.formats = ['zip'] cmd.finalize_options() # formats has to be known cmd.formats = 'supazipa' - self.assertRaises(DistutilsOptionError, cmd.finalize_options) + with pytest.raises(DistutilsOptionError): + cmd.finalize_options() # the following tests make sure there is a nice error message instead # of a traceback when parsing an invalid manifest template @@ -330,7 +331,7 @@ class SDistTestCase(BasePyPIRCCommandTestCase): cmd.filelist = FileList() cmd.read_template() warnings = self.get_logs(WARN) - self.assertEqual(len(warnings), 1) + assert len(warnings) == 1 def test_invalid_template_unknown_command(self): self._check_template('taunt knights *') @@ -339,7 +340,7 @@ class SDistTestCase(BasePyPIRCCommandTestCase): # this manifest command takes one argument self._check_template('prune') - @unittest.skipIf(os.name != 'nt', 'test relevant for Windows only') + @pytest.mark.skipif("platform.system() != 'Windows'") def test_invalid_template_wrong_path(self): # on Windows, trailing slashes are not allowed # this used to crash instead of raising a warning: #8286 @@ -365,7 +366,7 @@ class SDistTestCase(BasePyPIRCCommandTestCase): finally: f.close() - self.assertEqual(len(manifest), 5) + assert len(manifest) == 5 # adding a file self.write_file((self.tmp_dir, 'somecode', 'doc2.txt'), '#') @@ -386,8 +387,8 @@ class SDistTestCase(BasePyPIRCCommandTestCase): f.close() # do we have the new file in MANIFEST ? - self.assertEqual(len(manifest2), 6) - self.assertIn('doc2.txt', manifest2[-1]) + assert len(manifest2) == 6 + assert 'doc2.txt' in manifest2[-1] @pytest.mark.usefixtures('needs_zlib') def test_manifest_marker(self): @@ -404,7 +405,7 @@ class SDistTestCase(BasePyPIRCCommandTestCase): finally: f.close() - self.assertEqual(manifest[0], '# file GENERATED by distutils, do NOT edit') + assert manifest[0] == '# file GENERATED by distutils, do NOT edit' @pytest.mark.usefixtures('needs_zlib') def test_manifest_comments(self): @@ -423,7 +424,7 @@ class SDistTestCase(BasePyPIRCCommandTestCase): self.write_file((self.tmp_dir, 'bad.py'), "# don't pick me!") self.write_file((self.tmp_dir, '#bad.py'), "# don't pick me!") cmd.run() - self.assertEqual(cmd.filelist.files, ['good.py']) + assert cmd.filelist.files == ['good.py'] @pytest.mark.usefixtures('needs_zlib') def test_manual_manifest(self): @@ -437,7 +438,7 @@ class SDistTestCase(BasePyPIRCCommandTestCase): 'This project maintains its MANIFEST file itself.', ) cmd.run() - self.assertEqual(cmd.filelist.files, ['README.manual']) + assert cmd.filelist.files == ['README.manual'] f = open(cmd.manifest) try: @@ -447,7 +448,7 @@ class SDistTestCase(BasePyPIRCCommandTestCase): finally: f.close() - self.assertEqual(manifest, ['README.manual']) + assert manifest == ['README.manual'] archive_name = join(self.tmp_dir, 'dist', 'fake-1.0.tar.gz') archive = tarfile.open(archive_name) @@ -455,16 +456,17 @@ class SDistTestCase(BasePyPIRCCommandTestCase): filenames = [tarinfo.name for tarinfo in archive] finally: archive.close() - self.assertEqual( - sorted(filenames), - ['fake-1.0', 'fake-1.0/PKG-INFO', 'fake-1.0/README.manual'], - ) + assert sorted(filenames) == [ + 'fake-1.0', + 'fake-1.0/PKG-INFO', + 'fake-1.0/README.manual', + ] @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() @@ -481,8 +483,8 @@ class SDistTestCase(BasePyPIRCCommandTestCase): archive = tarfile.open(archive_name) try: for member in archive.getmembers(): - self.assertEqual(member.uid, 0) - self.assertEqual(member.gid, 0) + assert member.uid == 0 + assert member.gid == 0 finally: archive.close() @@ -503,6 +505,6 @@ class SDistTestCase(BasePyPIRCCommandTestCase): # rights (see #7408) try: for member in archive.getmembers(): - self.assertEqual(member.uid, os.getuid()) + assert member.uid == os.getuid() finally: archive.close() diff --git a/distutils/tests/test_spawn.py b/distutils/tests/test_spawn.py index b86c157f..d2a898ed 100644 --- a/distutils/tests/test_spawn.py +++ b/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 @@ -11,10 +12,11 @@ from distutils.spawn import find_executable from distutils.spawn import spawn from distutils.errors import DistutilsExecError from distutils.tests import support +import pytest -class SpawnTestCase(support.TempdirManager, support.LoggingSilencer, unittest.TestCase): - @unittest.skipUnless(os.name in ('nt', 'posix'), 'Runs only under posix or nt') +class TestSpawn(support.TempdirManager, support.LoggingSilencer): + @pytest.mark.skipif("os.name not in ('nt', 'posix')") def test_spawn(self): tmpdir = self.mkdtemp() @@ -28,7 +30,8 @@ class SpawnTestCase(support.TempdirManager, support.LoggingSilencer, unittest.Te self.write_file(exe, 'exit 1') os.chmod(exe, 0o777) - self.assertRaises(DistutilsExecError, spawn, [exe]) + with pytest.raises(DistutilsExecError): + spawn([exe]) # now something that works if sys.platform != 'win32': @@ -56,70 +59,70 @@ class SpawnTestCase(support.TempdirManager, support.LoggingSilencer, unittest.Te # test path parameter rv = find_executable(program, path=tmp_dir) - self.assertEqual(rv, filename) + assert rv == filename if sys.platform == 'win32': # test without ".exe" extension rv = find_executable(program_noeext, path=tmp_dir) - self.assertEqual(rv, filename) + assert rv == filename # test find in the current directory with os_helper.change_cwd(tmp_dir): rv = find_executable(program) - self.assertEqual(rv, program) + assert rv == program # test non-existent program dont_exist_program = "dontexist_" + program rv = find_executable(dont_exist_program, path=tmp_dir) - self.assertIsNone(rv) + assert rv is None # 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) - self.assertIsNone(rv) + assert rv is None # look in current directory with os_helper.change_cwd(tmp_dir): rv = find_executable(program) - self.assertEqual(rv, program) + assert rv == program # 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) - self.assertIsNone(rv) + assert rv is None # look in current directory with os_helper.change_cwd(tmp_dir): rv = find_executable(program) - self.assertEqual(rv, program) + assert rv == program # missing PATH: test os.confstr("CS_PATH") and os.defpath with os_helper.EnvironmentVarGuard() as env: 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) - self.assertEqual(rv, filename) + 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) - self.assertEqual(rv, filename) + assert rv == filename def test_spawn_missing_exe(self): - with self.assertRaises(DistutilsExecError) as ctx: + with pytest.raises(DistutilsExecError) as ctx: spawn(['does-not-exist']) - self.assertIn("command 'does-not-exist' failed", str(ctx.exception)) + assert "command 'does-not-exist' failed" in str(ctx.value) diff --git a/distutils/tests/test_sysconfig.py b/distutils/tests/test_sysconfig.py index 2d68b5a3..f1759839 100644 --- a/distutils/tests/test_sysconfig.py +++ b/distutils/tests/test_sysconfig.py @@ -5,14 +5,13 @@ import shutil import subprocess import sys import textwrap -import unittest import pytest import jaraco.envs import distutils from distutils import sysconfig -from distutils.ccompiler import get_default_compiler +from distutils.ccompiler import get_default_compiler # noqa: F401 from distutils.unixccompiler import UnixCCompiler from test.support import swap_item @@ -20,17 +19,8 @@ from .py38compat import TESTFN @pytest.mark.usefixtures('save_env') -class SysconfigTestCase(unittest.TestCase): - def setUp(self): - super(SysconfigTestCase, self).setUp() - self.makefile = None - - def tearDown(self): - if self.makefile is not None: - os.unlink(self.makefile) - self.cleanup_testfn() - super(SysconfigTestCase, self).tearDown() - +@pytest.mark.usefixtures('cleanup_testfn') +class TestSysconfig: def cleanup_testfn(self): if os.path.isfile(TESTFN): os.remove(TESTFN) @@ -39,47 +29,42 @@ class SysconfigTestCase(unittest.TestCase): def test_get_config_h_filename(self): config_h = sysconfig.get_config_h_filename() - self.assertTrue(os.path.isfile(config_h), config_h) + 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() - self.assertTrue(os.path.isfile(makefile), makefile) + assert os.path.isfile(makefile), makefile def test_get_python_lib(self): # XXX doesn't work on Linux when Python was never installed before # self.assertTrue(os.path.isdir(lib_dir), lib_dir) # test for pythonxx.lib? - self.assertNotEqual( - sysconfig.get_python_lib(), sysconfig.get_python_lib(prefix=TESTFN) - ) + assert sysconfig.get_python_lib() != sysconfig.get_python_lib(prefix=TESTFN) def test_get_config_vars(self): cvars = sysconfig.get_config_vars() - self.assertIsInstance(cvars, dict) - self.assertTrue(cvars) + 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') - self.assertTrue(os.path.isabs(srcdir), srcdir) - self.assertTrue(os.path.isdir(srcdir), srcdir) + assert os.path.isabs(srcdir), srcdir + assert os.path.isdir(srcdir), srcdir if sysconfig.python_build: # The python executable has not been installed so srcdir # should be a full source checkout. Python_h = os.path.join(srcdir, 'Include', 'Python.h') - self.assertTrue(os.path.exists(Python_h), Python_h) - self.assertTrue(sysconfig._is_python_source_dir(srcdir)) + assert os.path.exists(Python_h), Python_h + assert sysconfig._is_python_source_dir(srcdir) elif os.name == 'posix': - self.assertEqual(os.path.dirname(sysconfig.get_makefile_filename()), srcdir) + assert os.path.dirname(sysconfig.get_makefile_filename()) == srcdir def test_srcdir_independent_of_cwd(self): # srcdir should be independent of the current working directory @@ -91,7 +76,7 @@ class SysconfigTestCase(unittest.TestCase): srcdir2 = sysconfig.get_config_var('srcdir') finally: os.chdir(cwd) - self.assertEqual(srcdir, srcdir2) + assert srcdir == srcdir2 def customize_compiler(self): # make sure AR gets caught @@ -127,9 +112,7 @@ class SysconfigTestCase(unittest.TestCase): return comp - @unittest.skipUnless( - get_default_compiler() == 'unix', 'not testing if default compiler is not unix' - ) + @pytest.mark.skipif("get_default_compiler() != 'unix'") def test_customize_compiler(self): # Make sure that sysconfig._config_vars is initialized sysconfig.get_config_vars() @@ -146,27 +129,23 @@ class SysconfigTestCase(unittest.TestCase): os.environ['RANLIB'] = 'env_ranlib' comp = self.customize_compiler() - self.assertEqual(comp.exes['archiver'], 'env_ar --env-arflags') - self.assertEqual(comp.exes['preprocessor'], 'env_cpp --env-cppflags') - self.assertEqual( - comp.exes['compiler'], 'env_cc --sc-cflags --env-cflags --env-cppflags' + assert comp.exes['archiver'] == 'env_ar --env-arflags' + assert comp.exes['preprocessor'] == 'env_cpp --env-cppflags' + assert comp.exes['compiler'] == 'env_cc --sc-cflags --env-cflags --env-cppflags' + assert comp.exes['compiler_so'] == ( + 'env_cc --sc-cflags ' '--env-cflags ' '--env-cppflags --sc-ccshared' ) - self.assertEqual( - comp.exes['compiler_so'], - ('env_cc --sc-cflags ' '--env-cflags ' '--env-cppflags --sc-ccshared'), + assert comp.exes['compiler_cxx'] == 'env_cxx --env-cxx-flags' + assert comp.exes['linker_exe'] == 'env_cc' + assert comp.exes['linker_so'] == ( + 'env_ldshared --env-ldflags --env-cflags' ' --env-cppflags' ) - self.assertEqual(comp.exes['compiler_cxx'], 'env_cxx --env-cxx-flags') - self.assertEqual(comp.exes['linker_exe'], 'env_cc') - self.assertEqual( - comp.exes['linker_so'], - ('env_ldshared --env-ldflags --env-cflags' ' --env-cppflags'), - ) - self.assertEqual(comp.shared_lib_extension, 'sc_shutil_suffix') + assert comp.shared_lib_extension == 'sc_shutil_suffix' if sys.platform == "darwin": - self.assertEqual(comp.exes['ranlib'], 'env_ranlib') + assert comp.exes['ranlib'] == 'env_ranlib' else: - self.assertTrue('ranlib' not in comp.exes) + assert 'ranlib' not in comp.exes del os.environ['AR'] del os.environ['CC'] @@ -180,15 +159,15 @@ class SysconfigTestCase(unittest.TestCase): del os.environ['RANLIB'] comp = self.customize_compiler() - self.assertEqual(comp.exes['archiver'], 'sc_ar --sc-arflags') - self.assertEqual(comp.exes['preprocessor'], 'sc_cc -E') - self.assertEqual(comp.exes['compiler'], 'sc_cc --sc-cflags') - self.assertEqual(comp.exes['compiler_so'], 'sc_cc --sc-cflags --sc-ccshared') - self.assertEqual(comp.exes['compiler_cxx'], 'sc_cxx') - self.assertEqual(comp.exes['linker_exe'], 'sc_cc') - self.assertEqual(comp.exes['linker_so'], 'sc_ldshared') - self.assertEqual(comp.shared_lib_extension, 'sc_shutil_suffix') - self.assertTrue('ranlib' not in comp.exes) + assert comp.exes['archiver'] == 'sc_ar --sc-arflags' + assert comp.exes['preprocessor'] == 'sc_cc -E' + assert comp.exes['compiler'] == 'sc_cc --sc-cflags' + assert comp.exes['compiler_so'] == 'sc_cc --sc-cflags --sc-ccshared' + assert comp.exes['compiler_cxx'] == 'sc_cxx' + assert comp.exes['linker_exe'] == 'sc_cc' + assert comp.exes['linker_so'] == 'sc_ldshared' + assert comp.shared_lib_extension == 'sc_shutil_suffix' + assert 'ranlib' not in comp.exes def test_parse_makefile_base(self): self.makefile = TESTFN @@ -199,9 +178,7 @@ class SysconfigTestCase(unittest.TestCase): finally: fd.close() d = sysconfig.parse_makefile(self.makefile) - self.assertEqual( - d, {'CONFIG_ARGS': "'--arg1=optarg1' 'ENV=LIB'", 'OTHER': 'foo'} - ) + assert d == {'CONFIG_ARGS': "'--arg1=optarg1' 'ENV=LIB'", 'OTHER': 'foo'} def test_parse_makefile_literal_dollar(self): self.makefile = TESTFN @@ -212,25 +189,19 @@ class SysconfigTestCase(unittest.TestCase): finally: fd.close() d = sysconfig.parse_makefile(self.makefile) - self.assertEqual( - d, {'CONFIG_ARGS': r"'--arg1=optarg1' 'ENV=\$LIB'", 'OTHER': 'foo'} - ) + assert d == {'CONFIG_ARGS': r"'--arg1=optarg1' 'ENV=\$LIB'", 'OTHER': 'foo'} def test_sysconfig_module(self): import sysconfig as global_sysconfig - self.assertEqual( - global_sysconfig.get_config_var('CFLAGS'), - sysconfig.get_config_var('CFLAGS'), + assert global_sysconfig.get_config_var('CFLAGS') == sysconfig.get_config_var( + 'CFLAGS' ) - self.assertEqual( - global_sysconfig.get_config_var('LDFLAGS'), - sysconfig.get_config_var('LDFLAGS'), + assert global_sysconfig.get_config_var('LDFLAGS') == sysconfig.get_config_var( + '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 @@ -249,21 +220,16 @@ class SysconfigTestCase(unittest.TestCase): import sysconfig as global_sysconfig if sysconfig.get_config_var('CUSTOMIZED_OSX_COMPILER'): - self.skipTest('compiler flags customized') - self.assertEqual( - global_sysconfig.get_config_var('LDSHARED'), - sysconfig.get_config_var('LDSHARED'), - ) - self.assertEqual( - global_sysconfig.get_config_var('CC'), sysconfig.get_config_var('CC') + 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): - self.assertWarns(DeprecationWarning, sysconfig.get_config_var, 'SO') + with pytest.warns(DeprecationWarning): + sysconfig.get_config_var('SO') def test_customize_compiler_before_get_config_vars(self): # Issue #21923: test that a Distribution compiler @@ -288,33 +254,29 @@ class SysconfigTestCase(unittest.TestCase): universal_newlines=True, ) outs, errs = p.communicate() - self.assertEqual(0, p.returncode, "Subprocess failed: " + outs) + assert 0 == p.returncode, "Subprocess failed: " + outs def test_parse_config_h(self): config_h = sysconfig.get_config_h_filename() input = {} with open(config_h, encoding="utf-8") as f: result = sysconfig.parse_config_h(f, g=input) - self.assertTrue(input is result) + assert input is result with open(config_h, encoding="utf-8") as f: result = sysconfig.parse_config_h(f) - self.assertTrue(isinstance(result, dict)) + 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): - self.assertTrue(sysconfig.get_config_var("EXT_SUFFIX").endswith(".pyd")) - self.assertNotEqual(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', + assert sysconfig.get_config_var("EXT_SUFFIX").endswith(".pyd") + assert sysconfig.get_config_var("EXT_SUFFIX") != ".pyd" + + @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/distutils/tests/test_text_file.py b/distutils/tests/test_text_file.py index ad10b6d5..7c8dc5be 100644 --- a/distutils/tests/test_text_file.py +++ b/distutils/tests/test_text_file.py @@ -1,6 +1,5 @@ """Tests for distutils.text_file.""" import os -import unittest from distutils.text_file import TextFile from distutils.tests import support @@ -12,7 +11,7 @@ line 3 \\ """ -class TextFileTestCase(support.TempdirManager, unittest.TestCase): +class TestTextFile(support.TempdirManager): def test_class(self): # old tests moved from text_file.__main__ # so they are really called by the buildbots @@ -51,7 +50,7 @@ class TextFileTestCase(support.TempdirManager, unittest.TestCase): def test_input(count, description, file, expected_result): result = file.readlines() - self.assertEqual(result, expected_result) + assert result == expected_result tmpdir = self.mkdtemp() filename = os.path.join(tmpdir, "test.txt") diff --git a/distutils/tests/test_unixccompiler.py b/distutils/tests/test_unixccompiler.py index 3cd7c6ca..3978c239 100644 --- a/distutils/tests/test_unixccompiler.py +++ b/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 @@ -12,28 +11,27 @@ from distutils.unixccompiler import UnixCCompiler from distutils.util import _clear_cached_macosx_ver from . import support +import pytest -class UnixCCompilerTestCase(support.TempdirManager, unittest.TestCase): - def setUp(self): - super().setUp() - self._backup_platform = sys.platform - self._backup_get_config_var = sysconfig.get_config_var - self._backup_get_config_vars = sysconfig.get_config_vars +@pytest.fixture(autouse=True) +def save_values(monkeypatch): + monkeypatch.setattr(sys, 'platform', sys.platform) + monkeypatch.setattr(sysconfig, 'get_config_var', sysconfig.get_config_var) + monkeypatch.setattr(sysconfig, 'get_config_vars', sysconfig.get_config_vars) - class CompilerWrapper(UnixCCompiler): - def rpath_foo(self): - return self.runtime_library_dir_option('/foo') - self.cc = CompilerWrapper() +@pytest.fixture(autouse=True) +def compiler_wrapper(request): + class CompilerWrapper(UnixCCompiler): + def rpath_foo(self): + return self.runtime_library_dir_option('/foo') - def tearDown(self): - super().tearDown() - sys.platform = self._backup_platform - sysconfig.get_config_var = self._backup_get_config_var - sysconfig.get_config_vars = self._backup_get_config_vars + request.instance.cc = CompilerWrapper() - @unittest.skipIf(sys.platform == 'win32', "can't test on Windows") + +class TestUnixCCompiler(support.TempdirManager): + @pytest.mark.skipif('platform.system == "Windows"') # noqa: C901 def test_runtime_libdir_option(self): # noqa: C901 # Issue #5900; GitHub Issue #37 # @@ -74,7 +72,7 @@ class UnixCCompilerTestCase(support.TempdirManager, unittest.TestCase): def do_darwin_test(syscfg_macosx_ver, env_macosx_ver, expected_flag): env = os.environ - msg = "macOS version = (sysconfig=%r, env=%r)" % ( + msg = "macOS version = (sysconfig={!r}, env={!r})".format( syscfg_macosx_ver, env_macosx_ver, ) @@ -93,10 +91,10 @@ class UnixCCompilerTestCase(support.TempdirManager, unittest.TestCase): # Run the test if expected_flag is not None: - self.assertEqual(self.cc.rpath_foo(), expected_flag, msg=msg) + assert self.cc.rpath_foo() == expected_flag, msg else: - with self.assertRaisesRegex( - DistutilsPlatformError, darwin_ver_var + r' mismatch', msg=msg + with pytest.raises( + DistutilsPlatformError, match=darwin_ver_var + r' mismatch' ): self.cc.rpath_foo() @@ -128,19 +126,19 @@ class UnixCCompilerTestCase(support.TempdirManager, unittest.TestCase): return 'xxx' sysconfig.get_config_var = gcv - self.assertEqual(self.cc.rpath_foo(), ['+s', '-L/foo']) + assert self.cc.rpath_foo() == ['+s', '-L/foo'] def gcv(v): return 'gcc' sysconfig.get_config_var = gcv - self.assertEqual(self.cc.rpath_foo(), ['-Wl,+s', '-L/foo']) + assert self.cc.rpath_foo() == ['-Wl,+s', '-L/foo'] def gcv(v): return 'g++' sysconfig.get_config_var = gcv - self.assertEqual(self.cc.rpath_foo(), ['-Wl,+s', '-L/foo']) + assert self.cc.rpath_foo() == ['-Wl,+s', '-L/foo'] sysconfig.get_config_var = old_gcv @@ -154,7 +152,7 @@ class UnixCCompilerTestCase(support.TempdirManager, unittest.TestCase): return 'yes' sysconfig.get_config_var = gcv - self.assertEqual(self.cc.rpath_foo(), '-Wl,--enable-new-dtags,-R/foo') + assert self.cc.rpath_foo() == '-Wl,--enable-new-dtags,-R/foo' def gcv(v): if v == 'CC': @@ -163,7 +161,7 @@ class UnixCCompilerTestCase(support.TempdirManager, unittest.TestCase): return 'yes' sysconfig.get_config_var = gcv - self.assertEqual(self.cc.rpath_foo(), '-Wl,--enable-new-dtags,-R/foo') + assert self.cc.rpath_foo() == '-Wl,--enable-new-dtags,-R/foo' # GCC non-GNULD sys.platform = 'bar' @@ -175,7 +173,7 @@ class UnixCCompilerTestCase(support.TempdirManager, unittest.TestCase): return 'no' sysconfig.get_config_var = gcv - self.assertEqual(self.cc.rpath_foo(), '-Wl,-R/foo') + assert self.cc.rpath_foo() == '-Wl,-R/foo' # GCC GNULD with fully qualified configuration prefix # see #7617 @@ -188,7 +186,7 @@ class UnixCCompilerTestCase(support.TempdirManager, unittest.TestCase): return 'yes' sysconfig.get_config_var = gcv - self.assertEqual(self.cc.rpath_foo(), '-Wl,--enable-new-dtags,-R/foo') + assert self.cc.rpath_foo() == '-Wl,--enable-new-dtags,-R/foo' # non-GCC GNULD sys.platform = 'bar' @@ -200,7 +198,7 @@ class UnixCCompilerTestCase(support.TempdirManager, unittest.TestCase): return 'yes' sysconfig.get_config_var = gcv - self.assertEqual(self.cc.rpath_foo(), '-Wl,--enable-new-dtags,-R/foo') + assert self.cc.rpath_foo() == '-Wl,--enable-new-dtags,-R/foo' # non-GCC non-GNULD sys.platform = 'bar' @@ -212,9 +210,9 @@ class UnixCCompilerTestCase(support.TempdirManager, unittest.TestCase): return 'no' sysconfig.get_config_var = gcv - self.assertEqual(self.cc.rpath_foo(), '-Wl,-R/foo') + 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 @@ -234,9 +232,9 @@ class UnixCCompilerTestCase(support.TempdirManager, unittest.TestCase): env['CC'] = 'my_cc' del env['LDSHARED'] sysconfig.customize_compiler(self.cc) - self.assertEqual(self.cc.linker_so[0], 'my_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 @@ -259,24 +257,24 @@ class UnixCCompilerTestCase(support.TempdirManager, unittest.TestCase): sysconfig.get_config_var = gcv sysconfig.get_config_vars = gcvs - with patch.object( + with mock.patch.object( self.cc, 'spawn', return_value=None - ) as mock_spawn, patch.object( + ) as mock_spawn, mock.patch.object( self.cc, '_need_link', return_value=True - ), patch.object( + ), mock.patch.object( self.cc, 'mkpath', return_value=None ), EnvironmentVarGuard() as env: env['CC'] = 'ccache my_cc' env['CXX'] = 'my_cxx' del env['LDSHARED'] sysconfig.customize_compiler(self.cc) - self.assertEqual(self.cc.linker_so[0:2], ['ccache', 'my_cc']) + assert self.cc.linker_so[0:2] == ['ccache', 'my_cc'] self.cc.link(None, [], 'a.out', target_lang='c++') call_args = mock_spawn.call_args[0][0] 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 @@ -297,7 +295,7 @@ class UnixCCompilerTestCase(support.TempdirManager, unittest.TestCase): env['CC'] = 'my_cc' env['LDSHARED'] = 'my_ld -bundle -dynamic' sysconfig.customize_compiler(self.cc) - self.assertEqual(self.cc.linker_so[0], 'my_ld') + assert self.cc.linker_so[0] == 'my_ld' def test_has_function(self): # Issue https://github.com/pypa/distutils/issues/64: diff --git a/distutils/tests/test_upload.py b/distutils/tests/test_upload.py index a1d37306..fb905b64 100644 --- a/distutils/tests/test_upload.py +++ b/distutils/tests/test_upload.py @@ -11,6 +11,7 @@ from distutils.errors import DistutilsError from distutils.log import ERROR, INFO from distutils.tests.test_config import PYPIRC, BasePyPIRCCommandTestCase +import pytest PYPIRC_LONG_PASSWORD = """\ [distutils] @@ -42,7 +43,7 @@ username:me """ -class FakeOpen(object): +class FakeOpen: def __init__(self, url, msg=None, code=None): self.url = url if not isinstance(url, str): @@ -64,19 +65,14 @@ class FakeOpen(object): return self.code -class uploadTestCase(BasePyPIRCCommandTestCase): - def setUp(self): - super(uploadTestCase, self).setUp() - self.old_open = upload_mod.urlopen - upload_mod.urlopen = self._urlopen - self.last_open = None - self.next_msg = None - self.next_code = None +@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 - def tearDown(self): - upload_mod.urlopen = self.old_open - super(uploadTestCase, self).tearDown() +class TestUpload(BasePyPIRCCommandTestCase): def _urlopen(self, url): self.last_open = FakeOpen(url, msg=self.next_msg, code=self.next_code) return self.last_open @@ -94,7 +90,7 @@ class uploadTestCase(BasePyPIRCCommandTestCase): ('realm', 'pypi'), ('repository', 'https://upload.pypi.org/legacy/'), ): - self.assertEqual(getattr(cmd, attr), waited) + assert getattr(cmd, attr) == waited def test_saved_password(self): # file with no password @@ -104,14 +100,14 @@ class uploadTestCase(BasePyPIRCCommandTestCase): dist = Distribution() cmd = upload(dist) cmd.finalize_options() - self.assertEqual(cmd.password, None) + assert cmd.password is None # make sure we get it as well, if another command # initialized it at the dist level dist.password = 'xxx' cmd = upload(dist) cmd.finalize_options() - self.assertEqual(cmd.password, 'xxx') + assert cmd.password == 'xxx' def test_upload(self): tmp = self.mkdtemp() @@ -130,33 +126,32 @@ class uploadTestCase(BasePyPIRCCommandTestCase): # what did we send ? headers = dict(self.last_open.req.headers) - self.assertGreaterEqual(int(headers['Content-length']), 2162) + assert int(headers['Content-length']) >= 2162 content_type = headers['Content-type'] - self.assertTrue(content_type.startswith('multipart/form-data')) - self.assertEqual(self.last_open.req.get_method(), 'POST') + assert content_type.startswith('multipart/form-data') + assert self.last_open.req.get_method() == 'POST' expected_url = 'https://upload.pypi.org/legacy/' - self.assertEqual(self.last_open.req.get_full_url(), expected_url) + assert self.last_open.req.get_full_url() == expected_url data = self.last_open.req.data - self.assertIn(b'xxx', data) - self.assertIn(b'protocol_version', data) - self.assertIn(b'sha256_digest', data) - self.assertIn( - b'cd2eb0837c9b4c962c22d2ff8b5441b7b45805887f051d39bf133b583baf' b'6860', - data, + assert b'xxx' in data + assert b'protocol_version' in data + assert b'sha256_digest' in data + assert ( + b'cd2eb0837c9b4c962c22d2ff8b5441b7b45805887f051d39bf133b583baf' + b'6860' in data ) if b'md5_digest' in data: - self.assertIn(b'f561aaf6ef0bf14d4208bb46a4ccb3ad', data) + assert b'f561aaf6ef0bf14d4208bb46a4ccb3ad' in data if b'blake2_256_digest' in data: - self.assertIn( + assert ( b'b6f289a27d4fe90da63c503bfe0a9b761a8f76bb86148565065f040be' b'6d1c3044cf7ded78ef800509bccb4b648e507d88dc6383d67642aadcc' - b'ce443f1534330a', - data, + b'ce443f1534330a' in data ) # The PyPI response body was echoed results = self.get_logs(INFO) - self.assertEqual(results[-1], 75 * '-' + '\nxyzzy\n' + 75 * '-') + assert results[-1] == 75 * '-' + '\nxyzzy\n' + 75 * '-' # bpo-32304: archives whose last byte was b'\r' were corrupted due to # normalization intended for Mac OS 9. @@ -180,15 +175,28 @@ class uploadTestCase(BasePyPIRCCommandTestCase): cmd.run() headers = dict(self.last_open.req.headers) - self.assertGreaterEqual(int(headers['Content-length']), 2172) - self.assertIn(b'long description\r', self.last_open.req.data) + assert int(headers['Content-length']) >= 2172 + assert b'long description\r' in self.last_open.req.data def test_upload_fails(self): self.next_msg = "Not Found" self.next_code = 404 - self.assertRaises(DistutilsError, self.test_upload) + 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) @@ -196,24 +204,15 @@ class uploadTestCase(BasePyPIRCCommandTestCase): self.write_file(self.rc, PYPIRC_LONG_PASSWORD) pkg_dir, dist = self.create_dist(dist_files=dist_files) - tests = [ - (OSError('oserror'), 'oserror', OSError), - ( - HTTPError('url', 400, 'httperror', {}, None), - 'Upload failed (400): httperror', - DistutilsError, - ), - ] - for exception, expected, raised_exception in tests: - with self.subTest(exception=type(exception).__name__): - with mock.patch( - 'distutils.command.upload.urlopen', - new=mock.Mock(side_effect=exception), - ): - with self.assertRaises(raised_exception): - cmd = upload(dist) - cmd.ensure_finalized() - cmd.run() - results = self.get_logs(ERROR) - self.assertIn(expected, 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/distutils/tests/test_util.py b/distutils/tests/test_util.py index 5a44f10a..605b0d40 100644 --- a/distutils/tests/test_util.py +++ b/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,79 +19,45 @@ from distutils.util import ( grok_environment_error, get_host_platform, ) -from distutils import util # used to patch _environ_checked +from distutils import util from distutils import sysconfig from distutils.errors import DistutilsPlatformError, DistutilsByteCompileError -@pytest.mark.usefixtures('save_env') -class UtilTestCase(unittest.TestCase): - def setUp(self): - super(UtilTestCase, self).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(UtilTestCase, self).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)]'): - self.assertEqual(get_host_platform(), 'win-arm64') - with unittest.mock.patch('sys.version', '... [... (ARM)]'): - self.assertEqual(get_host_platform(), 'win-arm32') + with mock.patch('os.name', 'nt'): + with mock.patch('sys.version', '... [... (ARM64)]'): + assert get_host_platform() == 'win-arm64' + with mock.patch('sys.version', '... [... (ARM)]'): + assert get_host_platform() == 'win-arm32' - with unittest.mock.patch('sys.version_info', (3, 9, 0, 'final', 0)): - self.assertEqual(get_host_platform(), stdlib_sysconfig.get_platform()) + 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'}): - self.assertEqual(get_platform(), 'win32') - with unittest.mock.patch.dict('os.environ', {'VSCMD_ARG_TGT_ARCH': 'x64'}): - self.assertEqual(get_platform(), 'win-amd64') - with unittest.mock.patch.dict('os.environ', {'VSCMD_ARG_TGT_ARCH': 'arm'}): - self.assertEqual(get_platform(), 'win-arm32') - with unittest.mock.patch.dict( - 'os.environ', {'VSCMD_ARG_TGT_ARCH': 'arm64'} - ): - self.assertEqual(get_platform(), 'win-arm64') + with mock.patch('os.name', 'nt'): + with mock.patch.dict('os.environ', {'VSCMD_ARG_TGT_ARCH': 'x86'}): + assert get_platform() == 'win32' + with mock.patch.dict('os.environ', {'VSCMD_ARG_TGT_ARCH': 'x64'}): + assert get_platform() == 'win-amd64' + with mock.patch.dict('os.environ', {'VSCMD_ARG_TGT_ARCH': 'arm'}): + assert get_platform() == 'win-arm32' + with mock.patch.dict('os.environ', {'VSCMD_ARG_TGT_ARCH': 'arm64'}): + assert get_platform() == 'win-arm64' def test_convert_path(self): # linux/mac @@ -103,7 +68,7 @@ class UtilTestCase(unittest.TestCase): os.path.join = _join - self.assertEqual(convert_path('/home/to/my/stuff'), '/home/to/my/stuff') + assert convert_path('/home/to/my/stuff') == '/home/to/my/stuff' # win os.sep = '\\' @@ -113,11 +78,13 @@ class UtilTestCase(unittest.TestCase): os.path.join = _join - self.assertRaises(ValueError, convert_path, '/home/to/my/stuff') - self.assertRaises(ValueError, convert_path, 'home/to/my/stuff/') + with pytest.raises(ValueError): + convert_path('/home/to/my/stuff') + with pytest.raises(ValueError): + convert_path('home/to/my/stuff/') - self.assertEqual(convert_path('home/to/my/stuff'), 'home\\to\\my\\stuff') - self.assertEqual(convert_path('.'), os.curdir) + assert convert_path('home/to/my/stuff') == 'home\\to\\my\\stuff' + assert convert_path('.') == os.curdir def test_change_root(self): # linux/mac @@ -133,8 +100,8 @@ class UtilTestCase(unittest.TestCase): os.path.join = _join - self.assertEqual(change_root('/root', '/old/its/here'), '/root/old/its/here') - self.assertEqual(change_root('/root', 'its/here'), '/root/its/here') + assert change_root('/root', '/old/its/here') == '/root/old/its/here' + assert change_root('/root', 'its/here') == '/root/its/here' # windows os.name = 'nt' @@ -156,14 +123,15 @@ class UtilTestCase(unittest.TestCase): os.path.join = _join - self.assertEqual( - change_root('c:\\root', 'c:\\old\\its\\here'), 'c:\\root\\old\\its\\here' + assert ( + change_root('c:\\root', 'c:\\old\\its\\here') == 'c:\\root\\old\\its\\here' ) - self.assertEqual(change_root('c:\\root', 'its\\here'), 'c:\\root\\its\\here') + assert change_root('c:\\root', 'its\\here') == 'c:\\root\\its\\here' # BugsBunny os (it's a great os) os.name = 'BugsBunny' - self.assertRaises(DistutilsPlatformError, change_root, 'c:\\root', 'its\\here') + with pytest.raises(DistutilsPlatformError): + change_root('c:\\root', 'its\\here') # XXX platforms to be covered: mac @@ -173,10 +141,10 @@ class UtilTestCase(unittest.TestCase): check_environ() - self.assertEqual(os.environ['PLAT'], get_platform()) - self.assertEqual(util._environ_checked, 1) + 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) @@ -189,7 +157,7 @@ class UtilTestCase(unittest.TestCase): ) with mock.patch.object(pwd, 'getpwuid', return_value=result): check_environ() - self.assertEqual(os.environ['HOME'], '/home/distutils') + assert os.environ['HOME'] == '/home/distutils' util._environ_checked = 0 os.environ.pop('HOME', None) @@ -197,23 +165,25 @@ class UtilTestCase(unittest.TestCase): # bpo-10496: Catch pwd.getpwuid() error with mock.patch.object(pwd, 'getpwuid', side_effect=KeyError): check_environ() - self.assertNotIn('HOME', os.environ) + assert 'HOME' not in os.environ def test_split_quoted(self): - self.assertEqual( - split_quoted('""one"" "two" \'three\' \\four'), - ['one', 'two', 'three', 'four'], - ) + assert split_quoted('""one"" "two" \'three\' \\four') == [ + 'one', + 'two', + 'three', + 'four', + ] def test_strtobool(self): yes = ('y', 'Y', 'yes', 'True', 't', 'true', 'True', 'On', 'on', '1') no = ('n', 'no', 'f', 'false', 'off', '0', 'Off', 'No', 'N') for y in yes: - self.assertTrue(strtobool(y)) + assert strtobool(y) for n in no: - self.assertFalse(strtobool(n)) + assert not strtobool(n) def test_rfc822_escape(self): header = 'I am a\npoor\nlonesome\nheader\n' @@ -221,7 +191,7 @@ class UtilTestCase(unittest.TestCase): wanted = ('I am a%(8s)spoor%(8s)slonesome%(8s)s' 'header%(8s)s') % { '8s': '\n' + 8 * ' ' } - self.assertEqual(res, wanted) + assert res == wanted def test_dont_write_bytecode(self): # makes sure byte_compile raise a DistutilsError @@ -229,7 +199,8 @@ class UtilTestCase(unittest.TestCase): old_dont_write_bytecode = sys.dont_write_bytecode sys.dont_write_bytecode = True try: - self.assertRaises(DistutilsByteCompileError, byte_compile, []) + with pytest.raises(DistutilsByteCompileError): + byte_compile([]) finally: sys.dont_write_bytecode = old_dont_write_bytecode @@ -237,4 +208,4 @@ class UtilTestCase(unittest.TestCase): # test obsolete function to ensure backward compat (#4931) exc = IOError("Unable to find batch file") msg = grok_environment_error(exc) - self.assertEqual(msg, "error: Unable to find batch file") + assert msg == "error: Unable to find batch file" diff --git a/distutils/tests/test_version.py b/distutils/tests/test_version.py index 3727bac8..ff52ea46 100644 --- a/distutils/tests/test_version.py +++ b/distutils/tests/test_version.py @@ -1,26 +1,26 @@ """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') - self.assertEqual(version.version, (1, 2, 3)) - self.assertEqual(version.prerelease, ('a', 1)) - self.assertEqual(str(version), '1.2.3a1') + assert version.version == (1, 2, 3) + assert version.prerelease == ('a', 1) + assert str(version) == '1.2.3a1' version = StrictVersion('1.2.0') - self.assertEqual(str(version), '1.2') + assert str(version) == '1.2' def test_cmp_strict(self): versions = ( @@ -51,19 +51,17 @@ class VersionTestCase(unittest.TestCase): raise AssertionError( ("cmp(%s, %s) " "shouldn't raise ValueError") % (v1, v2) ) - self.assertEqual( - res, wanted, 'cmp(%s, %s) should be %s, got %s' % (v1, v2, wanted, res) + assert res == wanted, 'cmp({}, {}) should be {}, got {}'.format( + v1, v2, wanted, res ) res = StrictVersion(v1)._cmp(v2) - self.assertEqual( - res, wanted, 'cmp(%s, %s) should be %s, got %s' % (v1, v2, wanted, res) + assert res == wanted, 'cmp({}, {}) should be {}, got {}'.format( + v1, v2, wanted, res ) res = StrictVersion(v1)._cmp(object()) - self.assertIs( - res, - NotImplemented, - 'cmp(%s, %s) should be NotImplemented, got %s' % (v1, v2, res), - ) + assert ( + res is NotImplemented + ), 'cmp({}, {}) should be NotImplemented, got {}'.format(v1, v2, res) def test_cmp(self): versions = ( @@ -79,16 +77,14 @@ class VersionTestCase(unittest.TestCase): for v1, v2, wanted in versions: res = LooseVersion(v1)._cmp(LooseVersion(v2)) - self.assertEqual( - res, wanted, 'cmp(%s, %s) should be %s, got %s' % (v1, v2, wanted, res) + assert res == wanted, 'cmp({}, {}) should be {}, got {}'.format( + v1, v2, wanted, res ) res = LooseVersion(v1)._cmp(v2) - self.assertEqual( - res, wanted, 'cmp(%s, %s) should be %s, got %s' % (v1, v2, wanted, res) + assert res == wanted, 'cmp({}, {}) should be {}, got {}'.format( + v1, v2, wanted, res ) res = LooseVersion(v1)._cmp(object()) - self.assertIs( - res, - NotImplemented, - 'cmp(%s, %s) should be NotImplemented, got %s' % (v1, v2, res), - ) + assert ( + res is NotImplemented + ), 'cmp({}, {}) should be NotImplemented, got {}'.format(v1, v2, res) diff --git a/distutils/tests/unix_compat.py b/distutils/tests/unix_compat.py index 8250b363..95fc8eeb 100644 --- a/distutils/tests/unix_compat.py +++ b/distutils/tests/unix_compat.py @@ -1,5 +1,4 @@ import sys -import unittest try: import grp @@ -7,9 +6,13 @@ try: except ImportError: grp = pwd = None +import pytest + UNIX_ID_SUPPORT = grp and pwd UID_0_SUPPORT = UNIX_ID_SUPPORT and sys.platform != "cygwin" -require_unix_id = unittest.skipUnless(UNIX_ID_SUPPORT, "Requires grp and pwd support") -require_uid_0 = unittest.skipUnless(UID_0_SUPPORT, "Requires UID 0 support") +require_unix_id = pytest.mark.skipif( + not UNIX_ID_SUPPORT, reason="Requires grp and pwd support" +) +require_uid_0 = pytest.mark.skipif(not UID_0_SUPPORT, reason="Requires UID 0 support") diff --git a/distutils/tests/xxmodule-3.8.c b/distutils/tests/xxmodule-3.8.c new file mode 100644 index 00000000..0250031d --- /dev/null +++ b/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/distutils/tests/xxmodule.c b/distutils/tests/xxmodule.c new file mode 100644 index 00000000..a6e5071d --- /dev/null +++ b/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); +} diff --git a/distutils/text_file.py b/distutils/text_file.py index cffcd099..7274d4b1 100644 --- a/distutils/text_file.py +++ b/distutils/text_file.py @@ -5,7 +5,6 @@ that (optionally) takes care of stripping comments, ignoring blank lines, and joining lines with backslashes.""" import sys -import io class TextFile: @@ -116,7 +115,7 @@ class TextFile: """Open a new file named 'filename'. This overrides both the 'filename' and 'file' arguments to the constructor.""" self.filename = filename - self.file = io.open(self.filename, 'r', errors=self.errors) + self.file = open(self.filename, errors=self.errors) self.current_line = 0 def close(self): diff --git a/distutils/unixccompiler.py b/distutils/unixccompiler.py index b3eece97..4ab771a4 100644 --- a/distutils/unixccompiler.py +++ b/distutils/unixccompiler.py @@ -17,6 +17,7 @@ import os import sys import re import shlex +import itertools from distutils import sysconfig from distutils.dep_util import newer @@ -163,17 +164,21 @@ class UnixCCompiler(CCompiler): pp_args.extend(extra_postargs) pp_args.append(source) - # We need to preprocess: either we're being forced to, or we're - # generating output to stdout, or there's a target output file and - # the source file is newer than the target (or the target doesn't - # exist). - if self.force or output_file is None or newer(source, output_file): - if output_file: - self.mkpath(os.path.dirname(output_file)) - try: - self.spawn(pp_args) - except DistutilsExecError as msg: - raise CompileError(msg) + # reasons to preprocess: + # - force is indicated + # - output is directed to stdout + # - source file is newer than the target + preprocess = self.force or output_file is None or newer(source, output_file) + if not preprocess: + return + + if output_file: + self.mkpath(os.path.dirname(output_file)) + + try: + self.spawn(pp_args) + except DistutilsExecError as msg: + raise CompileError(msg) def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): compiler_so = compiler_fixup(self.compiler_so, cc_args + extra_postargs) @@ -320,66 +325,77 @@ class UnixCCompiler(CCompiler): def library_option(self, lib): return "-l" + lib - def find_library_file(self, dirs, lib, debug=0): - shared_f = self.library_filename(lib, lib_type='shared') - dylib_f = self.library_filename(lib, lib_type='dylib') - xcode_stub_f = self.library_filename(lib, lib_type='xcode_stub') - static_f = self.library_filename(lib, lib_type='static') - - if sys.platform == 'darwin': - # On OSX users can specify an alternate SDK using - # '-isysroot', calculate the SDK root if it is specified - # (and use it further on) - # - # Note that, as of Xcode 7, Apple SDKs may contain textual stub - # libraries with .tbd extensions rather than the normal .dylib - # shared libraries installed in /. The Apple compiler tool - # chain handles this transparently but it can cause problems - # for programs that are being built with an SDK and searching - # for specific libraries. Callers of find_library_file need to - # keep in mind that the base filename of the returned SDK library - # file might have a different extension from that of the library - # file installed on the running system, for example: - # /Applications/Xcode.app/Contents/Developer/Platforms/ - # MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/ - # usr/lib/libedit.tbd - # vs - # /usr/lib/libedit.dylib - cflags = sysconfig.get_config_var('CFLAGS') - m = re.search(r'-isysroot\s*(\S+)', cflags) - if m is None: - sysroot = '/' - else: - sysroot = m.group(1) - - for dir in dirs: - shared = os.path.join(dir, shared_f) - dylib = os.path.join(dir, dylib_f) - static = os.path.join(dir, static_f) - xcode_stub = os.path.join(dir, xcode_stub_f) - - if sys.platform == 'darwin' and ( + @staticmethod + def _library_root(dir): + """ + macOS users can specify an alternate SDK using'-isysroot'. + Calculate the SDK root if it is specified. + + Note that, as of Xcode 7, Apple SDKs may contain textual stub + libraries with .tbd extensions rather than the normal .dylib + shared libraries installed in /. The Apple compiler tool + chain handles this transparently but it can cause problems + for programs that are being built with an SDK and searching + for specific libraries. Callers of find_library_file need to + keep in mind that the base filename of the returned SDK library + file might have a different extension from that of the library + file installed on the running system, for example: + /Applications/Xcode.app/Contents/Developer/Platforms/ + MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/ + usr/lib/libedit.tbd + vs + /usr/lib/libedit.dylib + """ + cflags = sysconfig.get_config_var('CFLAGS') + match = re.search(r'-isysroot\s*(\S+)', cflags) + + apply_root = ( + sys.platform == 'darwin' + and match + and ( dir.startswith('/System/') or (dir.startswith('/usr/') and not dir.startswith('/usr/local/')) - ): - - shared = os.path.join(sysroot, dir[1:], shared_f) - dylib = os.path.join(sysroot, dir[1:], dylib_f) - static = os.path.join(sysroot, dir[1:], static_f) - xcode_stub = os.path.join(sysroot, dir[1:], xcode_stub_f) - - # We're second-guessing the linker here, with not much hard - # data to go on: GCC seems to prefer the shared library, so I'm - # assuming that *all* Unix C compilers do. And of course I'm - # ignoring even GCC's "-static" option. So sue me. - if os.path.exists(dylib): - return dylib - elif os.path.exists(xcode_stub): - return xcode_stub - elif os.path.exists(shared): - return shared - elif os.path.exists(static): - return static - - # Oops, didn't find it in *any* of 'dirs' - return None + ) + ) + + return os.path.join(match.group(1), dir[1:]) if apply_root else dir + + def find_library_file(self, dirs, lib, debug=0): + r""" + Second-guess the linker with not much hard + data to go on: GCC seems to prefer the shared library, so + assume that *all* Unix C compilers do, + ignoring even GCC's "-static" option. + + >>> compiler = UnixCCompiler() + >>> compiler._library_root = lambda dir: dir + >>> monkeypatch = getfixture('monkeypatch') + >>> monkeypatch.setattr(os.path, 'exists', lambda d: 'existing' in d) + >>> dirs = ('/foo/bar/missing', '/foo/bar/existing') + >>> compiler.find_library_file(dirs, 'abc').replace('\\', '/') + '/foo/bar/existing/libabc.dylib' + >>> compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/') + '/foo/bar/existing/libabc.dylib' + >>> monkeypatch.setattr(os.path, 'exists', + ... lambda d: 'existing' in d and '.a' in d) + >>> compiler.find_library_file(dirs, 'abc').replace('\\', '/') + '/foo/bar/existing/libabc.a' + >>> compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/') + '/foo/bar/existing/libabc.a' + """ + lib_names = ( + self.library_filename(lib, lib_type=type) + for type in 'dylib xcode_stub shared static'.split() + ) + + roots = map(self._library_root, dirs) + + searched = ( + os.path.join(root, lib_name) + for root, lib_name in itertools.product(roots, lib_names) + ) + + found = filter(os.path.exists, searched) + + # Return None if it could not be found in any dir. + return next(found, None) diff --git a/distutils/util.py b/distutils/util.py index b22cf984..d95992ec 100644 --- a/distutils/util.py +++ b/distutils/util.py @@ -334,7 +334,7 @@ def execute(func, args, msg=None, verbose=0, dry_run=0): print. """ if msg is None: - msg = "%s%r" % (func.__name__, args) + msg = "{}{!r}".format(func.__name__, args) if msg[-2:] == ',)': # correct for singleton tuple msg = msg[0:-2] + ')' @@ -356,7 +356,7 @@ def strtobool(val): elif val in ('n', 'no', 'f', 'false', 'off', '0'): return 0 else: - raise ValueError("invalid truth value %r" % (val,)) + raise ValueError("invalid truth value {!r}".format(val)) def byte_compile( # noqa: C901 diff --git a/distutils/version.py b/distutils/version.py index 7e33fb7c..e29e2657 100644 --- a/distutils/version.py +++ b/distutils/version.py @@ -60,7 +60,7 @@ class Version: ) def __repr__(self): - return "%s ('%s')" % (self.__class__.__name__, str(self)) + return "{} ('{}')".format(self.__class__.__name__, str(self)) def __eq__(self, other): c = self._cmp(other) diff --git a/pyproject.toml b/pyproject.toml index 0097e9f6..e6863cff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,11 @@ [tool.black] skip-string-normalization = true + +[tool.pytest-enabler.black] +addopts = "--black" + +[tool.pytest-enabler.flake8] +addopts = "--flake8" + +[tool.pytest-enabler.cov] +addopts = "--cov" @@ -1,6 +1,23 @@ [pytest] addopts=--doctest-modules filterwarnings= - # acknowledge that TestDistribution isn't a test - ignore:cannot collect test class 'TestDistribution' - ignore:Fallback spawn triggered + # Suppress deprecation warning in flake8 + ignore:SelectableGroups dict interface is deprecated::flake8 + + # shopkeep/pytest-black#55 + ignore:<class 'pytest_black.BlackItem'> is not using a cooperative constructor:pytest.PytestDeprecationWarning + ignore:The \(fspath. py.path.local\) argument to BlackItem is deprecated.:pytest.PytestDeprecationWarning + ignore:BlackItem is an Item subclass and should not be a collector:pytest.PytestWarning + + # tholo/pytest-flake8#83 + ignore:<class 'pytest_flake8.Flake8Item'> is not using a cooperative constructor:pytest.PytestDeprecationWarning + ignore:The \(fspath. py.path.local\) argument to Flake8Item is deprecated.:pytest.PytestDeprecationWarning + ignore:Flake8Item is an Item subclass and should not be a collector:pytest.PytestWarning + + # acknowledge that TestDistribution isn't a test + ignore:cannot collect test class 'TestDistribution' + ignore:Fallback spawn triggered + + # ignore spurious and unactionable warnings + ignore:The frontend.OptionParser class will be replaced by a subclass of argparse.ArgumentParser in Docutils 0.21 or later.:DeprecationWarning: + ignore: The frontend.Option class will be removed in Docutils 0.21 or later.:DeprecationWarning: @@ -1,10 +1,24 @@ [tox] minversion = 3.25 +toxworkdir={env:TOX_WORK_DIR:.tox} + [testenv] deps = pytest + + pytest-flake8 + # workaround for tholo/pytest-flake8#87 + flake8 < 5 + + pytest-black + pytest-cov + pytest-enabler >= 1.3 + jaraco.envs>=2.4 + jaraco.path + path + docutils commands = pytest {posargs} setenv = |
