Imported Upstream version 64.0.1 upstream/64.0.1
authorJinWang An <jinwang.an@samsung.com>
Mon, 27 Mar 2023 08:02:54 +0000 (17:02 +0900)
committerJinWang An <jinwang.an@samsung.com>
Mon, 27 Mar 2023 08:02:54 +0000 (17:02 +0900)
171 files changed:
.bumpversion.cfg
.github/workflows/main.yml
CHANGES.rst
changelog.d/3265.change.rst [deleted file]
changelog.d/3380.change.rst [deleted file]
changelog.d/3380.deprecation.rst [deleted file]
changelog.d/3392.change.rst [deleted file]
changelog.d/3412.change.rst [deleted file]
changelog.d/3414.change.rst [deleted file]
changelog.d/3414.doc.rst [deleted file]
docs/build_meta.rst
docs/conf.py
docs/deprecated/commands.rst
docs/setuptools.rst
docs/userguide/dependency_management.rst
docs/userguide/development_mode.rst
docs/userguide/extension.rst
docs/userguide/package_discovery.rst
docs/userguide/pyproject_config.rst
docs/userguide/quickstart.rst
pkg_resources/_vendor/pyparsing-3.0.9.dist-info/INSTALLER [new file with mode: 0644]
pkg_resources/_vendor/pyparsing-3.0.9.dist-info/LICENSE [new file with mode: 0644]
pkg_resources/_vendor/pyparsing-3.0.9.dist-info/METADATA [new file with mode: 0644]
pkg_resources/_vendor/pyparsing-3.0.9.dist-info/RECORD [new file with mode: 0644]
pkg_resources/_vendor/pyparsing-3.0.9.dist-info/REQUESTED [new file with mode: 0644]
pkg_resources/_vendor/pyparsing-3.0.9.dist-info/WHEEL [new file with mode: 0644]
pkg_resources/_vendor/pyparsing/__init__.py
pkg_resources/_vendor/pyparsing/actions.py
pkg_resources/_vendor/pyparsing/core.py
pkg_resources/_vendor/pyparsing/diagram/__init__.py
pkg_resources/_vendor/pyparsing/diagram/template.jinja2 [deleted file]
pkg_resources/_vendor/pyparsing/exceptions.py
pkg_resources/_vendor/pyparsing/helpers.py
pkg_resources/_vendor/pyparsing/results.py
pkg_resources/_vendor/pyparsing/testing.py
pkg_resources/_vendor/pyparsing/unicode.py
pkg_resources/_vendor/vendored.txt
pyproject.toml
setup.cfg
setuptools/_distutils/_msvccompiler.py
setuptools/_distutils/archive_util.py
setuptools/_distutils/bcppcompiler.py
setuptools/_distutils/ccompiler.py
setuptools/_distutils/cmd.py
setuptools/_distutils/command/__init__.py
setuptools/_distutils/command/_framework_compat.py
setuptools/_distutils/command/bdist.py
setuptools/_distutils/command/bdist_dumb.py
setuptools/_distutils/command/bdist_msi.py
setuptools/_distutils/command/bdist_rpm.py
setuptools/_distutils/command/bdist_wininst.py
setuptools/_distutils/command/build.py
setuptools/_distutils/command/build_clib.py
setuptools/_distutils/command/build_ext.py
setuptools/_distutils/command/build_py.py
setuptools/_distutils/command/build_scripts.py
setuptools/_distutils/command/check.py
setuptools/_distutils/command/config.py
setuptools/_distutils/command/install.py
setuptools/_distutils/command/install_egg_info.py
setuptools/_distutils/command/register.py
setuptools/_distutils/command/sdist.py
setuptools/_distutils/command/upload.py
setuptools/_distutils/config.py
setuptools/_distutils/core.py
setuptools/_distutils/cygwinccompiler.py
setuptools/_distutils/dep_util.py
setuptools/_distutils/dir_util.py
setuptools/_distutils/dist.py
setuptools/_distutils/extension.py
setuptools/_distutils/fancy_getopt.py
setuptools/_distutils/file_util.py
setuptools/_distutils/filelist.py
setuptools/_distutils/msvc9compiler.py
setuptools/_distutils/msvccompiler.py
setuptools/_distutils/py38compat.py
setuptools/_distutils/spawn.py
setuptools/_distutils/sysconfig.py
setuptools/_distutils/tests/__init__.py
setuptools/_distutils/tests/py38compat.py
setuptools/_distutils/tests/support.py
setuptools/_distutils/tests/test_archive_util.py
setuptools/_distutils/tests/test_bdist.py
setuptools/_distutils/tests/test_bdist_dumb.py
setuptools/_distutils/tests/test_bdist_msi.py
setuptools/_distutils/tests/test_bdist_rpm.py
setuptools/_distutils/tests/test_bdist_wininst.py
setuptools/_distutils/tests/test_build.py
setuptools/_distutils/tests/test_build_clib.py
setuptools/_distutils/tests/test_build_ext.py
setuptools/_distutils/tests/test_build_py.py
setuptools/_distutils/tests/test_build_scripts.py
setuptools/_distutils/tests/test_ccompiler.py [new file with mode: 0644]
setuptools/_distutils/tests/test_check.py
setuptools/_distutils/tests/test_clean.py
setuptools/_distutils/tests/test_cmd.py
setuptools/_distutils/tests/test_config.py
setuptools/_distutils/tests/test_config_cmd.py
setuptools/_distutils/tests/test_core.py
setuptools/_distutils/tests/test_cygwinccompiler.py
setuptools/_distutils/tests/test_dep_util.py
setuptools/_distutils/tests/test_dir_util.py
setuptools/_distutils/tests/test_dist.py
setuptools/_distutils/tests/test_extension.py
setuptools/_distutils/tests/test_file_util.py
setuptools/_distutils/tests/test_filelist.py
setuptools/_distutils/tests/test_install.py
setuptools/_distutils/tests/test_install_data.py
setuptools/_distutils/tests/test_install_headers.py
setuptools/_distutils/tests/test_install_lib.py
setuptools/_distutils/tests/test_install_scripts.py
setuptools/_distutils/tests/test_log.py
setuptools/_distutils/tests/test_msvc9compiler.py
setuptools/_distutils/tests/test_msvccompiler.py
setuptools/_distutils/tests/test_register.py
setuptools/_distutils/tests/test_sdist.py
setuptools/_distutils/tests/test_spawn.py
setuptools/_distutils/tests/test_sysconfig.py
setuptools/_distutils/tests/test_text_file.py
setuptools/_distutils/tests/test_unixccompiler.py
setuptools/_distutils/tests/test_upload.py
setuptools/_distutils/tests/test_util.py
setuptools/_distutils/tests/test_version.py
setuptools/_distutils/tests/test_versionpredicate.py
setuptools/_distutils/tests/unix_compat.py
setuptools/_distutils/tests/xxmodule-3.8.c [new file with mode: 0644]
setuptools/_distutils/tests/xxmodule.c [new file with mode: 0644]
setuptools/_distutils/text_file.py
setuptools/_distutils/unixccompiler.py
setuptools/_distutils/util.py
setuptools/_distutils/version.py
setuptools/_vendor/pyparsing-3.0.8.dist-info/INSTALLER [deleted file]
setuptools/_vendor/pyparsing-3.0.8.dist-info/LICENSE [deleted file]
setuptools/_vendor/pyparsing-3.0.8.dist-info/METADATA [deleted file]
setuptools/_vendor/pyparsing-3.0.8.dist-info/RECORD [deleted file]
setuptools/_vendor/pyparsing-3.0.8.dist-info/REQUESTED [deleted file]
setuptools/_vendor/pyparsing-3.0.8.dist-info/WHEEL [deleted file]
setuptools/_vendor/pyparsing-3.0.9.dist-info/INSTALLER [new file with mode: 0644]
setuptools/_vendor/pyparsing-3.0.9.dist-info/LICENSE [new file with mode: 0644]
setuptools/_vendor/pyparsing-3.0.9.dist-info/METADATA [new file with mode: 0644]
setuptools/_vendor/pyparsing-3.0.9.dist-info/RECORD [new file with mode: 0644]
setuptools/_vendor/pyparsing-3.0.9.dist-info/REQUESTED [new file with mode: 0644]
setuptools/_vendor/pyparsing-3.0.9.dist-info/WHEEL [new file with mode: 0644]
setuptools/_vendor/pyparsing/__init__.py
setuptools/_vendor/pyparsing/actions.py
setuptools/_vendor/pyparsing/core.py
setuptools/_vendor/pyparsing/diagram/__init__.py
setuptools/_vendor/pyparsing/diagram/template.jinja2 [deleted file]
setuptools/_vendor/pyparsing/exceptions.py
setuptools/_vendor/pyparsing/helpers.py
setuptools/_vendor/pyparsing/results.py
setuptools/_vendor/pyparsing/testing.py
setuptools/_vendor/pyparsing/unicode.py
setuptools/_vendor/vendored.txt
setuptools/build_meta.py
setuptools/command/__init__.py
setuptools/command/build_py.py
setuptools/command/editable_wheel.py
setuptools/command/upload_docs.py
setuptools/config/__init__.py
setuptools/config/pyprojecttoml.py
setuptools/config/setupcfg.py
setuptools/extension.py
setuptools/tests/config/test_pyprojecttoml.py
setuptools/tests/config/test_setupcfg.py
setuptools/tests/integration/test_pip_install_sdist.py
setuptools/tests/test_build_meta.py
setuptools/tests/test_build_py.py
setuptools/tests/test_editable_install.py
setuptools/tests/test_windows_wrappers.py
tox.ini

index 284f7c1ecb71dc4be83820dfec554f938c6545fb..e14a394e4cb7c59a1b7a6aca5410a07f9114b0dc 100644 (file)
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 63.1.0
+current_version = 64.0.1
 commit = True
 tag = True
 
index 7bd0af44d4c0d67d7e8b3e8d436bcc86cb6a43d2..83624a5bc4e5cec8c85a802ffde25b43ca52962d 100644 (file)
@@ -16,12 +16,10 @@ jobs:
         distutils:
         - local
         python:
-        # Build on pre-releases until stable, then stable releases.
-        # actions/setup-python#213
-        - ~3.7.0-0
-        - ~3.10.0-0
+        - 3.7-dev
+        - 3.10-dev
         # disabled due to #3365
-        # - ~3.11.0-0
+        # - 3.11-dev
         - pypy-3.7
         platform:
         - ubuntu-latest
@@ -151,7 +149,7 @@ jobs:
       - name: Setup Python
         uses: actions/setup-python@v3
         with:
-          python-version: "3.10"
+          python-version: "3.11-dev"
       - name: Install tox
         run: |
           python -m pip install tox
index c85d6dc9e19dc2d3cd2754e50ca252bf7e33eb82..20aba6cfd74058d3543e5db72fd49203e738d80b 100644 (file)
@@ -1,3 +1,162 @@
+v64.0.1
+-------
+
+
+Misc
+^^^^
+* #3497: Fixed ``editable_wheel`` for legacy namespaces.
+* #3502: Fixed issue with editable install and single module distributions.
+* #3503: Added filter to ignore external ``.egg-info`` files in manifest.
+
+  Some plugins might rely on the fact that the ``.egg-info`` directory is
+  produced inside the project dir, which may not be the case in editable installs
+  (the ``.egg-info`` directory is produced inside the metadata directory given by
+  the build frontend via PEP 660 hooks).
+
+
+v64.0.0
+-------
+
+
+Deprecations
+^^^^^^^^^^^^
+* #3380: Passing some types of parameters via ``--global-option`` to setuptools PEP 517/PEP 660 backend
+  is now considered deprecated. The user can pass the same arbitrary parameter
+  via ``--build-option`` (``--global-option`` is now reserved for flags like
+  ``--verbose`` or ``--quiet``).
+
+  Both ``--build-option`` and ``--global-option`` are supported as a **transitional** effort (a.k.a. "escape hatch").
+  In the future a proper list of allowed ``config_settings`` may be created.
+
+Breaking Changes
+^^^^^^^^^^^^^^^^
+* #3265: Added implementation for *editable install* hooks (PEP 660).
+
+  By default the users will experience a *lenient* behavior  which prioritises
+  the ability of the users of changing the distributed packages (e.g. adding new
+  files or removing old ones).
+  But they can also opt into a *strict* mode, which will try to replicate as much
+  as possible the behavior of the package as if it would be normally installed by
+  end users. The *strict* editable installation is not able to detect if files
+  are added or removed from the project (a new installation is required).
+
+  .. important::
+     The *editable* aspect of the *editable install* supported this implementation
+     is restricted to the Python modules contained in the distributed package.
+     Changes in binary extensions (e.g. C/C++), entry-point definitions,
+     dependencies, metadata, datafiles, etc may require a new installation.
+
+Changes
+^^^^^^^
+* #3380: Improved the handling of the ``config_settings`` parameter in both PEP 517 and
+  PEP 660 interfaces:
+
+  - It is possible now to pass both ``--global-option`` and ``--build-option``.
+    As discussed in #1928, arbitrary arguments passed via ``--global-option``
+    should be placed before the name of the setuptools' internal command, while
+    ``--build-option`` should come after.
+
+  - Users can pass ``editable-mode=strict`` to select a strict behaviour for the
+    editable installation.
+* #3392: Exposed ``get_output_mapping()`` from ``build_py`` and ``build_ext``
+  subcommands. This interface is reserved for the use of ``setuptools``
+  Extensions and third part packages are explicitly disallowed to calling it.
+  However, any implementation overwriting ``build_py`` or ``build_ext`` are
+  required to honour this interface.
+* #3412: Added ability of collecting source files from custom build sub-commands to
+  ``sdist``. This allows plugins and customization scripts to automatically
+  add required source files in the source distribution.
+* #3414: Users can *temporarily* specify an environment variable
+  ``SETUPTOOLS_ENABLE_FEATURE=legacy-editable`` as a escape hatch for the
+  :pep:`660` behavior. This setting is **transitional** and may be removed in the
+  future.
+* #3484: Added *transient* ``compat`` mode to editable installs.
+  This more will be temporarily available (to facilitate the transition period)
+  for those that want to emulate the behavior of the ``develop`` command
+  (in terms of what is added to ``sys.path``).
+  This mode is provided "as is", with limited support, and will be removed in
+  future versions of ``setuptools``.
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
+* #3414: Updated :doc:`Development Mode </userguide/development_mode>` to reflect on the
+  implementation of :pep:`660`.
+
+
+v63.4.3
+-------
+
+
+Misc
+^^^^
+* #3496: Update to pypa/distutils@b65aa40 including more robust support for library/include dir handling in msvccompiler (pypa/distutils#153) and test suite improvements.
+
+
+v63.4.2
+-------
+
+
+Misc
+^^^^
+* #3453: Bump vendored version of :pypi:`pyparsing` to 3.0.9.
+* #3481: Add warning for potential ``install_requires`` and ``extras_require``
+  misconfiguration in ``setup.cfg``
+* #3487: Modified ``pyproject.toml`` validation exception handling to
+  make relevant debugging information easier to spot.
+
+
+v63.4.1
+-------
+
+
+Misc
+^^^^
+* #3482: Sync with pypa/distutils@274758f1c02048d295efdbc13d2f88d9923547f8, restoring compatibility shim in bdist.format_commands.
+
+
+v63.4.0
+-------
+
+
+Changes
+^^^^^^^
+* #2971: ``upload_docs`` command is deprecated once again.
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
+* #3443: Installed ``sphinx-hoverxref`` extension to show tooltips on internal an external references.
+  -- by :user:`humitos`
+* #3444: Installed ``sphinx-notfound-page`` extension to generate nice 404 pages.
+  -- by :user:`humitos`
+
+Misc
+^^^^
+* #3480: Merge with pypa/distutils@c397f4c
+
+
+v63.3.0
+-------
+
+
+Changes
+^^^^^^^
+* #3475: Merge with pypa/distutils@129480b, including substantial delinting and cleanup, some refactoring around compiler logic, better messaging in cygwincompiler (pypa/distutils#161).
+
+
+v63.2.0
+-------
+
+
+Changes
+^^^^^^^
+* #3395: Included a performance optimization: ``setuptools.build_meta`` no longer tries
+  to :func:`compile` the setup script code before :func:`exec`-ing it.
+
+Misc
+^^^^
+* #3435: Corrected issue in macOS framework builds on Python 3.9 not installed by homebrew (pypa/distutils#158).
+
+
 v63.1.0
 -------
 
@@ -15,7 +174,7 @@ Breaking Changes
 ^^^^^^^^^^^^^^^^
 * #3421: Drop setuptools' support for installing an entrypoint extra requirements at load time:
   - the functionality has been broken since v60.8.0.
-  - the mechanism to do so is deprecated (`fetch_build_eggs`).
+  - the mechanism to do so is deprecated (``fetch_build_eggs``).
   - that use case (e.g. a custom command class entrypoint) is covered by making sure the necessary build requirements are declared.
 
 Documentation changes
diff --git a/changelog.d/3265.change.rst b/changelog.d/3265.change.rst
deleted file mode 100644 (file)
index ac20398..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-Added implementation for *editable install* hooks (PEP 660) - **beta** stage.
-
-- The user will be able select between two distinct behaviors:
-
-  - *lax*, which prioritises the ability of the users of changing the
-    distributed packages (e.g. adding new files or removing old ones)
-
-  - *strict*, which will try to replicate as much as possible the behavior of
-    the package as if it would be normally installed by end users.
-    The *strict* editable installation is not able to detect if files are
-    added or removed from the project (a new installation is required).
-
-.. important::
-   The *editable* aspect of the *editable install* supported this implementation
-   is restricted to the Python modules contained in the distributed package.
-   Changes in binary extensions (e.g. C/C++), entry-point definitions,
-   dependencies, metadata, datafiles, etc require a new installation.
diff --git a/changelog.d/3380.change.rst b/changelog.d/3380.change.rst
deleted file mode 100644 (file)
index 9622417..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-Improved the handling of the ``config_settings`` parameter in both PEP 517 and
-PEP 660 interfaces:
-
-- It is possible now to pass both ``--global-option`` and ``--build-option``.
-  As discussed in #1928, arbitrary arguments passed via ``--global-option``
-  should be placed before the name of the setuptools' internal command, while
-  ``--build-option`` should come after.
-
-- Users can pass ``editable-mode=strict`` to select a strict behaviour for the
-  editable installation.
diff --git a/changelog.d/3380.deprecation.rst b/changelog.d/3380.deprecation.rst
deleted file mode 100644 (file)
index 54d3c4c..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-Passing some types of parameters via ``--global-option`` to setuptools PEP 517/PEP 660 backend
-is now considered deprecated. The user can pass the same arbitrary parameter
-via ``--build-option`` (``--global-option`` is now reserved for flags like
-``--verbose`` or ``--quiet``).
-
-Both ``--build-option`` and ``--global-option`` are supported as a **transitional** effort (a.k.a. "escape hatch").
-In the future a proper list of allowed ``config_settings`` may be created.
diff --git a/changelog.d/3392.change.rst b/changelog.d/3392.change.rst
deleted file mode 100644 (file)
index 8ae7fd9..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-Exposed ``get_output_mapping()`` from ``build_py`` and ``build_ext``
-subcommands. This interface is reserved for the use of ``setuptools``
-Extensions and third part packages are explicitly disallowed to calling it.
-However, any implementation overwriting ``build_py`` or ``build_ext`` are
-required to honour this interface.
diff --git a/changelog.d/3412.change.rst b/changelog.d/3412.change.rst
deleted file mode 100644 (file)
index 69f02bc..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Added ability of collecting source files from custom build sub-commands to
-``sdist``. This allows plugins and customization scripts to automatically
-add required source files in the source distribution.
diff --git a/changelog.d/3414.change.rst b/changelog.d/3414.change.rst
deleted file mode 100644 (file)
index b29f2c5..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-Users can *temporarily* specify an environment variable
-``SETUPTOOLS_ENABLE_FEATURE=legacy-editable`` as a escape hatch for the
-:pep:`660` behavior. This setting is **transitional** and may be removed in the
-future.
diff --git a/changelog.d/3414.doc.rst b/changelog.d/3414.doc.rst
deleted file mode 100644 (file)
index b4756da..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Updated :doc:`Development Mode </userguide/development_mode>` to reflect on the
-implementation of :pep:`660`.
index 45a994fd6ba6c47aab3a8ee543f1cbaa095de906..3c778d800d8edc0c6ab9fdc15f3591e482ed9dfc 100644 (file)
@@ -126,7 +126,7 @@ If you put the following configuration in your ``pyproject.toml``:
 .. code-block:: toml
 
     [build-system]
-    requires = ["setuptools", "wheel"]
+    requires = ["setuptools"]
     build-backend = "backend"
     backend-path = ["_custom_build"]
 
index b7d05382dadd8ba3afcedcfa30449534cbdbd8bd..2b60bf57b2d7b4b1175fdeb41cc59894b76bc5f0 100644 (file)
@@ -102,6 +102,19 @@ intersphinx_mapping.update({
     ),
 })
 
+# Support tooltips on references
+extensions += ['hoverxref.extension']
+hoverxref_auto_ref = True
+hoverxref_intersphinx = [
+    'python',
+    'pip',
+    'build',
+    'PyPUG',
+    'packaging',
+    'twine',
+    'importlib-resources',
+]
+
 # Add support for linking usernames
 github_url = 'https://github.com'
 github_repo_org = 'pypa'
@@ -173,6 +186,7 @@ nitpick_ignore = [
     ('py:exc', 'LibError'),  # undocumented
     ('py:exc', 'LinkError'),  # undocumented
     ('py:exc', 'PreprocessError'),  # undocumented
+    ('py:exc', 'setuptools.errors.PlatformError'),  # sphinx cannot find it
     ('py:func', 'distutils.CCompiler.new_compiler'),  # undocumented
     # undocumented:
     ('py:func', 'distutils.dist.DistributionMetadata.read_pkg_file'),
@@ -202,6 +216,9 @@ extensions += ['jaraco.tidelift']
 extensions += ['sphinx-favicon']
 html_static_path = ['images']  # should contain the folder with icons
 
+# Add support for nice Not Found 404 pages
+extensions += ['notfound.extension']
+
 # List of dicts with <link> HTML attributes
 # static-file points to files in the html_static_path (href is computed)
 favicons = [
index ebd0687a8e566fbb2cddc4f5a7ae4da81d2978b5..d9d97a9efe15c0a1f078da25b5e5476adcdd0168 100644 (file)
@@ -15,7 +15,7 @@ You could also run commands in other circumstances:
 
 * ``setuptools`` projects without ``setup.py`` (e.g., ``setup.cfg``-only)::
 
-   python -c "import setuptools; setup()" --help
+   python -c "from setuptools import setup; setup()" --help
 
 * ``distutils`` projects (with a ``setup.py`` importing ``distutils``)::
 
index aa638300de2e86f07ca16642d4828b217f0b7c27..5317058945747e975a7914381966bab8ebec0f8c 100644 (file)
@@ -151,7 +151,6 @@ To use this feature:
       [build-system]
       requires = [
         "setuptools >= 40.9.0",
-        "wheel",
       ]
       build-backend = "setuptools.build_meta"
 
index 56fbd0bdd3add99581ca9b3d66d0ec3a24c2dd59..33aaf6c656fde7998b1d38bab7dd61c23a489745 100644 (file)
@@ -6,13 +6,10 @@ There are three types of dependency styles offered by setuptools:
 1) build system requirement, 2) required dependency and 3) optional
 dependency.
 
-.. attention::
-   Each dependency, regardless of type, needs to be specified according to :pep:`508`.
-   This allows adding version :pep:`range restrictions <440#version-specifiers>`
-   and :ref:`environment markers <environment-markers>`.
-   Please note however that public package indexes, such as `PyPI`_
-   might not accept packages that declare dependencies using
-   :pep:`direct URLs <440#direct-references>`.
+Each dependency, regardless of type, needs to be specified according to :pep:`508`
+and :pep:`440`.
+This allows adding version :pep:`range restrictions <440#version-specifiers>`
+and :ref:`environment markers <environment-markers>`.
 
 
 .. _build-requires:
@@ -181,6 +178,58 @@ detailed in :pep:`508`.
    to implement custom detection logic.
 
 
+Direct URL dependencies
+-----------------------
+
+.. attention::
+   `PyPI`_ and other standards-conformant package indices **do not** accept
+   packages that declare dependencies using direct URLs. ``pip`` will accept them
+   when installing packages from the local filesystem or from another URL,
+   however.
+
+Dependencies that are not available on a package index but can be downloaded
+elsewhere in the form of a source repository or archive may be specified
+using a variant of :pep:`PEP 440's direct references <440#direct-references>`:
+
+.. tab:: pyproject.toml
+
+    .. code-block:: toml
+
+        [project]
+        # ...
+        dependencies = [
+            "Package-A @ git+https://example.net/package-a.git@main",
+            "Package-B @ https://example.net/archives/package-b.whl",
+        ]
+
+.. tab:: setup.cfg
+
+    .. code-block:: ini
+
+        [options]
+        #...
+        install_requires =
+            Package-A @ git+https://example.net/package-a.git@main
+            Package-B @ https://example.net/archives/package-b.whl
+
+.. tab:: setup.py
+
+    .. code-block:: python
+
+        setup(
+            install_requires=[
+               "Package-A @ git+https://example.net/package-a.git@main",
+               "Package-B @ https://example.net/archives/package-b.whl",
+            ],
+            ...,
+        )
+
+For source repository URLs, a list of supported protocols and VCS-specific
+features such as selecting certain branches or tags can be found in pip's
+documentation on `VCS support <https://pip.pypa.io/en/latest/topics/vcs-support/>`_.
+Supported formats for archive URLs are sdists and wheels.
+
+
 Optional dependencies
 =====================
 Setuptools allows you to declare dependencies that are not installed by default.
@@ -235,7 +284,7 @@ The name ``PDF`` is an arbitrary :pep:`identifier <685>` of such a list of depen
 which other components can refer and have them installed.
 
 A use case for this approach is that other package can use this "extra" for their
-own dependencies. For example, if ``Package-B`` needs ``Package-B`` with PDF support
+own dependencies. For example, if ``Package-B`` needs ``Package-A`` with PDF support
 installed, it might declare the dependency like this:
 
 .. tab:: pyproject.toml
index bfc37a0a5d3b8552f70dee9f34bb6d2a84730988..e7c755ad6bf52344266f3aaa2f0bf3a894a14636 100644 (file)
@@ -53,11 +53,11 @@ Please have a look on the following section if you are looking for a different b
 
 .. admonition:: Virtual Environments
 
-   You can think virtual environments as "isolated Python runtime deployments"
+   You can think about virtual environments as "isolated Python runtime deployments"
    that allow users to install different sets of libraries and tools without
    messing with the global behaviour of the system.
 
-   They are the safest way of testing new projects and can be created easily
+   They are a safe way of testing new projects and can be created easily
    with the :mod:`venv` module from the standard library.
 
    Please note however that depending on your operating system or distribution,
@@ -118,7 +118,8 @@ by mistake otherwise your files may stop being accessible).
 
 .. note::
     .. versionadded:: v64.0.0
-       *Strict* mode implemented as **EXPERIMENTAL**.
+       Added new *strict* mode for editable installations.
+       The exact details of how this mode is implemented may vary.
 
 
 Limitations
@@ -135,10 +136,21 @@ Limitations
   </userguide/entry_point>` to work properly.
 - *Strict* editable installs require the file system to support
   either :wiki:`symbolic <symbolic link>` or :wiki:`hard links <hard link>`.
+  This installation mode might also generate auxiliary files under the project directory.
+- There is *no guarantee* that the editable installation will be performed
+  using a specific technique. Depending on each project, ``setuptools`` may
+  select a different approach to ensure the package is importable at runtime.
+- There is *no guarantee* that files outside the top-level package directory
+  will be accessible after an editable install.
+- There is *no guarantee* that attributes like ``__path__`` or ``__file__``
+  will correspond to the exact location of the original files (e.g.,
+  ``setuptools`` might employ file links to perform the editable installation).
+  Users are encouraged to use tools like :mod:`importlib.resources` or
+  :mod:`importlib.metadata` when trying to access package files directly.
 - Editable installations may not work with
   :doc:`namespaces created with pkgutil or pkg_resouces
   <PyPUG:guides/packaging-namespace-packages>`.
-  Please use :pep:`420`-style implicit namespaces.
+  Please use :pep:`420`-style implicit namespaces [#namespaces]_.
 - Support for :pep:`420`-style implicit namespace packages for
   projects structured using :ref:`flat-layout` is still **experimental**.
   If you experience problems, you can try converting your package structure
@@ -156,9 +168,93 @@ Legacy Behavior
 ---------------
 
 If your project is not compatible with the new "editable installs" or you wish
-to use the legacy behavior (that mimics the old and deprecated
-``python setup.py develop`` command), you can set an environment variable:
+to replicate the legacy behavior, for the time being you can also perform the
+installation in the ``compat`` mode:
+
+.. code-block:: bash
+
+    pip install -e . --config-settings editable_mode=compat
+
+This installation mode will try to emulate how ``python setup.py develop``
+works (still within the context of :pep:`660`).
+
+.. warning::
+   The ``compat`` mode is *transitional* and will be removed in
+   future versions of ``setuptools``, it exists only to help during the
+   migration period.
+   Also note that support for this mode is limited:
+   it is safe to assume that the ``compat`` mode is offered "as is", and
+   improvements are unlikely to be implemented.
+   Users are encouraged to try out the new editable installation techniques
+   and make the necessary adaptations.
+
+If the ``compat`` mode does not work for you, you can also disable the
+:pep:`editable install <660>` hooks in ``setuptools`` by setting an environment
+variable:
 
 .. code-block::
 
    SETUPTOOLS_USE_FEATURE="legacy-editable"
+
+This *may* cause the installer (e.g. ``pip``) to effectively run the "legacy"
+installation command: ``python setup.py develop`` [#installer]_.
+
+
+How editable installations work?
+--------------------------------
+
+*Advanced topic*
+
+There are many techniques that can be used to expose packages under development
+in such a way that they are available as if they were installed.
+Depending on the project file structure and the selected mode, ``setuptools``
+will choose one of these approaches for the editable installation [#criteria]_.
+
+A non-exhaustive list of implementation mechanisms is presented below.
+More information is available on the text of :pep:`PEP 660 <660#what-to-put-in-the-wheel>`.
+
+- A static ``.pth`` file [#static_pth]_ can be added to one of the directories
+  listed in :func:`site.getsitepackages` or :func:`site.getusersitepackages` to
+  extend :obj:`sys.path`.
+- A directory containing a *farm of file links* that mimic the
+  project structure and point to the original files can be employed.
+  This directory can then be added to :obj:`sys.path` using a static ``.pth`` file.
+- A dynamic ``.pth`` file [#dynamic_pth]_ can also be used to install an
+  "import :term:`finder`" (:obj:`~importlib.abc.MetaPathFinder` or
+  :obj:`~importlib.abc.PathEntryFinder`) that will hook into Python's
+  :doc:`import system <python:reference/import>` machinery.
+
+.. attention::
+   ``Setuptools`` offers **no guarantee** of which technique will be used to
+   perform an editable installation. This will vary from project to project
+   and may change depending on the specific version of ``setuptools`` being
+   used.
+
+
+----
+
+.. rubric:: Notes
+
+.. [#namespaces]
+   You *may* be able to use *strict* editable installations with namespace
+   packages created with ``pkgutil`` or ``pkg_namespaces``, however this is not
+   officially supported.
+
+.. [#installer]
+   For this workaround to work, the installer tool needs to support legacy
+   editable installations. (Future versions of ``pip``, for example, may drop
+   support for this feature).
+
+.. [#criteria]
+   ``setuptools`` strives to find a balance between allowing the user to see
+   the effects of project files being edited while still trying to keep the
+   editable installation as similar as possible to a regular installation.
+
+.. [#static_pth]
+   i.e., a ``.pth`` file where each line correspond to a path that should be
+   added to :obj:`sys.path`. See :mod:`Site-specific configuration hook <site>`.
+
+.. [#dynamic_pth]
+   i.e., a ``.pth`` file that starts where each line starts with an ``import``
+   statement and executes arbitrary Python code. See :mod:`Site-specific
+   configuration hook <site>`.
index e5fca01ec73a5456fc82887b6e974bd2e22ff4c2..b49816b0070fb89d15439736160010d9771ab8bd 100644 (file)
@@ -116,20 +116,20 @@ The idea here is that the entry point defines a function that will be called
 to validate the ``setup()`` argument, if it's supplied.  The ``Distribution``
 object will have the initial value of the attribute set to ``None``, and the
 validation function will only be called if the ``setup()`` call sets it to
-a non-None value.  Here's an example validation function::
+a non-``None`` value.  Here's an example validation function::
 
     def assert_bool(dist, attr, value):
         """Verify that value is True, False, 0, or 1"""
         if bool(value) != value:
-            raise DistutilsSetupError(
+            raise SetupError(
                 "%r must be a boolean value (got %r)" % (attr,value)
             )
 
 Your function should accept three arguments: the ``Distribution`` object,
 the attribute name, and the attribute value.  It should raise a
 ``SetupError`` (from the ``setuptools.errors`` module) if the argument
-is invalid.  Remember, your function will only be called with non-None values,
-and the default value of arguments defined this way is always None.  So, your
+is invalid.  Remember, your function will only be called with non-``None`` values,
+and the default value of arguments defined this way is always ``None``.  So, your
 commands should always be prepared for the possibility that the attribute will
 be ``None`` when they access it later.
 
@@ -142,12 +142,12 @@ what values of that argument are valid.
 Customizing Distribution Options
 --------------------------------
 
-Plugins may wish to extend or alter the options on a Distribution object to
+Plugins may wish to extend or alter the options on a ``Distribution`` object to
 suit the purposes of that project. For example, a tool that infers the
 ``Distribution.version`` from SCM-metadata may need to hook into the
 option finalization. To enable this feature, Setuptools offers an entry
-point "setuptools.finalize_distribution_options". That entry point must
-be a callable taking one argument (the Distribution instance).
+point ``setuptools.finalize_distribution_options``. That entry point must
+be a callable taking one argument (the ``Distribution`` instance).
 
 If the callable has an ``.order`` property, that value will be used to
 determine the order in which the hook is called. Lower numbers are called
index 2efc62b9ebc803cfe5f52a48507308a9b4e90785..7dda84a88243768c189e5cd85229ae55f47d5084 100644 (file)
@@ -279,7 +279,7 @@ the provided tools for package discovery:
         [tool.setuptools.packages]
         find = {}  # Scanning implicit namespaces is active by default
         # OR
-        find = {namespace = false}  # Disable implicit namespaces
+        find = {namespaces = false}  # Disable implicit namespaces
 
 
 Finding simple packages
index 28eb39d1a30e6e6667379dc04231bd9516e259c3..b1d4a4e3fe3c68b2baf125aa518c41a9a6ccdf72 100644 (file)
@@ -7,10 +7,10 @@ Configuring setuptools using ``pyproject.toml`` files
 .. note:: New in 61.0.0
 
 .. important::
-   For the time being, ``pip`` still might require a ``setup.py`` file
-   to support :doc:`editable installs <pip:cli/pip_install>`.
-
-   A simple script will suffice, for example:
+   If compatibility with legacy builds or versions of tools that don't support
+   certain packaging standards (e.g. :pep:`517` or :pep:`660`), a simple ``setup.py``
+   script can be added to your project [#setupcfg-caveats]_
+   (while keeping the configuration in ``pyproject.toml``):
 
    .. code-block:: python
 
@@ -214,6 +214,11 @@ however please keep in mind that all non-comment lines must conform with :pep:`5
 
 .. rubric:: Notes
 
+.. [#setupcfg-caveats] ``pip`` may allow editable install only with ``pyproject.toml``
+   and ``setup.cfg``. However, this behavior may not be consistent over various ``pip``
+   versions and other packaging-related tools
+   (``setup.py`` is more reliable on those scenarios).
+
 .. [#entry-points] Dynamic ``scripts`` and ``gui-scripts`` are a special case.
    When resolving these metadata keys, ``setuptools`` will look for
    ``tool.setuptool.dynamic.entry-points``, and use the values of the
index 6c39c3529cce62d4e3e46805a031a5b975ae234e..bf76f2c899b81a8a13c38b733c263bc04eba7d7a 100644 (file)
@@ -117,8 +117,7 @@ distributing into something that looks like the following
 (optional files marked with ``#``)::
 
     mypackage
-    ├── pyproject.toml
-    |   # setup.cfg or setup.py (depending on the confuguration method)
+    ├── pyproject.toml  # and/or setup.cfg/setup.py (depending on the configuration method)
     |   # README.rst or README.md (a nice description of your package)
     |   # LICENCE (properly chosen license information, e.g. MIT, BSD-3, GPL-3, MPL-2, etc...)
     └── mypackage
@@ -176,6 +175,7 @@ found, as shown in the example below:
 
         # OR
         [tool.setuptools.packages.find]
+        # All the following settings are optional:
         where = ["src"]  # ["."] by default
         include = ["mypackage*"]  # ["*"] by default
         exclude = ["mypackage.tests*"]  # empty by default
@@ -186,14 +186,13 @@ found, as shown in the example below:
     .. code-block:: ini
 
         [options]
-        packages = find: # OR `find_namespaces:` if you want to use namespaces
+        packages = find: # OR `find_namespace:` if you want to use namespaces
 
-        [options.packages.find] # (always `find` even if `find_namespaces:` was used before)
-        # This section is optional
-        # Each entry in this section is optional, and if not specified, the default values are:
-        # `where=.`, `include=*` and `exclude=` (empty).
-        include=mypackage*
-        exclude=mypackage.tests*
+        [options.packages.find]  # (always `find` even if `find_namespace:` was used before)
+        # This section is optional as well as each of the following options:
+        where=src  # . by default
+        include=mypackage*  # * by default
+        exclude=mypackage.tests*  # empty by default
 
 .. tab:: setup.py [#setup.py]_
 
@@ -204,18 +203,18 @@ found, as shown in the example below:
         setup(
             # ...
             packages=find_packages(
-                where='.',
-                include=['mypackage*'],  # ["*"] by default
+                # All keyword arguments below are optional:
+                where='src',  # '.' by default
+                include=['mypackage*'],  # ['*'] by default
                 exclude=['mypackage.tests'],  # empty by default
             ),
             # ...
         )
 
 When you pass the above information, alongside other necessary information,
-``setuptools`` walks through the directory specified in ``where`` (omitted
-here as the package resides in the current directory) and filters the packages
+``setuptools`` walks through the directory specified in ``where`` (defaults to ``.``) and filters the packages
 it can find following the ``include`` patterns (defaults to ``*``), then it removes
-those that match the ``exclude`` patterns and returns a list of Python packages.
+those that match the ``exclude`` patterns (defaults to empty) and returns a list of Python packages.
 
 For more details and advanced use, go to :ref:`package_discovery`.
 
@@ -378,19 +377,18 @@ Here's how to do it::
 
     pip install --editable .
 
-This creates a link file in your interpreter site package directory which
-associate with your source code. For more information, see :doc:`development_mode`.
+See :doc:`development_mode` for more information.
 
 .. tip::
 
     Prior to :ref:`pip v21.1 <pip:v21-1>`, a ``setup.py`` script was
     required to be compatible with development mode. With late
-    versions of pip, ``setup.cfg``-only projects may be installed in this mode.
+    versions of pip, projects without ``setup.py`` may be installed in this mode.
 
-    If you are experimenting with :doc:`configuration using pyproject.toml <pyproject_config>`,
-    or have version of ``pip`` older than v21.1, you might need to keep a
+    If you have a version of ``pip`` older than v21.1 or is using a different
+    packaging-related tool that does not support :pep:`660`, you might need to keep a
     ``setup.py`` file in file in your repository if you want to use editable
-    installs (for the time being).
+    installs.
 
     A simple script will suffice, for example:
 
@@ -400,8 +398,9 @@ associate with your source code. For more information, see :doc:`development_mod
 
         setup()
 
-    You can still keep all the configuration in :doc:`setup.cfg </userguide/declarative_config>`
-    (or :doc:`pyproject.toml </userguide/pyproject_config>`).
+    You can still keep all the configuration in
+    :doc:`pyproject.toml </userguide/pyproject_config>` and/or
+    :doc:`setup.cfg </userguide/declarative_config>`
 
 
 Uploading your package to PyPI
diff --git a/pkg_resources/_vendor/pyparsing-3.0.9.dist-info/INSTALLER b/pkg_resources/_vendor/pyparsing-3.0.9.dist-info/INSTALLER
new file mode 100644 (file)
index 0000000..a1b589e
--- /dev/null
@@ -0,0 +1 @@
+pip
diff --git a/pkg_resources/_vendor/pyparsing-3.0.9.dist-info/LICENSE b/pkg_resources/_vendor/pyparsing-3.0.9.dist-info/LICENSE
new file mode 100644 (file)
index 0000000..1bf9852
--- /dev/null
@@ -0,0 +1,18 @@
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/pkg_resources/_vendor/pyparsing-3.0.9.dist-info/METADATA b/pkg_resources/_vendor/pyparsing-3.0.9.dist-info/METADATA
new file mode 100644 (file)
index 0000000..33e5194
--- /dev/null
@@ -0,0 +1,105 @@
+Metadata-Version: 2.1
+Name: pyparsing
+Version: 3.0.9
+Summary: pyparsing module - Classes and methods to define and execute parsing grammars
+Author-email: Paul McGuire <ptmcg.gm+pyparsing@gmail.com>
+Requires-Python: >=3.6.8
+Description-Content-Type: text/x-rst
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: Information Technology
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Typing :: Typed
+Requires-Dist: railroad-diagrams ; extra == "diagrams"
+Requires-Dist: jinja2 ; extra == "diagrams"
+Project-URL: Homepage, https://github.com/pyparsing/pyparsing/
+Provides-Extra: diagrams
+
+PyParsing -- A Python Parsing Module
+====================================
+
+|Build Status| |Coverage|
+
+Introduction
+============
+
+The pyparsing module is an alternative approach to creating and
+executing simple grammars, vs. the traditional lex/yacc approach, or the
+use of regular expressions. The pyparsing module provides a library of
+classes that client code uses to construct the grammar directly in
+Python code.
+
+*[Since first writing this description of pyparsing in late 2003, this
+technique for developing parsers has become more widespread, under the
+name Parsing Expression Grammars - PEGs. See more information on PEGs*
+`here <https://en.wikipedia.org/wiki/Parsing_expression_grammar>`__
+*.]*
+
+Here is a program to parse ``"Hello, World!"`` (or any greeting of the form
+``"salutation, addressee!"``):
+
+.. code:: python
+
+    from pyparsing import Word, alphas
+    greet = Word(alphas) + "," + Word(alphas) + "!"
+    hello = "Hello, World!"
+    print(hello, "->", greet.parseString(hello))
+
+The program outputs the following::
+
+    Hello, World! -> ['Hello', ',', 'World', '!']
+
+The Python representation of the grammar is quite readable, owing to the
+self-explanatory class names, and the use of '+', '|' and '^' operator
+definitions.
+
+The parsed results returned from ``parseString()`` is a collection of type
+``ParseResults``, which can be accessed as a
+nested list, a dictionary, or an object with named attributes.
+
+The pyparsing module handles some of the problems that are typically
+vexing when writing text parsers:
+
+- extra or missing whitespace (the above program will also handle ``"Hello,World!"``, ``"Hello , World !"``, etc.)
+- quoted strings
+- embedded comments
+
+The examples directory includes a simple SQL parser, simple CORBA IDL
+parser, a config file parser, a chemical formula parser, and a four-
+function algebraic notation parser, among many others.
+
+Documentation
+=============
+
+There are many examples in the online docstrings of the classes
+and methods in pyparsing. You can find them compiled into `online docs <https://pyparsing-docs.readthedocs.io/en/latest/>`__. Additional
+documentation resources and project info are listed in the online
+`GitHub wiki <https://github.com/pyparsing/pyparsing/wiki>`__. An
+entire directory of examples can be found `here <https://github.com/pyparsing/pyparsing/tree/master/examples>`__.
+
+License
+=======
+
+MIT License. See header of the `pyparsing.py <https://github.com/pyparsing/pyparsing/blob/master/pyparsing/__init__.py#L1-L23>`__ file.
+
+History
+=======
+
+See `CHANGES <https://github.com/pyparsing/pyparsing/blob/master/CHANGES>`__ file.
+
+.. |Build Status| image:: https://github.com/pyparsing/pyparsing/actions/workflows/ci.yml/badge.svg
+   :target: https://github.com/pyparsing/pyparsing/actions/workflows/ci.yml
+.. |Coverage| image:: https://codecov.io/gh/pyparsing/pyparsing/branch/master/graph/badge.svg
+  :target: https://codecov.io/gh/pyparsing/pyparsing
+
diff --git a/pkg_resources/_vendor/pyparsing-3.0.9.dist-info/RECORD b/pkg_resources/_vendor/pyparsing-3.0.9.dist-info/RECORD
new file mode 100644 (file)
index 0000000..7a4e49a
--- /dev/null
@@ -0,0 +1,29 @@
+pyparsing-3.0.9.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+pyparsing-3.0.9.dist-info/LICENSE,sha256=ENUSChaAWAT_2otojCIL-06POXQbVzIGBNRVowngGXI,1023
+pyparsing-3.0.9.dist-info/METADATA,sha256=h_fpm9rwvgZsE8v5YNF4IAo-IpaFWCOfUEm5MMByIiM,4207
+pyparsing-3.0.9.dist-info/RECORD,,
+pyparsing-3.0.9.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pyparsing-3.0.9.dist-info/WHEEL,sha256=jPMR_Dzkc4X4icQtmz81lnNY_kAsfog7ry7qoRvYLXw,81
+pyparsing/__init__.py,sha256=52QH3lgPbJhba0estckoGPHRH8JvQSSCGoWiEn2m0bU,9159
+pyparsing/__pycache__/__init__.cpython-38.pyc,,
+pyparsing/__pycache__/actions.cpython-38.pyc,,
+pyparsing/__pycache__/common.cpython-38.pyc,,
+pyparsing/__pycache__/core.cpython-38.pyc,,
+pyparsing/__pycache__/exceptions.cpython-38.pyc,,
+pyparsing/__pycache__/helpers.cpython-38.pyc,,
+pyparsing/__pycache__/results.cpython-38.pyc,,
+pyparsing/__pycache__/testing.cpython-38.pyc,,
+pyparsing/__pycache__/unicode.cpython-38.pyc,,
+pyparsing/__pycache__/util.cpython-38.pyc,,
+pyparsing/actions.py,sha256=wU9i32e0y1ymxKE3OUwSHO-SFIrt1h_wv6Ws0GQjpNU,6426
+pyparsing/common.py,sha256=lFL97ooIeR75CmW5hjURZqwDCTgruqltcTCZ-ulLO2Q,12936
+pyparsing/core.py,sha256=u8GptQE_H6wMkl8OZhxeK1aAPIDXXNgwdShORBwBVS4,213310
+pyparsing/diagram/__init__.py,sha256=f_EfxahqrdkRVahmTwLJXkZ9EEDKNd-O7lBbpJYlE1g,23668
+pyparsing/diagram/__pycache__/__init__.cpython-38.pyc,,
+pyparsing/exceptions.py,sha256=3LbSafD32NYb1Tzt85GHNkhEAU1eZkTtNSk24cPMemo,9023
+pyparsing/helpers.py,sha256=QpUOjW0-psvueMwWb9bQpU2noqKCv98_wnw1VSzSdVo,39129
+pyparsing/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pyparsing/results.py,sha256=HgNvWVXBdQP-Q6PtJfoCEeOJk2nwEvG-2KVKC5sGA30,25341
+pyparsing/testing.py,sha256=7tu4Abp4uSeJV0N_yEPRmmNUhpd18ZQP3CrX41DM814,13402
+pyparsing/unicode.py,sha256=fwuhMj30SQ165Cv7HJpu-rSxGbRm93kN9L4Ei7VGc1Y,10787
+pyparsing/util.py,sha256=kq772O5YSeXOSdP-M31EWpbH_ayj7BMHImBYo9xPD5M,6805
diff --git a/pkg_resources/_vendor/pyparsing-3.0.9.dist-info/REQUESTED b/pkg_resources/_vendor/pyparsing-3.0.9.dist-info/REQUESTED
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/pkg_resources/_vendor/pyparsing-3.0.9.dist-info/WHEEL b/pkg_resources/_vendor/pyparsing-3.0.9.dist-info/WHEEL
new file mode 100644 (file)
index 0000000..c727d14
--- /dev/null
@@ -0,0 +1,4 @@
+Wheel-Version: 1.0
+Generator: flit 3.6.0
+Root-Is-Purelib: true
+Tag: py3-none-any
index 45f334d043fc72a77eef992cb4b2508f53837e2e..7802ff158d83eb88e6dbe78d9cd33ca14341662a 100644 (file)
@@ -128,8 +128,8 @@ class version_info(NamedTuple):
         )
 
 
-__version_info__ = version_info(3, 0, 8, "final", 0)
-__version_time__ = "09 Apr 2022 23:29 UTC"
+__version_info__ = version_info(3, 0, 9, "final", 0)
+__version_time__ = "05 May 2022 07:02 UTC"
 __version__ = __version_info__.__version__
 __versionTime__ = __version_time__
 __author__ = "Paul McGuire <ptmcg.gm+pyparsing@gmail.com>"
index 2bcc5502b075ff39be828a05f9bdc2ea3e574cf0..f72c66e743146c7a5b70a5440e9ab5459f10245b 100644 (file)
@@ -55,7 +55,7 @@ def replace_with(repl_str):
         na = one_of("N/A NA").set_parse_action(replace_with(math.nan))
         term = na | num
 
-        OneOrMore(term).parse_string("324 234 N/A 234") # -> [324, 234, nan, 234]
+        term[1, ...].parse_string("324 234 N/A 234") # -> [324, 234, nan, 234]
     """
     return lambda s, l, t: [repl_str]
 
index 454bd57d0419439944b455c9c06958a97e7c8925..9acba3f3e984b404f52702964805732f03965048 100644 (file)
@@ -2,9 +2,8 @@
 # core.py
 #
 import os
+import typing
 from typing import (
-    Optional as OptionalType,
-    Iterable as IterableType,
     NamedTuple,
     Union,
     Callable,
@@ -14,7 +13,6 @@ from typing import (
     List,
     TextIO,
     Set,
-    Dict as DictType,
     Sequence,
 )
 from abc import ABC, abstractmethod
@@ -192,7 +190,7 @@ del __config_flags
 
 
 def _should_enable_warnings(
-    cmd_line_warn_options: IterableType[str], warn_env_var: OptionalType[str]
+    cmd_line_warn_options: typing.Iterable[str], warn_env_var: typing.Optional[str]
 ) -> bool:
     enable = bool(warn_env_var)
     for warn_opt in cmd_line_warn_options:
@@ -404,7 +402,7 @@ class ParserElement(ABC):
 
     DEFAULT_WHITE_CHARS: str = " \n\t\r"
     verbose_stacktrace: bool = False
-    _literalStringClass: OptionalType[type] = None
+    _literalStringClass: typing.Optional[type] = None
 
     @staticmethod
     def set_default_whitespace_chars(chars: str) -> None:
@@ -414,11 +412,11 @@ class ParserElement(ABC):
         Example::
 
             # default whitespace chars are space, <TAB> and newline
-            OneOrMore(Word(alphas)).parse_string("abc def\nghi jkl")  # -> ['abc', 'def', 'ghi', 'jkl']
+            Word(alphas)[1, ...].parse_string("abc def\nghi jkl")  # -> ['abc', 'def', 'ghi', 'jkl']
 
             # change to just treat newline as significant
             ParserElement.set_default_whitespace_chars(" \t")
-            OneOrMore(Word(alphas)).parse_string("abc def\nghi jkl")  # -> ['abc', 'def']
+            Word(alphas)[1, ...].parse_string("abc def\nghi jkl")  # -> ['abc', 'def']
         """
         ParserElement.DEFAULT_WHITE_CHARS = chars
 
@@ -450,13 +448,13 @@ class ParserElement(ABC):
         ParserElement._literalStringClass = cls
 
     class DebugActions(NamedTuple):
-        debug_try: OptionalType[DebugStartAction]
-        debug_match: OptionalType[DebugSuccessAction]
-        debug_fail: OptionalType[DebugExceptionAction]
+        debug_try: typing.Optional[DebugStartAction]
+        debug_match: typing.Optional[DebugSuccessAction]
+        debug_fail: typing.Optional[DebugExceptionAction]
 
     def __init__(self, savelist: bool = False):
         self.parseAction: List[ParseAction] = list()
-        self.failAction: OptionalType[ParseFailAction] = None
+        self.failAction: typing.Optional[ParseFailAction] = None
         self.customName = None
         self._defaultName = None
         self.resultsName = None
@@ -510,7 +508,7 @@ class ParserElement(ABC):
             integerK = integer.copy().add_parse_action(lambda toks: toks[0] * 1024) + Suppress("K")
             integerM = integer.copy().add_parse_action(lambda toks: toks[0] * 1024 * 1024) + Suppress("M")
 
-            print(OneOrMore(integerK | integerM | integer).parse_string("5K 100 640K 256M"))
+            print((integerK | integerM | integer)[1, ...].parse_string("5K 100 640K 256M"))
 
         prints::
 
@@ -895,7 +893,7 @@ class ParserElement(ABC):
 
     # cache for left-recursion in Forward references
     recursion_lock = RLock()
-    recursion_memos: DictType[
+    recursion_memos: typing.Dict[
         Tuple[int, "Forward", bool], Tuple[int, Union[ParseResults, Exception]]
     ] = {}
 
@@ -985,7 +983,7 @@ class ParserElement(ABC):
 
     @staticmethod
     def enable_left_recursion(
-        cache_size_limit: OptionalType[int] = None, *, force=False
+        cache_size_limit: typing.Optional[int] = None, *, force=False
     ) -> None:
         """
         Enables "bounded recursion" parsing, which allows for both direct and indirect
@@ -1738,7 +1736,7 @@ class ParserElement(ABC):
 
         Example::
 
-            patt = OneOrMore(Word(alphas))
+            patt = Word(alphas)[1, ...]
             patt.parse_string('ablaj /* comment */ lskjd')
             # -> ['ablaj']
 
@@ -1798,7 +1796,7 @@ class ParserElement(ABC):
             # turn on debugging for wd
             wd.set_debug()
 
-            OneOrMore(term).parse_string("abc 123 xyz 890")
+            term[1, ...].parse_string("abc 123 xyz 890")
 
         prints::
 
@@ -1953,12 +1951,12 @@ class ParserElement(ABC):
         self,
         tests: Union[str, List[str]],
         parse_all: bool = True,
-        comment: OptionalType[Union["ParserElement", str]] = "#",
+        comment: typing.Optional[Union["ParserElement", str]] = "#",
         full_dump: bool = True,
         print_results: bool = True,
         failure_tests: bool = False,
         post_parse: Callable[[str, ParseResults], str] = None,
-        file: OptionalType[TextIO] = None,
+        file: typing.Optional[TextIO] = None,
         with_line_numbers: bool = False,
         *,
         parseAll: bool = True,
@@ -2385,11 +2383,11 @@ class Keyword(Token):
     def __init__(
         self,
         match_string: str = "",
-        ident_chars: OptionalType[str] = None,
+        ident_chars: typing.Optional[str] = None,
         caseless: bool = False,
         *,
         matchString: str = "",
-        identChars: OptionalType[str] = None,
+        identChars: typing.Optional[str] = None,
     ):
         super().__init__()
         identChars = identChars or ident_chars
@@ -2479,7 +2477,7 @@ class CaselessLiteral(Literal):
 
     Example::
 
-        OneOrMore(CaselessLiteral("CMD")).parse_string("cmd CMD Cmd10")
+        CaselessLiteral("CMD")[1, ...].parse_string("cmd CMD Cmd10")
         # -> ['CMD', 'CMD', 'CMD']
 
     (Contrast with example for :class:`CaselessKeyword`.)
@@ -2504,7 +2502,7 @@ class CaselessKeyword(Keyword):
 
     Example::
 
-        OneOrMore(CaselessKeyword("CMD")).parse_string("cmd CMD Cmd10")
+        CaselessKeyword("CMD")[1, ...].parse_string("cmd CMD Cmd10")
         # -> ['CMD', 'CMD']
 
     (Contrast with example for :class:`CaselessLiteral`.)
@@ -2513,10 +2511,10 @@ class CaselessKeyword(Keyword):
     def __init__(
         self,
         match_string: str = "",
-        ident_chars: OptionalType[str] = None,
+        ident_chars: typing.Optional[str] = None,
         *,
         matchString: str = "",
-        identChars: OptionalType[str] = None,
+        identChars: typing.Optional[str] = None,
     ):
         identChars = identChars or ident_chars
         match_string = matchString or match_string
@@ -2680,17 +2678,17 @@ class Word(Token):
     def __init__(
         self,
         init_chars: str = "",
-        body_chars: OptionalType[str] = None,
+        body_chars: typing.Optional[str] = None,
         min: int = 1,
         max: int = 0,
         exact: int = 0,
         as_keyword: bool = False,
-        exclude_chars: OptionalType[str] = None,
+        exclude_chars: typing.Optional[str] = None,
         *,
-        initChars: OptionalType[str] = None,
-        bodyChars: OptionalType[str] = None,
+        initChars: typing.Optional[str] = None,
+        bodyChars: typing.Optional[str] = None,
         asKeyword: bool = False,
-        excludeChars: OptionalType[str] = None,
+        excludeChars: typing.Optional[str] = None,
     ):
         initChars = initChars or init_chars
         bodyChars = bodyChars or body_chars
@@ -2872,10 +2870,10 @@ class Char(_WordRegex):
         self,
         charset: str,
         as_keyword: bool = False,
-        exclude_chars: OptionalType[str] = None,
+        exclude_chars: typing.Optional[str] = None,
         *,
         asKeyword: bool = False,
-        excludeChars: OptionalType[str] = None,
+        excludeChars: typing.Optional[str] = None,
     ):
         asKeyword = asKeyword or as_keyword
         excludeChars = excludeChars or exclude_chars
@@ -3088,18 +3086,18 @@ class QuotedString(Token):
     def __init__(
         self,
         quote_char: str = "",
-        esc_char: OptionalType[str] = None,
-        esc_quote: OptionalType[str] = None,
+        esc_char: typing.Optional[str] = None,
+        esc_quote: typing.Optional[str] = None,
         multiline: bool = False,
         unquote_results: bool = True,
-        end_quote_char: OptionalType[str] = None,
+        end_quote_char: typing.Optional[str] = None,
         convert_whitespace_escapes: bool = True,
         *,
         quoteChar: str = "",
-        escChar: OptionalType[str] = None,
-        escQuote: OptionalType[str] = None,
+        escChar: typing.Optional[str] = None,
+        escQuote: typing.Optional[str] = None,
         unquoteResults: bool = True,
-        endQuoteChar: OptionalType[str] = None,
+        endQuoteChar: typing.Optional[str] = None,
         convertWhitespaceEscapes: bool = True,
     ):
         super().__init__()
@@ -3600,7 +3598,7 @@ class ParseExpression(ParserElement):
     post-processing parsed tokens.
     """
 
-    def __init__(self, exprs: IterableType[ParserElement], savelist: bool = False):
+    def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
         super().__init__(savelist)
         self.exprs: List[ParserElement]
         if isinstance(exprs, _generatorType):
@@ -3767,7 +3765,7 @@ class And(ParseExpression):
     Example::
 
         integer = Word(nums)
-        name_expr = OneOrMore(Word(alphas))
+        name_expr = Word(alphas)[1, ...]
 
         expr = And([integer("id"), name_expr("name"), integer("age")])
         # more easily written as:
@@ -3782,7 +3780,9 @@ class And(ParseExpression):
         def _generateDefaultName(self):
             return "-"
 
-    def __init__(self, exprs_arg: IterableType[ParserElement], savelist: bool = True):
+    def __init__(
+        self, exprs_arg: typing.Iterable[ParserElement], savelist: bool = True
+    ):
         exprs: List[ParserElement] = list(exprs_arg)
         if exprs and Ellipsis in exprs:
             tmp = []
@@ -3926,7 +3926,7 @@ class Or(ParseExpression):
         [['123'], ['3.1416'], ['789']]
     """
 
-    def __init__(self, exprs: IterableType[ParserElement], savelist: bool = False):
+    def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
         super().__init__(exprs, savelist)
         if self.exprs:
             self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
@@ -4081,7 +4081,7 @@ class MatchFirst(ParseExpression):
         print(number.search_string("123 3.1416 789")) #  Better -> [['123'], ['3.1416'], ['789']]
     """
 
-    def __init__(self, exprs: IterableType[ParserElement], savelist: bool = False):
+    def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
         super().__init__(exprs, savelist)
         if self.exprs:
             self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
@@ -4232,7 +4232,7 @@ class Each(ParseExpression):
         - size: 20
     """
 
-    def __init__(self, exprs: IterableType[ParserElement], savelist: bool = True):
+    def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = True):
         super().__init__(exprs, savelist)
         if self.exprs:
             self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
@@ -4568,7 +4568,7 @@ class FollowedBy(ParseElementEnhance):
         label = data_word + FollowedBy(':')
         attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
 
-        OneOrMore(attr_expr).parse_string("shape: SQUARE color: BLACK posn: upper left").pprint()
+        attr_expr[1, ...].parse_string("shape: SQUARE color: BLACK posn: upper left").pprint()
 
     prints::
 
@@ -4619,7 +4619,7 @@ class PrecededBy(ParseElementEnhance):
     """
 
     def __init__(
-        self, expr: Union[ParserElement, str], retreat: OptionalType[int] = None
+        self, expr: Union[ParserElement, str], retreat: typing.Optional[int] = None
     ):
         super().__init__(expr)
         self.expr = self.expr().leave_whitespace()
@@ -4730,7 +4730,7 @@ class NotAny(ParseElementEnhance):
 
         # very crude boolean expression - to support parenthesis groups and
         # operation hierarchy, use infix_notation
-        boolean_expr = boolean_term + ZeroOrMore((AND | OR) + boolean_term)
+        boolean_expr = boolean_term + ((AND | OR) + boolean_term)[...]
 
         # integers that are followed by "." are actually floats
         integer = Word(nums) + ~Char(".")
@@ -4758,9 +4758,9 @@ class _MultipleMatch(ParseElementEnhance):
     def __init__(
         self,
         expr: ParserElement,
-        stop_on: OptionalType[Union[ParserElement, str]] = None,
+        stop_on: typing.Optional[Union[ParserElement, str]] = None,
         *,
-        stopOn: OptionalType[Union[ParserElement, str]] = None,
+        stopOn: typing.Optional[Union[ParserElement, str]] = None,
     ):
         super().__init__(expr)
         stopOn = stopOn or stop_on
@@ -4849,7 +4849,7 @@ class OneOrMore(_MultipleMatch):
         attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).set_parse_action(' '.join))
 
         text = "shape: SQUARE posn: upper left color: BLACK"
-        OneOrMore(attr_expr).parse_string(text).pprint()  # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']]
+        attr_expr[1, ...].parse_string(text).pprint()  # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']]
 
         # use stop_on attribute for OneOrMore to avoid reading label string as part of the data
         attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
@@ -4879,9 +4879,9 @@ class ZeroOrMore(_MultipleMatch):
     def __init__(
         self,
         expr: ParserElement,
-        stop_on: OptionalType[Union[ParserElement, str]] = None,
+        stop_on: typing.Optional[Union[ParserElement, str]] = None,
         *,
-        stopOn: OptionalType[Union[ParserElement, str]] = None,
+        stopOn: typing.Optional[Union[ParserElement, str]] = None,
     ):
         super().__init__(expr, stopOn=stopOn or stop_on)
         self.mayReturnEmpty = True
@@ -5046,7 +5046,7 @@ class SkipTo(ParseElementEnhance):
         other: Union[ParserElement, str],
         include: bool = False,
         ignore: bool = None,
-        fail_on: OptionalType[Union[ParserElement, str]] = None,
+        fail_on: typing.Optional[Union[ParserElement, str]] = None,
         *,
         failOn: Union[ParserElement, str] = None,
     ):
@@ -5143,7 +5143,7 @@ class Forward(ParseElementEnhance):
     parser created using ``Forward``.
     """
 
-    def __init__(self, other: OptionalType[Union[ParserElement, str]] = None):
+    def __init__(self, other: typing.Optional[Union[ParserElement, str]] = None):
         self.caller_frame = traceback.extract_stack(limit=2)[0]
         super().__init__(other, savelist=False)
         self.lshift_line = None
@@ -5395,7 +5395,7 @@ class Combine(TokenConverter):
         join_string: str = "",
         adjacent: bool = True,
         *,
-        joinString: OptionalType[str] = None,
+        joinString: typing.Optional[str] = None,
     ):
         super().__init__(expr)
         joinString = joinString if joinString is not None else join_string
@@ -5482,10 +5482,10 @@ class Dict(TokenConverter):
         attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
 
         # print attributes as plain groups
-        print(OneOrMore(attr_expr).parse_string(text).dump())
+        print(attr_expr[1, ...].parse_string(text).dump())
 
-        # instead of OneOrMore(expr), parse using Dict(OneOrMore(Group(expr))) - Dict will auto-assign names
-        result = Dict(OneOrMore(Group(attr_expr))).parse_string(text)
+        # instead of OneOrMore(expr), parse using Dict(Group(expr)[1, ...]) - Dict will auto-assign names
+        result = Dict(Group(attr_expr)[1, ...]).parse_string(text)
         print(result.dump())
 
         # access named fields as dict entries, or output as dict
@@ -5558,12 +5558,12 @@ class Suppress(TokenConverter):
 
         source = "a, b, c,d"
         wd = Word(alphas)
-        wd_list1 = wd + ZeroOrMore(',' + wd)
+        wd_list1 = wd + (',' + wd)[...]
         print(wd_list1.parse_string(source))
 
         # often, delimiters that are useful during parsing are just in the
         # way afterward - use Suppress to keep them out of the parsed output
-        wd_list2 = wd + ZeroOrMore(Suppress(',') + wd)
+        wd_list2 = wd + (Suppress(',') + wd)[...]
         print(wd_list2.parse_string(source))
 
         # Skipped text (using '...') can be suppressed as well
@@ -5622,7 +5622,7 @@ def trace_parse_action(f: ParseAction) -> ParseAction:
         def remove_duplicate_chars(tokens):
             return ''.join(sorted(set(''.join(tokens))))
 
-        wds = OneOrMore(wd).set_parse_action(remove_duplicate_chars)
+        wds = wd[1, ...].set_parse_action(remove_duplicate_chars)
         print(wds.parse_string("slkdjs sld sldd sdlf sdljf"))
 
     prints::
@@ -5728,18 +5728,18 @@ def token_map(func, *args) -> ParseAction:
 
     Example (compare the last to example in :class:`ParserElement.transform_string`::
 
-        hex_ints = OneOrMore(Word(hexnums)).set_parse_action(token_map(int, 16))
+        hex_ints = Word(hexnums)[1, ...].set_parse_action(token_map(int, 16))
         hex_ints.run_tests('''
             00 11 22 aa FF 0a 0d 1a
             ''')
 
         upperword = Word(alphas).set_parse_action(token_map(str.upper))
-        OneOrMore(upperword).run_tests('''
+        upperword[1, ...].run_tests('''
             my kingdom for a horse
             ''')
 
         wd = Word(alphas).set_parse_action(token_map(str.title))
-        OneOrMore(wd).set_parse_action(' '.join).run_tests('''
+        wd[1, ...].set_parse_action(' '.join).run_tests('''
             now is the winter of our discontent made glorious summer by this sun of york
             ''')
 
@@ -5795,7 +5795,9 @@ punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]")
 
 # build list of built-in expressions, for future reference if a global default value
 # gets updated
-_builtin_exprs = [v for v in vars().values() if isinstance(v, ParserElement)]
+_builtin_exprs: List[ParserElement] = [
+    v for v in vars().values() if isinstance(v, ParserElement)
+]
 
 # backward compatibility names
 tokenMap = token_map
index 2d0c587cbf42126eb903f27c11dc2dde9146c1cc..898644755cbbf9a8d4df562663114a7eb7e11fd1 100644 (file)
@@ -1,9 +1,8 @@
 import railroad
 import pyparsing
-from pkg_resources import resource_filename
+import typing
 from typing import (
     List,
-    Optional,
     NamedTuple,
     Generic,
     TypeVar,
@@ -17,13 +16,41 @@ from io import StringIO
 import inspect
 
 
-with open(resource_filename(__name__, "template.jinja2"), encoding="utf-8") as fp:
-    template = Template(fp.read())
+jinja2_template_source = """\
+<!DOCTYPE html>
+<html>
+<head>
+    {% if not head %}
+        <style type="text/css">
+            .railroad-heading {
+                font-family: monospace;
+            }
+        </style>
+    {% else %}
+        {{ head | safe }}
+    {% endif %}
+</head>
+<body>
+{{ body | safe }}
+{% for diagram in diagrams %}
+    <div class="railroad-group">
+        <h1 class="railroad-heading">{{ diagram.title }}</h1>
+        <div class="railroad-description">{{ diagram.text }}</div>
+        <div class="railroad-svg">
+            {{ diagram.svg }}
+        </div>
+    </div>
+{% endfor %}
+</body>
+</html>
+"""
+
+template = Template(jinja2_template_source)
 
 # Note: ideally this would be a dataclass, but we're supporting Python 3.5+ so we can't do this yet
 NamedDiagram = NamedTuple(
     "NamedDiagram",
-    [("name", str), ("diagram", Optional[railroad.DiagramItem]), ("index", int)],
+    [("name", str), ("diagram", typing.Optional[railroad.DiagramItem]), ("index", int)],
 )
 """
 A simple structure for associating a name with a railroad diagram
@@ -107,6 +134,8 @@ def railroad_to_html(diagrams: List[NamedDiagram], **kwargs) -> str:
     """
     data = []
     for diagram in diagrams:
+        if diagram.diagram is None:
+            continue
         io = StringIO()
         diagram.diagram.writeSvg(io.write)
         title = diagram.name
@@ -135,7 +164,7 @@ def resolve_partial(partial: "EditablePartial[T]") -> T:
 
 def to_railroad(
     element: pyparsing.ParserElement,
-    diagram_kwargs: Optional[dict] = None,
+    diagram_kwargs: typing.Optional[dict] = None,
     vertical: int = 3,
     show_results_names: bool = False,
     show_groups: bool = False,
@@ -216,12 +245,12 @@ class ElementState:
         parent: EditablePartial,
         number: int,
         name: str = None,
-        parent_index: Optional[int] = None,
+        parent_index: typing.Optional[int] = None,
     ):
         #: The pyparsing element that this represents
         self.element: pyparsing.ParserElement = element
         #: The name of the element
-        self.name: str = name
+        self.name: typing.Optional[str] = name
         #: The output Railroad element in an unconverted state
         self.converted: EditablePartial = converted
         #: The parent Railroad element, which we store so that we can extract this if it's duplicated
@@ -229,7 +258,7 @@ class ElementState:
         #: The order in which we found this element, used for sorting diagrams if this is extracted into a diagram
         self.number: int = number
         #: The index of this inside its parent
-        self.parent_index: Optional[int] = parent_index
+        self.parent_index: typing.Optional[int] = parent_index
         #: If true, we should extract this out into a subdiagram
         self.extract: bool = False
         #: If true, all of this element's children have been filled out
@@ -270,7 +299,7 @@ class ConverterState:
     Stores some state that persists between recursions into the element tree
     """
 
-    def __init__(self, diagram_kwargs: Optional[dict] = None):
+    def __init__(self, diagram_kwargs: typing.Optional[dict] = None):
         #: A dictionary mapping ParserElements to state relating to them
         self._element_diagram_states: Dict[int, ElementState] = {}
         #: A dictionary mapping ParserElement IDs to subdiagrams generated from them
@@ -361,14 +390,14 @@ def _apply_diagram_item_enhancements(fn):
 
     def _inner(
         element: pyparsing.ParserElement,
-        parent: Optional[EditablePartial],
+        parent: typing.Optional[EditablePartial],
         lookup: ConverterState = None,
         vertical: int = None,
         index: int = 0,
         name_hint: str = None,
         show_results_names: bool = False,
         show_groups: bool = False,
-    ) -> Optional[EditablePartial]:
+    ) -> typing.Optional[EditablePartial]:
 
         ret = fn(
             element,
@@ -412,14 +441,14 @@ def _visible_exprs(exprs: Iterable[pyparsing.ParserElement]):
 @_apply_diagram_item_enhancements
 def _to_diagram_element(
     element: pyparsing.ParserElement,
-    parent: Optional[EditablePartial],
+    parent: typing.Optional[EditablePartial],
     lookup: ConverterState = None,
     vertical: int = None,
     index: int = 0,
     name_hint: str = None,
     show_results_names: bool = False,
     show_groups: bool = False,
-) -> Optional[EditablePartial]:
+) -> typing.Optional[EditablePartial]:
     """
     Recursively converts a PyParsing Element to a railroad Element
     :param lookup: The shared converter state that keeps track of useful things
@@ -526,7 +555,9 @@ def _to_diagram_element(
         else:
             ret = EditablePartial.from_call(railroad.Group, label="", item="")
     elif isinstance(element, pyparsing.TokenConverter):
-        ret = EditablePartial.from_call(AnnotatedItem, label=type(element).__name__.lower(), item="")
+        ret = EditablePartial.from_call(
+            AnnotatedItem, label=type(element).__name__.lower(), item=""
+        )
     elif isinstance(element, pyparsing.Opt):
         ret = EditablePartial.from_call(railroad.Optional, item="")
     elif isinstance(element, pyparsing.OneOrMore):
diff --git a/pkg_resources/_vendor/pyparsing/diagram/template.jinja2 b/pkg_resources/_vendor/pyparsing/diagram/template.jinja2
deleted file mode 100644 (file)
index d2219fb..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    {% if not head %}
-        <style type="text/css">
-            .railroad-heading {
-                font-family: monospace;
-            }
-        </style>
-    {% else %}
-        {{ hear | safe }}
-    {% endif %}
-</head>
-<body>
-{{ body | safe }}
-{% for diagram in diagrams %}
-    <div class="railroad-group">
-        <h1 class="railroad-heading">{{ diagram.title }}</h1>
-        <div class="railroad-description">{{ diagram.text }}</div>
-        <div class="railroad-svg">
-            {{ diagram.svg }}
-        </div>
-    </div>
-{% endfor %}
-</body>
-</html>
index e06513eb00f723c3cb4efc4188141c2a6e303dd0..a38447bb05bd5d503a32651d6046ff8667785c0c 100644 (file)
@@ -2,7 +2,7 @@
 
 import re
 import sys
-from typing import Optional
+import typing
 
 from .util import col, line, lineno, _collapse_string_to_ranges
 from .unicode import pyparsing_unicode as ppu
@@ -25,7 +25,7 @@ class ParseBaseException(Exception):
         self,
         pstr: str,
         loc: int = 0,
-        msg: Optional[str] = None,
+        msg: typing.Optional[str] = None,
         elem=None,
     ):
         self.loc = loc
index be8a3657884806a8e7bf5e8e338b3fc86eeffa5b..9588b3b780159a2a2d23c7f84a4404ec350e2b65 100644 (file)
@@ -1,6 +1,7 @@
 # helpers.py
 import html.entities
 import re
+import typing
 
 from . import __diag__
 from .core import *
@@ -14,8 +15,8 @@ def delimited_list(
     expr: Union[str, ParserElement],
     delim: Union[str, ParserElement] = ",",
     combine: bool = False,
-    min: OptionalType[int] = None,
-    max: OptionalType[int] = None,
+    min: typing.Optional[int] = None,
+    max: typing.Optional[int] = None,
     *,
     allow_trailing_delim: bool = False,
 ) -> ParserElement:
@@ -69,9 +70,9 @@ def delimited_list(
 
 def counted_array(
     expr: ParserElement,
-    int_expr: OptionalType[ParserElement] = None,
+    int_expr: typing.Optional[ParserElement] = None,
     *,
-    intExpr: OptionalType[ParserElement] = None,
+    intExpr: typing.Optional[ParserElement] = None,
 ) -> ParserElement:
     """Helper to define a counted list of expressions.
 
@@ -197,7 +198,7 @@ def match_previous_expr(expr: ParserElement) -> ParserElement:
 
 
 def one_of(
-    strs: Union[IterableType[str], str],
+    strs: Union[typing.Iterable[str], str],
     caseless: bool = False,
     use_regex: bool = True,
     as_keyword: bool = False,
@@ -337,7 +338,7 @@ def dict_of(key: ParserElement, value: ParserElement) -> ParserElement:
 
         text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
         attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
-        print(OneOrMore(attr_expr).parse_string(text).dump())
+        print(attr_expr[1, ...].parse_string(text).dump())
 
         attr_label = label
         attr_value = Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)
@@ -461,7 +462,7 @@ def locatedExpr(expr: ParserElement) -> ParserElement:
 def nested_expr(
     opener: Union[str, ParserElement] = "(",
     closer: Union[str, ParserElement] = ")",
-    content: OptionalType[ParserElement] = None,
+    content: typing.Optional[ParserElement] = None,
     ignore_expr: ParserElement = quoted_string(),
     *,
     ignoreExpr: ParserElement = quoted_string(),
@@ -682,6 +683,8 @@ def make_xml_tags(
     return _makeTags(tag_str, True)
 
 
+any_open_tag: ParserElement
+any_close_tag: ParserElement
 any_open_tag, any_close_tag = make_html_tags(
     Word(alphas, alphanums + "_:").set_name("any tag")
 )
@@ -710,7 +713,7 @@ InfixNotationOperatorSpec = Union[
         InfixNotationOperatorArgType,
         int,
         OpAssoc,
-        OptionalType[ParseAction],
+        typing.Optional[ParseAction],
     ],
     Tuple[
         InfixNotationOperatorArgType,
@@ -840,7 +843,7 @@ def infix_notation(
         if rightLeftAssoc not in (OpAssoc.LEFT, OpAssoc.RIGHT):
             raise ValueError("operator must indicate right or left associativity")
 
-        thisExpr = Forward().set_name(term_name)
+        thisExpr: Forward = Forward().set_name(term_name)
         if rightLeftAssoc is OpAssoc.LEFT:
             if arity == 1:
                 matchExpr = _FB(lastExpr + opExpr) + Group(lastExpr + opExpr[1, ...])
@@ -945,7 +948,7 @@ def indentedBlock(blockStatementExpr, indentStack, indent=True, backup_stacks=[]
         assignment = Group(identifier + "=" + rvalue)
         stmt << (funcDef | assignment | identifier)
 
-        module_body = OneOrMore(stmt)
+        module_body = stmt[1, ...]
 
         parseTree = module_body.parseString(data)
         parseTree.pprint()
@@ -1055,7 +1058,9 @@ python_style_comment = Regex(r"#.*").set_name("Python style comment")
 
 # build list of built-in expressions, for future reference if a global default value
 # gets updated
-_builtin_exprs = [v for v in vars().values() if isinstance(v, ParserElement)]
+_builtin_exprs: List[ParserElement] = [
+    v for v in vars().values() if isinstance(v, ParserElement)
+]
 
 
 # pre-PEP8 compatible names
index bb444df4e5b2405f97d480a73d86927fb3260620..00c9421d3b0362526b8f90dc01e8db73841e0b61 100644 (file)
@@ -287,7 +287,7 @@ class ParseResults:
             print(numlist.parse_string("0 123 321")) # -> ['123', '321']
 
             label = Word(alphas)
-            patt = label("LABEL") + OneOrMore(Word(nums))
+            patt = label("LABEL") + Word(nums)[1, ...]
             print(patt.parse_string("AAB 123 321").dump())
 
             # Use pop() in a parse action to remove named result (note that corresponding value is not
@@ -394,7 +394,7 @@ class ParseResults:
 
         Example::
 
-            patt = OneOrMore(Word(alphas))
+            patt = Word(alphas)[1, ...]
 
             # use a parse action to append the reverse of the matched strings, to make a palindrome
             def make_palindrome(tokens):
@@ -487,7 +487,7 @@ class ParseResults:
 
         Example::
 
-            patt = OneOrMore(Word(alphas))
+            patt = Word(alphas)[1, ...]
             result = patt.parse_string("sldkj lsdkj sldkj")
             # even though the result prints in string-like form, it is actually a pyparsing ParseResults
             print(type(result), result) # -> <class 'pyparsing.ParseResults'> ['sldkj', 'lsdkj', 'sldkj']
@@ -554,7 +554,7 @@ class ParseResults:
             user_data = (Group(house_number_expr)("house_number")
                         | Group(ssn_expr)("ssn")
                         | Group(integer)("age"))
-            user_info = OneOrMore(user_data)
+            user_info = user_data[1, ...]
 
             result = user_info.parse_string("22 111-22-3333 #221B")
             for item in result:
index 991972f3fb2986d2d03951be87b07eeb6bce77bc..84a0ef17078c99e5917db41e3dbaf035fe206d7c 100644 (file)
@@ -1,7 +1,7 @@
 # testing.py
 
 from contextlib import contextmanager
-from typing import Optional
+import typing
 
 from .core import (
     ParserElement,
@@ -237,12 +237,12 @@ class pyparsing_test:
     @staticmethod
     def with_line_numbers(
         s: str,
-        start_line: Optional[int] = None,
-        end_line: Optional[int] = None,
+        start_line: typing.Optional[int] = None,
+        end_line: typing.Optional[int] = None,
         expand_tabs: bool = True,
         eol_mark: str = "|",
-        mark_spaces: Optional[str] = None,
-        mark_control: Optional[str] = None,
+        mark_spaces: typing.Optional[str] = None,
+        mark_control: typing.Optional[str] = None,
     ) -> str:
         """
         Helpful method for debugging a parser - prints a string with line and column numbers.
index 92261487c7af50ede7204c4b65299f2ed333bed1..06526203911de55da3c2a8c5ae73f48024c3f018 100644 (file)
@@ -120,7 +120,18 @@ class pyparsing_unicode(unicode_set):
     A namespace class for defining common language unicode_sets.
     """
 
-    _ranges: UnicodeRangeList = [(32, sys.maxunicode)]
+    # fmt: off
+
+    # define ranges in language character sets
+    _ranges: UnicodeRangeList = [
+        (0x0020, sys.maxunicode),
+    ]
+
+    class BasicMultilingualPlane(unicode_set):
+        "Unicode set for the Basic Multilingual Plane"
+        _ranges: UnicodeRangeList = [
+            (0x0020, 0xFFFF),
+        ]
 
     class Latin1(unicode_set):
         "Unicode set for Latin-1 Unicode Character Range"
@@ -278,11 +289,13 @@ class pyparsing_unicode(unicode_set):
 
     class CJK(Chinese, Japanese, Hangul):
         "Unicode set for combined Chinese, Japanese, and Korean (CJK) Unicode Character Range"
-        pass
 
     class Thai(unicode_set):
         "Unicode set for Thai Unicode Character Range"
-        _ranges: UnicodeRangeList = [(0x0E01, 0x0E3A), (0x0E3F, 0x0E5B)]
+        _ranges: UnicodeRangeList = [
+            (0x0E01, 0x0E3A),
+            (0x0E3F, 0x0E5B)
+        ]
 
     class Arabic(unicode_set):
         "Unicode set for Arabic Unicode Character Range"
@@ -308,7 +321,12 @@ class pyparsing_unicode(unicode_set):
 
     class Devanagari(unicode_set):
         "Unicode set for Devanagari Unicode Character Range"
-        _ranges: UnicodeRangeList = [(0x0900, 0x097F), (0xA8E0, 0xA8FF)]
+        _ranges: UnicodeRangeList = [
+            (0x0900, 0x097F),
+            (0xA8E0, 0xA8FF)
+        ]
+
+    # fmt: on
 
 
 pyparsing_unicode.Japanese._ranges = (
@@ -317,7 +335,9 @@ pyparsing_unicode.Japanese._ranges = (
     + pyparsing_unicode.Japanese.Katakana._ranges
 )
 
-# define ranges in language character sets
+pyparsing_unicode.BMP = pyparsing_unicode.BasicMultilingualPlane
+
+# add language identifiers using language Unicode
 pyparsing_unicode.العربية = pyparsing_unicode.Arabic
 pyparsing_unicode.中文 = pyparsing_unicode.Chinese
 pyparsing_unicode.кириллица = pyparsing_unicode.Cyrillic
index 8f9c2639206722ca47c5767831733983b73a9848..8e015069a5927893df3c3652e4b5c31544716fc2 100644 (file)
@@ -1,5 +1,5 @@
 packaging==21.3
-pyparsing==3.0.8
+pyparsing==3.0.9
 appdirs==1.4.3
 jaraco.text==3.7.0
 # required for jaraco.text on older Pythons
index f6fdfc9e7c7f2e83aa7dbd2f4f2bcb11fcb0f02a..480b136839fb98a337dab75760add1cf2a27c596 100644 (file)
@@ -20,7 +20,7 @@ addopts = "--flake8"
 [tool.pytest-enabler.cov]
 addopts = "--cov"
 
-[pytest.enabler.xdist]
+[tool.pytest-enabler.xdist]
 addopts = "-n auto"
 
 [tool.towncrier]
index e39b5ff80823a8f2f661382c1a7d5053b2a9df5d..9093b09bc127ea679ce3fbfe7d1b1257c18da802 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = setuptools
-version = 64.0.0b1
+version = 64.0.1
 author = Python Packaging Authority
 author_email = distutils-sig@python.org
 description = Easily download, build, install, upgrade, and uninstall Python packages
@@ -43,6 +43,8 @@ testing =
        pytest >= 6
        pytest-checkdocs >= 2.4
        pytest-flake8
+       # workaround for tholo/pytest-flake8#87
+       flake8 < 5
        pytest-black >= 0.3.7; \
                # workaround for jaraco/skeleton#22
                python_implementation != "PyPy"
@@ -88,6 +90,8 @@ docs =
        jaraco.packaging >= 9
        rst.linker >= 1.9
        jaraco.tidelift >= 1.4
+       sphinx-notfound-page == 0.8.3
+       sphinx-hoverxref < 2
 
        # local
        pygments-github-lexers==0.0.5
index 3b5a8179bd69f1e27480224791ea7cc4a55802b0..ade80056e9329051171c736103345d1bd7177b94 100644 (file)
@@ -17,7 +17,7 @@ import os
 import subprocess
 import contextlib
 import warnings
-import unittest.mock
+import unittest.mock as mock
 
 with contextlib.suppress(ImportError):
     import winreg
@@ -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,11 +346,11 @@ 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))
 
-    def compile(
+    def compile(  # noqa: C901
         self,
         sources,
         output_dir=None,
@@ -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 -----------------------------------------
index 5a70c32c2f84c139c18d88dbc263a2fd8f5b53fc..5dfe2a16ffbf5dc907aa3ce315757f4f9a055a82 100644 (file)
@@ -121,7 +121,7 @@ def make_tarball(
 
     # compression using `compress`
     if compress == 'compress':
-        warn("'compress' will be deprecated.", PendingDeprecationWarning)
+        warn("'compress' is deprecated.", DeprecationWarning)
         # the option varies depending on the platform
         compressed_name = archive_name + compress_ext[compress]
         if sys.platform == 'win32':
@@ -134,7 +134,7 @@ def make_tarball(
     return archive_name
 
 
-def make_zipfile(base_name, base_dir, verbose=0, dry_run=0):
+def make_zipfile(base_name, base_dir, verbose=0, dry_run=0):  # noqa: C901
     """Create a zip file from all the files under 'base_dir'.
 
     The output zip file will be named 'base_name' + ".zip".  Uses either the
index 056b2d98d9680c891b7409cceeff07a1d047c545..ee033ed94a1a36eaf82e0a4290ed7198834a1a33 100644 (file)
@@ -77,7 +77,7 @@ class BCPPCompiler(CCompiler):
 
     # -- Worker methods ------------------------------------------------
 
-    def compile(
+    def compile(  # noqa: C901
         self,
         sources,
         output_dir=None,
@@ -174,7 +174,7 @@ class BCPPCompiler(CCompiler):
 
     # create_static_lib ()
 
-    def link(
+    def link(  # noqa: C901
         self,
         target_desc,
         objects,
@@ -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
@@ -250,8 +250,8 @@ class BCPPCompiler(CCompiler):
                 else:
                     objects.append(file)
 
-            for l in library_dirs:
-                ld_args.append("/L%s" % os.path.normpath(l))
+            for ell in library_dirs:
+                ld_args.append("/L%s" % os.path.normpath(ell))
             ld_args.append("/L.")  # we sometimes use relative paths
 
             # list of object files
@@ -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)
index 005b64acdaca271f44da65b2722b063678527a50..3cf5761cf2aee4bae021687949df770a15c68fc2 100644 (file)
@@ -3,8 +3,16 @@
 Contains CCompiler, an abstract base class that defines the interface
 for the Distutils compiler abstraction model."""
 
-import sys, os, re
-from distutils.errors import *
+import sys
+import os
+import re
+from distutils.errors import (
+    CompileError,
+    LinkError,
+    UnknownFileError,
+    DistutilsPlatformError,
+    DistutilsModuleError,
+)
 from distutils.spawn import spawn
 from distutils.file_util import move_file
 from distutils.dir_util import mkpath
@@ -83,6 +91,16 @@ class CCompiler:
     }
     language_order = ["c++", "objc", "c"]
 
+    include_dirs = []
+    """
+    include dirs specific to this compiler class
+    """
+
+    library_dirs = []
+    """
+    library dirs specific to this compiler class
+    """
+
     def __init__(self, verbose=0, dry_run=0, force=0):
         self.dry_run = dry_run
         self.force = force
@@ -316,24 +334,7 @@ class CCompiler:
 
     def _setup_compile(self, outdir, macros, incdirs, sources, depends, extra):
         """Process arguments and decide which source files to compile."""
-        if outdir is None:
-            outdir = self.output_dir
-        elif not isinstance(outdir, str):
-            raise TypeError("'output_dir' must be a string or None")
-
-        if macros is None:
-            macros = self.macros
-        elif isinstance(macros, list):
-            macros = macros + (self.macros or [])
-        else:
-            raise TypeError("'macros' (if supplied) must be a list of tuples")
-
-        if incdirs is None:
-            incdirs = self.include_dirs
-        elif isinstance(incdirs, (list, tuple)):
-            incdirs = list(incdirs) + (self.include_dirs or [])
-        else:
-            raise TypeError("'include_dirs' (if supplied) must be a list of strings")
+        outdir, macros, incdirs = self._fix_compile_args(outdir, macros, incdirs)
 
         if extra is None:
             extra = []
@@ -392,6 +393,9 @@ class CCompiler:
         else:
             raise TypeError("'include_dirs' (if supplied) must be a list of strings")
 
+        # add include dirs for class
+        include_dirs += self.__class__.include_dirs
+
         return output_dir, macros, include_dirs
 
     def _prep_compile(self, sources, output_dir, depends=None):
@@ -448,6 +452,9 @@ class CCompiler:
         else:
             raise TypeError("'library_dirs' (if supplied) must be a list of strings")
 
+        # add library dirs for class
+        library_dirs += self.__class__.library_dirs
+
         if runtime_library_dirs is None:
             runtime_library_dirs = self.runtime_library_dirs
         elif isinstance(runtime_library_dirs, (list, tuple)):
@@ -808,7 +815,7 @@ class CCompiler:
         """
         raise NotImplementedError
 
-    def has_function(
+    def has_function(  # noqa: C901
         self,
         funcname,
         includes=None,
@@ -922,7 +929,7 @@ int main (int argc, char **argv) {
             base = base[os.path.isabs(base) :]  # If abs, chop off leading /
             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)
@@ -945,10 +952,9 @@ int main (int argc, char **argv) {
         self, libname, lib_type='static', strip_dir=0, output_dir=''  # or 'shared'
     ):
         assert output_dir is not None
-        if lib_type not in ("static", "shared", "dylib", "xcode_stub"):
-            raise ValueError(
-                "'lib_type' must be \"static\", \"shared\", \"dylib\", or \"xcode_stub\""
-            )
+        expected = '"static", "shared", "dylib", "xcode_stub"'
+        if lib_type not in eval(expected):
+            raise ValueError(f"'lib_type' must be {expected}")
         fmt = getattr(self, lib_type + "_lib_format")
         ext = getattr(self, lib_type + "_lib_extension")
 
index 4a9bcc2a733f77d3c6202311f8a2966e7c0630b3..68a9267c65babd799cec04213c20ad4f3289e109 100644 (file)
@@ -4,7 +4,9 @@ Provides the Command class, the base class for the command classes
 in the distutils.command package.
 """
 
-import sys, os, re
+import sys
+import os
+import re
 from distutils.errors import DistutilsOptionError
 from distutils import util, dir_util, file_util, archive_util, dep_util
 from distutils import log
@@ -161,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
@@ -213,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
 
@@ -241,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):
@@ -422,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
index d199c24277dea07bf3f3fe4aa0f50c6b43ebc242..a40c1f9471fc2a9a9bdf9573cb15a3de952158f2 100644 (file)
@@ -3,7 +3,7 @@
 Package containing implementation of all the standard Distutils
 commands."""
 
-__all__ = [
+__all__ = [  # noqa: F822
     'build',
     'build_py',
     'build_ext',
@@ -23,10 +23,4 @@ __all__ = [
     'bdist_wininst',
     'check',
     'upload',
-    # These two are reserved for future use:
-    #'bdist_sdux',
-    #'bdist_pkgtool',
-    # Note:
-    # bdist_packager is not included because it only provides
-    # an abstract base class
 ]
index e032603a9df5dca9b07bc1cf91651e57c48330bb..cffa27cb08285d1535e9812858dbad1551fc972f 100644 (file)
@@ -7,18 +7,21 @@ import sys
 import os
 import functools
 import subprocess
+import sysconfig
 
 
 @functools.lru_cache()
 def enabled():
     """
-    Only enabled for Python 3.9 framework builds except ensurepip and venv.
+    Only enabled for Python 3.9 framework homebrew builds
+    except ensurepip and venv.
     """
     PY39 = (3, 9) < sys.version_info < (3, 10)
     framework = sys.platform == 'darwin' and sys._framework
+    homebrew = "Cellar" in sysconfig.get_config_var('projectbase')
     venv = sys.prefix != sys.base_prefix
     ensurepip = os.environ.get("ENSUREPIP_OPTIONS")
-    return PY39 and framework and not venv and not ensurepip
+    return PY39 and framework and homebrew and not venv and not ensurepip
 
 
 schemes = dict(
index 2a639761c03642f1628925fb81cf1a9f8f33727d..c9fdbf131ced399fdae9cd337875a699f40a9afd 100644 (file)
@@ -4,8 +4,10 @@ Implements the Distutils 'bdist' command (create a built [binary]
 distribution)."""
 
 import os
+import warnings
+
 from distutils.core import Command
-from distutils.errors import *
+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,31 +76,23 @@ 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',
-        'msi',
-    ]
-
-    # 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"),
-        'msi': ('bdist_msi', "Microsoft Installer"),
-    }
+    # 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"),
+            'wininst': ('bdist_wininst', "Windows executable installer"),
+            'zip': ('bdist_dumb', "ZIP file"),
+            'msi': ('bdist_msi', "Microsoft Installer"),
+        }
+    )
+
+    # for compatibility until consumers only reference format_commands
+    format_command = format_commands
 
     def initialize_options(self):
         self.bdist_base = None
@@ -132,7 +136,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)
 
index 3c387828670b05616b10cab2aaf77f779c45c392..0f52330f67728e5f02d1673dc9683e95f6f9d294 100644 (file)
@@ -8,7 +8,7 @@ import os
 from distutils.core import Command
 from distutils.util import get_platform
 from distutils.dir_util import remove_tree, ensure_relative
-from distutils.errors import *
+from distutils.errors import DistutilsPlatformError
 from distutils.sysconfig import get_python_version
 from distutils import log
 
@@ -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:
index 2f292c960060cf3e2ad01fdccccaa8580a647f0d..57931c733d5f2dcfe56507b9837a395cb0d99ec3 100644 (file)
@@ -31,9 +31,6 @@ class PyDialog(Dialog):
         default, cancel, bitmap=true)"""
         super().__init__(*args)
         ruler = self.h - 36
-        bmwidth = 152 * ruler / 328
-        # if kw.get("bitmap", True):
-        #    self.bitmap("Bitmap", 0, 0, bmwidth, ruler, "PythonWin")
         self.line("BottomLine", 0, ruler, self.w, 0)
 
     def title(self, title):
@@ -231,7 +228,7 @@ class bdist_msi(Command):
                 )
         self.install_script_key = None
 
-    def run(self):
+    def run(self):  # noqa: C901
         if not self.skip_build:
             self.run_command('build')
 
@@ -256,7 +253,7 @@ class bdist_msi(Command):
             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)
+            plat_specifier = ".{}-{}".format(self.plat_name, target_version)
             build = self.get_finalized_command('build')
             build.build_lib = os.path.join(build.build_base, 'lib' + plat_specifier)
 
@@ -289,7 +286,7 @@ class bdist_msi(Command):
         # in Add-Remove-Programs (APR)
         fullname = self.distribution.get_fullname()
         if self.target_version:
-            product_name = "Python %s %s" % (self.target_version, fullname)
+            product_name = "Python {} {}".format(self.target_version, fullname)
         else:
             product_name = "Python %s" % (fullname)
         self.db = msilib.init_database(
@@ -318,7 +315,7 @@ class bdist_msi(Command):
         if not self.keep_temp:
             remove_tree(self.bdist_dir, dry_run=self.dry_run)
 
-    def add_files(self):
+    def add_files(self):  # noqa: C901
         db = self.db
         cab = msilib.CAB("distfiles")
         rootdir = os.path.abspath(self.bdist_dir)
@@ -350,7 +347,7 @@ class bdist_msi(Command):
                 for file in os.listdir(dir.absolute):
                     afile = os.path.join(dir.absolute, file)
                     if os.path.isdir(afile):
-                        short = "%s|%s" % (dir.make_short(file), file)
+                        short = "{}|{}".format(dir.make_short(file), file)
                         default = file + version
                         newdir = Directory(db, cab, dir, file, default, short)
                         todo.append(newdir)
@@ -406,11 +403,9 @@ class bdist_msi(Command):
             exe_action = "PythonExe" + ver
             target_dir_prop = "TARGETDIR" + ver
             exe_prop = "PYTHON" + ver
-            if msilib.Win64:
-                # type: msidbLocatorTypeRawValue + msidbLocatorType64bit
-                Type = 2 + 16
-            else:
-                Type = 2
+
+            # Type: msidbLocatorTypeRawValue + msidbLocatorType64bit
+            Type = 2 + 16 * bool(msilib.Win64)
             add_data(
                 self.db,
                 "RegLocator",
@@ -517,7 +512,6 @@ class bdist_msi(Command):
         # see "Dialog Style Bits"
         modal = 3  # visible | modal
         modeless = 1  # visible
-        track_disk_space = 32
 
         # UI customization properties
         add_data(
@@ -590,7 +584,9 @@ class bdist_msi(Command):
             320,
             80,
             0x30003,
-            "[ProductName] setup ended prematurely because of an error.  Your system has not been modified.  To install this program at a later time, please run the installation again.",
+            "[ProductName] setup ended prematurely because of an error. "
+            "Your system has not been modified.  To install this program "
+            "at a later time, please run the installation again.",
         )
         fatal.text(
             "Description2",
@@ -618,7 +614,8 @@ class bdist_msi(Command):
             80,
             0x30003,
             "[ProductName] setup was interrupted.  Your system has not been modified.  "
-            "To install this program at a later time, please run the installation again.",
+            "To install this program at a later time, please run the installation "
+            "again.",
         )
         user_exit.text(
             "Description2",
@@ -683,7 +680,10 @@ class bdist_msi(Command):
             330,
             50,
             3,
-            "The following applications are using files that need to be updated by this setup. Close these applications and then click Retry to continue the installation or Cancel to exit it.",
+            "The following applications are using files that need to be updated by "
+            "this "
+            "setup. Close these applications and then click Retry to continue the "
+            "installation or Cancel to exit it.",
         )
         inuse.control(
             "List",
@@ -720,7 +720,6 @@ class bdist_msi(Command):
             None,
         )
         error.text("ErrorText", 50, 9, 280, 48, 3, "")
-        # error.control("ErrorIcon", "Icon", 15, 9, 24, 24, 5242881, None, "py.ico", None, None)
         error.pushbutton("N", 120, 72, 81, 21, 3, "No", None).event(
             "EndDialog", "ErrorNo"
         )
@@ -785,7 +784,8 @@ class bdist_msi(Command):
             194,
             30,
             3,
-            "Please wait while the installer finishes determining your disk space requirements.",
+            "Please wait while the installer finishes determining your disk space "
+            "requirements.",
         )
         c = costing.pushbutton("Return", 102, 57, 56, 17, 3, "Return", None)
         c.event("EndDialog", "Exit")
@@ -802,7 +802,8 @@ class bdist_msi(Command):
             320,
             40,
             0x30003,
-            "Please wait while the Installer prepares to guide you through the installation.",
+            "Please wait while the Installer prepares to guide you through the "
+            "installation.",
         )
         prep.title("Welcome to the [ProductName] Installer")
         c = prep.text("ActionText", 15, 110, 320, 20, 0x30003, "Pondering...")
@@ -1096,19 +1097,18 @@ class bdist_msi(Command):
 
         # Close dialog when maintenance action scheduled
         c.event("EndDialog", "Return", 'MaintenanceForm_Action<>"Change"', 20)
-        # c.event("NewDialog", "SelectFeaturesDlg", 'MaintenanceForm_Action="Change"', 21)
 
         maint.cancel("Cancel", "RepairRadioGroup").event("SpawnDialog", "CancelDlg")
 
     def get_installer_filename(self, fullname):
         # Factored out to allow overriding in subclasses
         if self.target_version:
-            base_name = "%s.%s-py%s.msi" % (
+            base_name = "{}.{}-py{}.msi".format(
                 fullname,
                 self.plat_name,
                 self.target_version,
             )
         else:
-            base_name = "%s.%s.msi" % (fullname, self.plat_name)
+            base_name = "{}.{}.msi".format(fullname, self.plat_name)
         installer_name = os.path.join(self.dist_dir, base_name)
         return installer_name
index cf4b95258c923a28a04c1c5b6f8f75c14267c86a..6a50ef34eab60cf005ea604f83eaf6170437032e 100644 (file)
@@ -3,11 +3,19 @@
 Implements the Distutils 'bdist_rpm' command (create RPM source and binary
 distributions)."""
 
-import subprocess, sys, os
+import subprocess
+import sys
+import os
+
 from distutils.core import Command
 from distutils.debug import DEBUG
 from distutils.file_util import write_file
-from distutils.errors import *
+from distutils.errors import (
+    DistutilsOptionError,
+    DistutilsPlatformError,
+    DistutilsFileError,
+    DistutilsExecError,
+)
 from distutils.sysconfig import get_python_version
 from distutils import log
 
@@ -268,7 +276,7 @@ class bdist_rpm(Command):
 
         self.ensure_string('force_arch')
 
-    def run(self):
+    def run(self):  # noqa: C901
         if DEBUG:
             print("before _get_package_data():")
             print("vendor =", self.vendor)
@@ -345,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,
@@ -359,12 +367,12 @@ class bdist_rpm(Command):
                 line = out.readline()
                 if not line:
                     break
-                l = line.strip().split()
-                assert len(l) == 2
-                binary_rpms.append(l[1])
+                ell = line.strip().split()
+                assert len(ell) == 2
+                binary_rpms.append(ell[1])
                 # The source rpm is named after the first entry in the spec file
                 if source_rpm is None:
-                    source_rpm = l[0]
+                    source_rpm = ell[0]
 
             status = out.close()
             if status:
@@ -401,7 +409,7 @@ class bdist_rpm(Command):
     def _dist_path(self, path):
         return os.path.join(self.dist_dir, os.path.basename(path))
 
-    def _make_spec_file(self):
+    def _make_spec_file(self):  # noqa: C901
         """Generate the text of an RPM spec file and return it as a
         list of strings (one per line).
         """
@@ -480,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())
@@ -519,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
index 76b8a890df2046f4ddb94678055e3e7f52f88527..02bd7200c5a245be62f8fbb3cfb9fbabd7357a54 100644 (file)
@@ -9,7 +9,7 @@ import warnings
 from distutils.core import Command
 from distutils.util import get_platform
 from distutils.dir_util import remove_tree
-from distutils.errors import *
+from distutils.errors import DistutilsOptionError, DistutilsPlatformError
 from distutils.sysconfig import get_python_version
 from distutils import log
 
@@ -185,7 +185,7 @@ class bdist_wininst(Command):
             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)
+            plat_specifier = ".{}-{}".format(self.plat_name, target_version)
             build = self.get_finalized_command('build')
             build.build_lib = os.path.join(build.build_base, 'lib' + plat_specifier)
 
@@ -259,8 +259,8 @@ class bdist_wininst(Command):
         ]:
             data = getattr(metadata, name, "")
             if data:
-                info = info + ("\n    %s: %s" % (name.capitalize(), escape(data)))
-                lines.append("%s=%s" % (name, escape(data)))
+                info = info + ("\n    {}: {}".format(name.capitalize(), escape(data)))
+                lines.append("{}={}".format(name, escape(data)))
 
         # The [setup] section contains entries controlling
         # the installer runtime.
@@ -280,7 +280,7 @@ class bdist_wininst(Command):
         import time
         import distutils
 
-        build_info = "Built %s with distutils-%s" % (
+        build_info = "Built {} with distutils-{}".format(
             time.ctime(time.time()),
             distutils.__version__,
         )
@@ -319,7 +319,7 @@ class bdist_wininst(Command):
                 # 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:
+                with open(self.pre_install_script, encoding="latin-1") as script:
                     script_data = script.read().encode("latin-1")
                 cfgdata = cfgdata + script_data + b"\n\0"
             else:
@@ -349,15 +349,15 @@ class bdist_wininst(Command):
             # 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),
+                "{}.{}-py{}.exe".format(fullname, self.plat_name, self.target_version),
             )
         else:
             installer_name = os.path.join(
-                self.dist_dir, "%s.%s.exe" % (fullname, self.plat_name)
+                self.dist_dir, "{}.{}.exe".format(fullname, self.plat_name)
             )
         return installer_name
 
-    def get_exe_bytes(self):
+    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
@@ -410,7 +410,7 @@ class bdist_wininst(Command):
         else:
             sfix = ''
 
-        filename = os.path.join(directory, "wininst-%s%s.exe" % (bv, sfix))
+        filename = os.path.join(directory, "wininst-{}{}.exe".format(bv, sfix))
         f = open(filename, "rb")
         try:
             return f.read()
index 3aa7fac71b2a0eaec02db9c2a0d2c79886456d6c..6d453419d073677e33ab60a5e627ba412be1fd6a 100644 (file)
@@ -2,7 +2,8 @@
 
 Implements the Distutils 'build' command."""
 
-import sys, os
+import sys
+import os
 from distutils.core import Command
 from distutils.errors import DistutilsOptionError
 from distutils.util import get_platform
@@ -65,7 +66,7 @@ class build(Command):
         self.executable = None
         self.parallel = None
 
-    def finalize_options(self):
+    def finalize_options(self):  # noqa: C901
         if self.plat_name is None:
             self.plat_name = get_platform()
         else:
@@ -78,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
index 003499fa9765d62d2197b01bc7466eb11d1475aa..50bb9bbabb7ab00cd4763b524ab536e711e468a8 100644 (file)
@@ -16,7 +16,7 @@ module."""
 
 import os
 from distutils.core import Command
-from distutils.errors import *
+from distutils.errors import DistutilsSetupError
 from distutils.sysconfig import customize_compiler
 from distutils import log
 
index 1a6dd39400eb75037c9dc88ffa8b4c595a05b2a0..3c6cee7e3644fdbdeeb4b5bcb0124044eb0f50ed 100644 (file)
@@ -9,7 +9,14 @@ import os
 import re
 import sys
 from distutils.core import Command
-from distutils.errors import *
+from distutils.errors import (
+    DistutilsOptionError,
+    DistutilsSetupError,
+    CCompilerError,
+    DistutilsError,
+    CompileError,
+    DistutilsPlatformError,
+)
 from distutils.sysconfig import customize_compiler, get_python_version
 from distutils.sysconfig import get_config_h_filename
 from distutils.dep_util import newer_group
@@ -124,7 +131,7 @@ class build_ext(Command):
         self.user = None
         self.parallel = None
 
-    def finalize_options(self):
+    def finalize_options(self):  # noqa: C901
         from distutils import sysconfig
 
         self.set_undefined_options(
@@ -272,7 +279,7 @@ class build_ext(Command):
             except ValueError:
                 raise DistutilsOptionError("parallel should be an integer")
 
-    def run(self):
+    def run(self):  # noqa: C901
         from distutils.ccompiler import new_compiler
 
         # 'self.extensions', as supplied by setup.py, is a list of
@@ -338,7 +345,7 @@ class build_ext(Command):
         # Now actually compile and link everything.
         self.build_extensions()
 
-    def check_extensions_list(self, extensions):
+    def check_extensions_list(self, extensions):  # noqa: C901
         """Ensure that the list of extensions (presumably provided as a
         command option 'extensions') is valid, i.e. it is a list of
         Extension objects.  We also support the old-style list of 2-tuples,
@@ -491,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
@@ -724,7 +731,7 @@ class build_ext(Command):
             ext.export_symbols.append(initfunc_name)
         return ext.export_symbols
 
-    def get_libraries(self, ext):
+    def get_libraries(self, ext):  # noqa: C901
         """Return the list of libraries to link against when building a
         shared extension.  On most platforms, this is just 'ext.libraries';
         on Windows, we add the Python library (eg. python20.dll).
index 7723d359dba374a664c253da1e1d5209b5583ef9..47c6158e0f74033bfcfeb7424df227a3815651de 100644 (file)
@@ -8,7 +8,7 @@ import sys
 import glob
 
 from distutils.core import Command
-from distutils.errors import *
+from distutils.errors import DistutilsOptionError, DistutilsFileError
 from distutils.util import convert_path
 from distutils import log
 
@@ -137,7 +137,6 @@ class build_py(Command):
 
     def build_package_data(self):
         """Copy data files into build directory"""
-        lastdir = None
         for package, src_dir, build_dir, filenames in self.data_files:
             for filename in filenames:
                 target = os.path.join(build_dir, filename)
index 17058dbf6dc06c05a256a1898daba6b1816e267f..2cc5d1e09c09b6c674d47a26c5ebc6163705ecce 100644 (file)
@@ -75,7 +75,7 @@ class build_scripts(Command):
 
         return outfiles, updated_files
 
-    def _copy_script(self, script, outfiles, updated_files):
+    def _copy_script(self, script, outfiles, updated_files):  # noqa: C901
         shebang_match = None
         script = convert_path(script)
         outfile = os.path.join(self.build_dir, os.path.basename(script))
index 176a8b876469892029e4e0a6261aebed13311ca2..539481c946043c53aa61bd62cfd4b4146934697d 100644 (file)
@@ -2,19 +2,18 @@
 
 Implements the Distutils 'check' command.
 """
-from email.utils import getaddresses
+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,
@@ -32,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."""
@@ -83,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.')
 
@@ -119,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
@@ -141,7 +139,7 @@ class check(Command):
             error_handler=settings.error_encoding_error_handler,
         )
 
-        document = nodes.document(settings, reporter, source=source_path)
+        document = docutils.nodes.document(settings, reporter, source=source_path)
         document.note_source(source_path, -1)
         try:
             parser.parse(data, document)
index 73de1d3ec81d87bfde2b77f43662b0d1caf7d5e1..4492c89660c202acf882375258dffafff00a99ba 100644 (file)
@@ -9,7 +9,8 @@ configure-like tasks: "try to compile this C code", or "figure out where
 this header file lives".
 """
 
-import os, re
+import os
+import re
 
 from distutils.core import Command
 from distutils.errors import DistutilsExecError
index 7d9054e33f9a76e89bfc0c5ced9ff26abfa8bb47..a38cddcda5380aac99bade87e2cdf95d4c99348a 100644 (file)
@@ -12,11 +12,10 @@ from distutils import log
 from distutils.core import Command
 from distutils.debug import DEBUG
 from distutils.sysconfig import get_config_vars
-from distutils.errors import DistutilsPlatformError
 from distutils.file_util import write_file
 from distutils.util import convert_path, subst_vars, change_root
 from distutils.util import get_platform
-from distutils.errors import DistutilsOptionError
+from distutils.errors import DistutilsOptionError, DistutilsPlatformError
 from . import _framework_compat as fw
 from .. import _collections
 
@@ -36,8 +35,10 @@ WINDOWS_SCHEME = {
 INSTALL_SCHEMES = {
     'posix_prefix': {
         'purelib': '{base}/lib/{implementation_lower}{py_version_short}/site-packages',
-        'platlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}/site-packages',
-        'headers': '{base}/include/{implementation_lower}{py_version_short}{abiflags}/{dist_name}',
+        'platlib': '{platbase}/{platlibdir}/{implementation_lower}'
+        '{py_version_short}/site-packages',
+        'headers': '{base}/include/{implementation_lower}'
+        '{py_version_short}{abiflags}/{dist_name}',
         'scripts': '{base}/bin',
         'data': '{base}',
     },
@@ -70,7 +71,8 @@ if HAS_USER_SITE:
     INSTALL_SCHEMES['nt_user'] = {
         'purelib': '{usersite}',
         'platlib': '{usersite}',
-        'headers': '{userbase}/{implementation}{py_version_nodot_plat}/Include/{dist_name}',
+        'headers': '{userbase}/{implementation}{py_version_nodot_plat}'
+        '/Include/{dist_name}',
         'scripts': '{userbase}/{implementation}{py_version_nodot_plat}/Scripts',
         'data': '{userbase}',
     }
@@ -78,7 +80,8 @@ if HAS_USER_SITE:
     INSTALL_SCHEMES['posix_user'] = {
         'purelib': '{usersite}',
         'platlib': '{usersite}',
-        'headers': '{userbase}/include/{implementation_lower}{py_version_short}{abiflags}/{dist_name}',
+        'headers': '{userbase}/include/{implementation_lower}'
+        '{py_version_short}{abiflags}/{dist_name}',
         'scripts': '{userbase}/bin',
         'data': '{userbase}',
     }
@@ -327,7 +330,7 @@ class install(Command):
     # party Python modules on various platforms given a wide
     # array of user input is decided.  Yes, it's quite complex!)
 
-    def finalize_options(self):
+    def finalize_options(self):  # noqa: C901
         """Finalizes options."""
         # This method (and its helpers, like 'finalize_unix()',
         # 'finalize_other()', and 'select_scheme()') is where the default
index dc939633ee9e2cdd22a9fe91a8edd981bf956097..d5e68a6e47199372c79ec094e0385f49a6600f22 100644 (file)
@@ -1,12 +1,16 @@
-"""distutils.command.install_egg_info
+"""
+distutils.command.install_egg_info
 
 Implements the Distutils 'install_egg_info' command, for installing
-a package's PKG-INFO metadata."""
+a package's PKG-INFO metadata.
+"""
 
+import os
+import sys
+import re
 
 from distutils.cmd import Command
 from distutils import log, dir_util
-import os, sys, re
 
 
 class install_egg_info(Command):
index ca407eb70396cb036e754dc23143bbf555316fc2..c1402650d7f7defdde15741aabafa9f42843dcdf 100644 (file)
@@ -7,11 +7,11 @@ Implements the Distutils 'register' command (register with the repository).
 
 import getpass
 import io
-import urllib.parse, urllib.request
+import urllib.parse
+import urllib.request
 from warnings import warn
 
 from distutils.core import PyPIRCCommand
-from distutils.errors import *
 from distutils import log
 
 
@@ -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()
@@ -104,7 +104,7 @@ class register(PyPIRCCommand):
         (code, result) = self.post_to_server(self.build_post_data('verify'))
         log.info('Server response (%s): %s', code, result)
 
-    def send_metadata(self):
+    def send_metadata(self):  # noqa: C901
         '''Send the metadata to the package index server.
 
         Well, do the following:
@@ -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'] = ''
@@ -261,11 +261,11 @@ Your selection [default 1]: ''',
             data['metadata_version'] = '1.1'
         return data
 
-    def post_to_server(self, data, auth=None):
+    def post_to_server(self, data, auth=None):  # noqa: C901
         '''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'
index aad3e7134c05a62c279a5a2a0d55e4ec74888221..d6e9489d1b1913f7090b225db69c42fc0454c17a 100644 (file)
@@ -15,7 +15,7 @@ from distutils.text_file import TextFile
 from distutils.filelist import FileList
 from distutils import log
 from distutils.util import convert_path
-from distutils.errors import DistutilsTemplateError, DistutilsOptionError
+from distutils.errors import DistutilsOptionError, DistutilsTemplateError
 
 
 def show_formats():
@@ -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):
index 782e3dea96509a5a274cc37856871b2f4b8626d5..6af5394339437497134fa74a97463e3e5309ff29 100644 (file)
@@ -71,7 +71,7 @@ class upload(PyPIRCCommand):
         for command, pyversion, filename in self.distribution.dist_files:
             self.upload_file(command, pyversion, filename)
 
-    def upload_file(self, command, pyversion, filename):
+    def upload_file(self, command, pyversion, filename):  # noqa: C901
         # Makes sure the repository URL is compliant
         schema, netloc, url, params, query, fragments = urlparse(self.repository)
         if params or query or fragments:
@@ -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)
index 34a1c3b7c9e81f7d0228e5c9836c9c22788347ae..6e0c3a71f10cf216aaa19053564159353e47e66a 100644 (file)
@@ -44,7 +44,7 @@ class PyPIRCCommand(Command):
         with os.fdopen(os.open(rc, os.O_CREAT | os.O_WRONLY, 0o600), 'w') as f:
             f.write(DEFAULT_PYPIRC % (username, password))
 
-    def _read_pypirc(self):
+    def _read_pypirc(self):  # noqa: C901
         """Reads the .pypirc file."""
         rc = self._get_rc_file()
         if os.path.exists(rc):
index 305ecf6922f4511492c74b6a79abb32264e52a55..de13978f02aa85ac70aa49a0d39178cbba913199 100644 (file)
@@ -11,7 +11,12 @@ import sys
 import tokenize
 
 from distutils.debug import DEBUG
-from distutils.errors import *
+from distutils.errors import (
+    DistutilsSetupError,
+    DistutilsError,
+    CCompilerError,
+    DistutilsArgError,
+)
 
 # Mainly import these so setup scripts can "from distutils.core import" them.
 from distutils.dist import Distribution
@@ -19,6 +24,9 @@ from distutils.cmd import Command
 from distutils.config import PyPIRCCommand
 from distutils.extension import Extension
 
+
+__all__ = ['Distribution', 'Command', 'PyPIRCCommand', 'Extension', 'setup']
+
 # This is a barebones help message generated displayed when the user
 # runs the setup script with no arguments at all.  More useful help
 # is generated with various --help options: global help, list commands,
@@ -33,7 +41,7 @@ usage: %(script)s [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
 
 def gen_usage(script_name):
     script = os.path.basename(script_name)
-    return USAGE % vars()
+    return USAGE % locals()
 
 
 # Some mild magic to control the behaviour of 'setup()' from 'run_setup()'.
@@ -85,7 +93,7 @@ extension_keywords = (
 )
 
 
-def setup(**attrs):
+def setup(**attrs):  # noqa: C901
     """The gateway to the Distutils: do everything your setup script needs
     to do, in a highly flexible and user-driven way.  Briefly: create a
     Distribution instance; find and parse config files; parse the command
@@ -141,7 +149,7 @@ def setup(**attrs):
         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
@@ -195,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:
@@ -241,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
index 445e2e51e5054c871ca88f498dec1d5004d61681..2c6dbae83fbddf4b16e01296efe8f34971bb924f 100644 (file)
@@ -101,6 +101,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 +125,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 +146,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 +207,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)
@@ -228,7 +233,6 @@ class CygwinCCompiler(UnixCCompiler):
 
             # generate the filenames for these files
             def_file = os.path.join(temp_dir, dll_name + ".def")
-            lib_file = os.path.join(temp_dir, 'lib' + dll_name + ".a")
 
             # Generate .def file
             contents = ["LIBRARY %s" % os.path.basename(output_filename), "EXPORTS"]
@@ -236,10 +240,8 @@ class CygwinCCompiler(UnixCCompiler):
                 contents.append(sym)
             self.execute(write_file, (def_file, contents), "writing %s" % def_file)
 
-            # next add options for def-file and to creating import libraries
+            # next add options for def-file
 
-            # doesn't work: bfd_close build\...\libfoo.a: Invalid operation
-            # extra_preargs.extend(["-Wl,--out-implib,%s" % lib_file])
             # for gcc/ld the def-file is specified as any object files
             objects.append(def_file)
 
@@ -276,7 +278,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 -----------------------------------------
@@ -291,7 +293,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)
@@ -325,7 +327,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
@@ -340,9 +342,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
@@ -398,7 +398,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):
index d94e111ca6c4ae6dc35bc36ccf094f1c0ccb34f6..db1fa01996ce0d47cd7f070c53b085926440d377 100644 (file)
@@ -82,9 +82,9 @@ def newer_group(sources, target, missing='error'):
             if missing == 'error':  # blow up when we stat() the file
                 pass
             elif missing == 'ignore':  # missing source dropped from
-                continue  #  target's dependency list
+                continue  # target's dependency list
             elif missing == 'newer':  # missing source means target is
-                return 1  #  out-of-date
+                return 1  # out-of-date
 
         source_mtime = os.stat(source)[ST_MTIME]
         if source_mtime > target_mtime:
index 2c19b9833cb2c3508b871f65e548146cf2e5adcb..6f0bb8ad76a064dad843db670c91e493d0e19a0c 100644 (file)
@@ -4,17 +4,15 @@ Utility functions for manipulating directories and directory trees."""
 
 import os
 import errno
-from distutils.errors import DistutilsFileError, DistutilsInternalError
+from distutils.errors import DistutilsInternalError, DistutilsFileError
 from distutils import log
 
 # cache for by mkpath() -- in addition to cheapening redundant calls,
 # eliminates redundant "creating /foo/bar/baz" messages in dry-run mode
 _path_created = {}
 
-# I don't use os.makedirs because a) it's new to Python 1.5.2, and
-# b) it blows up if the directory already exists (I want to silently
-# succeed in that case).
-def mkpath(name, mode=0o777, verbose=1, dry_run=0):
+
+def mkpath(name, mode=0o777, verbose=1, dry_run=0):  # noqa: C901
     """Create a directory and any missing ancestor directories.
 
     If the directory already exists (or if 'name' is the empty string, which
@@ -23,6 +21,12 @@ def mkpath(name, mode=0o777, verbose=1, dry_run=0):
     (eg. some sub-path exists, but is a file rather than a directory).
     If 'verbose' is true, print a one-line summary of each mkdir to stdout.
     Return the list of directories actually created.
+
+    os.makedirs is not used because:
+
+    a) It's new to Python 1.5.2, and
+    b) it blows up if the directory already exists (in which case it should
+       silently succeed).
     """
 
     global _path_created
@@ -30,7 +34,7 @@ def mkpath(name, mode=0o777, verbose=1, dry_run=0):
     # 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
@@ -72,7 +76,7 @@ def mkpath(name, mode=0o777, verbose=1, dry_run=0):
             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)
 
@@ -100,7 +104,7 @@ def create_tree(base_dir, files, mode=0o777, verbose=1, dry_run=0):
         mkpath(dir, mode, verbose=verbose, dry_run=dry_run)
 
 
-def copy_tree(
+def copy_tree(  # noqa: C901
     src,
     dst,
     preserve_mode=1,
@@ -140,7 +144,7 @@ def copy_tree(
             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:
index 82e3684daaa911dcb542cce847b8354e242385da..0406ab19cbd2db758a46154a5d58d304cc9450dc 100644 (file)
@@ -14,7 +14,12 @@ try:
 except ImportError:
     warnings = None
 
-from distutils.errors import *
+from distutils.errors import (
+    DistutilsOptionError,
+    DistutilsModuleError,
+    DistutilsArgError,
+    DistutilsClassError,
+)
 from distutils.fancy_getopt import FancyGetopt, translate_longopt
 from distutils.util import check_environ, strtobool, rfc822_escape
 from distutils import log
@@ -116,7 +121,7 @@ Common commands: (see '--help-commands' for more)
 
     # -- Creation/initialization methods -------------------------------
 
-    def __init__(self, attrs=None):
+    def __init__(self, attrs=None):  # noqa: C901
         """Construct a new Distribution instance: initialize all the
         attributes of a Distribution, and then use 'attrs' (a dictionary
         mapping attribute names to values) to assign some of those
@@ -359,7 +364,7 @@ Common commands: (see '--help-commands' for more)
 
         return files
 
-    def parse_config_files(self, filenames=None):
+    def parse_config_files(self, filenames=None):  # noqa: C901
         from configparser import ConfigParser
 
         # Ignore install directory options if we have a venv
@@ -508,7 +513,7 @@ Common commands: (see '--help-commands' for more)
             ),
         ]
 
-    def _parse_command_opts(self, parser, args):
+    def _parse_command_opts(self, parser, args):  # noqa: C901
         """Parse the command-line options for a single command.
         'parser' must be a FancyGetopt instance; 'args' must be the list
         of arguments, starting with the current command (whose options
@@ -820,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:
@@ -871,7 +876,7 @@ Common commands: (see '--help-commands' for more)
 
         return cmd_obj
 
-    def _set_command_options(self, command_obj, option_dict=None):
+    def _set_command_options(self, command_obj, option_dict=None):  # noqa: C901
         """Set the options for 'command_obj' from 'option_dict'.  Basically
         this means copying elements of a dictionary ('option_dict') to
         attributes of an instance ('command').
@@ -888,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:
@@ -1154,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())
@@ -1177,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 ----------------------------------------
 
@@ -1188,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
index b05c5d909fcebaa994f81efa018e0a177c81551e..6b8575de2949cd0519ee5f26b6eb00df417e2113 100644 (file)
@@ -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,
@@ -142,7 +142,7 @@ class Extension:
         )
 
 
-def read_setup_file(filename):
+def read_setup_file(filename):  # noqa: C901
     """Reads a Setup file and returns Extension instances."""
     from distutils.sysconfig import parse_makefile, expand_makefile_vars, _variable_rx
 
index b9b21766e161ebbdb9fc7366cf5ab839cc632e62..830f047e28aa3b25295174d44d735448a1a43098 100644 (file)
@@ -8,9 +8,11 @@ additional features:
   * options set attributes of a passed-in object
 """
 
-import sys, string, re
+import sys
+import string
+import re
 import getopt
-from distutils.errors import *
+from distutils.errors import DistutilsGetoptError, DistutilsArgError
 
 # Much like command_re in distutils.core, this is close to but not quite
 # the same as a Python NAME -- except, in the spirit of most GNU
@@ -20,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).
@@ -136,7 +138,7 @@ class FancyGetopt:
         self._check_alias_dict(negative_alias, "negative alias")
         self.negative_alias = negative_alias
 
-    def _grok_option_table(self):
+    def _grok_option_table(self):  # noqa: C901
         """Populate the various data structures that keep tabs on the
         option table.  Called by 'getopt()' before it can do anything
         worthwhile.
@@ -155,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:
@@ -218,7 +220,7 @@ class FancyGetopt:
                 self.short_opts.append(short)
                 self.short2long[short[0]] = long
 
-    def getopt(self, args=None, object=None):
+    def getopt(self, args=None, object=None):  # noqa: C901
         """Parse command-line options in args. Store as attributes on object.
 
         If 'args' is None or not supplied, uses 'sys.argv[1:]'.  If
@@ -289,7 +291,7 @@ class FancyGetopt:
         else:
             return self.option_order
 
-    def generate_help(self, header=None):
+    def generate_help(self, header=None):  # noqa: C901
         """Generate help text (a list of strings, one per suggested line of
         output) from the option table for this FancyGetopt object.
         """
@@ -301,13 +303,13 @@ class FancyGetopt:
         for option in self.option_table:
             long = option[0]
             short = option[1]
-            l = len(long)
+            ell = len(long)
             if long[-1] == '=':
-                l = l - 1
+                ell = ell - 1
             if short is not None:
-                l = l + 5  # " (-x)" where short == 'x'
-            if l > max_opt:
-                max_opt = l
+                ell = ell + 5  # " (-x)" where short == 'x'
+            if ell > max_opt:
+                max_opt = ell
 
         opt_width = max_opt + 2 + 2 + 2  # room for indent + dashes + gutter
 
@@ -357,14 +359,14 @@ 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:
                     lines.append("  --%-*s" % opt_names)
 
-            for l in text[1:]:
-                lines.append(big_indent + l)
+            for ell in text[1:]:
+                lines.append(big_indent + ell)
         return lines
 
     def print_help(self, header=None, file=None):
@@ -405,11 +407,11 @@ def wrap_text(text, width):
         cur_len = 0  # length of current line
 
         while chunks:
-            l = len(chunks[0])
-            if cur_len + l <= width:  # can squeeze (at least) this chunk in
+            ell = len(chunks[0])
+            if cur_len + ell <= width:  # can squeeze (at least) this chunk in
                 cur_line.append(chunks[0])
                 del chunks[0]
-                cur_len = cur_len + l
+                cur_len = cur_len + ell
             else:  # this line is full
                 # drop last chunk if all space
                 if cur_line and cur_line[-1][0] == ' ':
index 4ff2230c000b838ea182ace9935af87cb85b5f3a..1f1e444b1c30d93ca28ac15115ef73e63b9f6169 100644 (file)
@@ -11,7 +11,7 @@ from distutils import log
 _copy_action = {None: 'copying', 'hard': 'hard linking', 'sym': 'symbolically linking'}
 
 
-def _copy_file_contents(src, dst, buffer_size=16 * 1024):
+def _copy_file_contents(src, dst, buffer_size=16 * 1024):  # noqa: C901
     """Copy the file 'src' to 'dst'; both must be filenames.  Any error
     opening either file, reading from 'src', or writing to 'dst', raises
     DistutilsFileError.  Data is read/written in chunks of 'buffer_size'
@@ -26,27 +26,29 @@ def _copy_file_contents(src, dst, buffer_size=16 * 1024):
         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):
                 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:
@@ -65,7 +67,7 @@ def _copy_file_contents(src, dst, buffer_size=16 * 1024):
             fsrc.close()
 
 
-def copy_file(
+def copy_file(  # noqa: C901
     src,
     dst,
     preserve_mode=1,
@@ -173,7 +175,7 @@ def copy_file(
 
 
 # XXX I suspect this is Unix-specific -- need porting help!
-def move_file(src, dst, verbose=1, dry_run=0):
+def move_file(src, dst, verbose=1, dry_run=0):  # noqa: C901
 
     """Move a file 'src' to 'dst'.  If 'dst' is a directory, the file will
     be moved into it with the same name; otherwise, 'src' is just renamed
@@ -198,12 +200,12 @@ def move_file(src, dst, verbose=1, dry_run=0):
         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):
         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)
index 37ab341e9010b5d6b918669ae98d0ef2f7e936f4..987931a9883ff36862dbd0831bd0a16903977879 100644 (file)
@@ -105,7 +105,7 @@ class FileList:
 
         return (action, patterns, dir, dir_pattern)
 
-    def process_template_line(self, line):
+    def process_template_line(self, line):  # noqa: C901
         # Parse the line: split it up, make sure the right number of words
         # is there, and return the relevant words.  'action' is always
         # defined: it's the first word of the line.  Which of the other
@@ -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)
index 225f1a2f52da774b36049e0ec38a7fe90d1c3473..62075c0bf11952e4249aa31f8cd20955e6468dad 100644 (file)
@@ -167,7 +167,7 @@ you can try compiling with MingW32, by passing "-c mingw32" to setup.py."""
                 except RegError:
                     continue
                 key = RegEnumKey(h, 0)
-                d = Reg.get_value(base, r"%s\%s" % (p, key))
+                d = Reg.get_value(base, r"{}\{}".format(p, key))
                 self.macros["$(FrameworkVersion)"] = d["version"]
 
     def sub(self, s):
@@ -273,7 +273,7 @@ def query_vcvarsall(version, arch="x86"):
         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),
+        '"{}" {} & set'.format(vcvarsall, arch),
         stdout=subprocess.PIPE,
         stderr=subprocess.PIPE,
     )
@@ -350,7 +350,7 @@ class MSVCCompiler(CCompiler):
         self.__arch = None  # deprecated name
         self.initialized = False
 
-    def initialize(self, plat_name=None):
+    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:
@@ -362,7 +362,9 @@ class MSVCCompiler(CCompiler):
         # 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,))
+            raise DistutilsPlatformError(
+                "--plat-name must be one of {}".format(ok_plats)
+            )
 
         if (
             "DISTUTILS_USE_SDK" in os.environ
@@ -478,7 +480,7 @@ class MSVCCompiler(CCompiler):
                 obj_names.append(os.path.join(output_dir, base + self.obj_extension))
         return obj_names
 
-    def compile(
+    def compile(  # noqa: C901
         self,
         sources,
         output_dir=None,
@@ -555,7 +557,9 @@ class MSVCCompiler(CCompiler):
                 continue
             else:
                 # how to handle this file?
-                raise CompileError("Don't know how to compile %s to %s" % (src, obj))
+                raise CompileError(
+                    "Don't know how to compile {} to {}".format(src, obj)
+                )
 
             output_opt = "/Fo" + obj
             try:
@@ -591,7 +595,7 @@ class MSVCCompiler(CCompiler):
         else:
             log.debug("skipping %s (up-to-date)", output_filename)
 
-    def link(
+    def link(  # noqa: C901
         self,
         target_desc,
         objects,
@@ -678,7 +682,7 @@ class MSVCCompiler(CCompiler):
             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)
+                out_arg = '-outputresource:{};{}'.format(output_filename, mfid)
                 try:
                     self.spawn(['mt.exe', '-nologo', '-manifest', mffilename, out_arg])
                 except DistutilsExecError as msg:
index 00c630be5092469c01bac86a2a6a1e05515fcc62..51e60175ee8d17104335f030431b4fd707888f20 100644 (file)
@@ -8,7 +8,8 @@ for the Microsoft Visual Studio.
 # hacked by Robin Becker and Thomas Heller to do a better job of
 #   finding DevStudio (through the registry)
 
-import sys, os
+import sys
+import os
 from distutils.errors import (
     DistutilsExecError,
     DistutilsPlatformError,
@@ -134,7 +135,7 @@ class MacroExpander:
                 self.set_macro("FrameworkSDKDir", net, "sdkinstallrootv1.1")
             else:
                 self.set_macro("FrameworkSDKDir", net, "sdkinstallroot")
-        except KeyError as exc:  #
+        except KeyError:
             raise DistutilsPlatformError(
                 """Python was built with Visual Studio 2003;
 extensions must be built with a compiler than can generate compatible binaries.
@@ -149,7 +150,7 @@ you can try compiling with MingW32, by passing "-c mingw32" to setup.py."""
             except RegError:
                 continue
             key = RegEnumKey(h, 0)
-            d = read_values(base, r"%s\%s" % (p, key))
+            d = read_values(base, r"{}\{}".format(p, key))
             self.macros["$(FrameworkVersion)"] = d["version"]
 
     def sub(self, s):
@@ -368,7 +369,7 @@ class MSVCCompiler(CCompiler):
                 obj_names.append(os.path.join(output_dir, base + self.obj_extension))
         return obj_names
 
-    def compile(
+    def compile(  # noqa: C901
         self,
         sources,
         output_dir=None,
@@ -445,7 +446,9 @@ class MSVCCompiler(CCompiler):
                 continue
             else:
                 # how to handle this file?
-                raise CompileError("Don't know how to compile %s to %s" % (src, obj))
+                raise CompileError(
+                    "Don't know how to compile {} to {}".format(src, obj)
+                )
 
             output_opt = "/Fo" + obj
             try:
@@ -481,7 +484,7 @@ class MSVCCompiler(CCompiler):
         else:
             log.debug("skipping %s (up-to-date)", output_filename)
 
-    def link(
+    def link(  # noqa: C901
         self,
         target_desc,
         objects,
@@ -628,7 +631,7 @@ class MSVCCompiler(CCompiler):
 
         path = path + " dirs"
         if self.__version >= 7:
-            key = r"%s\%0.1f\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories" % (
+            key = r"{}\{:0.1f}\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories".format(
                 self.__root,
                 self.__version,
             )
@@ -680,4 +683,4 @@ if get_build_version() >= 8.0:
     from distutils.msvc9compiler import MSVCCompiler
 
     # get_build_architecture not really relevant now we support cross-compile
-    from distutils.msvc9compiler import MacroExpander
+    from distutils.msvc9compiler import MacroExpander  # noqa: F811
index e556b69ee9d7d6dacb3f256d6ed79ac31724c443..59224e71e50c49e5f9f6f925837597c035a8ab7f 100644 (file)
@@ -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)
index acd20148c7e6f8a81fbf1dfdea0feadf6bc6160f..b18ba9db7d2e5919c853e7dcf8d5b7c180607c3f 100644 (file)
@@ -15,7 +15,7 @@ from distutils.debug import DEBUG
 from distutils import log
 
 
-def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None):
+def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None):  # noqa: C901
     """Run another program, specified as a command list 'cmd', in a new process.
 
     'cmd' is just the argument list for the new process, ie.
@@ -60,13 +60,15 @@ def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None):
     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)
         )
 
 
index e41d51ee55b3f11bc39687336f935aeafaab02ef..aae9c1b320aa5cf5b95ef0fa9c46e43b6e84561f 100644 (file)
@@ -250,7 +250,7 @@ def get_python_lib(plat_specific=0, standard_lib=0, prefix=None):
         )
 
 
-def customize_compiler(compiler):
+def customize_compiler(compiler):  # noqa: C901
     """Do any platform-specific customization of a CCompiler instance.
 
     Mainly needed on Unix, so we can plug in the information that
@@ -376,7 +376,7 @@ _findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)")
 _findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}")
 
 
-def parse_makefile(fn, g=None):
+def parse_makefile(fn, g=None):  # noqa: C901
     """Parse a Makefile-style file.
 
     A dictionary containing name/value pairs is returned.  If an
index c7dcc7ec3d45d126631a8a54a9010a883233c7d9..27e73393a0a52491d566d10ef1299669928f3ef7 100644 (file)
@@ -1,42 +1,8 @@
-"""Test suite for distutils.
-
-This test suite consists of a collection of test modules in the
-distutils.tests package.  Each test module has a name starting with
-'test' and contains a function test_suite().  The function is expected
-to return an initialized unittest.TestSuite instance.
+"""
+Test suite for distutils.
 
 Tests for the command classes in the distutils.command package are
 included in distutils.tests as well, instead of using a separate
 distutils.command.tests package, since command identification is done
 by import rather than matching pre-defined names.
-
 """
-
-import os
-import sys
-import unittest
-from test.support import run_unittest
-
-from .py38compat import save_restore_warnings_filters
-
-
-here = os.path.dirname(__file__) or os.curdir
-
-
-def test_suite():
-    suite = unittest.TestSuite()
-    for fn in os.listdir(here):
-        if fn.startswith("test") and fn.endswith(".py"):
-            modname = "distutils.tests." + fn[:-3]
-            # bpo-40055: Save/restore warnings filters to leave them unchanged.
-            # Importing tests imports docutils which imports pkg_resources
-            # which adds a warnings filter.
-            with save_restore_warnings_filters():
-                __import__(modname)
-            module = sys.modules[modname]
-            suite.addTest(module.test_suite())
-    return suite
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
index c949f58ecde0180764322ab973922907dfd84e59..35ddbb5bde6dfcc3b690a74109abf7f831258eb4 100644 (file)
@@ -42,20 +42,16 @@ except (ModuleNotFoundError, ImportError):
     )
 
 
-# From Python 3.9
-@contextlib.contextmanager
-def _save_restore_warnings_filters():
-    old_filters = warnings.filters[:]
-    try:
-        yield
-    finally:
-        warnings.filters[:] = old_filters
-
-
 try:
-    from test.support.warnings_helper import save_restore_warnings_filters
+    from test.support.import_helper import (
+        DirsOnSysPath,
+        CleanImport,
+    )
 except (ModuleNotFoundError, ImportError):
-    save_restore_warnings_filters = _save_restore_warnings_filters
+    from test.support import (
+        DirsOnSysPath,
+        CleanImport,
+    )
 
 
 if sys.version_info < (3, 9):
index 3085468a214ff3c4e68dc91c982b86831e5a13e5..5203ed19d4ab3b54517ac6c5a7a536ca42a6925e 100644 (file)
@@ -3,33 +3,17 @@ import os
 import sys
 import shutil
 import tempfile
-import unittest
 import sysconfig
-from copy import deepcopy
+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))
@@ -44,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.
@@ -109,30 +79,12 @@ 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
 
 
-class EnvironGuard(object):
-    def setUp(self):
-        super(EnvironGuard, self).setUp()
-        self.old_environ = deepcopy(os.environ)
-
-    def tearDown(self):
-        for key, value in self.old_environ.items():
-            if os.environ.get(key) != value:
-                os.environ[key] = value
-
-        for key in tuple(os.environ.keys()):
-            if key not in self.old_environ:
-                del os.environ[key]
-
-        super(EnvironGuard, self).tearDown()
-
-
 def copy_xxmodule_c(directory):
     """Helper for tests that need the xxmodule.c source file.
 
@@ -145,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):
@@ -205,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
index 8fb95744223bc0e60988e219ed6d858fdced9307..72aa9d7c7bb04ef9372d1180dffb577e5e0a3081 100644 (file)
@@ -1,11 +1,14 @@
-# -*- 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
 
 from distutils import archive_util
 from distutils.archive_util import (
@@ -15,40 +18,15 @@ 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 run_unittest, patch
+from test.support import patch
 from .unix_compat import require_unix_id, require_uid_0, grp, pwd, UID_0_SUPPORT
 
 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 zlib
-
-    ZLIB_SUPPORT = True
-except ImportError:
-    ZLIB_SUPPORT = False
-
-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.
@@ -62,10 +40,16 @@ def can_fs_encode(filename):
     return True
 
 
-class ArchiveUtilTestCase(
-    support.TempdirManager, support.LoggingSilencer, unittest.TestCase
-):
-    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
+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
         tmpdir = self._create_files()
@@ -73,33 +57,29 @@ class ArchiveUtilTestCase(
         # trying an uncompressed one
         self._make_tarball(tmpdir, name, '.tar', compress=None)
 
-    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
+    @pytest.mark.usefixtures('needs_zlib')
     def test_make_tarball_gzip(self):
         tmpdir = self._create_files()
         self._make_tarball(tmpdir, 'archive', '.tar.gz', compress='gzip')
 
-    @unittest.skipUnless(bz2, 'Need bz2 support to run')
     def test_make_tarball_bzip2(self):
+        pytest.importorskip('bz2')
         tmpdir = self._create_files()
         self._make_tarball(tmpdir, 'archive', '.tar.bz2', compress='bzip2')
 
-    @unittest.skipUnless(lzma, 'Need lzma support to run')
     def test_make_tarball_xz(self):
+        pytest.importorskip('lzma')
         tmpdir = self._create_files()
         self._make_tarball(tmpdir, 'archive', '.tar.xz', compress='xz')
 
-    @unittest.skipUnless(
-        can_fs_encode('årchiv'), 'File system cannot handle this filename'
-    )
+    @pytest.mark.skipif("not can_fs_encode('årchiv')")
     def test_make_tarball_latin1(self):
         """
         Mirror test_make_tarball, except filename contains latin characters.
         """
         self.test_make_tarball('årchiv')  # note this isn't a real word
 
-    @unittest.skipUnless(
-        can_fs_encode('のアーカイブ'), 'File system cannot handle this filename'
-    )
+    @pytest.mark.skipif("not can_fs_encode('のアーカイブ')")
     def test_make_tarball_extended(self):
         """
         Mirror test_make_tarball, except filename contains extended
@@ -109,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)
 
@@ -122,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)
@@ -156,10 +134,8 @@ class ArchiveUtilTestCase(
         os.mkdir(os.path.join(dist, 'sub2'))
         return tmpdir
 
-    @unittest.skipUnless(
-        find_executable('tar') and find_executable('gzip') and ZLIB_SUPPORT,
-        'Need the tar, gzip and zlib command to run',
-    )
+    @pytest.mark.usefixtures('needs_zlib')
+    @pytest.mark.skipif("not (find_executable('tar') and find_executable('gzip'))")
     def test_tarfile_vs_tar(self):
         tmpdir = self._create_files()
         tmpdir2 = self.mkdtemp()
@@ -173,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')
@@ -187,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')
@@ -201,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')
@@ -212,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:
@@ -231,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)
@@ -244,13 +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
 
-    @unittest.skipUnless(
-        ZIP_SUPPORT and ZLIB_SUPPORT, 'Need zip and zlib support to run'
-    )
+    @pytest.mark.usefixtures('needs_zlib')
     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')
@@ -259,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 = []
@@ -284,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()
@@ -312,9 +285,9 @@ class ArchiveUtilTestCase(
         try:
             try:
                 make_archive('xxx', 'xxx', root_dir=self.mkdtemp())
-            except:
+            except Exception:
                 pass
-            self.assertEqual(os.getcwd(), current_dir)
+            assert os.getcwd() == current_dir
         finally:
             del ARCHIVE_FORMATS['xxx']
 
@@ -322,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
 
-    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
+    @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
@@ -368,22 +341,22 @@ 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)
 
-    @unittest.skipUnless(ZLIB_SUPPORT, "Requires zlib")
+    @pytest.mark.usefixtures('needs_zlib')
     @require_unix_id
     @require_uid_0
     def test_tarfile_root_owner(self):
@@ -401,21 +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()
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(ArchiveUtilTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
index 2d0bb95b6f04990fac208cafc33bbd19cde735bc..5b8774e5eb1dd8c815327cedbab9f36207ad52a2 100644 (file)
@@ -1,14 +1,12 @@
 """Tests for distutils.command.bdist."""
 import os
-import unittest
-from test.support import run_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
@@ -16,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 = [
@@ -30,8 +28,8 @@ class BuildTestCase(support.TempdirManager, unittest.TestCase):
             '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
@@ -57,14 +55,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
-            )
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(BuildTestCase)
-
-
-if __name__ == '__main__':
-    run_unittest(test_suite())
+            assert subcmd.skip_build, '%s should take --skip-build from bdist' % name
index 83ab217d9e610ce7b440091c9fc5d0e4455a163d..8624a4290dd105fe768ea22518b20970a80a4900 100644 (file)
@@ -3,8 +3,8 @@
 import os
 import sys
 import zipfile
-import unittest
-from test.support import run_unittest
+
+import pytest
 
 from distutils.core import Distribution
 from distutils.command.bdist_dumb import bdist_dumb
@@ -19,32 +19,16 @@ setup(name='foo', version='0.1', py_modules=['foo'],
 
 """
 
-try:
-    import zlib
-
-    ZLIB_SUPPORT = True
-except ImportError:
-    ZLIB_SUPPORT = False
 
-
-class BuildDumbTestCase(
+@support.combine_markers
+@pytest.mark.usefixtures('save_env')
+@pytest.mark.usefixtures('save_argv')
+@pytest.mark.usefixtures('save_cwd')
+class TestBuildDumb(
     support.TempdirManager,
     support.LoggingSilencer,
-    support.EnvironGuard,
-    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()
-
-    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
+    @pytest.mark.usefixtures('needs_zlib')
     def test_simple_built(self):
 
         # let's create a simple package
@@ -81,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))
@@ -96,12 +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))
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(BuildDumbTestCase)
-
-
-if __name__ == '__main__':
-    run_unittest(test_suite())
+        assert contents == sorted(wanted)
index a9f3dbb59427ff05612579499c71700b5c901da0..f36b39835032311ef6b161e181dbf828899f54c8 100644 (file)
@@ -1,16 +1,15 @@
 """Tests for distutils.command.bdist_msi."""
-import sys
-import unittest
-from test.support import run_unittest
+import pytest
+
 from distutils.tests import support
 
 from .py38compat import check_warnings
 
 
-@unittest.skipUnless(sys.platform == 'win32', 'these tests require Windows')
-class BDistMSITestCase(
-    support.TempdirManager, support.LoggingSilencer, unittest.TestCase
-):
+pytest.importorskip('msilib')
+
+
+class TestBDistMSI(support.TempdirManager, support.LoggingSilencer):
     def test_minimal(self):
         # minimal test XXX need more tests
         from distutils.command.bdist_msi import bdist_msi
@@ -19,11 +18,3 @@ class BDistMSITestCase(
         with check_warnings(("", DeprecationWarning)):
             cmd = bdist_msi(dist)
         cmd.ensure_finalized()
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(BDistMSITestCase)
-
-
-if __name__ == '__main__':
-    run_unittest(test_suite())
index f60a582a98215ac269a0dd7777037877da89da13..2d14bafc9823c24cbf1c9acf7a7a846be3ff107f 100644 (file)
@@ -1,14 +1,14 @@
 """Tests for distutils.command.bdist_rpm."""
 
-import unittest
 import sys
 import os
-from test.support import run_unittest
+
+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
 
@@ -23,38 +23,31 @@ setup(name='foo', version='0.1', py_modules=['foo'],
 """
 
 
-class BuildRpmTestCase(
+@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')
+@pytest.mark.usefixtures('save_argv')
+@pytest.mark.usefixtures('save_cwd')
+class TestBuildRpm(
     support.TempdirManager,
-    support.EnvironGuard,
     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()
@@ -89,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()
@@ -141,20 +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'))
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(BuildRpmTestCase)
-
-
-if __name__ == '__main__':
-    run_unittest(test_suite())
index c103a63ba7a3d8ac8cf76daa8142d057a6a980f0..c432d24be690cc88f33ac9959cc20bbb6304e409 100644 (file)
@@ -1,8 +1,5 @@
 """Tests for distutils.command.bdist_wininst."""
-import sys
-import platform
-import unittest
-from test.support import run_unittest
+import pytest
 
 from .py38compat import check_warnings
 
@@ -10,17 +7,9 @@ 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
-):
+@pytest.mark.skipif("platform.machine() == 'ARM64'")
+@pytest.mark.skipif("bdist_wininst._unsupported")
+class TestBuildWinInst(support.TempdirManager, support.LoggingSilencer):
     def test_get_exe_bytes(self):
 
         # issue5731: command was broken on non-windows platforms
@@ -35,12 +24,4 @@ class BuildWinInstTestCase(
         # 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)
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(BuildWinInstTestCase)
-
-
-if __name__ == '__main__':
-    run_unittest(test_suite())
+        assert len(exe_file) > 10
index 190bbdfdd6209da2cb6117106fa758e6949fba54..80367607f5401384a800a52663707fb5b2643ea1 100644 (file)
@@ -1,55 +1,45 @@
 """Tests for distutils.command.build."""
-import unittest
 import os
 import sys
-from test.support import run_unittest
 
 from distutils.command.build import build
 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))
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(BuildTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+        assert cmd.executable == os.path.normpath(sys.executable)
index 24c7478753139f1812ad8d2ae863347ec2953b3e..c931c06ec5880fb1a39b4ac6eb0edc520ff8d1d3 100644 (file)
@@ -1,47 +1,44 @@
 """Tests for distutils.command.build_clib."""
-import unittest
 import os
-import sys
 
-from test.support import run_unittest, missing_compiler_executable
+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,12 +134,4 @@ class BuildCLibTestCase(
         cmd.run()
 
         # let's check the result
-        self.assertIn('libfoo.a', os.listdir(build_temp))
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(BuildCLibTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+        assert 'libfoo.a' in os.listdir(build_temp)
index c42ceabd957f456234fafb1019f458b90f7976a7..e60814ff64e191754315d75b94ad21933634fd8f 100644 (file)
@@ -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__
+        with safe_extension_import('xx', self.tmp_dir):
+            self._test_xx()
 
-        code = textwrap.dedent(
-            """
-            tmp_dir = {self.tmp_dir!r}
+    @staticmethod
+    def _test_xx():
+        import xx
 
-            import sys
-            import unittest
-            from test import support
+        for attr in ('error', 'foo', 'new', 'roj'):
+            assert hasattr(xx, attr)
 
-            sys.path.insert(0, tmp_dir)
-            import xx
-
-            class Tests(unittest.TestCase):
-                def test_xx(self):
-                    for attr in ('error', 'foo', 'new', 'roj'):
-                        self.assertTrue(hasattr(xx, attr))
-
-                    self.assertEqual(xx.foo(2, 5), 7)
-                    self.assertEqual(xx.foo(13,15), 28)
-                    self.assertEqual(xx.new().demo(), None)
-                    if support.HAVE_DOCSTRINGS:
-                        doc = 'This is a template module just for instruction.'
-                        self.assertEqual(xx.__doc__, doc)
-                    self.assertIsInstance(xx.Null(), xx.Null)
-                    self.assertIsInstance(xx.Str(), xx.Str)
-
-
-            unittest.main()
-        """.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,61 +248,65 @@ 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()
         cmd = self.build_ext(dist)
         cmd.finalize_options()
 
-        #'extensions' option must be a list of Extension instances
-        self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, 'foo')
+        # 'extensions' option must be a list of Extension instances
+        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,19 +558,8 @@ 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
         return build_ext
-
-
-def test_suite():
-    suite = unittest.TestSuite()
-    suite.addTest(unittest.TestLoader().loadTestsFromTestCase(BuildExtTestCase))
-    suite.addTest(unittest.TestLoader().loadTestsFromTestCase(ParallelBuildExtTestCase))
-    return suite
-
-
-if __name__ == '__main__':
-    support.run_unittest(__name__)
index eb01d81aea3bddb85a25de3de872f02db5fa23b1..63543dcaa1a6e236417f61a765c7a09afc8c354d 100644 (file)
@@ -2,20 +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
-from test.support import run_unittest
 
 
-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")
@@ -42,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
@@ -88,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)
@@ -100,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)
@@ -117,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):
         """
@@ -166,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
@@ -208,11 +207,3 @@ class BuildPyTestCase(
 
         cmd.run()
         # Test should complete successfully with no exception
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(BuildPyTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
index 8c7061d7a20b469c7f8ba2e4977408ac0da7d5ac..00d7fc59292047bc8d2be84a0e4b71bbcacbe28e 100644 (file)
@@ -1,28 +1,24 @@
 """Tests for distutils.command.build_scripts."""
 
 import os
-import unittest
 
 from distutils.command.build_scripts import build_scripts
 from distutils.core import Distribution
 from distutils import sysconfig
 
 from distutils.tests import support
-from test.support import run_unittest
 
 
-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()
@@ -37,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
@@ -107,12 +103,4 @@ class BuildScriptsTestCase(
 
         built = os.listdir(target)
         for name in expected:
-            self.assertIn(name, built)
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(BuildScriptsTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+            assert name in built
diff --git a/setuptools/_distutils/tests/test_ccompiler.py b/setuptools/_distutils/tests/test_ccompiler.py
new file mode 100644 (file)
index 0000000..da1879f
--- /dev/null
@@ -0,0 +1,55 @@
+import os
+import sys
+import platform
+import textwrap
+import sysconfig
+
+import pytest
+
+from distutils import ccompiler
+
+
+def _make_strs(paths):
+    """
+    Convert paths to strings for legacy compatibility.
+    """
+    if sys.version_info > (3, 8) and platform.system() != "Windows":
+        return paths
+    return list(map(os.fspath, paths))
+
+
+@pytest.fixture
+def c_file(tmp_path):
+    c_file = tmp_path / 'foo.c'
+    gen_headers = ('Python.h',)
+    is_windows = platform.system() == "Windows"
+    plat_headers = ('windows.h',) * is_windows
+    all_headers = gen_headers + plat_headers
+    headers = '\n'.join(f'#include <{header}>\n' for header in all_headers)
+    payload = (
+        textwrap.dedent(
+            """
+        #headers
+        void PyInit_foo(void) {}
+        """
+        )
+        .lstrip()
+        .replace('#headers', headers)
+    )
+    c_file.write_text(payload)
+    return c_file
+
+
+def test_set_include_dirs(c_file):
+    """
+    Extensions should build even if set_include_dirs is invoked.
+    In particular, compiler-specific paths should not be overridden.
+    """
+    compiler = ccompiler.new_compiler()
+    python = sysconfig.get_paths()['include']
+    compiler.set_include_dirs([python])
+    compiler.compile(_make_strs([c_file]))
+
+    # do it again, setting include dirs after any initialization
+    compiler.set_include_dirs([python])
+    compiler.compile(_make_strs([c_file]))
index 424c5e08e50d3542191618178285d44383caecab..3e5f6034bfb1c31edaa0768d48566f561867fa2a 100644 (file)
@@ -1,10 +1,10 @@
 """Tests for distutils.command.check."""
 import os
 import textwrap
-import unittest
-from test.support import run_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
 
@@ -17,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 = {}
@@ -40,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
@@ -53,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 = {
@@ -74,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"):
@@ -87,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 = {
@@ -133,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 = []
@@ -186,24 +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):
-
-        metadata = {'url': 'xxx', 'author': 'xxx'}
-        self.assertRaises(
-            DistutilsSetupError, self._run, {}, **{'strict': 1, 'restructuredtext': 1}
-        )
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(CheckTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+        with pytest.raises(DistutilsSetupError):
+            self._run({}, **{'strict': 1, 'restructuredtext': 1})
index 92e58f78a1eac3a7e8a8174750f4c942735de2ca..4166bb7e9bbbd1312822e369ec5cf394a84e27b4 100644 (file)
@@ -1,13 +1,11 @@
 """Tests for distutils.command.clean."""
 import os
-import unittest
 
 from distutils.command.clean import clean
 from distutils.tests import support
-from test.support import run_unittest
 
 
-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)
@@ -39,17 +37,9 @@ 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
         cmd.ensure_finalized()
         cmd.run()
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(cleanTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
index 12a8a20c68cf4452356440755115a836a07ee55e..e4d5bf3c0167b0e1d93c08a38107ac8bc332b8f2 100644 (file)
@@ -1,12 +1,12 @@
 """Tests for distutils.cmd."""
-import unittest
 import os
-from test.support import captured_stdout, run_unittest
+from test.support import captured_stdout
 
 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,54 +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
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(CommandTestCase)
-
-
-if __name__ == '__main__':
-    run_unittest(test_suite())
index a4b4850969c1f9804e8366be7ce3bc3b2ac69328..43ba6766aef4e13d4a8a195c65ff59cb28be2a61 100644 (file)
@@ -1,14 +1,9 @@
 """Tests for distutils.pypirc.pypirc."""
 import os
-import unittest
 
-from distutils.core import PyPIRCCommand
-from distutils.core import Distribution
-from distutils.log import set_threshold
-from distutils.log import WARN
+import pytest
 
 from distutils.tests import support
-from test.support import run_unittest
 
 PYPIRC = """\
 [distutils]
@@ -50,37 +45,14 @@ password:xxx
 """
 
 
+@support.combine_markers
+@pytest.mark.usefixtures('threshold_warn')
+@pytest.mark.usefixtures('pypirc')
 class BasePyPIRCCommandTestCase(
     support.TempdirManager,
     support.LoggingSilencer,
-    support.EnvironGuard,
-    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):
@@ -102,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)
@@ -115,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()
 
@@ -145,12 +117,4 @@ class PyPIRCCommandTestCase(BasePyPIRCCommandTestCase):
             ('server', 'server3'),
             ('username', 'cbiggles'),
         ]
-        self.assertEqual(config, waited)
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(PyPIRCCommandTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+        assert config == waited
index 0c1a9d253bf294a7321c5c67ffd6135a2a44242b..65c60f64dd4f7613e990e70c82e7dbe16a782f0a 100644 (file)
@@ -1,31 +1,28 @@
 """Tests for distutils.command.config."""
-import unittest
 import os
 import sys
-from test.support import run_unittest, missing_compiler_executable
+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,19 +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))
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(ConfigTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+            assert not os.path.exists(f)
index 23402fb88fb5f1d7952eaf5b965c067c0a9c2d36..86b0040f60e191bd99ec37a5cc9355b0f78be839 100644 (file)
@@ -3,13 +3,12 @@
 import io
 import distutils.core
 import os
-import shutil
 import sys
-from test.support import captured_stdout, run_unittest
+from test.support import captured_stdout
+
+import pytest
+
 from . import py38compat as os_helper
-import unittest
-from distutils.tests import support
-from distutils import log
 from distutils.dist import Distribution
 
 # setup script that uses __file__
@@ -57,28 +56,15 @@ if __name__ == "__main__":
 """
 
 
-class CoreTestCase(support.EnvironGuard, 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:
@@ -96,14 +82,14 @@ class CoreTestCase(support.EnvironGuard, 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
@@ -120,23 +106,23 @@ class CoreTestCase(support.EnvironGuard, 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
@@ -144,7 +130,7 @@ class CoreTestCase(support.EnvironGuard, 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:
@@ -154,12 +140,4 @@ class CoreTestCase(support.EnvironGuard, unittest.TestCase):
             distutils.core.DEBUG = False
         stdout.seek(0)
         wanted = "options (after parsing config files):\n"
-        self.assertEqual(stdout.readlines()[0], wanted)
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(CoreTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+        assert stdout.readlines()[0] == wanted
index 7760436a6df71ad19729fa46b1ac2ec5f73f42b2..ef01ae219901bf0f0f4c8d3578cbb06bf1a91c4a 100644 (file)
@@ -1,8 +1,8 @@
 """Tests for distutils.cygwinccompiler."""
-import unittest
 import sys
 import os
-from test.support import run_unittest
+
+import pytest
 
 from distutils.cygwinccompiler import (
     check_config_h,
@@ -12,47 +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,45 +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'])
+        sys.version = (
+            '3.10.0 (tags/v3.10.0:b494f59, Oct  4 2021, 18:46:30) '
+            '[MSC v.1929 32 bit (Intel)]'
+        )
+        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)
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(CygwinCCompilerTestCase)
-
-
-if __name__ == '__main__':
-    run_unittest(test_suite())
+        with pytest.raises(ValueError):
+            get_msvcr()
index 89ae05d1778f41a11d78ca279eea3c25d4ec4f16..2dcce1dd02a33548a583b0f27294fb121afa1952 100644 (file)
@@ -1,14 +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
-from test.support import run_unittest
+import pytest
 
 
-class DepUtilTestCase(support.TempdirManager, unittest.TestCase):
+class TestDepUtil(support.TempdirManager):
     def test_newer(self):
 
         tmpdir = self.mkdtemp()
@@ -16,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()
@@ -42,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()
@@ -58,21 +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'))
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(DepUtilTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+        assert newer_group([one, two, old_file], three, missing='newer')
index a1f9a240144e0fac13eecf4425efb7f81c787144..cd7e018f5e5219d98e6454f0fd1f209205e532e3 100644 (file)
@@ -1,9 +1,7 @@
 """Tests for distutils.dir_util."""
-import unittest
 import os
 import stat
-import sys
-from unittest.mock import patch
+import unittest.mock as mock
 
 from distutils import dir_util, errors
 from distutils.dir_util import (
@@ -16,68 +14,62 @@ from distutils.dir_util import (
 
 from distutils import log
 from distutils.tests import support
-from test.support import run_unittest
+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)
 
@@ -86,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)
 
@@ -95,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)
@@ -112,33 +104,25 @@ 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]
             dir_util.copy_tree(src, None)
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(DirUtilTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
index 6520a46d53a066dcb825d66c6290a962717988c7..ddfaf9216772f5db2beba07fc1bba4bb3913a6f2 100644 (file)
@@ -2,16 +2,17 @@
 import os
 import io
 import sys
-import unittest
 import warnings
 import textwrap
+import functools
+import unittest.mock as mock
 
-from unittest import mock
+import pytest
 
 from distutils.dist import Distribution, fix_help_options
 from distutils.cmd import Command
 
-from test.support import captured_stdout, captured_stderr, run_unittest
+from test.support import captured_stdout, captured_stderr
 from .py38compat import TESTFN
 from distutils.tests import support
 from distutils import log
@@ -40,22 +41,18 @@ class TestDistribution(Distribution):
         return self._config_files
 
 
-class DistributionTestCase(
+@pytest.fixture
+def clear_argv():
+    del sys.argv[1:]
+
+
+@support.combine_markers
+@pytest.mark.usefixtures('save_env')
+@pytest.mark.usefixtures('save_argv')
+class TestDistributionBehavior(
     support.LoggingSilencer,
     support.TempdirManager,
-    support.EnvironGuard,
-    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
@@ -63,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(
@@ -81,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'
 
@@ -121,7 +119,7 @@ class DistributionTestCase(
             )
 
         # Base case: Not in a Virtual Environment
-        with mock.patch.multiple(sys, prefix='/a', base_prefix='/a') as values:
+        with mock.patch.multiple(sys, prefix='/a', base_prefix='/a'):
             d = self.create_distribution([TESTFN])
 
         option_tuple = (TESTFN, fakepath)
@@ -142,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') as values:
+        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)
@@ -167,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
 
@@ -192,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={
@@ -204,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'}
@@ -214,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.
@@ -267,19 +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)
 
-class MetadataTestCase(support.TempdirManager, support.EnvironGuard, 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_env')
+@pytest.mark.usefixtures('save_argv')
+class MetadataTestCase(support.TempdirManager):
     def format_metadata(self, dist):
         sio = io.StringIO()
         dist.metadata.write_pkg_file(sio)
@@ -289,10 +281,10 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, unittest.Te
         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 = {
@@ -301,19 +293,18 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, unittest.Te
             "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 = {
@@ -322,26 +313,25 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, unittest.Te
             "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 = {
@@ -350,26 +340,25 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, unittest.Te
             "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 = {
@@ -378,11 +367,9 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, unittest.Te
             '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 = {
@@ -393,10 +380,10 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, unittest.Te
         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 = {
@@ -405,7 +392,7 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, unittest.Te
             '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 = {
@@ -416,10 +403,10 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, unittest.Te
         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 = {
@@ -428,7 +415,7 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, unittest.Te
             '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 = {
@@ -439,10 +426,10 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, unittest.Te
         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 = {
@@ -452,7 +439,7 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, unittest.Te
         }
         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(
@@ -467,7 +454,7 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, unittest.Te
         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
@@ -492,15 +479,15 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, unittest.Te
             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)
@@ -508,8 +495,8 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, unittest.Te
     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
@@ -522,7 +509,7 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, unittest.Te
             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 = {
@@ -544,22 +531,11 @@ class MetadataTestCase(support.TempdirManager, support.EnvironGuard, unittest.Te
         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'])
-
-
-def test_suite():
-    suite = unittest.TestSuite()
-    suite.addTest(unittest.TestLoader().loadTestsFromTestCase(DistributionTestCase))
-    suite.addTest(unittest.TestLoader().loadTestsFromTestCase(MetadataTestCase))
-    return suite
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+        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']
index 77fe3f82a6e538c025a5595e18359182400605be..f86af07376fe588b32f64c34317c6bfb02ddfcb5 100644 (file)
@@ -1,15 +1,14 @@
 """Tests for distutils.extension."""
-import unittest
 import os
 import warnings
 
-from test.support import run_unittest
 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)
@@ -58,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 (
@@ -88,25 +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'"
-        )
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(ExtensionTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+        assert len(w.warnings) == 1
+        assert str(w.warnings[0].message) == "Unknown Extension options: 'chic'"
index 22898b958074bf1bff0df58792bca5ff9281489b..b2e83c52f2981beef8d720066c46855bbe9f99dc 100644 (file)
@@ -1,38 +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 test.support import run_unittest
 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:
@@ -42,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)
@@ -58,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:
@@ -72,9 +68,11 @@ class FileUtilTestCase(support.TempdirManager, unittest.TestCase):
 
     def test_move_file_exception_unpacking_unlink(self):
         # see issue 22182
-        with patch("os.rename", side_effect=OSError(errno.EXDEV, "wrong")), patch(
-            "os.unlink", side_effect=OSError("wrong", 1)
-        ), 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)
@@ -94,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
@@ -106,20 +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')
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(FileUtilTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+            with open(fn) as f:
+                assert f.read() == 'some content'
index 71718a866fc2b3f0b21bda50329963758f3bccb2..26071820648604989d9097b1b1f1985f65bad717 100644 (file)
@@ -1,17 +1,17 @@
 """Tests for distutils.filelist."""
 import os
 import re
-import unittest
 from distutils import debug
 from distutils.log import WARN
 from distutils.errors import DistutilsTemplateError
 from distutils.filelist import glob_to_re, translate_pattern, FileList
 from distutils import filelist
 
-from test.support import captured_stdout, run_unittest
+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):
@@ -349,16 +344,3 @@ class FindAllTestCase(unittest.TestCase):
             os.symlink('.', link)
             files = filelist.findall(temp_dir)
             assert len(files) == 1
-
-
-def test_suite():
-    return unittest.TestSuite(
-        [
-            unittest.TestLoader().loadTestsFromTestCase(FileListTestCase),
-            unittest.TestLoader().loadTestsFromTestCase(FindAllTestCase),
-        ]
-    )
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
index 5f0a64dba11d0bd720909f2add66a3e0aaed4116..32a18b2f2f26b8ac543674c4cc48e7f41328d0ee 100644 (file)
@@ -2,10 +2,12 @@
 
 import os
 import sys
-import unittest
 import site
+import pathlib
 
-from test.support import captured_stdout, run_unittest
+from test.support import captured_stdout
+
+import pytest
 
 from distutils import sysconfig
 from distutils.command.install import install
@@ -19,18 +21,16 @@ from distutils.extension import Extension
 from distutils.tests import support
 from test import support as test_support
 
-import pytest
-
 
 def _make_ext_name(modname):
     return modname + sysconfig.get_config_var('EXT_SUFFIX')
 
 
-class InstallTestCase(
+@support.combine_markers
+@pytest.mark.usefixtures('save_env')
+class TestInstall(
     support.TempdirManager,
-    support.EnvironGuard,
     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,12 +255,4 @@ class InstallTestCase(
                 self.test_record()
         finally:
             install_module.DEBUG = False
-        self.assertGreater(len(self.logs), old_logs_len)
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(InstallTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+        assert len(self.logs) > old_logs_len
index a08168b22e72ec74b28054e959482c0e7407388d..f77c790fcad59ee0784321efa4cbf506839975f6 100644 (file)
@@ -1,17 +1,16 @@
 """Tests for distutils.command.install_data."""
 import os
-import unittest
+
+import pytest
 
 from distutils.command.install_data import install_data
 from distutils.tests import support
-from test.support import run_unittest
 
 
-class InstallDataTestCase(
+@pytest.mark.usefixtures('save_env')
+class TestInstallData(
     support.TempdirManager,
     support.LoggingSilencer,
-    support.EnvironGuard,
-    unittest.TestCase,
 ):
     def test_simple_run(self):
         pkg_dir, dist = self.create_dist()
@@ -28,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
@@ -48,14 +47,13 @@ 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
         cmd.root = os.path.join(pkg_dir, 'root')
-        inst3 = os.path.join(cmd.install_dir, 'inst3')
         inst4 = os.path.join(pkg_dir, 'inst4')
         three = os.path.join(cmd.install_dir, 'three')
         self.write_file(three, 'xx')
@@ -64,14 +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)))
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(InstallDataTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+        assert len(cmd.get_outputs()) == 4
+        assert os.path.exists(os.path.join(inst2, rtwo))
+        assert os.path.exists(os.path.join(inst, rone))
index db4f4dbb9e54e0dc7b161ba07dcc47d14a1e3a68..7594f5af3c6da13720f35fb10d489f4e91fee6da 100644 (file)
@@ -1,17 +1,16 @@
 """Tests for distutils.command.install_headers."""
 import os
-import unittest
+
+import pytest
 
 from distutils.command.install_headers import install_headers
 from distutils.tests import support
-from test.support import run_unittest
 
 
-class InstallHeadersTestCase(
+@pytest.mark.usefixtures('save_env')
+class TestInstallHeaders(
     support.TempdirManager,
     support.LoggingSilencer,
-    support.EnvironGuard,
-    unittest.TestCase,
 ):
     def test_simple_run(self):
         # we have two headers
@@ -24,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')
@@ -32,12 +31,4 @@ class InstallHeadersTestCase(
         cmd.run()
 
         # let's check the results
-        self.assertEqual(len(cmd.get_outputs()), 2)
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(InstallHeadersTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+        assert len(cmd.get_outputs()) == 2
index 1ef233a7e922b7c55142e51534d92812203e1a7f..a654a66a79b6a8a2bb2cdddd7422a6d756690bf3 100644 (file)
@@ -2,40 +2,42 @@
 import sys
 import os
 import importlib.util
-import unittest
+
+import pytest
 
 from distutils.command.install_lib import install_lib
 from distutils.extension import Extension
 from distutils.tests import support
 from distutils.errors import DistutilsOptionError
-from test.support import run_unittest
 
 
-class InstallLibTestCase(
+@support.combine_markers
+@pytest.mark.usefixtures('save_env')
+class TestInstallLib(
     support.TempdirManager,
     support.LoggingSilencer,
-    support.EnvironGuard,
-    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)
@@ -49,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()
@@ -70,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()
@@ -90,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
@@ -106,12 +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])
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(InstallLibTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+        assert 'byte-compiling is disabled' in self.logs[0][1] % self.logs[0][2]
index bac7880115e31a0b8dcefa030c821e938267f33c..0d17f11b5b8130f726dc70a81fd47df0447e7747 100644 (file)
@@ -1,18 +1,14 @@
 """Tests for distutils.command.install_scripts."""
 
 import os
-import unittest
 
 from distutils.command.install_scripts import install_scripts
 from distutils.core import Distribution
 
 from distutils.tests import support
-from test.support import run_unittest
 
 
-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")
@@ -22,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()
@@ -76,12 +72,4 @@ class InstallScriptsTestCase(
 
         installed = os.listdir(target)
         for name in expected:
-            self.assertIn(name, installed)
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(InstallScriptsTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+            assert name in installed
index 33f7f96c34fc9ec6fd05df1ab9a5d98ada5b5c34..7aeee4057fdea53e11e98c32e4c282a4ca406928 100644 (file)
@@ -2,60 +2,51 @@
 
 import io
 import sys
-import unittest
-from test.support import swap_attr, run_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)
-
-                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',
-                )
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(TestLog)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+        ),
+    )
+    def test_non_ascii(self, errors):
+        # Issues #8663, #34421: test that non-encodable text is escaped with
+        # backslashreplace error handler and encodable non-ASCII text is
+        # output as is.
+        stdout = io.TextIOWrapper(io.BytesIO(), encoding='cp437', errors=errors)
+        stderr = io.TextIOWrapper(io.BytesIO(), encoding='cp437', errors=errors)
+        old_threshold = log.set_threshold(log.DEBUG)
+        try:
+            with swap_attr(sys, 'stdout', stdout), swap_attr(sys, 'stderr', stderr):
+                log.debug('Dεbug\tMėssãge')
+                log.fatal('Fαtal\tÈrrōr')
+        finally:
+            log.set_threshold(old_threshold)
+
+        stdout.seek(0)
+        assert stdout.read().rstrip() == (
+            'Dεbug\tM?ss?ge'
+            if errors == 'replace'
+            else 'Dεbug\tMssge'
+            if errors == 'ignore'
+            else 'Dεbug\tM\\u0117ss\\xe3ge'
+        )
+        stderr.seek(0)
+        assert stderr.read().rstrip() == (
+            'Fαtal\t?rr?r'
+            if errors == 'replace'
+            else 'Fαtal\trrr'
+            if errors == 'ignore'
+            else 'Fαtal\t\\xc8rr\\u014dr'
+        )
index ec4781af1147d8b87a7dd6601f1df7a086b92f4e..fe5693e1d8650e47d9527d05c1c182f8bf08dab8 100644 (file)
@@ -1,11 +1,10 @@
 """Tests for distutils.msvc9compiler."""
 import sys
-import unittest
 import os
 
 from distutils.errors import DistutilsPlatformError
 from distutils.tests import support
-from test.support import run_unittest
+import pytest
 
 # A manifest with the only assembly reference being the msvcrt assembly, so
 # should have the assembly completely stripped.  Note that although the
@@ -101,8 +100,8 @@ else:
     SKIP_MESSAGE = "These tests are only for win32"
 
 
-@unittest.skipUnless(SKIP_MESSAGE is None, SKIP_MESSAGE)
-class msvc9compilerTestCase(support.TempdirManager, unittest.TestCase):
+@pytest.mark.skipif('SKIP_MESSAGE', reason=SKIP_MESSAGE)
+class Testmsvc9compiler(support.TempdirManager):
     def test_no_compiler(self):
         # makes sure query_vcvarsall raises
         # a DistutilsPlatformError if the compiler
@@ -117,31 +116,31 @@ class msvc9compilerTestCase(support.TempdirManager, unittest.TestCase):
         old_find_vcvarsall = msvc9compiler.find_vcvarsall
         msvc9compiler.find_vcvarsall = _find_vcvarsall
         try:
-            self.assertRaises(
-                DistutilsPlatformError, query_vcvarsall, 'wont find this version'
-            )
+            with pytest.raises(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')
+        with pytest.raises(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'))
+        assert v in ('0', '1', '2')
 
         import winreg
 
         HKCU = winreg.HKEY_CURRENT_USER
         keys = Reg.read_keys(HKCU, 'xxxx')
-        self.assertEqual(keys, None)
+        assert keys is None
 
         keys = Reg.read_keys(HKCU, r'Control Panel')
-        self.assertIn('Desktop', keys)
+        assert 'Desktop' in keys
 
     def test_remove_visual_c_ref(self):
         from distutils.msvc9compiler import MSVCCompiler
@@ -166,7 +165,7 @@ class msvc9compilerTestCase(support.TempdirManager, unittest.TestCase):
             f.close()
 
         # makes sure the manifest was properly cleaned
-        self.assertEqual(content, _CLEANED_MANIFEST)
+        assert content == _CLEANED_MANIFEST
 
     def test_remove_entire_manifest(self):
         from distutils.msvc9compiler import MSVCCompiler
@@ -181,12 +180,4 @@ class msvc9compilerTestCase(support.TempdirManager, unittest.TestCase):
 
         compiler = MSVCCompiler()
         got = compiler._remove_visual_c_ref(manifest)
-        self.assertIsNone(got)
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(msvc9compilerTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+        assert got is None
index 21fe57f8459d540cbaed0044452f8bb60327f03f..f63537b8e5b009c72975ad2fd6e8e95a44a582ac 100644 (file)
@@ -1,22 +1,21 @@
 """Tests for distutils._msvccompiler."""
 import sys
-import unittest
 import os
 import threading
+import unittest.mock as mock
+
+import pytest
 
 from distutils.errors import DistutilsPlatformError
 from distutils.tests import support
-from test.support import run_unittest
+from distutils import _msvccompiler
 
 
-SKIP_MESSAGE = None if sys.platform == "win32" else "These tests are only for win32"
+needs_winreg = pytest.mark.skipif('not hasattr(_msvccompiler, "winreg")')
 
 
-@unittest.skipUnless(SKIP_MESSAGE is None, SKIP_MESSAGE)
-class msvccompilerTestCase(support.TempdirManager, unittest.TestCase):
+class Testmsvccompiler(support.TempdirManager):
     def test_no_compiler(self):
-        import distutils._msvccompiler as _msvccompiler
-
         # makes sure query_vcvarsall raises
         # a DistutilsPlatformError if the compiler
         # is not found
@@ -26,17 +25,15 @@ 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
 
+    @needs_winreg
     def test_get_vc_env_unicode(self):
-        import distutils._msvccompiler as _msvccompiler
-
         test_var = 'ṰḖṤṪ┅ṼẨṜ'
         test_value = '₃⁴₅'
 
@@ -45,36 +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
 
-    def test_get_vc2017(self):
-        import distutils._msvccompiler as _msvccompiler
-
-        # 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")
-
-    def test_get_vc2015(self):
-        import distutils._msvccompiler as _msvccompiler
-
-        # 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")
+    @needs_winreg
+    @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):
@@ -90,13 +76,11 @@ 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.
         """
-        import distutils._msvccompiler as _msvccompiler
-
         compiler = _msvccompiler.MSVCCompiler()
         compiler._paths = "expected"
         inner_cmd = 'import os; assert os.environ["PATH"] == "expected"'
@@ -116,7 +100,6 @@ class TestSpawn(unittest.TestCase):
         If CCompiler.spawn has been monkey-patched without support
         for an env, it should still execute.
         """
-        import distutils._msvccompiler as _msvccompiler
         from distutils import ccompiler
 
         compiler = _msvccompiler.MSVCCompiler()
@@ -126,15 +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"
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(msvccompilerTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
index 0f91ad3673516dcf21509ad558e2334671ae5a48..0a5765f1fdc231eecf03e08f97dbb2cb255abede 100644 (file)
@@ -1,13 +1,7 @@
 """Tests for distutils.command.register."""
 import os
-import unittest
 import getpass
 import urllib
-import warnings
-
-from test.support import run_unittest
-
-from .py38compat import check_warnings
 
 from distutils.command import register as register_module
 from distutils.command.register import register
@@ -15,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
@@ -42,7 +37,7 @@ password:password
 """
 
 
-class Inputs(object):
+class Inputs:
     """Fakes user inputs."""
 
     def __init__(self, *answers):
@@ -56,7 +51,7 @@ class Inputs(object):
             self.index += 1
 
 
-class FakeOpener(object):
+class FakeOpener:
     """Fakes a PyPI server"""
 
     def __init__(self):
@@ -78,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 = {
@@ -106,6 +95,7 @@ class RegisterTestCase(BasePyPIRCCommandTestCase):
                 'author_email': 'xxx',
                 'name': 'xxx',
                 'version': 'xxx',
+                'long_description': 'xxx',
             }
         pkg_info, dist = self.create_dist(**metadata)
         return register(dist)
@@ -118,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
@@ -137,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()
 
@@ -160,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):
 
@@ -178,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
@@ -192,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
@@ -210,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 = {
@@ -242,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'
@@ -290,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',
@@ -305,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
@@ -337,12 +322,4 @@ class RegisterTestCase(BasePyPIRCCommandTestCase):
             del register_module.input
 
         results = self.get_logs(INFO)
-        self.assertEqual(results[3], 75 * '-' + '\nxxx\n' + 75 * '-')
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(RegisterTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+        assert results[3] == 75 * '-' + '\nxxx\n' + 75 * '-'
index 3a6aea237128048d03cffce8df15958e43c43476..b11fe7c41ea440929723dd64b16b2ca8dab58075 100644 (file)
@@ -1,28 +1,24 @@
 """Tests for distutils.command.sdist."""
 import os
 import tarfile
-import unittest
 import warnings
 import zipfile
 from os.path import join
 from textwrap import dedent
-from test.support import captured_stdout, run_unittest
+from test.support import captured_stdout
 from .unix_compat import require_unix_id, require_uid_0, pwd, grp
 
-from .py38compat import check_warnings
-
-try:
-    import zlib
+import pytest
+import path
+import jaraco.path
 
-    ZLIB_SUPPORT = True
-except ImportError:
-    ZLIB_SUPPORT = False
+from .py38compat import check_warnings
 
 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
@@ -50,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:
@@ -88,7 +82,7 @@ class SDistTestCase(BasePyPIRCCommandTestCase):
         cmd.dist_dir = 'dist'
         return dist, cmd
 
-    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
+    @pytest.mark.usefixtures('needs_zlib')
     def test_prune_file_list(self):
         # this test creates a project with some VCS dirs and an NFS rename
         # file, then launches sdist to check they get pruned on all systems
@@ -118,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:
@@ -135,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]
 
-    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
-    @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.usefixtures('needs_zlib')
+    @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()
@@ -153,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'))
@@ -166,9 +160,9 @@ 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']
 
-    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
+    @pytest.mark.usefixtures('needs_zlib')
     def test_add_defaults(self):
 
         # http://bugs.python.org/issue2279
@@ -220,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:
@@ -248,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'))
@@ -256,9 +250,9 @@ class SDistTestCase(BasePyPIRCCommandTestCase):
             manifest = f.read()
         finally:
             f.close()
-        self.assertEqual(manifest, MANIFEST % {'sep': os.sep})
+        assert manifest == MANIFEST % {'sep': os.sep}
 
-    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
+    @pytest.mark.usefixtures('needs_zlib')
     def test_metadata_check_option(self):
         # testing the `medata-check` option
         dist, cmd = self.get_cmd(metadata={})
@@ -270,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()
@@ -281,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
@@ -289,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:
@@ -302,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
@@ -335,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 *')
@@ -344,13 +340,13 @@ 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
         self._check_template('include examples/')
 
-    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
+    @pytest.mark.usefixtures('needs_zlib')
     def test_get_file_list(self):
         # make sure MANIFEST is recalculated
         dist, cmd = self.get_cmd()
@@ -370,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'), '#')
@@ -391,10 +387,10 @@ 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]
 
-    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
+    @pytest.mark.usefixtures('needs_zlib')
     def test_manifest_marker(self):
         # check that autogenerated MANIFESTs have a marker
         dist, cmd = self.get_cmd()
@@ -409,9 +405,9 @@ 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'
 
-    @unittest.skipUnless(ZLIB_SUPPORT, "Need zlib support to run")
+    @pytest.mark.usefixtures('needs_zlib')
     def test_manifest_comments(self):
         # make sure comments don't cause exceptions or wrong includes
         contents = dedent(
@@ -428,9 +424,9 @@ 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']
 
-    @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run')
+    @pytest.mark.usefixtures('needs_zlib')
     def test_manual_manifest(self):
         # check that a MANIFEST without a marker is left alone
         dist, cmd = self.get_cmd()
@@ -442,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:
@@ -452,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)
@@ -460,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',
+        ]
 
-    @unittest.skipUnless(ZLIB_SUPPORT, "requires zlib")
+    @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()
@@ -486,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()
 
@@ -508,14 +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()
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(SDistTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
index a773256287bf4c6f666ff8eb86a4b991860a9ee8..d2a898ed3f61ed61fb78a20de33cebeaf9088fb4 100644 (file)
@@ -2,8 +2,9 @@
 import os
 import stat
 import sys
-import unittest.mock
-from test.support import run_unittest, unix_shell
+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,78 +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))
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(SpawnTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+        assert "command 'does-not-exist' failed" in str(ctx.value)
index a033e0750b7ae4ea032996d107383cd2ecd84136..f1759839bb99d553532a16dd07c9e528f0ae68e0 100644 (file)
@@ -5,31 +5,22 @@ 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 distutils.tests import support
-from test.support import run_unittest, swap_item
+from test.support import swap_item
 
 from .py38compat import TESTFN
 
 
-class SysconfigTestCase(support.EnvironGuard, 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('save_env')
+@pytest.mark.usefixtures('cleanup_testfn')
+class TestSysconfig:
     def cleanup_testfn(self):
         if os.path.isfile(TESTFN):
             os.remove(TESTFN)
@@ -38,47 +29,42 @@ class SysconfigTestCase(support.EnvironGuard, 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
@@ -90,7 +76,7 @@ class SysconfigTestCase(support.EnvironGuard, 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
@@ -126,9 +112,7 @@ class SysconfigTestCase(support.EnvironGuard, 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()
@@ -145,27 +129,23 @@ class SysconfigTestCase(support.EnvironGuard, 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'
-        )
-        self.assertEqual(
-            comp.exes['compiler_so'],
-            ('env_cc --sc-cflags ' '--env-cflags ' '--env-cppflags --sc-ccshared'),
+        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_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'),
+        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.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']
@@ -179,15 +159,15 @@ class SysconfigTestCase(support.EnvironGuard, 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
@@ -198,9 +178,7 @@ class SysconfigTestCase(support.EnvironGuard, 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
@@ -211,25 +189,19 @@ class SysconfigTestCase(support.EnvironGuard, 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
@@ -248,21 +220,16 @@ class SysconfigTestCase(support.EnvironGuard, 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
@@ -287,33 +254,29 @@ class SysconfigTestCase(support.EnvironGuard, 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."""
@@ -331,13 +294,3 @@ class SysconfigTestCase(support.EnvironGuard, unittest.TestCase):
             cmd, env={**os.environ, "PYTHONPATH": distutils_path}
         )
         assert out == "True"
-
-
-def test_suite():
-    suite = unittest.TestSuite()
-    suite.addTest(unittest.TestLoader().loadTestsFromTestCase(SysconfigTestCase))
-    return suite
-
-
-if __name__ == '__main__':
-    run_unittest(test_suite())
index 16de9caae05c459d25afb8b222fe48ec4cb40543..7c8dc5be54548337f20a6fb1c2ac36df88e61256 100644 (file)
@@ -1,9 +1,7 @@
 """Tests for distutils.text_file."""
 import os
-import unittest
 from distutils.text_file import TextFile
 from distutils.tests import support
-from test.support import run_unittest
 
 TEST_DATA = """# test file
 
@@ -13,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
@@ -52,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")
@@ -112,11 +110,3 @@ class TextFileTestCase(support.TempdirManager, unittest.TestCase):
             test_input(6, "join lines with collapsing", in_file, result6)
         finally:
             in_file.close()
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(TextFileTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
index 879769fc8834e08eac83523cfcfcd6ba186459d6..3978c23952e9c2c4d0848426a8b5745f636b6a1a 100644 (file)
@@ -1,9 +1,7 @@
 """Tests for distutils.unixccompiler."""
 import os
 import sys
-import unittest
-from test.support import run_unittest
-from unittest.mock import patch
+import unittest.mock as mock
 
 from .py38compat import EnvironmentVarGuard
 
@@ -13,29 +11,28 @@ 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")
-    def test_runtime_libdir_option(self):
+
+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
         #
         # Ensure RUNPATH is added to extension modules with RPATH if
@@ -75,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,
             )
@@ -94,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()
 
@@ -129,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
 
@@ -155,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':
@@ -164,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'
@@ -176,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
@@ -189,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'
@@ -201,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'
@@ -213,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
@@ -235,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
@@ -260,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
@@ -298,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:
@@ -307,11 +304,3 @@ class UnixCCompilerTestCase(support.TempdirManager, unittest.TestCase):
         self.cc.output_dir = 'scratch'
         os.chdir(self.mkdtemp())
         self.cc.has_function('abort', includes=['stdlib.h'])
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(UnixCCompilerTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
index afba2faeddd2a624932c72167eee73b5649c10ae..fb905b641ab819fc58eaac3ebd8540a1709aa154 100644 (file)
@@ -1,10 +1,8 @@
 """Tests for distutils.command.upload."""
 import os
-import unittest
 import unittest.mock as mock
 from urllib.request import HTTPError
 
-from test.support import run_unittest
 
 from distutils.command import upload as upload_mod
 from distutils.command.upload import upload
@@ -13,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]
@@ -44,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):
@@ -66,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
@@ -96,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
@@ -106,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()
@@ -132,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.
@@ -182,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)
@@ -198,32 +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()
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(uploadTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+
+        with mock.patch(
+            'distutils.command.upload.urlopen',
+            new=mock.Mock(side_effect=exception),
+        ):
+            with pytest.raises(raised_exception):
+                cmd = upload(dist)
+                cmd.ensure_finalized()
+                cmd.run()
+            results = self.get_logs(ERROR)
+            assert expected in results[-1]
+            self.clear_logs()
index cebd61cca65e8c01c2c46ce2b808d007c3e06044..605b0d40b725121044298fd8643a91757ebaa4ee 100644 (file)
@@ -1,13 +1,12 @@
 """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 test.support import run_unittest
-from unittest import mock
 
-from distutils.errors import DistutilsPlatformError, DistutilsByteCompileError
+import pytest
+
 from distutils.util import (
     get_platform,
     convert_path,
@@ -20,78 +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.tests import support
-
-
-class UtilTestCase(support.EnvironGuard, 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
+from distutils.errors import DistutilsPlatformError, DistutilsByteCompileError
+
+
+@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
@@ -102,7 +68,7 @@ class UtilTestCase(support.EnvironGuard, 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 = '\\'
@@ -112,11 +78,13 @@ class UtilTestCase(support.EnvironGuard, 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
@@ -132,8 +100,8 @@ class UtilTestCase(support.EnvironGuard, 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'
@@ -155,14 +123,15 @@ class UtilTestCase(support.EnvironGuard, 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
 
@@ -172,10 +141,10 @@ class UtilTestCase(support.EnvironGuard, 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)
@@ -188,7 +157,7 @@ class UtilTestCase(support.EnvironGuard, 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)
@@ -196,23 +165,25 @@ class UtilTestCase(support.EnvironGuard, 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'
@@ -220,7 +191,7 @@ class UtilTestCase(support.EnvironGuard, 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
@@ -228,7 +199,8 @@ class UtilTestCase(support.EnvironGuard, 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
 
@@ -236,12 +208,4 @@ class UtilTestCase(support.EnvironGuard, 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")
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(UtilTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+        assert msg == "error: Unable to find batch file"
index cecb279f07b8ca43cc7a218ec6fedd0a26f18941..ff52ea468308417479a7d48b5704608267d1d884 100644 (file)
@@ -1,27 +1,26 @@
 """Tests for distutils.version."""
-import unittest
+import pytest
+
 import distutils
 from distutils.version import LooseVersion
 from distutils.version import StrictVersion
-from test.support import run_unittest
 
 
-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 = (
@@ -52,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 = (
@@ -80,24 +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),
-            )
-
-
-def test_suite():
-    return unittest.TestLoader().loadTestsFromTestCase(VersionTestCase)
-
-
-if __name__ == "__main__":
-    run_unittest(test_suite())
+            assert (
+                res is NotImplemented
+            ), 'cmp({}, {}) should be NotImplemented, got {}'.format(v1, v2, res)
index ce3d0f46e05b703686e23302c6e267e87c47a5e7..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 (file)
@@ -1,15 +0,0 @@
-"""Tests harness for distutils.versionpredicate.
-
-"""
-
-import distutils.versionpredicate
-import doctest
-from test.support import run_unittest
-
-
-def test_suite():
-    return doctest.DocTestSuite(distutils.versionpredicate)
-
-
-if __name__ == '__main__':
-    run_unittest(test_suite())
index 8250b36327850f4a864cf0601b27afc9921c1947..95fc8eebe288657f95bcd92163f0a78faec78bfd 100644 (file)
@@ -1,5 +1,4 @@
 import sys
-import unittest
 
 try:
     import grp
@@ -7,9 +6,13 @@ try:
 except ImportError:
     grp = pwd = None
 
+import pytest
+
 
 UNIX_ID_SUPPORT = grp and pwd
 UID_0_SUPPORT = UNIX_ID_SUPPORT and sys.platform != "cygwin"
 
-require_unix_id = unittest.skipUnless(UNIX_ID_SUPPORT, "Requires grp and pwd support")
-require_uid_0 = unittest.skipUnless(UID_0_SUPPORT, "Requires UID 0 support")
+require_unix_id = pytest.mark.skipif(
+    not UNIX_ID_SUPPORT, reason="Requires grp and pwd support"
+)
+require_uid_0 = pytest.mark.skipif(not UID_0_SUPPORT, reason="Requires UID 0 support")
diff --git a/setuptools/_distutils/tests/xxmodule-3.8.c b/setuptools/_distutils/tests/xxmodule-3.8.c
new file mode 100644 (file)
index 0000000..0250031
--- /dev/null
@@ -0,0 +1,411 @@
+
+/* Use this file as a template to start implementing a module that
+   also declares object types. All occurrences of 'Xxo' should be changed
+   to something reasonable for your objects. After that, all other
+   occurrences of 'xx' should be changed to something reasonable for your
+   module. If your module is named foo your sourcefile should be named
+   foomodule.c.
+
+   You will probably want to delete all references to 'x_attr' and add
+   your own types of attributes instead.  Maybe you want to name your
+   local variables other than 'self'.  If your object type is needed in
+   other files, you'll have to create a file "foobarobject.h"; see
+   floatobject.h for an example. */
+
+/* Xxo objects */
+
+#include "Python.h"
+
+static PyObject *ErrorObject;
+
+typedef struct {
+    PyObject_HEAD
+    PyObject            *x_attr;        /* Attributes dictionary */
+} XxoObject;
+
+static PyTypeObject Xxo_Type;
+
+#define XxoObject_Check(v)      (Py_TYPE(v) == &Xxo_Type)
+
+static XxoObject *
+newXxoObject(PyObject *arg)
+{
+    XxoObject *self;
+    self = PyObject_New(XxoObject, &Xxo_Type);
+    if (self == NULL)
+        return NULL;
+    self->x_attr = NULL;
+    return self;
+}
+
+/* Xxo methods */
+
+static void
+Xxo_dealloc(XxoObject *self)
+{
+    Py_XDECREF(self->x_attr);
+    PyObject_Del(self);
+}
+
+static PyObject *
+Xxo_demo(XxoObject *self, PyObject *args)
+{
+    if (!PyArg_ParseTuple(args, ":demo"))
+        return NULL;
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyMethodDef Xxo_methods[] = {
+    {"demo",            (PyCFunction)Xxo_demo,  METH_VARARGS,
+        PyDoc_STR("demo() -> None")},
+    {NULL,              NULL}           /* sentinel */
+};
+
+static PyObject *
+Xxo_getattro(XxoObject *self, PyObject *name)
+{
+    if (self->x_attr != NULL) {
+        PyObject *v = PyDict_GetItemWithError(self->x_attr, name);
+        if (v != NULL) {
+            Py_INCREF(v);
+            return v;
+        }
+        else if (PyErr_Occurred()) {
+            return NULL;
+        }
+    }
+    return PyObject_GenericGetAttr((PyObject *)self, name);
+}
+
+static int
+Xxo_setattr(XxoObject *self, const char *name, PyObject *v)
+{
+    if (self->x_attr == NULL) {
+        self->x_attr = PyDict_New();
+        if (self->x_attr == NULL)
+            return -1;
+    }
+    if (v == NULL) {
+        int rv = PyDict_DelItemString(self->x_attr, name);
+        if (rv < 0 && PyErr_ExceptionMatches(PyExc_KeyError))
+            PyErr_SetString(PyExc_AttributeError,
+                "delete non-existing Xxo attribute");
+        return rv;
+    }
+    else
+        return PyDict_SetItemString(self->x_attr, name, v);
+}
+
+static PyTypeObject Xxo_Type = {
+    /* The ob_type field must be initialized in the module init function
+     * to be portable to Windows without using C++. */
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "xxmodule.Xxo",             /*tp_name*/
+    sizeof(XxoObject),          /*tp_basicsize*/
+    0,                          /*tp_itemsize*/
+    /* methods */
+    (destructor)Xxo_dealloc,    /*tp_dealloc*/
+    0,                          /*tp_vectorcall_offset*/
+    (getattrfunc)0,             /*tp_getattr*/
+    (setattrfunc)Xxo_setattr,   /*tp_setattr*/
+    0,                          /*tp_as_async*/
+    0,                          /*tp_repr*/
+    0,                          /*tp_as_number*/
+    0,                          /*tp_as_sequence*/
+    0,                          /*tp_as_mapping*/
+    0,                          /*tp_hash*/
+    0,                          /*tp_call*/
+    0,                          /*tp_str*/
+    (getattrofunc)Xxo_getattro, /*tp_getattro*/
+    0,                          /*tp_setattro*/
+    0,                          /*tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT,         /*tp_flags*/
+    0,                          /*tp_doc*/
+    0,                          /*tp_traverse*/
+    0,                          /*tp_clear*/
+    0,                          /*tp_richcompare*/
+    0,                          /*tp_weaklistoffset*/
+    0,                          /*tp_iter*/
+    0,                          /*tp_iternext*/
+    Xxo_methods,                /*tp_methods*/
+    0,                          /*tp_members*/
+    0,                          /*tp_getset*/
+    0,                          /*tp_base*/
+    0,                          /*tp_dict*/
+    0,                          /*tp_descr_get*/
+    0,                          /*tp_descr_set*/
+    0,                          /*tp_dictoffset*/
+    0,                          /*tp_init*/
+    0,                          /*tp_alloc*/
+    0,                          /*tp_new*/
+    0,                          /*tp_free*/
+    0,                          /*tp_is_gc*/
+};
+/* --------------------------------------------------------------------- */
+
+/* Function of two integers returning integer */
+
+PyDoc_STRVAR(xx_foo_doc,
+"foo(i,j)\n\
+\n\
+Return the sum of i and j.");
+
+static PyObject *
+xx_foo(PyObject *self, PyObject *args)
+{
+    long i, j;
+    long res;
+    if (!PyArg_ParseTuple(args, "ll:foo", &i, &j))
+        return NULL;
+    res = i+j; /* XXX Do something here */
+    return PyLong_FromLong(res);
+}
+
+
+/* Function of no arguments returning new Xxo object */
+
+static PyObject *
+xx_new(PyObject *self, PyObject *args)
+{
+    XxoObject *rv;
+
+    if (!PyArg_ParseTuple(args, ":new"))
+        return NULL;
+    rv = newXxoObject(args);
+    if (rv == NULL)
+        return NULL;
+    return (PyObject *)rv;
+}
+
+/* Example with subtle bug from extensions manual ("Thin Ice"). */
+
+static PyObject *
+xx_bug(PyObject *self, PyObject *args)
+{
+    PyObject *list, *item;
+
+    if (!PyArg_ParseTuple(args, "O:bug", &list))
+        return NULL;
+
+    item = PyList_GetItem(list, 0);
+    /* Py_INCREF(item); */
+    PyList_SetItem(list, 1, PyLong_FromLong(0L));
+    PyObject_Print(item, stdout, 0);
+    printf("\n");
+    /* Py_DECREF(item); */
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+/* Test bad format character */
+
+static PyObject *
+xx_roj(PyObject *self, PyObject *args)
+{
+    PyObject *a;
+    long b;
+    if (!PyArg_ParseTuple(args, "O#:roj", &a, &b))
+        return NULL;
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+/* ---------- */
+
+static PyTypeObject Str_Type = {
+    /* The ob_type field must be initialized in the module init function
+     * to be portable to Windows without using C++. */
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "xxmodule.Str",             /*tp_name*/
+    0,                          /*tp_basicsize*/
+    0,                          /*tp_itemsize*/
+    /* methods */
+    0,                          /*tp_dealloc*/
+    0,                          /*tp_vectorcall_offset*/
+    0,                          /*tp_getattr*/
+    0,                          /*tp_setattr*/
+    0,                          /*tp_as_async*/
+    0,                          /*tp_repr*/
+    0,                          /*tp_as_number*/
+    0,                          /*tp_as_sequence*/
+    0,                          /*tp_as_mapping*/
+    0,                          /*tp_hash*/
+    0,                          /*tp_call*/
+    0,                          /*tp_str*/
+    0,                          /*tp_getattro*/
+    0,                          /*tp_setattro*/
+    0,                          /*tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+    0,                          /*tp_doc*/
+    0,                          /*tp_traverse*/
+    0,                          /*tp_clear*/
+    0,                          /*tp_richcompare*/
+    0,                          /*tp_weaklistoffset*/
+    0,                          /*tp_iter*/
+    0,                          /*tp_iternext*/
+    0,                          /*tp_methods*/
+    0,                          /*tp_members*/
+    0,                          /*tp_getset*/
+    0, /* see PyInit_xx */      /*tp_base*/
+    0,                          /*tp_dict*/
+    0,                          /*tp_descr_get*/
+    0,                          /*tp_descr_set*/
+    0,                          /*tp_dictoffset*/
+    0,                          /*tp_init*/
+    0,                          /*tp_alloc*/
+    0,                          /*tp_new*/
+    0,                          /*tp_free*/
+    0,                          /*tp_is_gc*/
+};
+
+/* ---------- */
+
+static PyObject *
+null_richcompare(PyObject *self, PyObject *other, int op)
+{
+    Py_INCREF(Py_NotImplemented);
+    return Py_NotImplemented;
+}
+
+static PyTypeObject Null_Type = {
+    /* The ob_type field must be initialized in the module init function
+     * to be portable to Windows without using C++. */
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "xxmodule.Null",            /*tp_name*/
+    0,                          /*tp_basicsize*/
+    0,                          /*tp_itemsize*/
+    /* methods */
+    0,                          /*tp_dealloc*/
+    0,                          /*tp_vectorcall_offset*/
+    0,                          /*tp_getattr*/
+    0,                          /*tp_setattr*/
+    0,                          /*tp_as_async*/
+    0,                          /*tp_repr*/
+    0,                          /*tp_as_number*/
+    0,                          /*tp_as_sequence*/
+    0,                          /*tp_as_mapping*/
+    0,                          /*tp_hash*/
+    0,                          /*tp_call*/
+    0,                          /*tp_str*/
+    0,                          /*tp_getattro*/
+    0,                          /*tp_setattro*/
+    0,                          /*tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+    0,                          /*tp_doc*/
+    0,                          /*tp_traverse*/
+    0,                          /*tp_clear*/
+    null_richcompare,           /*tp_richcompare*/
+    0,                          /*tp_weaklistoffset*/
+    0,                          /*tp_iter*/
+    0,                          /*tp_iternext*/
+    0,                          /*tp_methods*/
+    0,                          /*tp_members*/
+    0,                          /*tp_getset*/
+    0, /* see PyInit_xx */      /*tp_base*/
+    0,                          /*tp_dict*/
+    0,                          /*tp_descr_get*/
+    0,                          /*tp_descr_set*/
+    0,                          /*tp_dictoffset*/
+    0,                          /*tp_init*/
+    0,                          /*tp_alloc*/
+    PyType_GenericNew,          /*tp_new*/
+    0,                          /*tp_free*/
+    0,                          /*tp_is_gc*/
+};
+
+
+/* ---------- */
+
+
+/* List of functions defined in the module */
+
+static PyMethodDef xx_methods[] = {
+    {"roj",             xx_roj,         METH_VARARGS,
+        PyDoc_STR("roj(a,b) -> None")},
+    {"foo",             xx_foo,         METH_VARARGS,
+        xx_foo_doc},
+    {"new",             xx_new,         METH_VARARGS,
+        PyDoc_STR("new() -> new Xx object")},
+    {"bug",             xx_bug,         METH_VARARGS,
+        PyDoc_STR("bug(o) -> None")},
+    {NULL,              NULL}           /* sentinel */
+};
+
+PyDoc_STRVAR(module_doc,
+"This is a template module just for instruction.");
+
+
+static int
+xx_exec(PyObject *m)
+{
+    /* Slot initialization is subject to the rules of initializing globals.
+       C99 requires the initializers to be "address constants".  Function
+       designators like 'PyType_GenericNew', with implicit conversion to
+       a pointer, are valid C99 address constants.
+
+       However, the unary '&' operator applied to a non-static variable
+       like 'PyBaseObject_Type' is not required to produce an address
+       constant.  Compilers may support this (gcc does), MSVC does not.
+
+       Both compilers are strictly standard conforming in this particular
+       behavior.
+    */
+    Null_Type.tp_base = &PyBaseObject_Type;
+    Str_Type.tp_base = &PyUnicode_Type;
+
+    /* Finalize the type object including setting type of the new type
+     * object; doing it here is required for portability, too. */
+    if (PyType_Ready(&Xxo_Type) < 0)
+        goto fail;
+
+    /* Add some symbolic constants to the module */
+    if (ErrorObject == NULL) {
+        ErrorObject = PyErr_NewException("xx.error", NULL, NULL);
+        if (ErrorObject == NULL)
+            goto fail;
+    }
+    Py_INCREF(ErrorObject);
+    PyModule_AddObject(m, "error", ErrorObject);
+
+    /* Add Str */
+    if (PyType_Ready(&Str_Type) < 0)
+        goto fail;
+    PyModule_AddObject(m, "Str", (PyObject *)&Str_Type);
+
+    /* Add Null */
+    if (PyType_Ready(&Null_Type) < 0)
+        goto fail;
+    PyModule_AddObject(m, "Null", (PyObject *)&Null_Type);
+    return 0;
+ fail:
+    Py_XDECREF(m);
+    return -1;
+}
+
+static struct PyModuleDef_Slot xx_slots[] = {
+    {Py_mod_exec, xx_exec},
+    {0, NULL},
+};
+
+static struct PyModuleDef xxmodule = {
+    PyModuleDef_HEAD_INIT,
+    "xx",
+    module_doc,
+    0,
+    xx_methods,
+    xx_slots,
+    NULL,
+    NULL,
+    NULL
+};
+
+/* Export function for the module (*must* be called PyInit_xx) */
+
+PyMODINIT_FUNC
+PyInit_xx(void)
+{
+    return PyModuleDef_Init(&xxmodule);
+}
diff --git a/setuptools/_distutils/tests/xxmodule.c b/setuptools/_distutils/tests/xxmodule.c
new file mode 100644 (file)
index 0000000..a6e5071
--- /dev/null
@@ -0,0 +1,412 @@
+
+/* Use this file as a template to start implementing a module that
+   also declares object types. All occurrences of 'Xxo' should be changed
+   to something reasonable for your objects. After that, all other
+   occurrences of 'xx' should be changed to something reasonable for your
+   module. If your module is named foo your sourcefile should be named
+   foomodule.c.
+
+   You will probably want to delete all references to 'x_attr' and add
+   your own types of attributes instead.  Maybe you want to name your
+   local variables other than 'self'.  If your object type is needed in
+   other files, you'll have to create a file "foobarobject.h"; see
+   floatobject.h for an example. */
+
+/* Xxo objects */
+
+#include "Python.h"
+
+static PyObject *ErrorObject;
+
+typedef struct {
+    PyObject_HEAD
+    PyObject            *x_attr;        /* Attributes dictionary */
+} XxoObject;
+
+static PyTypeObject Xxo_Type;
+
+#define XxoObject_Check(v)      Py_IS_TYPE(v, &Xxo_Type)
+
+static XxoObject *
+newXxoObject(PyObject *arg)
+{
+    XxoObject *self;
+    self = PyObject_New(XxoObject, &Xxo_Type);
+    if (self == NULL)
+        return NULL;
+    self->x_attr = NULL;
+    return self;
+}
+
+/* Xxo methods */
+
+static void
+Xxo_dealloc(XxoObject *self)
+{
+    Py_XDECREF(self->x_attr);
+    PyObject_Free(self);
+}
+
+static PyObject *
+Xxo_demo(XxoObject *self, PyObject *args)
+{
+    if (!PyArg_ParseTuple(args, ":demo"))
+        return NULL;
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyMethodDef Xxo_methods[] = {
+    {"demo",            (PyCFunction)Xxo_demo,  METH_VARARGS,
+        PyDoc_STR("demo() -> None")},
+    {NULL,              NULL}           /* sentinel */
+};
+
+static PyObject *
+Xxo_getattro(XxoObject *self, PyObject *name)
+{
+    if (self->x_attr != NULL) {
+        PyObject *v = PyDict_GetItemWithError(self->x_attr, name);
+        if (v != NULL) {
+            Py_INCREF(v);
+            return v;
+        }
+        else if (PyErr_Occurred()) {
+            return NULL;
+        }
+    }
+    return PyObject_GenericGetAttr((PyObject *)self, name);
+}
+
+static int
+Xxo_setattr(XxoObject *self, const char *name, PyObject *v)
+{
+    if (self->x_attr == NULL) {
+        self->x_attr = PyDict_New();
+        if (self->x_attr == NULL)
+            return -1;
+    }
+    if (v == NULL) {
+        int rv = PyDict_DelItemString(self->x_attr, name);
+        if (rv < 0 && PyErr_ExceptionMatches(PyExc_KeyError))
+            PyErr_SetString(PyExc_AttributeError,
+                "delete non-existing Xxo attribute");
+        return rv;
+    }
+    else
+        return PyDict_SetItemString(self->x_attr, name, v);
+}
+
+static PyTypeObject Xxo_Type = {
+    /* The ob_type field must be initialized in the module init function
+     * to be portable to Windows without using C++. */
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "xxmodule.Xxo",             /*tp_name*/
+    sizeof(XxoObject),          /*tp_basicsize*/
+    0,                          /*tp_itemsize*/
+    /* methods */
+    (destructor)Xxo_dealloc,    /*tp_dealloc*/
+    0,                          /*tp_vectorcall_offset*/
+    (getattrfunc)0,             /*tp_getattr*/
+    (setattrfunc)Xxo_setattr,   /*tp_setattr*/
+    0,                          /*tp_as_async*/
+    0,                          /*tp_repr*/
+    0,                          /*tp_as_number*/
+    0,                          /*tp_as_sequence*/
+    0,                          /*tp_as_mapping*/
+    0,                          /*tp_hash*/
+    0,                          /*tp_call*/
+    0,                          /*tp_str*/
+    (getattrofunc)Xxo_getattro, /*tp_getattro*/
+    0,                          /*tp_setattro*/
+    0,                          /*tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT,         /*tp_flags*/
+    0,                          /*tp_doc*/
+    0,                          /*tp_traverse*/
+    0,                          /*tp_clear*/
+    0,                          /*tp_richcompare*/
+    0,                          /*tp_weaklistoffset*/
+    0,                          /*tp_iter*/
+    0,                          /*tp_iternext*/
+    Xxo_methods,                /*tp_methods*/
+    0,                          /*tp_members*/
+    0,                          /*tp_getset*/
+    0,                          /*tp_base*/
+    0,                          /*tp_dict*/
+    0,                          /*tp_descr_get*/
+    0,                          /*tp_descr_set*/
+    0,                          /*tp_dictoffset*/
+    0,                          /*tp_init*/
+    0,                          /*tp_alloc*/
+    0,                          /*tp_new*/
+    0,                          /*tp_free*/
+    0,                          /*tp_is_gc*/
+};
+/* --------------------------------------------------------------------- */
+
+/* Function of two integers returning integer */
+
+PyDoc_STRVAR(xx_foo_doc,
+"foo(i,j)\n\
+\n\
+Return the sum of i and j.");
+
+static PyObject *
+xx_foo(PyObject *self, PyObject *args)
+{
+    long i, j;
+    long res;
+    if (!PyArg_ParseTuple(args, "ll:foo", &i, &j))
+        return NULL;
+    res = i+j; /* XXX Do something here */
+    return PyLong_FromLong(res);
+}
+
+
+/* Function of no arguments returning new Xxo object */
+
+static PyObject *
+xx_new(PyObject *self, PyObject *args)
+{
+    XxoObject *rv;
+
+    if (!PyArg_ParseTuple(args, ":new"))
+        return NULL;
+    rv = newXxoObject(args);
+    if (rv == NULL)
+        return NULL;
+    return (PyObject *)rv;
+}
+
+/* Example with subtle bug from extensions manual ("Thin Ice"). */
+
+static PyObject *
+xx_bug(PyObject *self, PyObject *args)
+{
+    PyObject *list, *item;
+
+    if (!PyArg_ParseTuple(args, "O:bug", &list))
+        return NULL;
+
+    item = PyList_GetItem(list, 0);
+    /* Py_INCREF(item); */
+    PyList_SetItem(list, 1, PyLong_FromLong(0L));
+    PyObject_Print(item, stdout, 0);
+    printf("\n");
+    /* Py_DECREF(item); */
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+/* Test bad format character */
+
+static PyObject *
+xx_roj(PyObject *self, PyObject *args)
+{
+    PyObject *a;
+    long b;
+    if (!PyArg_ParseTuple(args, "O#:roj", &a, &b))
+        return NULL;
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+/* ---------- */
+
+static PyTypeObject Str_Type = {
+    /* The ob_type field must be initialized in the module init function
+     * to be portable to Windows without using C++. */
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "xxmodule.Str",             /*tp_name*/
+    0,                          /*tp_basicsize*/
+    0,                          /*tp_itemsize*/
+    /* methods */
+    0,                          /*tp_dealloc*/
+    0,                          /*tp_vectorcall_offset*/
+    0,                          /*tp_getattr*/
+    0,                          /*tp_setattr*/
+    0,                          /*tp_as_async*/
+    0,                          /*tp_repr*/
+    0,                          /*tp_as_number*/
+    0,                          /*tp_as_sequence*/
+    0,                          /*tp_as_mapping*/
+    0,                          /*tp_hash*/
+    0,                          /*tp_call*/
+    0,                          /*tp_str*/
+    0,                          /*tp_getattro*/
+    0,                          /*tp_setattro*/
+    0,                          /*tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+    0,                          /*tp_doc*/
+    0,                          /*tp_traverse*/
+    0,                          /*tp_clear*/
+    0,                          /*tp_richcompare*/
+    0,                          /*tp_weaklistoffset*/
+    0,                          /*tp_iter*/
+    0,                          /*tp_iternext*/
+    0,                          /*tp_methods*/
+    0,                          /*tp_members*/
+    0,                          /*tp_getset*/
+    0, /* see PyInit_xx */      /*tp_base*/
+    0,                          /*tp_dict*/
+    0,                          /*tp_descr_get*/
+    0,                          /*tp_descr_set*/
+    0,                          /*tp_dictoffset*/
+    0,                          /*tp_init*/
+    0,                          /*tp_alloc*/
+    0,                          /*tp_new*/
+    0,                          /*tp_free*/
+    0,                          /*tp_is_gc*/
+};
+
+/* ---------- */
+
+static PyObject *
+null_richcompare(PyObject *self, PyObject *other, int op)
+{
+    Py_INCREF(Py_NotImplemented);
+    return Py_NotImplemented;
+}
+
+static PyTypeObject Null_Type = {
+    /* The ob_type field must be initialized in the module init function
+     * to be portable to Windows without using C++. */
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "xxmodule.Null",            /*tp_name*/
+    0,                          /*tp_basicsize*/
+    0,                          /*tp_itemsize*/
+    /* methods */
+    0,                          /*tp_dealloc*/
+    0,                          /*tp_vectorcall_offset*/
+    0,                          /*tp_getattr*/
+    0,                          /*tp_setattr*/
+    0,                          /*tp_as_async*/
+    0,                          /*tp_repr*/
+    0,                          /*tp_as_number*/
+    0,                          /*tp_as_sequence*/
+    0,                          /*tp_as_mapping*/
+    0,                          /*tp_hash*/
+    0,                          /*tp_call*/
+    0,                          /*tp_str*/
+    0,                          /*tp_getattro*/
+    0,                          /*tp_setattro*/
+    0,                          /*tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+    0,                          /*tp_doc*/
+    0,                          /*tp_traverse*/
+    0,                          /*tp_clear*/
+    null_richcompare,           /*tp_richcompare*/
+    0,                          /*tp_weaklistoffset*/
+    0,                          /*tp_iter*/
+    0,                          /*tp_iternext*/
+    0,                          /*tp_methods*/
+    0,                          /*tp_members*/
+    0,                          /*tp_getset*/
+    0, /* see PyInit_xx */      /*tp_base*/
+    0,                          /*tp_dict*/
+    0,                          /*tp_descr_get*/
+    0,                          /*tp_descr_set*/
+    0,                          /*tp_dictoffset*/
+    0,                          /*tp_init*/
+    0,                          /*tp_alloc*/
+    PyType_GenericNew,          /*tp_new*/
+    0,                          /*tp_free*/
+    0,                          /*tp_is_gc*/
+};
+
+
+/* ---------- */
+
+
+/* List of functions defined in the module */
+
+static PyMethodDef xx_methods[] = {
+    {"roj",             xx_roj,         METH_VARARGS,
+        PyDoc_STR("roj(a,b) -> None")},
+    {"foo",             xx_foo,         METH_VARARGS,
+        xx_foo_doc},
+    {"new",             xx_new,         METH_VARARGS,
+        PyDoc_STR("new() -> new Xx object")},
+    {"bug",             xx_bug,         METH_VARARGS,
+        PyDoc_STR("bug(o) -> None")},
+    {NULL,              NULL}           /* sentinel */
+};
+
+PyDoc_STRVAR(module_doc,
+"This is a template module just for instruction.");
+
+
+static int
+xx_exec(PyObject *m)
+{
+    /* Slot initialization is subject to the rules of initializing globals.
+       C99 requires the initializers to be "address constants".  Function
+       designators like 'PyType_GenericNew', with implicit conversion to
+       a pointer, are valid C99 address constants.
+
+       However, the unary '&' operator applied to a non-static variable
+       like 'PyBaseObject_Type' is not required to produce an address
+       constant.  Compilers may support this (gcc does), MSVC does not.
+
+       Both compilers are strictly standard conforming in this particular
+       behavior.
+    */
+    Null_Type.tp_base = &PyBaseObject_Type;
+    Str_Type.tp_base = &PyUnicode_Type;
+
+    /* Finalize the type object including setting type of the new type
+     * object; doing it here is required for portability, too. */
+    if (PyType_Ready(&Xxo_Type) < 0) {
+        return -1;
+    }
+
+    /* Add some symbolic constants to the module */
+    if (ErrorObject == NULL) {
+        ErrorObject = PyErr_NewException("xx.error", NULL, NULL);
+        if (ErrorObject == NULL) {
+            return -1;
+        }
+    }
+    int rc = PyModule_AddType(m, (PyTypeObject *)ErrorObject);
+    Py_DECREF(ErrorObject);
+    if (rc < 0) {
+        return -1;
+    }
+
+    /* Add Str and Null types */
+    if (PyModule_AddType(m, &Str_Type) < 0) {
+        return -1;
+    }
+    if (PyModule_AddType(m, &Null_Type) < 0) {
+        return -1;
+    }
+
+    return 0;
+}
+
+static struct PyModuleDef_Slot xx_slots[] = {
+    {Py_mod_exec, xx_exec},
+    {0, NULL},
+};
+
+static struct PyModuleDef xxmodule = {
+    PyModuleDef_HEAD_INIT,
+    "xx",
+    module_doc,
+    0,
+    xx_methods,
+    xx_slots,
+    NULL,
+    NULL,
+    NULL
+};
+
+/* Export function for the module (*must* be called PyInit_xx) */
+
+PyMODINIT_FUNC
+PyInit_xx(void)
+{
+    return PyModuleDef_Init(&xxmodule);
+}
index 015d68027cb328f73bdb3ace2e609e8d1c8094d8..7274d4b16e1bee16751515f42793ebefdd769b96 100644 (file)
@@ -4,7 +4,7 @@ provides the TextFile class, which gives an interface to text files
 that (optionally) takes care of stripping comments, ignoring blank
 lines, and joining lines with backslashes."""
 
-import sys, io
+import sys
 
 
 class TextFile:
@@ -115,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):
@@ -152,7 +152,7 @@ class TextFile:
         line."""
         sys.stderr.write("warning: " + self.gen_error(msg, line) + "\n")
 
-    def readline(self):
+    def readline(self):  # noqa: C901
         """Read and return a single logical line from the current file (or
         from an internal buffer if lines have previously been "unread"
         with 'unreadline()').  If the 'join_lines' option is true, this
index 4be74fdf3a0c1cde6fe2fab555302b49ed10b793..4ab771a475df8f53f4054d7869366a2457397a09 100644 (file)
@@ -13,7 +13,11 @@ the "typical" Unix-style command-line C compiler:
   * link shared library handled by 'cc -shared'
 """
 
-import os, sys, re, shlex
+import os
+import sys
+import re
+import shlex
+import itertools
 
 from distutils import sysconfig
 from distutils.dep_util import newer
@@ -160,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)
@@ -317,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)
index d59c362b86a7625aee2d0c975e438c0a02a0db6b..d95992ec99f590a5fd194a91e7b9515df5e49706 100644 (file)
@@ -11,11 +11,10 @@ import string
 import subprocess
 import sys
 import sysconfig
-from distutils.errors import DistutilsPlatformError
+from distutils.errors import DistutilsPlatformError, DistutilsByteCompileError
 from distutils.dep_util import newer
 from distutils.spawn import spawn
 from distutils import log
-from distutils.errors import DistutilsByteCompileError
 
 
 def get_host_platform():
@@ -335,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] + ')'
 
@@ -357,10 +356,10 @@ 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(
+def byte_compile(  # noqa: C901
     py_files,
     optimize=0,
     force=0,
index a406a301446b1c1079137f1834caa4448807a693..e29e265750fbccfbd072d1541e376aa150724be2 100644 (file)
@@ -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)
@@ -180,7 +180,7 @@ class StrictVersion(Version):
 
         return vstring
 
-    def _cmp(self, other):
+    def _cmp(self, other):  # noqa: C901
         if isinstance(other, str):
             with suppress_known_deprecation():
                 other = StrictVersion(other)
diff --git a/setuptools/_vendor/pyparsing-3.0.8.dist-info/INSTALLER b/setuptools/_vendor/pyparsing-3.0.8.dist-info/INSTALLER
deleted file mode 100644 (file)
index a1b589e..0000000
+++ /dev/null
@@ -1 +0,0 @@
-pip
diff --git a/setuptools/_vendor/pyparsing-3.0.8.dist-info/LICENSE b/setuptools/_vendor/pyparsing-3.0.8.dist-info/LICENSE
deleted file mode 100644 (file)
index 1bf9852..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/setuptools/_vendor/pyparsing-3.0.8.dist-info/METADATA b/setuptools/_vendor/pyparsing-3.0.8.dist-info/METADATA
deleted file mode 100644 (file)
index d6c8e9b..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-Metadata-Version: 2.1
-Name: pyparsing
-Version: 3.0.8
-Summary: pyparsing module - Classes and methods to define and execute parsing grammars
-Author-email: Paul McGuire <ptmcg.gm+pyparsing@gmail.com>
-Requires-Python: >=3.6.8
-Description-Content-Type: text/x-rst
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Intended Audience :: Developers
-Classifier: Intended Audience :: Information Technology
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.6
-Classifier: Programming Language :: Python :: 3.7
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3.9
-Classifier: Programming Language :: Python :: 3.10
-Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: Implementation :: CPython
-Classifier: Programming Language :: Python :: Implementation :: PyPy
-Classifier: Typing :: Typed
-Requires-Dist: railroad-diagrams ; extra == "diagrams"
-Requires-Dist: jinja2 ; extra == "diagrams"
-Project-URL: Homepage, https://github.com/pyparsing/pyparsing/
-Provides-Extra: diagrams
-
-PyParsing -- A Python Parsing Module
-====================================
-
-|Build Status| |Coverage|
-
-Introduction
-============
-
-The pyparsing module is an alternative approach to creating and
-executing simple grammars, vs. the traditional lex/yacc approach, or the
-use of regular expressions. The pyparsing module provides a library of
-classes that client code uses to construct the grammar directly in
-Python code.
-
-*[Since first writing this description of pyparsing in late 2003, this
-technique for developing parsers has become more widespread, under the
-name Parsing Expression Grammars - PEGs. See more information on PEGs*
-`here <https://en.wikipedia.org/wiki/Parsing_expression_grammar>`__
-*.]*
-
-Here is a program to parse ``"Hello, World!"`` (or any greeting of the form
-``"salutation, addressee!"``):
-
-.. code:: python
-
-    from pyparsing import Word, alphas
-    greet = Word(alphas) + "," + Word(alphas) + "!"
-    hello = "Hello, World!"
-    print(hello, "->", greet.parseString(hello))
-
-The program outputs the following::
-
-    Hello, World! -> ['Hello', ',', 'World', '!']
-
-The Python representation of the grammar is quite readable, owing to the
-self-explanatory class names, and the use of '+', '|' and '^' operator
-definitions.
-
-The parsed results returned from ``parseString()`` is a collection of type
-``ParseResults``, which can be accessed as a
-nested list, a dictionary, or an object with named attributes.
-
-The pyparsing module handles some of the problems that are typically
-vexing when writing text parsers:
-
-- extra or missing whitespace (the above program will also handle ``"Hello,World!"``, ``"Hello , World !"``, etc.)
-- quoted strings
-- embedded comments
-
-The examples directory includes a simple SQL parser, simple CORBA IDL
-parser, a config file parser, a chemical formula parser, and a four-
-function algebraic notation parser, among many others.
-
-Documentation
-=============
-
-There are many examples in the online docstrings of the classes
-and methods in pyparsing. You can find them compiled into `online docs <https://pyparsing-docs.readthedocs.io/en/latest/>`__. Additional
-documentation resources and project info are listed in the online
-`GitHub wiki <https://github.com/pyparsing/pyparsing/wiki>`__. An
-entire directory of examples can be found `here <https://github.com/pyparsing/pyparsing/tree/master/examples>`__.
-
-License
-=======
-
-MIT License. See header of the `pyparsing.py <https://github.com/pyparsing/pyparsing/blob/master/pyparsing/__init__.py#L1-L23>`__ file.
-
-History
-=======
-
-See `CHANGES <https://github.com/pyparsing/pyparsing/blob/master/CHANGES>`__ file.
-
-.. |Build Status| image:: https://github.com/pyparsing/pyparsing/actions/workflows/ci.yml/badge.svg
-   :target: https://github.com/pyparsing/pyparsing/actions/workflows/ci.yml
-.. |Coverage| image:: https://codecov.io/gh/pyparsing/pyparsing/branch/master/graph/badge.svg
-  :target: https://codecov.io/gh/pyparsing/pyparsing
-
diff --git a/setuptools/_vendor/pyparsing-3.0.8.dist-info/RECORD b/setuptools/_vendor/pyparsing-3.0.8.dist-info/RECORD
deleted file mode 100644 (file)
index 72947b0..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-pyparsing-3.0.8.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
-pyparsing-3.0.8.dist-info/LICENSE,sha256=ENUSChaAWAT_2otojCIL-06POXQbVzIGBNRVowngGXI,1023
-pyparsing-3.0.8.dist-info/METADATA,sha256=dEvZBGz3Owm5LYEaqDeKb6e3ZgOrF48WaCI_PG1n5BE,4207
-pyparsing-3.0.8.dist-info/RECORD,,
-pyparsing-3.0.8.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-pyparsing-3.0.8.dist-info/WHEEL,sha256=jPMR_Dzkc4X4icQtmz81lnNY_kAsfog7ry7qoRvYLXw,81
-pyparsing/__init__.py,sha256=EMa1HCuq9HJhEDR8fUThu2gD0nl6Cs8FFEWZZ0eRCM8,9159
-pyparsing/__pycache__/__init__.cpython-38.pyc,,
-pyparsing/__pycache__/actions.cpython-38.pyc,,
-pyparsing/__pycache__/common.cpython-38.pyc,,
-pyparsing/__pycache__/core.cpython-38.pyc,,
-pyparsing/__pycache__/exceptions.cpython-38.pyc,,
-pyparsing/__pycache__/helpers.cpython-38.pyc,,
-pyparsing/__pycache__/results.cpython-38.pyc,,
-pyparsing/__pycache__/testing.cpython-38.pyc,,
-pyparsing/__pycache__/unicode.cpython-38.pyc,,
-pyparsing/__pycache__/util.cpython-38.pyc,,
-pyparsing/actions.py,sha256=60v7mETOBzc01YPH_qQD5isavgcSJpAfIKpzgjM3vaU,6429
-pyparsing/common.py,sha256=lFL97ooIeR75CmW5hjURZqwDCTgruqltcTCZ-ulLO2Q,12936
-pyparsing/core.py,sha256=zBzGw5vcSd58pB1QkYpY6O_XCcHVKX_nH5xglRx_L-M,213278
-pyparsing/diagram/__init__.py,sha256=oU_UEh6O5voKSFjUdq462_mpmURLOfUIsmWvxi1qgTQ,23003
-pyparsing/diagram/__pycache__/__init__.cpython-38.pyc,,
-pyparsing/diagram/template.jinja2,sha256=SfQ8SLktSBqI5W1DGcUVH1vdflRD6x2sQBApxrcNg7s,589
-pyparsing/exceptions.py,sha256=H4D9gqMavqmAFSsdrU_J6bO-jA-T-A7yvtXWZpooIUA,9030
-pyparsing/helpers.py,sha256=EyjpgDOc3ivwRsU4VXxAWdgIs5gaqMDaLWcwRh5mqxc,39007
-pyparsing/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-pyparsing/results.py,sha256=Hd6FAAh5sF8zGXpwsamdVqFUblIwyQf0FH0t7FCb1OY,25353
-pyparsing/testing.py,sha256=szs8AKZREZMhL0y0vsMfaTVAnpqPHetg6VKJBNmc4QY,13388
-pyparsing/unicode.py,sha256=IR-ioeGY29cZ49tG8Ts7ITPWWNP5G2DcZs58oa8zn44,10381
-pyparsing/util.py,sha256=kq772O5YSeXOSdP-M31EWpbH_ayj7BMHImBYo9xPD5M,6805
diff --git a/setuptools/_vendor/pyparsing-3.0.8.dist-info/REQUESTED b/setuptools/_vendor/pyparsing-3.0.8.dist-info/REQUESTED
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/setuptools/_vendor/pyparsing-3.0.8.dist-info/WHEEL b/setuptools/_vendor/pyparsing-3.0.8.dist-info/WHEEL
deleted file mode 100644 (file)
index c727d14..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-Wheel-Version: 1.0
-Generator: flit 3.6.0
-Root-Is-Purelib: true
-Tag: py3-none-any
diff --git a/setuptools/_vendor/pyparsing-3.0.9.dist-info/INSTALLER b/setuptools/_vendor/pyparsing-3.0.9.dist-info/INSTALLER
new file mode 100644 (file)
index 0000000..a1b589e
--- /dev/null
@@ -0,0 +1 @@
+pip
diff --git a/setuptools/_vendor/pyparsing-3.0.9.dist-info/LICENSE b/setuptools/_vendor/pyparsing-3.0.9.dist-info/LICENSE
new file mode 100644 (file)
index 0000000..1bf9852
--- /dev/null
@@ -0,0 +1,18 @@
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/setuptools/_vendor/pyparsing-3.0.9.dist-info/METADATA b/setuptools/_vendor/pyparsing-3.0.9.dist-info/METADATA
new file mode 100644 (file)
index 0000000..33e5194
--- /dev/null
@@ -0,0 +1,105 @@
+Metadata-Version: 2.1
+Name: pyparsing
+Version: 3.0.9
+Summary: pyparsing module - Classes and methods to define and execute parsing grammars
+Author-email: Paul McGuire <ptmcg.gm+pyparsing@gmail.com>
+Requires-Python: >=3.6.8
+Description-Content-Type: text/x-rst
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: Information Technology
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Typing :: Typed
+Requires-Dist: railroad-diagrams ; extra == "diagrams"
+Requires-Dist: jinja2 ; extra == "diagrams"
+Project-URL: Homepage, https://github.com/pyparsing/pyparsing/
+Provides-Extra: diagrams
+
+PyParsing -- A Python Parsing Module
+====================================
+
+|Build Status| |Coverage|
+
+Introduction
+============
+
+The pyparsing module is an alternative approach to creating and
+executing simple grammars, vs. the traditional lex/yacc approach, or the
+use of regular expressions. The pyparsing module provides a library of
+classes that client code uses to construct the grammar directly in
+Python code.
+
+*[Since first writing this description of pyparsing in late 2003, this
+technique for developing parsers has become more widespread, under the
+name Parsing Expression Grammars - PEGs. See more information on PEGs*
+`here <https://en.wikipedia.org/wiki/Parsing_expression_grammar>`__
+*.]*
+
+Here is a program to parse ``"Hello, World!"`` (or any greeting of the form
+``"salutation, addressee!"``):
+
+.. code:: python
+
+    from pyparsing import Word, alphas
+    greet = Word(alphas) + "," + Word(alphas) + "!"
+    hello = "Hello, World!"
+    print(hello, "->", greet.parseString(hello))
+
+The program outputs the following::
+
+    Hello, World! -> ['Hello', ',', 'World', '!']
+
+The Python representation of the grammar is quite readable, owing to the
+self-explanatory class names, and the use of '+', '|' and '^' operator
+definitions.
+
+The parsed results returned from ``parseString()`` is a collection of type
+``ParseResults``, which can be accessed as a
+nested list, a dictionary, or an object with named attributes.
+
+The pyparsing module handles some of the problems that are typically
+vexing when writing text parsers:
+
+- extra or missing whitespace (the above program will also handle ``"Hello,World!"``, ``"Hello , World !"``, etc.)
+- quoted strings
+- embedded comments
+
+The examples directory includes a simple SQL parser, simple CORBA IDL
+parser, a config file parser, a chemical formula parser, and a four-
+function algebraic notation parser, among many others.
+
+Documentation
+=============
+
+There are many examples in the online docstrings of the classes
+and methods in pyparsing. You can find them compiled into `online docs <https://pyparsing-docs.readthedocs.io/en/latest/>`__. Additional
+documentation resources and project info are listed in the online
+`GitHub wiki <https://github.com/pyparsing/pyparsing/wiki>`__. An
+entire directory of examples can be found `here <https://github.com/pyparsing/pyparsing/tree/master/examples>`__.
+
+License
+=======
+
+MIT License. See header of the `pyparsing.py <https://github.com/pyparsing/pyparsing/blob/master/pyparsing/__init__.py#L1-L23>`__ file.
+
+History
+=======
+
+See `CHANGES <https://github.com/pyparsing/pyparsing/blob/master/CHANGES>`__ file.
+
+.. |Build Status| image:: https://github.com/pyparsing/pyparsing/actions/workflows/ci.yml/badge.svg
+   :target: https://github.com/pyparsing/pyparsing/actions/workflows/ci.yml
+.. |Coverage| image:: https://codecov.io/gh/pyparsing/pyparsing/branch/master/graph/badge.svg
+  :target: https://codecov.io/gh/pyparsing/pyparsing
+
diff --git a/setuptools/_vendor/pyparsing-3.0.9.dist-info/RECORD b/setuptools/_vendor/pyparsing-3.0.9.dist-info/RECORD
new file mode 100644 (file)
index 0000000..7a4e49a
--- /dev/null
@@ -0,0 +1,29 @@
+pyparsing-3.0.9.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+pyparsing-3.0.9.dist-info/LICENSE,sha256=ENUSChaAWAT_2otojCIL-06POXQbVzIGBNRVowngGXI,1023
+pyparsing-3.0.9.dist-info/METADATA,sha256=h_fpm9rwvgZsE8v5YNF4IAo-IpaFWCOfUEm5MMByIiM,4207
+pyparsing-3.0.9.dist-info/RECORD,,
+pyparsing-3.0.9.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pyparsing-3.0.9.dist-info/WHEEL,sha256=jPMR_Dzkc4X4icQtmz81lnNY_kAsfog7ry7qoRvYLXw,81
+pyparsing/__init__.py,sha256=52QH3lgPbJhba0estckoGPHRH8JvQSSCGoWiEn2m0bU,9159
+pyparsing/__pycache__/__init__.cpython-38.pyc,,
+pyparsing/__pycache__/actions.cpython-38.pyc,,
+pyparsing/__pycache__/common.cpython-38.pyc,,
+pyparsing/__pycache__/core.cpython-38.pyc,,
+pyparsing/__pycache__/exceptions.cpython-38.pyc,,
+pyparsing/__pycache__/helpers.cpython-38.pyc,,
+pyparsing/__pycache__/results.cpython-38.pyc,,
+pyparsing/__pycache__/testing.cpython-38.pyc,,
+pyparsing/__pycache__/unicode.cpython-38.pyc,,
+pyparsing/__pycache__/util.cpython-38.pyc,,
+pyparsing/actions.py,sha256=wU9i32e0y1ymxKE3OUwSHO-SFIrt1h_wv6Ws0GQjpNU,6426
+pyparsing/common.py,sha256=lFL97ooIeR75CmW5hjURZqwDCTgruqltcTCZ-ulLO2Q,12936
+pyparsing/core.py,sha256=u8GptQE_H6wMkl8OZhxeK1aAPIDXXNgwdShORBwBVS4,213310
+pyparsing/diagram/__init__.py,sha256=f_EfxahqrdkRVahmTwLJXkZ9EEDKNd-O7lBbpJYlE1g,23668
+pyparsing/diagram/__pycache__/__init__.cpython-38.pyc,,
+pyparsing/exceptions.py,sha256=3LbSafD32NYb1Tzt85GHNkhEAU1eZkTtNSk24cPMemo,9023
+pyparsing/helpers.py,sha256=QpUOjW0-psvueMwWb9bQpU2noqKCv98_wnw1VSzSdVo,39129
+pyparsing/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pyparsing/results.py,sha256=HgNvWVXBdQP-Q6PtJfoCEeOJk2nwEvG-2KVKC5sGA30,25341
+pyparsing/testing.py,sha256=7tu4Abp4uSeJV0N_yEPRmmNUhpd18ZQP3CrX41DM814,13402
+pyparsing/unicode.py,sha256=fwuhMj30SQ165Cv7HJpu-rSxGbRm93kN9L4Ei7VGc1Y,10787
+pyparsing/util.py,sha256=kq772O5YSeXOSdP-M31EWpbH_ayj7BMHImBYo9xPD5M,6805
diff --git a/setuptools/_vendor/pyparsing-3.0.9.dist-info/REQUESTED b/setuptools/_vendor/pyparsing-3.0.9.dist-info/REQUESTED
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/setuptools/_vendor/pyparsing-3.0.9.dist-info/WHEEL b/setuptools/_vendor/pyparsing-3.0.9.dist-info/WHEEL
new file mode 100644 (file)
index 0000000..c727d14
--- /dev/null
@@ -0,0 +1,4 @@
+Wheel-Version: 1.0
+Generator: flit 3.6.0
+Root-Is-Purelib: true
+Tag: py3-none-any
index 45f334d043fc72a77eef992cb4b2508f53837e2e..7802ff158d83eb88e6dbe78d9cd33ca14341662a 100644 (file)
@@ -128,8 +128,8 @@ class version_info(NamedTuple):
         )
 
 
-__version_info__ = version_info(3, 0, 8, "final", 0)
-__version_time__ = "09 Apr 2022 23:29 UTC"
+__version_info__ = version_info(3, 0, 9, "final", 0)
+__version_time__ = "05 May 2022 07:02 UTC"
 __version__ = __version_info__.__version__
 __versionTime__ = __version_time__
 __author__ = "Paul McGuire <ptmcg.gm+pyparsing@gmail.com>"
index 2bcc5502b075ff39be828a05f9bdc2ea3e574cf0..f72c66e743146c7a5b70a5440e9ab5459f10245b 100644 (file)
@@ -55,7 +55,7 @@ def replace_with(repl_str):
         na = one_of("N/A NA").set_parse_action(replace_with(math.nan))
         term = na | num
 
-        OneOrMore(term).parse_string("324 234 N/A 234") # -> [324, 234, nan, 234]
+        term[1, ...].parse_string("324 234 N/A 234") # -> [324, 234, nan, 234]
     """
     return lambda s, l, t: [repl_str]
 
index 454bd57d0419439944b455c9c06958a97e7c8925..9acba3f3e984b404f52702964805732f03965048 100644 (file)
@@ -2,9 +2,8 @@
 # core.py
 #
 import os
+import typing
 from typing import (
-    Optional as OptionalType,
-    Iterable as IterableType,
     NamedTuple,
     Union,
     Callable,
@@ -14,7 +13,6 @@ from typing import (
     List,
     TextIO,
     Set,
-    Dict as DictType,
     Sequence,
 )
 from abc import ABC, abstractmethod
@@ -192,7 +190,7 @@ del __config_flags
 
 
 def _should_enable_warnings(
-    cmd_line_warn_options: IterableType[str], warn_env_var: OptionalType[str]
+    cmd_line_warn_options: typing.Iterable[str], warn_env_var: typing.Optional[str]
 ) -> bool:
     enable = bool(warn_env_var)
     for warn_opt in cmd_line_warn_options:
@@ -404,7 +402,7 @@ class ParserElement(ABC):
 
     DEFAULT_WHITE_CHARS: str = " \n\t\r"
     verbose_stacktrace: bool = False
-    _literalStringClass: OptionalType[type] = None
+    _literalStringClass: typing.Optional[type] = None
 
     @staticmethod
     def set_default_whitespace_chars(chars: str) -> None:
@@ -414,11 +412,11 @@ class ParserElement(ABC):
         Example::
 
             # default whitespace chars are space, <TAB> and newline
-            OneOrMore(Word(alphas)).parse_string("abc def\nghi jkl")  # -> ['abc', 'def', 'ghi', 'jkl']
+            Word(alphas)[1, ...].parse_string("abc def\nghi jkl")  # -> ['abc', 'def', 'ghi', 'jkl']
 
             # change to just treat newline as significant
             ParserElement.set_default_whitespace_chars(" \t")
-            OneOrMore(Word(alphas)).parse_string("abc def\nghi jkl")  # -> ['abc', 'def']
+            Word(alphas)[1, ...].parse_string("abc def\nghi jkl")  # -> ['abc', 'def']
         """
         ParserElement.DEFAULT_WHITE_CHARS = chars
 
@@ -450,13 +448,13 @@ class ParserElement(ABC):
         ParserElement._literalStringClass = cls
 
     class DebugActions(NamedTuple):
-        debug_try: OptionalType[DebugStartAction]
-        debug_match: OptionalType[DebugSuccessAction]
-        debug_fail: OptionalType[DebugExceptionAction]
+        debug_try: typing.Optional[DebugStartAction]
+        debug_match: typing.Optional[DebugSuccessAction]
+        debug_fail: typing.Optional[DebugExceptionAction]
 
     def __init__(self, savelist: bool = False):
         self.parseAction: List[ParseAction] = list()
-        self.failAction: OptionalType[ParseFailAction] = None
+        self.failAction: typing.Optional[ParseFailAction] = None
         self.customName = None
         self._defaultName = None
         self.resultsName = None
@@ -510,7 +508,7 @@ class ParserElement(ABC):
             integerK = integer.copy().add_parse_action(lambda toks: toks[0] * 1024) + Suppress("K")
             integerM = integer.copy().add_parse_action(lambda toks: toks[0] * 1024 * 1024) + Suppress("M")
 
-            print(OneOrMore(integerK | integerM | integer).parse_string("5K 100 640K 256M"))
+            print((integerK | integerM | integer)[1, ...].parse_string("5K 100 640K 256M"))
 
         prints::
 
@@ -895,7 +893,7 @@ class ParserElement(ABC):
 
     # cache for left-recursion in Forward references
     recursion_lock = RLock()
-    recursion_memos: DictType[
+    recursion_memos: typing.Dict[
         Tuple[int, "Forward", bool], Tuple[int, Union[ParseResults, Exception]]
     ] = {}
 
@@ -985,7 +983,7 @@ class ParserElement(ABC):
 
     @staticmethod
     def enable_left_recursion(
-        cache_size_limit: OptionalType[int] = None, *, force=False
+        cache_size_limit: typing.Optional[int] = None, *, force=False
     ) -> None:
         """
         Enables "bounded recursion" parsing, which allows for both direct and indirect
@@ -1738,7 +1736,7 @@ class ParserElement(ABC):
 
         Example::
 
-            patt = OneOrMore(Word(alphas))
+            patt = Word(alphas)[1, ...]
             patt.parse_string('ablaj /* comment */ lskjd')
             # -> ['ablaj']
 
@@ -1798,7 +1796,7 @@ class ParserElement(ABC):
             # turn on debugging for wd
             wd.set_debug()
 
-            OneOrMore(term).parse_string("abc 123 xyz 890")
+            term[1, ...].parse_string("abc 123 xyz 890")
 
         prints::
 
@@ -1953,12 +1951,12 @@ class ParserElement(ABC):
         self,
         tests: Union[str, List[str]],
         parse_all: bool = True,
-        comment: OptionalType[Union["ParserElement", str]] = "#",
+        comment: typing.Optional[Union["ParserElement", str]] = "#",
         full_dump: bool = True,
         print_results: bool = True,
         failure_tests: bool = False,
         post_parse: Callable[[str, ParseResults], str] = None,
-        file: OptionalType[TextIO] = None,
+        file: typing.Optional[TextIO] = None,
         with_line_numbers: bool = False,
         *,
         parseAll: bool = True,
@@ -2385,11 +2383,11 @@ class Keyword(Token):
     def __init__(
         self,
         match_string: str = "",
-        ident_chars: OptionalType[str] = None,
+        ident_chars: typing.Optional[str] = None,
         caseless: bool = False,
         *,
         matchString: str = "",
-        identChars: OptionalType[str] = None,
+        identChars: typing.Optional[str] = None,
     ):
         super().__init__()
         identChars = identChars or ident_chars
@@ -2479,7 +2477,7 @@ class CaselessLiteral(Literal):
 
     Example::
 
-        OneOrMore(CaselessLiteral("CMD")).parse_string("cmd CMD Cmd10")
+        CaselessLiteral("CMD")[1, ...].parse_string("cmd CMD Cmd10")
         # -> ['CMD', 'CMD', 'CMD']
 
     (Contrast with example for :class:`CaselessKeyword`.)
@@ -2504,7 +2502,7 @@ class CaselessKeyword(Keyword):
 
     Example::
 
-        OneOrMore(CaselessKeyword("CMD")).parse_string("cmd CMD Cmd10")
+        CaselessKeyword("CMD")[1, ...].parse_string("cmd CMD Cmd10")
         # -> ['CMD', 'CMD']
 
     (Contrast with example for :class:`CaselessLiteral`.)
@@ -2513,10 +2511,10 @@ class CaselessKeyword(Keyword):
     def __init__(
         self,
         match_string: str = "",
-        ident_chars: OptionalType[str] = None,
+        ident_chars: typing.Optional[str] = None,
         *,
         matchString: str = "",
-        identChars: OptionalType[str] = None,
+        identChars: typing.Optional[str] = None,
     ):
         identChars = identChars or ident_chars
         match_string = matchString or match_string
@@ -2680,17 +2678,17 @@ class Word(Token):
     def __init__(
         self,
         init_chars: str = "",
-        body_chars: OptionalType[str] = None,
+        body_chars: typing.Optional[str] = None,
         min: int = 1,
         max: int = 0,
         exact: int = 0,
         as_keyword: bool = False,
-        exclude_chars: OptionalType[str] = None,
+        exclude_chars: typing.Optional[str] = None,
         *,
-        initChars: OptionalType[str] = None,
-        bodyChars: OptionalType[str] = None,
+        initChars: typing.Optional[str] = None,
+        bodyChars: typing.Optional[str] = None,
         asKeyword: bool = False,
-        excludeChars: OptionalType[str] = None,
+        excludeChars: typing.Optional[str] = None,
     ):
         initChars = initChars or init_chars
         bodyChars = bodyChars or body_chars
@@ -2872,10 +2870,10 @@ class Char(_WordRegex):
         self,
         charset: str,
         as_keyword: bool = False,
-        exclude_chars: OptionalType[str] = None,
+        exclude_chars: typing.Optional[str] = None,
         *,
         asKeyword: bool = False,
-        excludeChars: OptionalType[str] = None,
+        excludeChars: typing.Optional[str] = None,
     ):
         asKeyword = asKeyword or as_keyword
         excludeChars = excludeChars or exclude_chars
@@ -3088,18 +3086,18 @@ class QuotedString(Token):
     def __init__(
         self,
         quote_char: str = "",
-        esc_char: OptionalType[str] = None,
-        esc_quote: OptionalType[str] = None,
+        esc_char: typing.Optional[str] = None,
+        esc_quote: typing.Optional[str] = None,
         multiline: bool = False,
         unquote_results: bool = True,
-        end_quote_char: OptionalType[str] = None,
+        end_quote_char: typing.Optional[str] = None,
         convert_whitespace_escapes: bool = True,
         *,
         quoteChar: str = "",
-        escChar: OptionalType[str] = None,
-        escQuote: OptionalType[str] = None,
+        escChar: typing.Optional[str] = None,
+        escQuote: typing.Optional[str] = None,
         unquoteResults: bool = True,
-        endQuoteChar: OptionalType[str] = None,
+        endQuoteChar: typing.Optional[str] = None,
         convertWhitespaceEscapes: bool = True,
     ):
         super().__init__()
@@ -3600,7 +3598,7 @@ class ParseExpression(ParserElement):
     post-processing parsed tokens.
     """
 
-    def __init__(self, exprs: IterableType[ParserElement], savelist: bool = False):
+    def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
         super().__init__(savelist)
         self.exprs: List[ParserElement]
         if isinstance(exprs, _generatorType):
@@ -3767,7 +3765,7 @@ class And(ParseExpression):
     Example::
 
         integer = Word(nums)
-        name_expr = OneOrMore(Word(alphas))
+        name_expr = Word(alphas)[1, ...]
 
         expr = And([integer("id"), name_expr("name"), integer("age")])
         # more easily written as:
@@ -3782,7 +3780,9 @@ class And(ParseExpression):
         def _generateDefaultName(self):
             return "-"
 
-    def __init__(self, exprs_arg: IterableType[ParserElement], savelist: bool = True):
+    def __init__(
+        self, exprs_arg: typing.Iterable[ParserElement], savelist: bool = True
+    ):
         exprs: List[ParserElement] = list(exprs_arg)
         if exprs and Ellipsis in exprs:
             tmp = []
@@ -3926,7 +3926,7 @@ class Or(ParseExpression):
         [['123'], ['3.1416'], ['789']]
     """
 
-    def __init__(self, exprs: IterableType[ParserElement], savelist: bool = False):
+    def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
         super().__init__(exprs, savelist)
         if self.exprs:
             self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
@@ -4081,7 +4081,7 @@ class MatchFirst(ParseExpression):
         print(number.search_string("123 3.1416 789")) #  Better -> [['123'], ['3.1416'], ['789']]
     """
 
-    def __init__(self, exprs: IterableType[ParserElement], savelist: bool = False):
+    def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
         super().__init__(exprs, savelist)
         if self.exprs:
             self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
@@ -4232,7 +4232,7 @@ class Each(ParseExpression):
         - size: 20
     """
 
-    def __init__(self, exprs: IterableType[ParserElement], savelist: bool = True):
+    def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = True):
         super().__init__(exprs, savelist)
         if self.exprs:
             self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
@@ -4568,7 +4568,7 @@ class FollowedBy(ParseElementEnhance):
         label = data_word + FollowedBy(':')
         attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
 
-        OneOrMore(attr_expr).parse_string("shape: SQUARE color: BLACK posn: upper left").pprint()
+        attr_expr[1, ...].parse_string("shape: SQUARE color: BLACK posn: upper left").pprint()
 
     prints::
 
@@ -4619,7 +4619,7 @@ class PrecededBy(ParseElementEnhance):
     """
 
     def __init__(
-        self, expr: Union[ParserElement, str], retreat: OptionalType[int] = None
+        self, expr: Union[ParserElement, str], retreat: typing.Optional[int] = None
     ):
         super().__init__(expr)
         self.expr = self.expr().leave_whitespace()
@@ -4730,7 +4730,7 @@ class NotAny(ParseElementEnhance):
 
         # very crude boolean expression - to support parenthesis groups and
         # operation hierarchy, use infix_notation
-        boolean_expr = boolean_term + ZeroOrMore((AND | OR) + boolean_term)
+        boolean_expr = boolean_term + ((AND | OR) + boolean_term)[...]
 
         # integers that are followed by "." are actually floats
         integer = Word(nums) + ~Char(".")
@@ -4758,9 +4758,9 @@ class _MultipleMatch(ParseElementEnhance):
     def __init__(
         self,
         expr: ParserElement,
-        stop_on: OptionalType[Union[ParserElement, str]] = None,
+        stop_on: typing.Optional[Union[ParserElement, str]] = None,
         *,
-        stopOn: OptionalType[Union[ParserElement, str]] = None,
+        stopOn: typing.Optional[Union[ParserElement, str]] = None,
     ):
         super().__init__(expr)
         stopOn = stopOn or stop_on
@@ -4849,7 +4849,7 @@ class OneOrMore(_MultipleMatch):
         attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).set_parse_action(' '.join))
 
         text = "shape: SQUARE posn: upper left color: BLACK"
-        OneOrMore(attr_expr).parse_string(text).pprint()  # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']]
+        attr_expr[1, ...].parse_string(text).pprint()  # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']]
 
         # use stop_on attribute for OneOrMore to avoid reading label string as part of the data
         attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
@@ -4879,9 +4879,9 @@ class ZeroOrMore(_MultipleMatch):
     def __init__(
         self,
         expr: ParserElement,
-        stop_on: OptionalType[Union[ParserElement, str]] = None,
+        stop_on: typing.Optional[Union[ParserElement, str]] = None,
         *,
-        stopOn: OptionalType[Union[ParserElement, str]] = None,
+        stopOn: typing.Optional[Union[ParserElement, str]] = None,
     ):
         super().__init__(expr, stopOn=stopOn or stop_on)
         self.mayReturnEmpty = True
@@ -5046,7 +5046,7 @@ class SkipTo(ParseElementEnhance):
         other: Union[ParserElement, str],
         include: bool = False,
         ignore: bool = None,
-        fail_on: OptionalType[Union[ParserElement, str]] = None,
+        fail_on: typing.Optional[Union[ParserElement, str]] = None,
         *,
         failOn: Union[ParserElement, str] = None,
     ):
@@ -5143,7 +5143,7 @@ class Forward(ParseElementEnhance):
     parser created using ``Forward``.
     """
 
-    def __init__(self, other: OptionalType[Union[ParserElement, str]] = None):
+    def __init__(self, other: typing.Optional[Union[ParserElement, str]] = None):
         self.caller_frame = traceback.extract_stack(limit=2)[0]
         super().__init__(other, savelist=False)
         self.lshift_line = None
@@ -5395,7 +5395,7 @@ class Combine(TokenConverter):
         join_string: str = "",
         adjacent: bool = True,
         *,
-        joinString: OptionalType[str] = None,
+        joinString: typing.Optional[str] = None,
     ):
         super().__init__(expr)
         joinString = joinString if joinString is not None else join_string
@@ -5482,10 +5482,10 @@ class Dict(TokenConverter):
         attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
 
         # print attributes as plain groups
-        print(OneOrMore(attr_expr).parse_string(text).dump())
+        print(attr_expr[1, ...].parse_string(text).dump())
 
-        # instead of OneOrMore(expr), parse using Dict(OneOrMore(Group(expr))) - Dict will auto-assign names
-        result = Dict(OneOrMore(Group(attr_expr))).parse_string(text)
+        # instead of OneOrMore(expr), parse using Dict(Group(expr)[1, ...]) - Dict will auto-assign names
+        result = Dict(Group(attr_expr)[1, ...]).parse_string(text)
         print(result.dump())
 
         # access named fields as dict entries, or output as dict
@@ -5558,12 +5558,12 @@ class Suppress(TokenConverter):
 
         source = "a, b, c,d"
         wd = Word(alphas)
-        wd_list1 = wd + ZeroOrMore(',' + wd)
+        wd_list1 = wd + (',' + wd)[...]
         print(wd_list1.parse_string(source))
 
         # often, delimiters that are useful during parsing are just in the
         # way afterward - use Suppress to keep them out of the parsed output
-        wd_list2 = wd + ZeroOrMore(Suppress(',') + wd)
+        wd_list2 = wd + (Suppress(',') + wd)[...]
         print(wd_list2.parse_string(source))
 
         # Skipped text (using '...') can be suppressed as well
@@ -5622,7 +5622,7 @@ def trace_parse_action(f: ParseAction) -> ParseAction:
         def remove_duplicate_chars(tokens):
             return ''.join(sorted(set(''.join(tokens))))
 
-        wds = OneOrMore(wd).set_parse_action(remove_duplicate_chars)
+        wds = wd[1, ...].set_parse_action(remove_duplicate_chars)
         print(wds.parse_string("slkdjs sld sldd sdlf sdljf"))
 
     prints::
@@ -5728,18 +5728,18 @@ def token_map(func, *args) -> ParseAction:
 
     Example (compare the last to example in :class:`ParserElement.transform_string`::
 
-        hex_ints = OneOrMore(Word(hexnums)).set_parse_action(token_map(int, 16))
+        hex_ints = Word(hexnums)[1, ...].set_parse_action(token_map(int, 16))
         hex_ints.run_tests('''
             00 11 22 aa FF 0a 0d 1a
             ''')
 
         upperword = Word(alphas).set_parse_action(token_map(str.upper))
-        OneOrMore(upperword).run_tests('''
+        upperword[1, ...].run_tests('''
             my kingdom for a horse
             ''')
 
         wd = Word(alphas).set_parse_action(token_map(str.title))
-        OneOrMore(wd).set_parse_action(' '.join).run_tests('''
+        wd[1, ...].set_parse_action(' '.join).run_tests('''
             now is the winter of our discontent made glorious summer by this sun of york
             ''')
 
@@ -5795,7 +5795,9 @@ punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]")
 
 # build list of built-in expressions, for future reference if a global default value
 # gets updated
-_builtin_exprs = [v for v in vars().values() if isinstance(v, ParserElement)]
+_builtin_exprs: List[ParserElement] = [
+    v for v in vars().values() if isinstance(v, ParserElement)
+]
 
 # backward compatibility names
 tokenMap = token_map
index 2d0c587cbf42126eb903f27c11dc2dde9146c1cc..898644755cbbf9a8d4df562663114a7eb7e11fd1 100644 (file)
@@ -1,9 +1,8 @@
 import railroad
 import pyparsing
-from pkg_resources import resource_filename
+import typing
 from typing import (
     List,
-    Optional,
     NamedTuple,
     Generic,
     TypeVar,
@@ -17,13 +16,41 @@ from io import StringIO
 import inspect
 
 
-with open(resource_filename(__name__, "template.jinja2"), encoding="utf-8") as fp:
-    template = Template(fp.read())
+jinja2_template_source = """\
+<!DOCTYPE html>
+<html>
+<head>
+    {% if not head %}
+        <style type="text/css">
+            .railroad-heading {
+                font-family: monospace;
+            }
+        </style>
+    {% else %}
+        {{ head | safe }}
+    {% endif %}
+</head>
+<body>
+{{ body | safe }}
+{% for diagram in diagrams %}
+    <div class="railroad-group">
+        <h1 class="railroad-heading">{{ diagram.title }}</h1>
+        <div class="railroad-description">{{ diagram.text }}</div>
+        <div class="railroad-svg">
+            {{ diagram.svg }}
+        </div>
+    </div>
+{% endfor %}
+</body>
+</html>
+"""
+
+template = Template(jinja2_template_source)
 
 # Note: ideally this would be a dataclass, but we're supporting Python 3.5+ so we can't do this yet
 NamedDiagram = NamedTuple(
     "NamedDiagram",
-    [("name", str), ("diagram", Optional[railroad.DiagramItem]), ("index", int)],
+    [("name", str), ("diagram", typing.Optional[railroad.DiagramItem]), ("index", int)],
 )
 """
 A simple structure for associating a name with a railroad diagram
@@ -107,6 +134,8 @@ def railroad_to_html(diagrams: List[NamedDiagram], **kwargs) -> str:
     """
     data = []
     for diagram in diagrams:
+        if diagram.diagram is None:
+            continue
         io = StringIO()
         diagram.diagram.writeSvg(io.write)
         title = diagram.name
@@ -135,7 +164,7 @@ def resolve_partial(partial: "EditablePartial[T]") -> T:
 
 def to_railroad(
     element: pyparsing.ParserElement,
-    diagram_kwargs: Optional[dict] = None,
+    diagram_kwargs: typing.Optional[dict] = None,
     vertical: int = 3,
     show_results_names: bool = False,
     show_groups: bool = False,
@@ -216,12 +245,12 @@ class ElementState:
         parent: EditablePartial,
         number: int,
         name: str = None,
-        parent_index: Optional[int] = None,
+        parent_index: typing.Optional[int] = None,
     ):
         #: The pyparsing element that this represents
         self.element: pyparsing.ParserElement = element
         #: The name of the element
-        self.name: str = name
+        self.name: typing.Optional[str] = name
         #: The output Railroad element in an unconverted state
         self.converted: EditablePartial = converted
         #: The parent Railroad element, which we store so that we can extract this if it's duplicated
@@ -229,7 +258,7 @@ class ElementState:
         #: The order in which we found this element, used for sorting diagrams if this is extracted into a diagram
         self.number: int = number
         #: The index of this inside its parent
-        self.parent_index: Optional[int] = parent_index
+        self.parent_index: typing.Optional[int] = parent_index
         #: If true, we should extract this out into a subdiagram
         self.extract: bool = False
         #: If true, all of this element's children have been filled out
@@ -270,7 +299,7 @@ class ConverterState:
     Stores some state that persists between recursions into the element tree
     """
 
-    def __init__(self, diagram_kwargs: Optional[dict] = None):
+    def __init__(self, diagram_kwargs: typing.Optional[dict] = None):
         #: A dictionary mapping ParserElements to state relating to them
         self._element_diagram_states: Dict[int, ElementState] = {}
         #: A dictionary mapping ParserElement IDs to subdiagrams generated from them
@@ -361,14 +390,14 @@ def _apply_diagram_item_enhancements(fn):
 
     def _inner(
         element: pyparsing.ParserElement,
-        parent: Optional[EditablePartial],
+        parent: typing.Optional[EditablePartial],
         lookup: ConverterState = None,
         vertical: int = None,
         index: int = 0,
         name_hint: str = None,
         show_results_names: bool = False,
         show_groups: bool = False,
-    ) -> Optional[EditablePartial]:
+    ) -> typing.Optional[EditablePartial]:
 
         ret = fn(
             element,
@@ -412,14 +441,14 @@ def _visible_exprs(exprs: Iterable[pyparsing.ParserElement]):
 @_apply_diagram_item_enhancements
 def _to_diagram_element(
     element: pyparsing.ParserElement,
-    parent: Optional[EditablePartial],
+    parent: typing.Optional[EditablePartial],
     lookup: ConverterState = None,
     vertical: int = None,
     index: int = 0,
     name_hint: str = None,
     show_results_names: bool = False,
     show_groups: bool = False,
-) -> Optional[EditablePartial]:
+) -> typing.Optional[EditablePartial]:
     """
     Recursively converts a PyParsing Element to a railroad Element
     :param lookup: The shared converter state that keeps track of useful things
@@ -526,7 +555,9 @@ def _to_diagram_element(
         else:
             ret = EditablePartial.from_call(railroad.Group, label="", item="")
     elif isinstance(element, pyparsing.TokenConverter):
-        ret = EditablePartial.from_call(AnnotatedItem, label=type(element).__name__.lower(), item="")
+        ret = EditablePartial.from_call(
+            AnnotatedItem, label=type(element).__name__.lower(), item=""
+        )
     elif isinstance(element, pyparsing.Opt):
         ret = EditablePartial.from_call(railroad.Optional, item="")
     elif isinstance(element, pyparsing.OneOrMore):
diff --git a/setuptools/_vendor/pyparsing/diagram/template.jinja2 b/setuptools/_vendor/pyparsing/diagram/template.jinja2
deleted file mode 100644 (file)
index d2219fb..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    {% if not head %}
-        <style type="text/css">
-            .railroad-heading {
-                font-family: monospace;
-            }
-        </style>
-    {% else %}
-        {{ hear | safe }}
-    {% endif %}
-</head>
-<body>
-{{ body | safe }}
-{% for diagram in diagrams %}
-    <div class="railroad-group">
-        <h1 class="railroad-heading">{{ diagram.title }}</h1>
-        <div class="railroad-description">{{ diagram.text }}</div>
-        <div class="railroad-svg">
-            {{ diagram.svg }}
-        </div>
-    </div>
-{% endfor %}
-</body>
-</html>
index e06513eb00f723c3cb4efc4188141c2a6e303dd0..a38447bb05bd5d503a32651d6046ff8667785c0c 100644 (file)
@@ -2,7 +2,7 @@
 
 import re
 import sys
-from typing import Optional
+import typing
 
 from .util import col, line, lineno, _collapse_string_to_ranges
 from .unicode import pyparsing_unicode as ppu
@@ -25,7 +25,7 @@ class ParseBaseException(Exception):
         self,
         pstr: str,
         loc: int = 0,
-        msg: Optional[str] = None,
+        msg: typing.Optional[str] = None,
         elem=None,
     ):
         self.loc = loc
index be8a3657884806a8e7bf5e8e338b3fc86eeffa5b..9588b3b780159a2a2d23c7f84a4404ec350e2b65 100644 (file)
@@ -1,6 +1,7 @@
 # helpers.py
 import html.entities
 import re
+import typing
 
 from . import __diag__
 from .core import *
@@ -14,8 +15,8 @@ def delimited_list(
     expr: Union[str, ParserElement],
     delim: Union[str, ParserElement] = ",",
     combine: bool = False,
-    min: OptionalType[int] = None,
-    max: OptionalType[int] = None,
+    min: typing.Optional[int] = None,
+    max: typing.Optional[int] = None,
     *,
     allow_trailing_delim: bool = False,
 ) -> ParserElement:
@@ -69,9 +70,9 @@ def delimited_list(
 
 def counted_array(
     expr: ParserElement,
-    int_expr: OptionalType[ParserElement] = None,
+    int_expr: typing.Optional[ParserElement] = None,
     *,
-    intExpr: OptionalType[ParserElement] = None,
+    intExpr: typing.Optional[ParserElement] = None,
 ) -> ParserElement:
     """Helper to define a counted list of expressions.
 
@@ -197,7 +198,7 @@ def match_previous_expr(expr: ParserElement) -> ParserElement:
 
 
 def one_of(
-    strs: Union[IterableType[str], str],
+    strs: Union[typing.Iterable[str], str],
     caseless: bool = False,
     use_regex: bool = True,
     as_keyword: bool = False,
@@ -337,7 +338,7 @@ def dict_of(key: ParserElement, value: ParserElement) -> ParserElement:
 
         text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
         attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
-        print(OneOrMore(attr_expr).parse_string(text).dump())
+        print(attr_expr[1, ...].parse_string(text).dump())
 
         attr_label = label
         attr_value = Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)
@@ -461,7 +462,7 @@ def locatedExpr(expr: ParserElement) -> ParserElement:
 def nested_expr(
     opener: Union[str, ParserElement] = "(",
     closer: Union[str, ParserElement] = ")",
-    content: OptionalType[ParserElement] = None,
+    content: typing.Optional[ParserElement] = None,
     ignore_expr: ParserElement = quoted_string(),
     *,
     ignoreExpr: ParserElement = quoted_string(),
@@ -682,6 +683,8 @@ def make_xml_tags(
     return _makeTags(tag_str, True)
 
 
+any_open_tag: ParserElement
+any_close_tag: ParserElement
 any_open_tag, any_close_tag = make_html_tags(
     Word(alphas, alphanums + "_:").set_name("any tag")
 )
@@ -710,7 +713,7 @@ InfixNotationOperatorSpec = Union[
         InfixNotationOperatorArgType,
         int,
         OpAssoc,
-        OptionalType[ParseAction],
+        typing.Optional[ParseAction],
     ],
     Tuple[
         InfixNotationOperatorArgType,
@@ -840,7 +843,7 @@ def infix_notation(
         if rightLeftAssoc not in (OpAssoc.LEFT, OpAssoc.RIGHT):
             raise ValueError("operator must indicate right or left associativity")
 
-        thisExpr = Forward().set_name(term_name)
+        thisExpr: Forward = Forward().set_name(term_name)
         if rightLeftAssoc is OpAssoc.LEFT:
             if arity == 1:
                 matchExpr = _FB(lastExpr + opExpr) + Group(lastExpr + opExpr[1, ...])
@@ -945,7 +948,7 @@ def indentedBlock(blockStatementExpr, indentStack, indent=True, backup_stacks=[]
         assignment = Group(identifier + "=" + rvalue)
         stmt << (funcDef | assignment | identifier)
 
-        module_body = OneOrMore(stmt)
+        module_body = stmt[1, ...]
 
         parseTree = module_body.parseString(data)
         parseTree.pprint()
@@ -1055,7 +1058,9 @@ python_style_comment = Regex(r"#.*").set_name("Python style comment")
 
 # build list of built-in expressions, for future reference if a global default value
 # gets updated
-_builtin_exprs = [v for v in vars().values() if isinstance(v, ParserElement)]
+_builtin_exprs: List[ParserElement] = [
+    v for v in vars().values() if isinstance(v, ParserElement)
+]
 
 
 # pre-PEP8 compatible names
index bb444df4e5b2405f97d480a73d86927fb3260620..00c9421d3b0362526b8f90dc01e8db73841e0b61 100644 (file)
@@ -287,7 +287,7 @@ class ParseResults:
             print(numlist.parse_string("0 123 321")) # -> ['123', '321']
 
             label = Word(alphas)
-            patt = label("LABEL") + OneOrMore(Word(nums))
+            patt = label("LABEL") + Word(nums)[1, ...]
             print(patt.parse_string("AAB 123 321").dump())
 
             # Use pop() in a parse action to remove named result (note that corresponding value is not
@@ -394,7 +394,7 @@ class ParseResults:
 
         Example::
 
-            patt = OneOrMore(Word(alphas))
+            patt = Word(alphas)[1, ...]
 
             # use a parse action to append the reverse of the matched strings, to make a palindrome
             def make_palindrome(tokens):
@@ -487,7 +487,7 @@ class ParseResults:
 
         Example::
 
-            patt = OneOrMore(Word(alphas))
+            patt = Word(alphas)[1, ...]
             result = patt.parse_string("sldkj lsdkj sldkj")
             # even though the result prints in string-like form, it is actually a pyparsing ParseResults
             print(type(result), result) # -> <class 'pyparsing.ParseResults'> ['sldkj', 'lsdkj', 'sldkj']
@@ -554,7 +554,7 @@ class ParseResults:
             user_data = (Group(house_number_expr)("house_number")
                         | Group(ssn_expr)("ssn")
                         | Group(integer)("age"))
-            user_info = OneOrMore(user_data)
+            user_info = user_data[1, ...]
 
             result = user_info.parse_string("22 111-22-3333 #221B")
             for item in result:
index 991972f3fb2986d2d03951be87b07eeb6bce77bc..84a0ef17078c99e5917db41e3dbaf035fe206d7c 100644 (file)
@@ -1,7 +1,7 @@
 # testing.py
 
 from contextlib import contextmanager
-from typing import Optional
+import typing
 
 from .core import (
     ParserElement,
@@ -237,12 +237,12 @@ class pyparsing_test:
     @staticmethod
     def with_line_numbers(
         s: str,
-        start_line: Optional[int] = None,
-        end_line: Optional[int] = None,
+        start_line: typing.Optional[int] = None,
+        end_line: typing.Optional[int] = None,
         expand_tabs: bool = True,
         eol_mark: str = "|",
-        mark_spaces: Optional[str] = None,
-        mark_control: Optional[str] = None,
+        mark_spaces: typing.Optional[str] = None,
+        mark_control: typing.Optional[str] = None,
     ) -> str:
         """
         Helpful method for debugging a parser - prints a string with line and column numbers.
index 92261487c7af50ede7204c4b65299f2ed333bed1..06526203911de55da3c2a8c5ae73f48024c3f018 100644 (file)
@@ -120,7 +120,18 @@ class pyparsing_unicode(unicode_set):
     A namespace class for defining common language unicode_sets.
     """
 
-    _ranges: UnicodeRangeList = [(32, sys.maxunicode)]
+    # fmt: off
+
+    # define ranges in language character sets
+    _ranges: UnicodeRangeList = [
+        (0x0020, sys.maxunicode),
+    ]
+
+    class BasicMultilingualPlane(unicode_set):
+        "Unicode set for the Basic Multilingual Plane"
+        _ranges: UnicodeRangeList = [
+            (0x0020, 0xFFFF),
+        ]
 
     class Latin1(unicode_set):
         "Unicode set for Latin-1 Unicode Character Range"
@@ -278,11 +289,13 @@ class pyparsing_unicode(unicode_set):
 
     class CJK(Chinese, Japanese, Hangul):
         "Unicode set for combined Chinese, Japanese, and Korean (CJK) Unicode Character Range"
-        pass
 
     class Thai(unicode_set):
         "Unicode set for Thai Unicode Character Range"
-        _ranges: UnicodeRangeList = [(0x0E01, 0x0E3A), (0x0E3F, 0x0E5B)]
+        _ranges: UnicodeRangeList = [
+            (0x0E01, 0x0E3A),
+            (0x0E3F, 0x0E5B)
+        ]
 
     class Arabic(unicode_set):
         "Unicode set for Arabic Unicode Character Range"
@@ -308,7 +321,12 @@ class pyparsing_unicode(unicode_set):
 
     class Devanagari(unicode_set):
         "Unicode set for Devanagari Unicode Character Range"
-        _ranges: UnicodeRangeList = [(0x0900, 0x097F), (0xA8E0, 0xA8FF)]
+        _ranges: UnicodeRangeList = [
+            (0x0900, 0x097F),
+            (0xA8E0, 0xA8FF)
+        ]
+
+    # fmt: on
 
 
 pyparsing_unicode.Japanese._ranges = (
@@ -317,7 +335,9 @@ pyparsing_unicode.Japanese._ranges = (
     + pyparsing_unicode.Japanese.Katakana._ranges
 )
 
-# define ranges in language character sets
+pyparsing_unicode.BMP = pyparsing_unicode.BasicMultilingualPlane
+
+# add language identifiers using language Unicode
 pyparsing_unicode.العربية = pyparsing_unicode.Arabic
 pyparsing_unicode.中文 = pyparsing_unicode.Chinese
 pyparsing_unicode.кириллица = pyparsing_unicode.Cyrillic
index 84c4006cd68d0d9110c668872f6470b1feef3cc9..e9d5bed82a43192c4739559a9b25bcba1b2c29d4 100644 (file)
@@ -1,5 +1,5 @@
 packaging==21.3
-pyparsing==3.0.8
+pyparsing==3.0.9
 ordered-set==3.1.1
 more_itertools==8.8.0
 jaraco.text==3.7.0
index 801ec30527eab6061dc3d1ba6123a7094df17c83..98fb148a5dc62471892c15fe740fcb3531a6428b 100644 (file)
@@ -52,6 +52,7 @@ __all__ = ['get_requires_for_build_sdist',
            'build_wheel',
            'build_sdist',
            'get_requires_for_build_editable',
+           'prepare_metadata_for_build_editable',
            'build_editable',
            '__legacy__',
            'SetupRequirementsError']
@@ -254,20 +255,13 @@ class _ConfigSettingsTranslator:
         >>> list(fn(None))
         []
         >>> list(fn({"editable-mode": "strict"}))
-        ['--strict']
-        >>> list(fn({"editable-mode": "other"}))
-        Traceback (most recent call last):
-           ...
-        ValueError: Invalid value for `editable-mode`: 'other'. Try: 'strict'.
+        ['--mode', 'strict']
         """
         cfg = config_settings or {}
-        if "editable-mode" not in cfg and "editable_mode" not in cfg:
-            return
         mode = cfg.get("editable-mode") or cfg.get("editable_mode")
-        if mode != "strict":
-            msg = f"Invalid value for `editable-mode`: {mode!r}. Try: 'strict'."
-            raise ValueError(msg)
-        yield "--strict"
+        if not mode:
+            return
+        yield from ["--mode", str(mode)]
 
     def _arbitrary_args(self, config_settings: _ConfigSettings) -> Iterator[str]:
         """
@@ -337,7 +331,7 @@ class _BuildMetaBackend(_ConfigSettingsTranslator):
         with _open_setup_script(__file__) as f:
             code = f.read().replace(r'\r\n', r'\n')
 
-        exec(compile(code, __file__, 'exec'), locals())
+        exec(code, locals())
 
     def get_requires_for_build_wheel(self, config_settings=None):
         return self._get_build_requires(config_settings, requirements=['wheel'])
index b966dcea57a2072f98b96dbba75ceb26bd26d2dd..5acd7687d642f06de84b38f5842c41ae14d5f24a 100644 (file)
@@ -2,7 +2,11 @@ from distutils.command.bdist import bdist
 import sys
 
 if 'egg' not in bdist.format_commands:
-    bdist.format_command['egg'] = ('bdist_egg', "Python .egg file")
-    bdist.format_commands.append('egg')
+    try:
+        bdist.format_commands['egg'] = ('bdist_egg', "Python .egg file")
+    except TypeError:
+        # For backward compatibility with older distutils (stdlib)
+        bdist.format_command['egg'] = ('bdist_egg', "Python .egg file")
+        bdist.format_commands.append('egg')
 
 del bdist, sys
index 923a32329f3bac4e6f3d58c8118205e26d64fe22..8b1a3320a7f7b2d2206cb752e7e0eda118163429 100644 (file)
@@ -11,7 +11,7 @@ import itertools
 import stat
 import warnings
 from pathlib import Path
-from typing import Dict, Iterator, List, Optional, Tuple
+from typing import Dict, Iterable, Iterator, List, Optional, Tuple
 
 from setuptools._deprecation_warning import SetuptoolsDeprecationWarning
 from setuptools.extern.more_itertools import unique_everseen
@@ -52,7 +52,6 @@ class build_py(orig.build_py):
 
     def run(self):
         """Build modules, packages, and copy data files to build directory"""
-        # if self.editable_mode or not (self.py_modules and self.packages):
         if not (self.py_modules or self.packages) or self.editable_mode:
             return
 
@@ -175,15 +174,17 @@ class build_py(orig.build_py):
             getattr(self, 'existing_egg_info_dir', None)
             and Path(self.existing_egg_info_dir, "SOURCES.txt").exists()
         ):
-            manifest = Path(self.existing_egg_info_dir, "SOURCES.txt")
+            egg_info_dir = self.existing_egg_info_dir
+            manifest = Path(egg_info_dir, "SOURCES.txt")
             files = manifest.read_text(encoding="utf-8").splitlines()
         else:
             self.run_command('egg_info')
             ei_cmd = self.get_finalized_command('egg_info')
+            egg_info_dir = ei_cmd.egg_info
             files = ei_cmd.filelist.files
 
         check = _IncludePackageDataAbuse()
-        for path in files:
+        for path in _filter_absolute_egg_info(files, egg_info_dir):
             d, f = os.path.split(assert_relative(path))
             prev = None
             oldf = f
@@ -346,3 +347,15 @@ class _IncludePackageDataAbuse:
             msg = textwrap.dedent(self.MESSAGE).format(importable=importable)
             warnings.warn(msg, SetuptoolsDeprecationWarning, stacklevel=2)
             self._already_warned.add(importable)
+
+
+def _filter_absolute_egg_info(files: Iterable[str], egg_info: str) -> Iterator[str]:
+    """
+    ``build_meta`` may try to create egg_info outside of the project directory,
+    and this can be problematic for certain plugins (reported in issue #3500).
+    This function should filter this case of invalid files out.
+    """
+    egg_info_name = Path(egg_info).name
+    for file in files:
+        if not (egg_info_name in file and os.path.isabs(file)):
+            yield file
index 5e205a4d1947a6faeaea4da9306d917990e035b6..1bb7ddfb7021aa2a9a93097ee0e4cd4f0fe60c70 100644 (file)
@@ -18,6 +18,7 @@ import sys
 import traceback
 import warnings
 from contextlib import suppress
+from enum import Enum
 from inspect import cleandoc
 from itertools import chain
 from pathlib import Path
@@ -32,10 +33,10 @@ from typing import (
     Optional,
     Tuple,
     TypeVar,
-    Union
+    Union,
 )
 
-from setuptools import Command, errors, namespaces
+from setuptools import Command, SetuptoolsDeprecationWarning, errors, namespaces
 from setuptools.discovery import find_package_path
 from setuptools.dist import Distribution
 
@@ -54,34 +55,70 @@ _P = TypeVar("_P", bound=_Path)
 _logger = logging.getLogger(__name__)
 
 
+class _EditableMode(Enum):
+    """
+    Possible editable installation modes:
+    `lenient` (new files automatically added to the package - DEFAULT);
+    `strict` (requires a new installation when files are added/removed); or
+    `compat` (attempts to emulate `python setup.py develop` - DEPRECATED).
+    """
+
+    STRICT = "strict"
+    LENIENT = "lenient"
+    COMPAT = "compat"  # TODO: Remove `compat` after Dec/2022.
+
+    @classmethod
+    def convert(cls, mode: Optional[str]) -> "_EditableMode":
+        if not mode:
+            return _EditableMode.LENIENT  # default
+
+        _mode = mode.upper()
+        if _mode not in _EditableMode.__members__:
+            raise errors.OptionError(f"Invalid editable mode: {mode!r}. Try: 'strict'.")
+
+        if _mode == "COMPAT":
+            msg = """
+            The 'compat' editable mode is transitional and will be removed
+            in future versions of `setuptools`.
+            Please adapt your code accordingly to use either the 'strict' or the
+            'lenient' modes.
+
+            For more information, please check:
+            https://setuptools.pypa.io/en/latest/userguide/development_mode.html
+            """
+            warnings.warn(msg, SetuptoolsDeprecationWarning)
+
+        return _EditableMode[_mode]
+
+
 _STRICT_WARNING = """
 New or renamed files may not be automatically picked up without a new installation.
 """
 
-_LAX_WARNING = """
+_LENIENT_WARNING = """
 Options like `package-data`, `include/exclude-package-data` or
 `packages.find.exclude/include` may have no effect.
 """
 
 
 class editable_wheel(Command):
-    """Build 'editable' wheel for development"""
+    """Build 'editable' wheel for development.
+    (This command is reserved for internal use of setuptools).
+    """
 
     description = "create a PEP 660 'editable' wheel"
 
     user_options = [
         ("dist-dir=", "d", "directory to put final built distributions in"),
         ("dist-info-dir=", "I", "path to a pre-build .dist-info directory"),
-        ("strict", None, "perform an strict installation"),
+        ("mode=", None, cleandoc(_EditableMode.__doc__ or "")),
     ]
 
-    boolean_options = ["strict"]
-
     def initialize_options(self):
         self.dist_dir = None
         self.dist_info_dir = None
         self.project_dir = None
-        self.strict = False
+        self.mode = None
 
     def finalize_options(self):
         dist = self.distribution
@@ -131,7 +168,7 @@ class editable_wheel(Command):
         if not dist.namespace_packages:
             return
 
-        src_root = Path(self.project_dir, self.pakcage_dir.get("", ".")).resolve()
+        src_root = Path(self.project_dir, self.package_dir.get("", ".")).resolve()
         installer = _NamespaceInstaller(dist, installation_dir, pth_prefix, src_root)
         installer.install_namespaces()
 
@@ -267,16 +304,18 @@ class editable_wheel(Command):
         """Decides which strategy to use to implement an editable installation."""
         build_name = f"__editable__.{name}-{tag}"
         project_dir = Path(self.project_dir)
+        mode = _EditableMode.convert(self.mode)
 
-        if self.strict or os.getenv("SETUPTOOLS_EDITABLE", None) == "strict":
+        if mode is _EditableMode.STRICT:
             auxiliary_dir = _empty_dir(Path(self.project_dir, "build", build_name))
             return _LinkTree(self.distribution, name, auxiliary_dir, build_lib)
 
         packages = _find_packages(self.distribution)
         has_simple_layout = _simple_layout(packages, self.package_dir, project_dir)
-        if set(self.package_dir) == {""} and has_simple_layout:
+        is_compat_mode = mode is _EditableMode.COMPAT
+        if set(self.package_dir) == {""} and has_simple_layout or is_compat_mode:
             # src-layout(ish) is relatively safe for a simple pth file
-            src_dir = self.package_dir[""]
+            src_dir = self.package_dir.get("", ".")
             return _StaticPth(self.distribution, name, [Path(project_dir, src_dir)])
 
         # Use a MetaPathFinder to avoid adding accidental top-level packages/modules
@@ -310,7 +349,7 @@ class _StaticPth:
         Editable install will be performed using .pth file to extend `sys.path` with:
         {self.path_entries!r}
         """
-        _logger.warning(msg + _LAX_WARNING)
+        _logger.warning(msg + _LENIENT_WARNING)
         return self
 
     def __exit__(self, _exc_type, _exc_value, _traceback):
@@ -400,7 +439,7 @@ class _TopLevelFinder:
         roots = _find_package_roots(top_level, package_dir, src_root)
 
         namespaces_: Dict[str, List[str]] = dict(chain(
-            _find_namespaces(self.dist.packages, roots),
+            _find_namespaces(self.dist.packages or [], roots),
             ((ns, []) for ns in _find_virtual_namespaces(roots)),
         ))
 
@@ -414,7 +453,7 @@ class _TopLevelFinder:
 
     def __enter__(self):
         msg = "Editable install will be performed using a meta path finder.\n"
-        _logger.warning(msg + _LAX_WARNING)
+        _logger.warning(msg + _LENIENT_WARNING)
         return self
 
     def __exit__(self, _exc_type, _exc_value, _traceback):
@@ -633,6 +672,7 @@ class _NamespaceInstaller(namespaces.Installer):
         self.installation_dir = installation_dir
         self.editable_name = editable_name
         self.outputs = []
+        self.dry_run = False
 
     def _get_target(self):
         """Installation target."""
index a5480005c7aeb605d47ad9607f7b1275c0a2e8a0..3263f07f4877ad6f9ecc881c12df29a4a65b03f4 100644 (file)
@@ -59,6 +59,9 @@ class upload_docs(upload):
         self.target_dir = None
 
     def finalize_options(self):
+        log.warn(
+            "Upload_docs command is deprecated. Use Read the Docs "
+            "(https://readthedocs.org) instead.")
         upload.finalize_options(self)
         if self.upload_dir is None:
             if self.has_sphinx():
@@ -70,8 +73,6 @@ class upload_docs(upload):
         else:
             self.ensure_dirname('upload_dir')
             self.target_dir = self.upload_dir
-        if 'pypi.python.org' in self.repository:
-            log.warn("Upload_docs command is deprecated for PyPi. Use RTD instead.")
         self.announce('Using upload directory %s' % self.target_dir)
 
     def create_zipfile(self, filename):
index 35458d8e70a4d687064ade61404abeff20fb7a58..1a5153ad4fa51e14c83b8bb00345354d42ed3f0a 100644 (file)
@@ -25,7 +25,7 @@ def _deprecation_notice(fn: Fn) -> Fn:
         to access a backward compatible API, but this module is provisional
         and might be removed in the future.
         """
-        warnings.warn(dedent(msg), SetuptoolsDeprecationWarning)
+        warnings.warn(dedent(msg), SetuptoolsDeprecationWarning, stacklevel=2)
         return fn(*args, **kwargs)
 
     return cast(Fn, _wrapper)
index 0e9e3c9cd003f0b72cd1355ba06ccc31795b55a2..9ff0c87fa82c8876577f4a2f0cf0ba93fcf3d71b 100644 (file)
@@ -41,10 +41,14 @@ def validate(config: dict, filepath: _Path) -> bool:
     try:
         return validator.validate(config)
     except validator.ValidationError as ex:
-        _logger.error(f"configuration error: {ex.summary}")  # type: ignore
-        _logger.debug(ex.details)  # type: ignore
-        error = ValueError(f"invalid pyproject.toml config: {ex.name}")  # type: ignore
-        raise error from None
+        summary = f"configuration error: {ex.summary}"
+        if ex.name.strip("`") != "project":
+            # Probably it is just a field missing/misnamed, not worthy the verbosity...
+            _logger.debug(summary)
+            _logger.debug(ex.details)
+
+        error = f"invalid pyproject.toml config: {ex.name}."
+        raise ValueError(f"{error}\n{summary}") from None
 
 
 def apply_configuration(
index af128968a5a6d073ddd59f0a30c9b85fa291fa87..c2a974de6368c9f4f9b9943c94a457227370f143 100644 (file)
@@ -5,8 +5,9 @@ Load setuptools configuration from ``setup.cfg`` files.
 """
 import os
 
-import warnings
+import contextlib
 import functools
+import warnings
 from collections import defaultdict
 from functools import partial
 from functools import wraps
@@ -14,6 +15,7 @@ from typing import (TYPE_CHECKING, Callable, Any, Dict, Generic, Iterable, List,
                     Optional, Tuple, TypeVar, Union)
 
 from distutils.errors import DistutilsOptionError, DistutilsFileError
+from setuptools.extern.packaging.requirements import Requirement, InvalidRequirement
 from setuptools.extern.packaging.version import Version, InvalidVersion
 from setuptools.extern.packaging.specifiers import SpecifierSet
 from setuptools._deprecation_warning import SetuptoolsDeprecationWarning
@@ -174,6 +176,39 @@ def parse_configuration(
     return meta, options
 
 
+def _warn_accidental_env_marker_misconfig(label: str, orig_value: str, parsed: list):
+    """Because users sometimes misinterpret this configuration:
+
+    [options.extras_require]
+    foo = bar;python_version<"4"
+
+    It looks like one requirement with an environment marker
+    but because there is no newline, it's parsed as two requirements
+    with a semicolon as separator.
+
+    Therefore, if:
+        * input string does not contain a newline AND
+        * parsed result contains two requirements AND
+        * parsing of the two parts from the result ("<first>;<second>")
+        leads in a valid Requirement with a valid marker
+    a UserWarning is shown to inform the user about the possible problem.
+    """
+    if "\n" in orig_value or len(parsed) != 2:
+        return
+
+    with contextlib.suppress(InvalidRequirement):
+        original_requirements_str = ";".join(parsed)
+        req = Requirement(original_requirements_str)
+        if req.marker is not None:
+            msg = (
+                f"One of the parsed requirements in `{label}` "
+                f"looks like a valid environment marker: '{parsed[1]}'\n"
+                "Make sure that the config is correct and check "
+                "https://setuptools.pypa.io/en/latest/userguide/declarative_config.html#opt-2"  # noqa: E501
+            )
+            warnings.warn(msg, UserWarning)
+
+
 class ConfigHandler(Generic[Target]):
     """Handles metadata supplied in configuration files."""
 
@@ -397,33 +432,43 @@ class ConfigHandler(Generic[Target]):
         return parse
 
     @classmethod
-    def _parse_section_to_dict(cls, section_options, values_parser=None):
+    def _parse_section_to_dict_with_key(cls, section_options, values_parser):
         """Parses section options into a dictionary.
 
-        Optionally applies a given parser to values.
+        Applies a given parser to each option in a section.
 
         :param dict section_options:
-        :param callable values_parser:
+        :param callable values_parser: function with 2 args corresponding to key, value
         :rtype: dict
         """
         value = {}
-        values_parser = values_parser or (lambda val: val)
         for key, (_, val) in section_options.items():
-            value[key] = values_parser(val)
+            value[key] = values_parser(key, val)
         return value
 
+    @classmethod
+    def _parse_section_to_dict(cls, section_options, values_parser=None):
+        """Parses section options into a dictionary.
+
+        Optionally applies a given parser to each value.
+
+        :param dict section_options:
+        :param callable values_parser: function with 1 arg corresponding to option value
+        :rtype: dict
+        """
+        parser = (lambda _, v: values_parser(v)) if values_parser else (lambda _, v: v)
+        return cls._parse_section_to_dict_with_key(section_options, parser)
+
     def parse_section(self, section_options):
         """Parses configuration file section.
 
         :param dict section_options:
         """
         for (name, (_, value)) in section_options.items():
-            try:
+            with contextlib.suppress(KeyError):
+                # Keep silent for a new option may appear anytime.
                 self[name] = value
 
-            except KeyError:
-                pass  # Keep silent for a new option may appear anytime.
-
     def parse(self):
         """Parses configuration file items from one
         or more related sections.
@@ -579,9 +624,10 @@ class ConfigOptionsHandler(ConfigHandler["Distribution"]):
     def _parse_file_in_root(self, value):
         return self._parse_file(value, root_dir=self.root_dir)
 
-    def _parse_requirements_list(self, value):
+    def _parse_requirements_list(self, label: str, value: str):
         # Parse a requirements list, either by reading in a `file:`, or a list.
         parsed = self._parse_list_semicolon(self._parse_file_in_root(value))
+        _warn_accidental_env_marker_misconfig(label, value, parsed)
         # Filter it to only include lines that are not comments. `parse_list`
         # will have stripped each line and filtered out empties.
         return [line for line in parsed if not line.startswith("#")]
@@ -607,7 +653,9 @@ class ConfigOptionsHandler(ConfigHandler["Distribution"]):
                 "consider using implicit namespaces instead (PEP 420).",
                 SetuptoolsDeprecationWarning,
             ),
-            'install_requires': self._parse_requirements_list,
+            'install_requires': partial(
+                self._parse_requirements_list, "install_requires"
+            ),
             'setup_requires': self._parse_list_semicolon,
             'tests_require': self._parse_list_semicolon,
             'packages': self._parse_packages,
@@ -698,10 +746,11 @@ class ConfigOptionsHandler(ConfigHandler["Distribution"]):
 
         :param dict section_options:
         """
-        parsed = self._parse_section_to_dict(
+        parsed = self._parse_section_to_dict_with_key(
             section_options,
-            self._parse_requirements_list,
+            lambda k, v: self._parse_requirements_list(f"extras_require[{k}]", v)
         )
+
         self['extras_require'] = parsed
 
     def parse_section_data_files(self, section_options):
index b9a2bad3212e0374ede9f04ff2e3df6c48001712..58c023f6b4479c631f382e5062932793d2bee26b 100644 (file)
@@ -73,7 +73,9 @@ class Extension(_Extension):
 
     :keyword list[str] runtime_library_dirs:
       list of directories to search for C/C++ libraries at run time
-      (for shared extensions, this is when the extension is loaded)
+      (for shared extensions, this is when the extension is loaded).
+      Setting this will cause an exception during build on Windows
+      platforms.
 
     :keyword list[str] extra_objects:
       list of extra files to link with (eg. object files not implied
@@ -116,6 +118,9 @@ class Extension(_Extension):
 
     :keyword bool py_limited_api:
       opt-in flag for the usage of :doc:`Python's limited API <python:c-api/stable>`.
+
+    :raises setuptools.errors.PlatformError: if 'runtime_library_dirs' is
+      specified on Windows. (since v63)
     """
 
     def __init__(self, name, sources, *args, **kw):
index 200312b54d9eee38c05467d46337c0ac87d204f1..811328f52becdd109de7ff75e9a0fe44a1d2bff1 100644 (file)
@@ -1,4 +1,3 @@
-import logging
 import re
 from configparser import ConfigParser
 from inspect import cleandoc
@@ -307,7 +306,7 @@ def test_ignore_unrelated_config(tmp_path, example):
 
 
 @pytest.mark.parametrize(
-    "example, error_msg, value_shown_in_debug",
+    "example, error_msg",
     [
         (
             """
@@ -316,30 +315,18 @@ def test_ignore_unrelated_config(tmp_path, example):
             version = "1.2"
             requires = ['pywin32; platform_system=="Windows"' ]
             """,
-            "configuration error: `project` must not contain {'requires'} properties",
-            '"requires": ["pywin32; platform_system==\\"Windows\\""]',
+            "configuration error: .project. must not contain ..requires.. properties",
         ),
     ],
 )
-def test_invalid_example(tmp_path, caplog, example, error_msg, value_shown_in_debug):
-    caplog.set_level(logging.DEBUG)
+def test_invalid_example(tmp_path, example, error_msg):
     pyproject = tmp_path / "pyproject.toml"
     pyproject.write_text(cleandoc(example))
 
-    caplog.clear()
-    with pytest.raises(ValueError, match="invalid pyproject.toml"):
+    pattern = re.compile(f"invalid pyproject.toml.*{error_msg}.*", re.M | re.S)
+    with pytest.raises(ValueError, match=pattern):
         read_configuration(pyproject)
 
-    # Make sure the logs give guidance to the user
-    error_log = caplog.record_tuples[0]
-    assert error_log[1] == logging.ERROR
-    assert error_msg in error_log[2]
-
-    debug_log = caplog.record_tuples[1]
-    assert debug_log[1] == logging.DEBUG
-    debug_msg = "".join(line.strip() for line in debug_log[2].splitlines())
-    assert value_shown_in_debug in debug_msg
-
 
 @pytest.mark.parametrize("config", ("", "[tool.something]\nvalue = 42"))
 def test_empty(tmp_path, config):
index b2563a103d8b6769cd77af6ab45215af72f76a36..d2964fdaf471dc76743668963a9cf80f7d04b6bb 100644 (file)
@@ -716,6 +716,51 @@ class TestOptions:
             }
             assert dist.metadata.provides_extras == set(['pdf', 'rest'])
 
+    @pytest.mark.parametrize(
+        "config",
+        [
+            "[options.extras_require]\nfoo = bar;python_version<'3'",
+            "[options.extras_require]\nfoo = bar;os_name=='linux'",
+            "[options.extras_require]\nfoo = bar;python_version<'3'\n",
+            "[options.extras_require]\nfoo = bar;os_name=='linux'\n",
+            "[options]\ninstall_requires = bar;python_version<'3'",
+            "[options]\ninstall_requires = bar;os_name=='linux'",
+            "[options]\ninstall_requires = bar;python_version<'3'\n",
+            "[options]\ninstall_requires = bar;os_name=='linux'\n",
+        ],
+    )
+    def test_warn_accidental_env_marker_misconfig(self, config, tmpdir):
+        fake_env(tmpdir, config)
+        match = (
+            r"One of the parsed requirements in `(install_requires|extras_require.+)` "
+            "looks like a valid environment marker.*"
+        )
+        with pytest.warns(UserWarning, match=match):
+            with get_dist(tmpdir) as _:
+                pass
+
+    @pytest.mark.parametrize(
+        "config",
+        [
+            "[options.extras_require]\nfoo =\n    bar;python_version<'3'",
+            "[options.extras_require]\nfoo = bar;baz\nboo = xxx;yyy",
+            "[options.extras_require]\nfoo =\n    bar;python_version<'3'\n",
+            "[options.extras_require]\nfoo = bar;baz\nboo = xxx;yyy\n",
+            "[options.extras_require]\nfoo =\n    bar\n    python_version<'3'\n",
+            "[options]\ninstall_requires =\n    bar;python_version<'3'",
+            "[options]\ninstall_requires = bar;baz\nboo = xxx;yyy",
+            "[options]\ninstall_requires =\n    bar;python_version<'3'\n",
+            "[options]\ninstall_requires = bar;baz\nboo = xxx;yyy\n",
+            "[options]\ninstall_requires =\n    bar\n    python_version<'3'\n",
+        ],
+    )
+    def test_nowarn_accidental_env_marker_misconfig(self, config, tmpdir, recwarn):
+        fake_env(tmpdir, config)
+        with get_dist(tmpdir) as _:
+            pass
+        # The examples are valid, no warnings shown
+        assert not any(w.category == UserWarning for w in recwarn)
+
     def test_dash_preserved_extras_require(self, tmpdir):
         fake_env(tmpdir, '[options.extras_require]\n' 'foo-a = foo\n' 'foo_b = test\n')
 
index 9d11047bc5baea0419b5bad57a09ec754eab80f5..b44e32fcb8e5af484b51ea8eec7aea8099f90b7f 100644 (file)
@@ -27,7 +27,7 @@ from .helpers import Archive, run
 
 pytestmark = pytest.mark.integration
 
-LATEST, = list(Enum("v", "LATEST"))
+LATEST, = Enum("v", "LATEST")
 """Default version to be checked"""
 # There are positive and negative aspects of checking the latest version of the
 # packages.
@@ -72,11 +72,11 @@ VIRTUALENV = (sys.executable, "-m", "virtualenv")
 # means it will download the previous stable version of setuptools.
 # `pip` flags can avoid that (the version of setuptools under test
 # should be the one to be used)
-SDIST_OPTIONS = (
+INSTALL_OPTIONS = (
     "--ignore-installed",
     "--no-build-isolation",
-    # We don't need "--no-binary :all:" since we specify the path to the sdist.
-    # It also helps with performance, since dependencies can come from wheels.
+    # Omit "--no-binary :all:" the sdist is supplied directly.
+    # Allows dependencies as wheels.
 )
 # The downside of `--no-build-isolation` is that pip will not download build
 # dependencies. The test script will have to also handle that.
@@ -125,7 +125,7 @@ def test_install_sdist(package, version, tmp_path, venv_python, setuptools_wheel
     # Use a virtualenv to simulate PEP 517 isolation
     # but install fresh setuptools wheel to ensure the version under development
     run([*venv_pip, "install", "-I", setuptools_wheel])
-    run([*venv_pip, "install", *SDIST_OPTIONS, sdist])
+    run([*venv_pip, "install", *INSTALL_OPTIONS, sdist])
 
     # Execute a simple script to make sure the package was installed correctly
     script = f"import {package}; print(getattr({package}, '__version__', 0))"
@@ -165,17 +165,9 @@ def retrieve_pypi_sdist_metadata(package, version):
         raise ValueError(f"Release for {package} {version} was yanked")
 
     version = metadata["info"]["version"]
-    release = metadata["releases"][version]
-    dists = [d for d in release if d["packagetype"] == "sdist"]
-    if len(dists) == 0:
-        raise ValueError(f"No sdist found for {package} {version}")
-
-    for dist in dists:
-        if dist["filename"].endswith(".tar.gz"):
-            return dist
-
-    # Not all packages are publishing tar.gz
-    return dist
+    release = metadata["releases"][version] if version is LATEST else metadata["urls"]
+    sdist, = filter(lambda d: d["packagetype"] == "sdist", release)
+    return sdist
 
 
 def download(url, dest, md5_digest):
@@ -193,7 +185,7 @@ def download(url, dest, md5_digest):
 def build_deps(package, sdist_file):
     """Find out what are the build dependencies for a package.
 
-    We need to "manually" install them, since pip will not install build
+    "Manually" install them, since pip will not install build
     deps with `--no-build-isolation`.
     """
     import tomli as toml
@@ -202,9 +194,7 @@ def build_deps(package, sdist_file):
     # testenv without tomli
 
     archive = Archive(sdist_file)
-    pyproject = _read_pyproject(archive)
-
-    info = toml.loads(pyproject)
+    info = toml.loads(_read_pyproject(archive))
     deps = info.get("build-system", {}).get("requires", [])
     deps += EXTRA_BUILD_DEPS.get(package, [])
     # Remove setuptools from requirements (and deduplicate)
@@ -213,7 +203,9 @@ def build_deps(package, sdist_file):
 
 
 def _read_pyproject(archive):
-    for member in archive:
-        if os.path.basename(archive.get_name(member)) == "pyproject.toml":
-            return archive.get_content(member)
-    return ""
+    contents = (
+        archive.get_content(member)
+        for member in archive
+        if os.path.basename(archive.get_name(member)) == "pyproject.toml"
+    )
+    return next(contents, "")
index 026c8ae492caec3e2f3132fab2f0d0d3a180ea92..e70c71bdd0d479b7e0ebc36910ff04d52fb3bcfd 100644 (file)
@@ -5,7 +5,6 @@ import signal
 import tarfile
 import importlib
 import contextlib
-import subprocess
 from concurrent import futures
 import re
 from zipfile import ZipFile
@@ -644,7 +643,7 @@ class TestBuildMetaBackend:
         build_backend = self.get_build_backend()
         assert not Path("build").exists()
 
-        cfg = {"--global-option": "--strict"}
+        cfg = {"--global-option": ["--mode", "strict"]}
         build_backend.prepare_metadata_for_build_editable("_meta", cfg)
         build_backend.build_editable("temp", cfg, "_meta")
 
@@ -652,10 +651,10 @@ class TestBuildMetaBackend:
 
     def test_editable_without_config_settings(self, tmpdir_cwd):
         """
-        Sanity check to ensure tests with --strict are different from the ones
-        without --strict.
+        Sanity check to ensure tests with --mode=strict are different from the ones
+        without --mode.
 
-        --strict should create a local directory with a package tree.
+        --mode=strict should create a local directory with a package tree.
         The directory should not get created otherwise.
         """
         path.build(self._simple_pyproject_example)
@@ -666,7 +665,7 @@ class TestBuildMetaBackend:
 
     @pytest.mark.parametrize(
         "config_settings", [
-            {"--build-option": "--strict"},
+            {"--build-option": ["--mode", "strict"]},
             {"editable-mode": "strict"},
         ]
     )
@@ -833,7 +832,7 @@ class TestBuildMetaLegacyBackend(TestBuildMetaBackend):
         build_backend.build_sdist("temp")
 
 
-def test_legacy_editable_install(tmpdir, tmpdir_cwd):
+def test_legacy_editable_install(venv, tmpdir, tmpdir_cwd):
     pyproject = """
     [build-system]
     requires = ["setuptools"]
@@ -845,13 +844,13 @@ def test_legacy_editable_install(tmpdir, tmpdir_cwd):
     path.build({"pyproject.toml": DALS(pyproject), "mymod.py": ""})
 
     # First: sanity check
-    cmd = [sys.executable, "-m", "pip", "install", "--no-build-isolation", "-e", "."]
-    output = str(subprocess.check_output(cmd, cwd=tmpdir), "utf-8").lower()
+    cmd = ["pip", "install", "--no-build-isolation", "-e", "."]
+    output = str(venv.run(cmd, cwd=tmpdir), "utf-8").lower()
     assert "running setup.py develop for myproj" not in output
     assert "created wheel for myproj" in output
 
     # Then: real test
     env = {**os.environ, "SETUPTOOLS_ENABLE_FEATURES": "legacy-editable"}
-    cmd = [sys.executable, "-m", "pip", "install", "--no-build-isolation", "-e", "."]
-    output = str(subprocess.check_output(cmd, cwd=tmpdir, env=env), "utf-8").lower()
+    cmd = ["pip", "install", "--no-build-isolation", "-e", "."]
+    output = str(venv.run(cmd, cwd=tmpdir, env=env), "utf-8").lower()
     assert "running setup.py develop for myproj" in output
index 2b32edbc5910c61249d9e5b0042524a0844b9cbf..77738f23fa99c31c78138ba12d4f60a9ef87e423 100644 (file)
@@ -278,8 +278,8 @@ def test_get_outputs(tmpdir_cwd):
     build_py.editable_mode = True
     build_py.ensure_finalized()
     build_lib = build_py.build_lib.replace(os.sep, "/")
-    outputs = [x.replace(os.sep, "/") for x in build_py.get_outputs()]
-    assert outputs == [
+    outputs = {x.replace(os.sep, "/") for x in build_py.get_outputs()}
+    assert outputs == {
         f"{build_lib}/mypkg/__init__.py",
         f"{build_lib}/mypkg/resource_file.txt",
         f"{build_lib}/mypkg/sub1/__init__.py",
@@ -287,7 +287,7 @@ def test_get_outputs(tmpdir_cwd):
         f"{build_lib}/mypkg/sub2/mod2.py",
         f"{build_lib}/mypkg/sub2/nested/__init__.py",
         f"{build_lib}/mypkg/sub2/nested/mod3.py",
-    ]
+    }
     mapping = {
         k.replace(os.sep, "/"): v.replace(os.sep, "/")
         for k, v in build_py.get_output_mapping().items()
index a76ab082330f5936ef53b8b2437dbe2c61191d52..ea31cb46103fe4e95a68d37bddfaaceadc09e4b0 100644 (file)
@@ -29,11 +29,11 @@ from setuptools.command.editable_wheel import (
 from setuptools.dist import Distribution
 
 
-@pytest.fixture(params=["strict", "lax"])
-def editable_mode(request, monkeypatch):
+@pytest.fixture(params=["strict", "lenient"])
+def editable_opts(request):
     if request.param == "strict":
-        monkeypatch.setenv("SETUPTOOLS_EDITABLE", "strict")
-    yield
+        return ["--config-settings", "editable-mode=strict"]
+    return []
 
 
 EXAMPLE = {
@@ -114,14 +114,14 @@ SETUP_SCRIPT_STUB = "__import__('setuptools').setup()"
         EXAMPLE,  # No setup.py script
     ]
 )
-def test_editable_with_pyproject(tmp_path, venv, files, editable_mode):
+def test_editable_with_pyproject(tmp_path, venv, files, editable_opts):
     project = tmp_path / "mypkg"
     project.mkdir()
     jaraco.path.build(files, prefix=project)
 
     cmd = [venv.exe(), "-m", "pip", "install",
            "--no-build-isolation",  # required to force current version of setuptools
-           "-e", str(project)]
+           "-e", str(project), *editable_opts]
     print(str(subprocess.check_output(cmd), "utf-8"))
 
     cmd = [venv.exe(), "-m", "mypkg"]
@@ -132,7 +132,7 @@ def test_editable_with_pyproject(tmp_path, venv, files, editable_mode):
     assert subprocess.check_output(cmd).strip() == b"3.14159.post0 foobar 42"
 
 
-def test_editable_with_flat_layout(tmp_path, venv, editable_mode):
+def test_editable_with_flat_layout(tmp_path, venv, editable_opts):
     files = {
         "mypkg": {
             "pyproject.toml": dedent("""\
@@ -157,26 +157,63 @@ def test_editable_with_flat_layout(tmp_path, venv, editable_mode):
 
     cmd = [venv.exe(), "-m", "pip", "install",
            "--no-build-isolation",  # required to force current version of setuptools
-           "-e", str(project)]
+           "-e", str(project), *editable_opts]
     print(str(subprocess.check_output(cmd), "utf-8"))
     cmd = [venv.exe(), "-c", "import pkg, mod; print(pkg.a, mod.b)"]
     assert subprocess.check_output(cmd).strip() == b"4 2"
 
 
+def test_editable_with_single_module(tmp_path, venv, editable_opts):
+    files = {
+        "mypkg": {
+            "pyproject.toml": dedent("""\
+                [build-system]
+                requires = ["setuptools", "wheel"]
+                build-backend = "setuptools.build_meta"
+
+                [project]
+                name = "mod"
+                version = "3.14159"
+
+                [tool.setuptools]
+                py-modules = ["mod"]
+                """),
+            "mod.py": "b = 2",
+        },
+    }
+    jaraco.path.build(files, prefix=tmp_path)
+    project = tmp_path / "mypkg"
+
+    cmd = [venv.exe(), "-m", "pip", "install",
+           "--no-build-isolation",  # required to force current version of setuptools
+           "-e", str(project), *editable_opts]
+    print(str(subprocess.check_output(cmd), "utf-8"))
+    cmd = [venv.exe(), "-c", "import mod; print(mod.b)"]
+    assert subprocess.check_output(cmd).strip() == b"2"
+
+
 class TestLegacyNamespaces:
     """Ported from test_develop"""
 
-    def test_namespace_package_importable(self, venv, tmp_path, editable_mode):
+    def test_namespace_package_importable(self, venv, tmp_path, editable_opts):
         """
         Installing two packages sharing the same namespace, one installed
         naturally using pip or `--single-version-externally-managed`
         and the other installed in editable mode should leave the namespace
         intact and both packages reachable by import.
         """
+        build_system = """\
+        [build-system]
+        requires = ["setuptools"]
+        build-backend = "setuptools.build_meta"
+        """
         pkg_A = namespaces.build_namespace_package(tmp_path, 'myns.pkgA')
         pkg_B = namespaces.build_namespace_package(tmp_path, 'myns.pkgB')
+        (pkg_A / "pyproject.toml").write_text(build_system, encoding="utf-8")
+        (pkg_B / "pyproject.toml").write_text(build_system, encoding="utf-8")
         # use pip to install to the target directory
-        opts = ["--no-build-isolation"]  # force current version of setuptools
+        opts = editable_opts[:]
+        opts.append("--no-build-isolation")  # force current version of setuptools
         venv.run(["python", "-m", "pip", "install", str(pkg_A), *opts])
         venv.run(["python", "-m", "pip", "install", "-e", str(pkg_B), *opts])
         venv.run(["python", "-c", "import myns.pkgA; import myns.pkgB"])
@@ -185,7 +222,7 @@ class TestLegacyNamespaces:
 
 
 class TestPep420Namespaces:
-    def test_namespace_package_importable(self, venv, tmp_path, editable_mode):
+    def test_namespace_package_importable(self, venv, tmp_path, editable_opts):
         """
         Installing two packages sharing the same namespace, one installed
         normally using pip and the other installed in editable mode
@@ -194,12 +231,13 @@ class TestPep420Namespaces:
         pkg_A = namespaces.build_pep420_namespace_package(tmp_path, 'myns.n.pkgA')
         pkg_B = namespaces.build_pep420_namespace_package(tmp_path, 'myns.n.pkgB')
         # use pip to install to the target directory
-        opts = ["--no-build-isolation"]  # force current version of setuptools
+        opts = editable_opts[:]
+        opts.append("--no-build-isolation")  # force current version of setuptools
         venv.run(["python", "-m", "pip", "install", str(pkg_A), *opts])
         venv.run(["python", "-m", "pip", "install", "-e", str(pkg_B), *opts])
         venv.run(["python", "-c", "import myns.n.pkgA; import myns.n.pkgB"])
 
-    def test_namespace_created_via_package_dir(self, venv, tmp_path, editable_mode):
+    def test_namespace_created_via_package_dir(self, venv, tmp_path, editable_opts):
         """Currently users can create a namespace by tweaking `package_dir`"""
         files = {
             "pkgA": {
@@ -224,7 +262,8 @@ class TestPep420Namespaces:
         pkg_C = namespaces.build_pep420_namespace_package(tmp_path, 'myns.n.pkgC')
 
         # use pip to install to the target directory
-        opts = ["--no-build-isolation"]  # force current version of setuptools
+        opts = editable_opts[:]
+        opts.append("--no-build-isolation")  # force current version of setuptools
         venv.run(["python", "-m", "pip", "install", str(pkg_A), *opts])
         venv.run(["python", "-m", "pip", "install", "-e", str(pkg_B), *opts])
         venv.run(["python", "-m", "pip", "install", "-e", str(pkg_C), *opts])
@@ -236,8 +275,7 @@ class TestPep420Namespaces:
     platform.python_implementation() == 'PyPy',
     reason="Workaround fails on PyPy (why?)",
 )
-@pytest.mark.parametrize("mode", ("strict", "lax"))
-def test_editable_with_prefix(tmp_path, sample_project, mode):
+def test_editable_with_prefix(tmp_path, sample_project, editable_opts):
     """
     Editable install to a prefix should be discoverable.
     """
@@ -254,7 +292,7 @@ def test_editable_with_prefix(tmp_path, sample_project, mode):
     # install workaround
     pip_run.launch.inject_sitecustomize(str(site_packages))
 
-    env = dict(os.environ, PYTHONPATH=str(site_packages), SETUPTOOLS_EDITABLE=mode)
+    env = dict(os.environ, PYTHONPATH=str(site_packages))
     cmd = [
         sys.executable,
         '-m',
@@ -265,6 +303,7 @@ def test_editable_with_prefix(tmp_path, sample_project, mode):
         '--prefix',
         str(prefix),
         '--no-build-isolation',
+        *editable_opts,
     ]
     subprocess.check_call(cmd, env=env)
 
@@ -518,8 +557,9 @@ class TestOverallBehaviour:
     }
 
     @pytest.mark.parametrize("layout", EXAMPLES.keys())
-    def test_editable_install(self, tmp_path, venv, layout, editable_mode):
-        project = install_project("mypkg", venv, tmp_path, self.EXAMPLES[layout])
+    def test_editable_install(self, tmp_path, venv, layout, editable_opts):
+        opts = editable_opts
+        project = install_project("mypkg", venv, tmp_path, self.EXAMPLES[layout], *opts)
 
         # Ensure stray files are not importable
         cmd_import_error = """\
@@ -616,9 +656,9 @@ class TestLinkTree:
 
             assert next(aux.glob("**/resource.not_in_manifest"), None) is None
 
-    def test_strict_install(self, tmp_path, venv, monkeypatch):
-        monkeypatch.setenv("SETUPTOOLS_EDITABLE", "strict")
-        install_project("mypkg", venv, tmp_path, self.FILES)
+    def test_strict_install(self, tmp_path, venv):
+        opts = ["--config-settings", "editable-mode=strict"]
+        install_project("mypkg", venv, tmp_path, self.FILES, *opts)
 
         out = venv.run(["python", "-c", "import mypkg.mod1; print(mypkg.mod1.var)"])
         assert b"42" in out
@@ -648,11 +688,81 @@ class TestLinkTree:
         assert b"resource.not_in_manifest" in out
 
 
-def install_project(name, venv, tmp_path, files):
+@pytest.mark.filterwarnings("ignore:.*compat.*:setuptools.SetuptoolsDeprecationWarning")
+def test_compat_install(tmp_path, venv):
+    # TODO: Remove `compat` after Dec/2022.
+    opts = ["--config-settings", "editable-mode=compat"]
+    files = TestOverallBehaviour.EXAMPLES["custom-layout"]
+    install_project("mypkg", venv, tmp_path, files, *opts)
+
+    out = venv.run(["python", "-c", "import mypkg.mod1; print(mypkg.mod1.var)"])
+    assert b"42" in out
+
+    expected_path = comparable_path(str(tmp_path))
+
+    # Compatible behaviour will make spurious modules and excluded
+    # files importable directly from the original path
+    for cmd in (
+        "import otherfile; print(otherfile)",
+        "import other; print(other)",
+        "import mypkg; print(mypkg)",
+    ):
+        out = comparable_path(str(venv.run(["python", "-c", cmd]), "utf-8"))
+        assert expected_path in out
+
+    # Compatible behaviour will not consider custom mappings
+    cmd = """\
+    try:
+        from mypkg import subpackage;
+    except ImportError as ex:
+        print(ex)
+    """
+    out = str(venv.run(["python", "-c", dedent(cmd)]), "utf-8")
+    assert "cannot import name 'subpackage'" in out
+
+
+def test_pbr_integration(tmp_path, venv, editable_opts):
+    """Ensure editable installs work with pbr, issue #3500"""
+    files = {
+        "pyproject.toml": dedent("""\
+            [build-system]
+            requires = ["setuptools"]
+            build-backend = "setuptools.build_meta"
+            """),
+        "setup.py": dedent("""\
+            __import__('setuptools').setup(
+                pbr=True,
+                setup_requires=["pbr"],
+            )
+            """),
+        "setup.cfg": dedent("""\
+            [metadata]
+            name = mypkg
+
+            [files]
+            packages =
+                mypkg
+            """),
+        "mypkg": {
+            "__init__.py": "",
+            "hello.py": "print('Hello world!')",
+        },
+        "other": {"test.txt": "Another file in here."},
+    }
+    venv.run(["python", "-m", "pip", "install", "pbr"])
+
+    with contexts.environment(PBR_VERSION="0.42"):
+        install_project("mypkg", venv, tmp_path, files, *editable_opts)
+
+    out = venv.run(["python", "-c", "import mypkg.hello"])
+    assert b"Hello world!" in out
+
+
+def install_project(name, venv, tmp_path, files, *opts):
     project = tmp_path / name
     project.mkdir()
     jaraco.path.build(files, prefix=project)
-    opts = ["--no-build-isolation"]  # force current version of setuptools
+    opts = [*opts, "--no-build-isolation"]  # force current version of setuptools
     venv.run(["python", "-m", "pip", "install", "-e", str(project), *opts])
     return project
 
@@ -676,3 +786,7 @@ def assert_link_to(file: Path, other: Path):
         other_stat = other.stat()
         assert file_stat[stat.ST_INO] == other_stat[stat.ST_INO]
         assert file_stat[stat.ST_DEV] == other_stat[stat.ST_DEV]
+
+
+def comparable_path(str_with_path: str) -> str:
+    return str_with_path.lower().replace(os.sep, "/").replace("//", "/")
index 8ac9bd072c05fd004581ea4847a291734a555a94..f8b82fcc37d6aa7ae49030296f6dd501a057948e 100644 (file)
@@ -107,9 +107,9 @@ class TestCLI(WrapperTester):
             'arg5 a\\\\b',
         ]
         proc = subprocess.Popen(
-            cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
-        stdout, stderr = proc.communicate('hello\nworld\n'.encode('ascii'))
-        actual = stdout.decode('ascii').replace('\r\n', '\n')
+            cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, text=True)
+        stdout, stderr = proc.communicate('hello\nworld\n')
+        actual = stdout.replace('\r\n', '\n')
         expected = textwrap.dedent(r"""
             \foo-script.py
             ['arg1', 'arg 2', 'arg "2\\"', 'arg 4\\', 'arg5 a\\\\b']
@@ -148,9 +148,11 @@ class TestCLI(WrapperTester):
             cmd,
             stdout=subprocess.PIPE,
             stdin=subprocess.PIPE,
-            stderr=subprocess.STDOUT)
+            stderr=subprocess.STDOUT,
+            text=True,
+        )
         stdout, stderr = proc.communicate()
-        actual = stdout.decode('ascii').replace('\r\n', '\n')
+        actual = stdout.replace('\r\n', '\n')
         expected = textwrap.dedent(r"""
             \foo-script.py
             []
@@ -188,7 +190,7 @@ class TestGUI(WrapperTester):
         ]
         proc = subprocess.Popen(
             cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
-            stderr=subprocess.STDOUT)
+            stderr=subprocess.STDOUT, text=True)
         stdout, stderr = proc.communicate()
         assert not stdout
         assert not stderr
diff --git a/tox.ini b/tox.ini
index bb2e7cb17d4e1910c651d9e84cfb2bcfa7635f23..13d944e5cc775b2e0d8f33fbc1bb2514945cddad 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -30,7 +30,7 @@ passenv =
 setenv =
     PROJECT_ROOT = {toxinidir}
 commands =
-       pytest --integration {posargs:-vv --durations=10 setuptools/tests/integration}
+       pytest --integration {posargs:-vv --durations=10} setuptools/tests/integration
        # use verbose mode by default to facilitate debugging from CI logs
 
 [testenv:docs]