From: JinWang An Date: Mon, 27 Mar 2023 08:02:54 +0000 (+0900) Subject: Imported Upstream version 64.0.1 X-Git-Tag: upstream/64.0.1^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=9bb6182daaa85b0eff9829ec9268aa6bf807784b;p=platform%2Fupstream%2Fpython-setuptools.git Imported Upstream version 64.0.1 --- diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 284f7c1..e14a394 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 63.1.0 +current_version = 64.0.1 commit = True tag = True diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7bd0af4..83624a5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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 diff --git a/CHANGES.rst b/CHANGES.rst index c85d6dc..20aba6c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -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 ` 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 index ac20398..0000000 --- a/changelog.d/3265.change.rst +++ /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 index 9622417..0000000 --- a/changelog.d/3380.change.rst +++ /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 index 54d3c4c..0000000 --- a/changelog.d/3380.deprecation.rst +++ /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 index 8ae7fd9..0000000 --- a/changelog.d/3392.change.rst +++ /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 index 69f02bc..0000000 --- a/changelog.d/3412.change.rst +++ /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 index b29f2c5..0000000 --- a/changelog.d/3414.change.rst +++ /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 index b4756da..0000000 --- a/changelog.d/3414.doc.rst +++ /dev/null @@ -1,2 +0,0 @@ -Updated :doc:`Development Mode ` to reflect on the -implementation of :pep:`660`. diff --git a/docs/build_meta.rst b/docs/build_meta.rst index 45a994f..3c778d8 100644 --- a/docs/build_meta.rst +++ b/docs/build_meta.rst @@ -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"] diff --git a/docs/conf.py b/docs/conf.py index b7d0538..2b60bf5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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 HTML attributes # static-file points to files in the html_static_path (href is computed) favicons = [ diff --git a/docs/deprecated/commands.rst b/docs/deprecated/commands.rst index ebd0687..d9d97a9 100644 --- a/docs/deprecated/commands.rst +++ b/docs/deprecated/commands.rst @@ -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``):: diff --git a/docs/setuptools.rst b/docs/setuptools.rst index aa63830..5317058 100644 --- a/docs/setuptools.rst +++ b/docs/setuptools.rst @@ -151,7 +151,6 @@ To use this feature: [build-system] requires = [ "setuptools >= 40.9.0", - "wheel", ] build-backend = "setuptools.build_meta" diff --git a/docs/userguide/dependency_management.rst b/docs/userguide/dependency_management.rst index 56fbd0b..33aaf6c 100644 --- a/docs/userguide/dependency_management.rst +++ b/docs/userguide/dependency_management.rst @@ -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 `. - 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 `. .. _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 `_. +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 diff --git a/docs/userguide/development_mode.rst b/docs/userguide/development_mode.rst index bfc37a0..e7c755a 100644 --- a/docs/userguide/development_mode.rst +++ b/docs/userguide/development_mode.rst @@ -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 ` to work properly. - *Strict* editable installs require the file system to support either :wiki:`symbolic ` or :wiki:`hard links `. + 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 `. - 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 ` 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 `. + +.. [#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 `. diff --git a/docs/userguide/extension.rst b/docs/userguide/extension.rst index e5fca01..b49816b 100644 --- a/docs/userguide/extension.rst +++ b/docs/userguide/extension.rst @@ -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 diff --git a/docs/userguide/package_discovery.rst b/docs/userguide/package_discovery.rst index 2efc62b..7dda84a 100644 --- a/docs/userguide/package_discovery.rst +++ b/docs/userguide/package_discovery.rst @@ -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 diff --git a/docs/userguide/pyproject_config.rst b/docs/userguide/pyproject_config.rst index 28eb39d..b1d4a4e 100644 --- a/docs/userguide/pyproject_config.rst +++ b/docs/userguide/pyproject_config.rst @@ -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 `. - - 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 diff --git a/docs/userguide/quickstart.rst b/docs/userguide/quickstart.rst index 6c39c35..bf76f2c 100644 --- a/docs/userguide/quickstart.rst +++ b/docs/userguide/quickstart.rst @@ -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 `, 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 `, - 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 ` - (or :doc:`pyproject.toml `). + You can still keep all the configuration in + :doc:`pyproject.toml ` and/or + :doc:`setup.cfg ` 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 index 0000000..a1b589e --- /dev/null +++ b/pkg_resources/_vendor/pyparsing-3.0.9.dist-info/INSTALLER @@ -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 index 0000000..1bf9852 --- /dev/null +++ b/pkg_resources/_vendor/pyparsing-3.0.9.dist-info/LICENSE @@ -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 index 0000000..33e5194 --- /dev/null +++ b/pkg_resources/_vendor/pyparsing-3.0.9.dist-info/METADATA @@ -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 +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 `__ +*.]* + +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 `__. Additional +documentation resources and project info are listed in the online +`GitHub wiki `__. An +entire directory of examples can be found `here `__. + +License +======= + +MIT License. See header of the `pyparsing.py `__ file. + +History +======= + +See `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 index 0000000..7a4e49a --- /dev/null +++ b/pkg_resources/_vendor/pyparsing-3.0.9.dist-info/RECORD @@ -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 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 index 0000000..c727d14 --- /dev/null +++ b/pkg_resources/_vendor/pyparsing-3.0.9.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.6.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/pkg_resources/_vendor/pyparsing/__init__.py b/pkg_resources/_vendor/pyparsing/__init__.py index 45f334d..7802ff1 100644 --- a/pkg_resources/_vendor/pyparsing/__init__.py +++ b/pkg_resources/_vendor/pyparsing/__init__.py @@ -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 " diff --git a/pkg_resources/_vendor/pyparsing/actions.py b/pkg_resources/_vendor/pyparsing/actions.py index 2bcc550..f72c66e 100644 --- a/pkg_resources/_vendor/pyparsing/actions.py +++ b/pkg_resources/_vendor/pyparsing/actions.py @@ -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] diff --git a/pkg_resources/_vendor/pyparsing/core.py b/pkg_resources/_vendor/pyparsing/core.py index 454bd57..9acba3f 100644 --- a/pkg_resources/_vendor/pyparsing/core.py +++ b/pkg_resources/_vendor/pyparsing/core.py @@ -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, 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 diff --git a/pkg_resources/_vendor/pyparsing/diagram/__init__.py b/pkg_resources/_vendor/pyparsing/diagram/__init__.py index 2d0c587..8986447 100644 --- a/pkg_resources/_vendor/pyparsing/diagram/__init__.py +++ b/pkg_resources/_vendor/pyparsing/diagram/__init__.py @@ -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 = """\ + + + + {% if not head %} + + {% else %} + {{ head | safe }} + {% endif %} + + +{{ body | safe }} +{% for diagram in diagrams %} +
+

{{ diagram.title }}

+
{{ diagram.text }}
+
+ {{ diagram.svg }} +
+
+{% endfor %} + + +""" + +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 index d2219fb..0000000 --- a/pkg_resources/_vendor/pyparsing/diagram/template.jinja2 +++ /dev/null @@ -1,26 +0,0 @@ - - - - {% if not head %} - - {% else %} - {{ hear | safe }} - {% endif %} - - -{{ body | safe }} -{% for diagram in diagrams %} -
-

{{ diagram.title }}

-
{{ diagram.text }}
-
- {{ diagram.svg }} -
-
-{% endfor %} - - diff --git a/pkg_resources/_vendor/pyparsing/exceptions.py b/pkg_resources/_vendor/pyparsing/exceptions.py index e06513e..a38447b 100644 --- a/pkg_resources/_vendor/pyparsing/exceptions.py +++ b/pkg_resources/_vendor/pyparsing/exceptions.py @@ -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 diff --git a/pkg_resources/_vendor/pyparsing/helpers.py b/pkg_resources/_vendor/pyparsing/helpers.py index be8a365..9588b3b 100644 --- a/pkg_resources/_vendor/pyparsing/helpers.py +++ b/pkg_resources/_vendor/pyparsing/helpers.py @@ -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 diff --git a/pkg_resources/_vendor/pyparsing/results.py b/pkg_resources/_vendor/pyparsing/results.py index bb444df..00c9421 100644 --- a/pkg_resources/_vendor/pyparsing/results.py +++ b/pkg_resources/_vendor/pyparsing/results.py @@ -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) # -> ['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: diff --git a/pkg_resources/_vendor/pyparsing/testing.py b/pkg_resources/_vendor/pyparsing/testing.py index 991972f..84a0ef1 100644 --- a/pkg_resources/_vendor/pyparsing/testing.py +++ b/pkg_resources/_vendor/pyparsing/testing.py @@ -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. diff --git a/pkg_resources/_vendor/pyparsing/unicode.py b/pkg_resources/_vendor/pyparsing/unicode.py index 9226148..0652620 100644 --- a/pkg_resources/_vendor/pyparsing/unicode.py +++ b/pkg_resources/_vendor/pyparsing/unicode.py @@ -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 diff --git a/pkg_resources/_vendor/vendored.txt b/pkg_resources/_vendor/vendored.txt index 8f9c263..8e01506 100644 --- a/pkg_resources/_vendor/vendored.txt +++ b/pkg_resources/_vendor/vendored.txt @@ -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 diff --git a/pyproject.toml b/pyproject.toml index f6fdfc9..480b136 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ addopts = "--flake8" [tool.pytest-enabler.cov] addopts = "--cov" -[pytest.enabler.xdist] +[tool.pytest-enabler.xdist] addopts = "-n auto" [tool.towncrier] diff --git a/setup.cfg b/setup.cfg index e39b5ff..9093b09 100644 --- 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 diff --git a/setuptools/_distutils/_msvccompiler.py b/setuptools/_distutils/_msvccompiler.py index 3b5a817..ade8005 100644 --- a/setuptools/_distutils/_msvccompiler.py +++ b/setuptools/_distutils/_msvccompiler.py @@ -17,7 +17,7 @@ import os import subprocess import contextlib import warnings -import unittest.mock +import unittest.mock as mock with contextlib.suppress(ImportError): import winreg @@ -144,12 +144,12 @@ def _get_vc_env(plat_spec): try: out = subprocess.check_output( - 'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec), + f'cmd /u /c "{vcvarsall}" {plat_spec} && set', stderr=subprocess.STDOUT, ).decode('utf-16le', errors='replace') except subprocess.CalledProcessError as exc: log.error(exc.output) - raise DistutilsPlatformError("Error executing {}".format(exc.cmd)) + raise DistutilsPlatformError(f"Error executing {exc.cmd}") env = { key.lower(): value @@ -224,6 +224,18 @@ class MSVCCompiler(CCompiler): self.plat_name = None self.initialized = False + @classmethod + def _configure(cls, vc_env): + """ + Set class-level include/lib dirs. + """ + cls.include_dirs = cls._parse_path(vc_env.get('include', '')) + cls.library_dirs = cls._parse_path(vc_env.get('lib', '')) + + @staticmethod + def _parse_path(val): + return [dir.rstrip(os.sep) for dir in val.split(os.pathsep) if dir] + def initialize(self, plat_name=None): # multi-init means we would need to check platform same each time... assert not self.initialized, "don't init multiple times" @@ -232,7 +244,7 @@ class MSVCCompiler(CCompiler): # sanity check for platforms to prevent obscure errors later. if plat_name not in PLAT_TO_VCVARS: raise DistutilsPlatformError( - "--plat-name must be one of {}".format(tuple(PLAT_TO_VCVARS)) + f"--plat-name must be one of {tuple(PLAT_TO_VCVARS)}" ) # Get the vcvarsall.bat spec for the requested platform. @@ -243,6 +255,7 @@ class MSVCCompiler(CCompiler): raise DistutilsPlatformError( "Unable to find a compatible " "Visual Studio installation." ) + self._configure(vc_env) self._paths = vc_env.get('path', '') paths = self._paths.split(os.pathsep) @@ -253,14 +266,6 @@ class MSVCCompiler(CCompiler): self.mc = _find_exe("mc.exe", paths) # message compiler self.mt = _find_exe("mt.exe", paths) # message compiler - for dir in vc_env.get('include', '').split(os.pathsep): - if dir: - self.add_include_dir(dir.rstrip(os.sep)) - - for dir in vc_env.get('lib', '').split(os.pathsep): - if dir: - self.add_library_dir(dir.rstrip(os.sep)) - self.preprocess_options = None # bpo-38597: Always compile with dynamic linking # Future releases of Python 3.x will include all past @@ -341,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 ----------------------------------------- diff --git a/setuptools/_distutils/archive_util.py b/setuptools/_distutils/archive_util.py index 5a70c32..5dfe2a1 100644 --- a/setuptools/_distutils/archive_util.py +++ b/setuptools/_distutils/archive_util.py @@ -121,7 +121,7 @@ def make_tarball( # compression using `compress` if compress == 'compress': - warn("'compress' will be deprecated.", PendingDeprecationWarning) + warn("'compress' is deprecated.", DeprecationWarning) # the option varies depending on the platform compressed_name = archive_name + compress_ext[compress] if sys.platform == 'win32': @@ -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 diff --git a/setuptools/_distutils/bcppcompiler.py b/setuptools/_distutils/bcppcompiler.py index 056b2d9..ee033ed 100644 --- a/setuptools/_distutils/bcppcompiler.py +++ b/setuptools/_distutils/bcppcompiler.py @@ -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) diff --git a/setuptools/_distutils/ccompiler.py b/setuptools/_distutils/ccompiler.py index 005b64a..3cf5761 100644 --- a/setuptools/_distutils/ccompiler.py +++ b/setuptools/_distutils/ccompiler.py @@ -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") diff --git a/setuptools/_distutils/cmd.py b/setuptools/_distutils/cmd.py index 4a9bcc2..68a9267 100644 --- a/setuptools/_distutils/cmd.py +++ b/setuptools/_distutils/cmd.py @@ -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 diff --git a/setuptools/_distutils/command/__init__.py b/setuptools/_distutils/command/__init__.py index d199c24..a40c1f9 100644 --- a/setuptools/_distutils/command/__init__.py +++ b/setuptools/_distutils/command/__init__.py @@ -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 ] diff --git a/setuptools/_distutils/command/_framework_compat.py b/setuptools/_distutils/command/_framework_compat.py index e032603..cffa27c 100644 --- a/setuptools/_distutils/command/_framework_compat.py +++ b/setuptools/_distutils/command/_framework_compat.py @@ -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( diff --git a/setuptools/_distutils/command/bdist.py b/setuptools/_distutils/command/bdist.py index 2a63976..c9fdbf1 100644 --- a/setuptools/_distutils/command/bdist.py +++ b/setuptools/_distutils/command/bdist.py @@ -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) diff --git a/setuptools/_distutils/command/bdist_dumb.py b/setuptools/_distutils/command/bdist_dumb.py index 3c38782..0f52330 100644 --- a/setuptools/_distutils/command/bdist_dumb.py +++ b/setuptools/_distutils/command/bdist_dumb.py @@ -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: diff --git a/setuptools/_distutils/command/bdist_msi.py b/setuptools/_distutils/command/bdist_msi.py index 2f292c9..57931c7 100644 --- a/setuptools/_distutils/command/bdist_msi.py +++ b/setuptools/_distutils/command/bdist_msi.py @@ -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 diff --git a/setuptools/_distutils/command/bdist_rpm.py b/setuptools/_distutils/command/bdist_rpm.py index cf4b952..6a50ef3 100644 --- a/setuptools/_distutils/command/bdist_rpm.py +++ b/setuptools/_distutils/command/bdist_rpm.py @@ -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 diff --git a/setuptools/_distutils/command/bdist_wininst.py b/setuptools/_distutils/command/bdist_wininst.py index 76b8a89..02bd720 100644 --- a/setuptools/_distutils/command/bdist_wininst.py +++ b/setuptools/_distutils/command/bdist_wininst.py @@ -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() diff --git a/setuptools/_distutils/command/build.py b/setuptools/_distutils/command/build.py index 3aa7fac..6d45341 100644 --- a/setuptools/_distutils/command/build.py +++ b/setuptools/_distutils/command/build.py @@ -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 diff --git a/setuptools/_distutils/command/build_clib.py b/setuptools/_distutils/command/build_clib.py index 003499f..50bb9bb 100644 --- a/setuptools/_distutils/command/build_clib.py +++ b/setuptools/_distutils/command/build_clib.py @@ -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 diff --git a/setuptools/_distutils/command/build_ext.py b/setuptools/_distutils/command/build_ext.py index 1a6dd39..3c6cee7 100644 --- a/setuptools/_distutils/command/build_ext.py +++ b/setuptools/_distutils/command/build_ext.py @@ -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). diff --git a/setuptools/_distutils/command/build_py.py b/setuptools/_distutils/command/build_py.py index 7723d35..47c6158 100644 --- a/setuptools/_distutils/command/build_py.py +++ b/setuptools/_distutils/command/build_py.py @@ -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) diff --git a/setuptools/_distutils/command/build_scripts.py b/setuptools/_distutils/command/build_scripts.py index 17058db..2cc5d1e 100644 --- a/setuptools/_distutils/command/build_scripts.py +++ b/setuptools/_distutils/command/build_scripts.py @@ -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)) diff --git a/setuptools/_distutils/command/check.py b/setuptools/_distutils/command/check.py index 176a8b8..539481c 100644 --- a/setuptools/_distutils/command/check.py +++ b/setuptools/_distutils/command/check.py @@ -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) diff --git a/setuptools/_distutils/command/config.py b/setuptools/_distutils/command/config.py index 73de1d3..4492c89 100644 --- a/setuptools/_distutils/command/config.py +++ b/setuptools/_distutils/command/config.py @@ -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 diff --git a/setuptools/_distutils/command/install.py b/setuptools/_distutils/command/install.py index 7d9054e..a38cddc 100644 --- a/setuptools/_distutils/command/install.py +++ b/setuptools/_distutils/command/install.py @@ -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 diff --git a/setuptools/_distutils/command/install_egg_info.py b/setuptools/_distutils/command/install_egg_info.py index dc93963..d5e68a6 100644 --- a/setuptools/_distutils/command/install_egg_info.py +++ b/setuptools/_distutils/command/install_egg_info.py @@ -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): diff --git a/setuptools/_distutils/command/register.py b/setuptools/_distutils/command/register.py index ca407eb..c140265 100644 --- a/setuptools/_distutils/command/register.py +++ b/setuptools/_distutils/command/register.py @@ -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' diff --git a/setuptools/_distutils/command/sdist.py b/setuptools/_distutils/command/sdist.py index aad3e71..d6e9489 100644 --- a/setuptools/_distutils/command/sdist.py +++ b/setuptools/_distutils/command/sdist.py @@ -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): diff --git a/setuptools/_distutils/command/upload.py b/setuptools/_distutils/command/upload.py index 782e3de..6af5394 100644 --- a/setuptools/_distutils/command/upload.py +++ b/setuptools/_distutils/command/upload.py @@ -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) diff --git a/setuptools/_distutils/config.py b/setuptools/_distutils/config.py index 34a1c3b..6e0c3a7 100644 --- a/setuptools/_distutils/config.py +++ b/setuptools/_distutils/config.py @@ -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): diff --git a/setuptools/_distutils/core.py b/setuptools/_distutils/core.py index 305ecf6..de13978 100644 --- a/setuptools/_distutils/core.py +++ b/setuptools/_distutils/core.py @@ -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 diff --git a/setuptools/_distutils/cygwinccompiler.py b/setuptools/_distutils/cygwinccompiler.py index 445e2e5..2c6dbae 100644 --- a/setuptools/_distutils/cygwinccompiler.py +++ b/setuptools/_distutils/cygwinccompiler.py @@ -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): diff --git a/setuptools/_distutils/dep_util.py b/setuptools/_distutils/dep_util.py index d94e111..db1fa01 100644 --- a/setuptools/_distutils/dep_util.py +++ b/setuptools/_distutils/dep_util.py @@ -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: diff --git a/setuptools/_distutils/dir_util.py b/setuptools/_distutils/dir_util.py index 2c19b98..6f0bb8a 100644 --- a/setuptools/_distutils/dir_util.py +++ b/setuptools/_distutils/dir_util.py @@ -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: diff --git a/setuptools/_distutils/dist.py b/setuptools/_distutils/dist.py index 82e3684..0406ab1 100644 --- a/setuptools/_distutils/dist.py +++ b/setuptools/_distutils/dist.py @@ -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 diff --git a/setuptools/_distutils/extension.py b/setuptools/_distutils/extension.py index b05c5d9..6b8575d 100644 --- a/setuptools/_distutils/extension.py +++ b/setuptools/_distutils/extension.py @@ -134,7 +134,7 @@ class Extension: warnings.warn(msg) def __repr__(self): - return '<%s.%s(%r) at %#x>' % ( + return '<{}.{}({!r}) at {:#x}>'.format( self.__class__.__module__, self.__class__.__qualname__, self.name, @@ -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 diff --git a/setuptools/_distutils/fancy_getopt.py b/setuptools/_distutils/fancy_getopt.py index b9b2176..830f047 100644 --- a/setuptools/_distutils/fancy_getopt.py +++ b/setuptools/_distutils/fancy_getopt.py @@ -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] == ' ': diff --git a/setuptools/_distutils/file_util.py b/setuptools/_distutils/file_util.py index 4ff2230..1f1e444 100644 --- a/setuptools/_distutils/file_util.py +++ b/setuptools/_distutils/file_util.py @@ -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) diff --git a/setuptools/_distutils/filelist.py b/setuptools/_distutils/filelist.py index 37ab341..987931a 100644 --- a/setuptools/_distutils/filelist.py +++ b/setuptools/_distutils/filelist.py @@ -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) diff --git a/setuptools/_distutils/msvc9compiler.py b/setuptools/_distutils/msvc9compiler.py index 225f1a2..62075c0 100644 --- a/setuptools/_distutils/msvc9compiler.py +++ b/setuptools/_distutils/msvc9compiler.py @@ -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: diff --git a/setuptools/_distutils/msvccompiler.py b/setuptools/_distutils/msvccompiler.py index 00c630b..51e6017 100644 --- a/setuptools/_distutils/msvccompiler.py +++ b/setuptools/_distutils/msvccompiler.py @@ -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 diff --git a/setuptools/_distutils/py38compat.py b/setuptools/_distutils/py38compat.py index e556b69..59224e7 100644 --- a/setuptools/_distutils/py38compat.py +++ b/setuptools/_distutils/py38compat.py @@ -5,4 +5,4 @@ def aix_platform(osname, version, release): return _aix_support.aix_platform() except ImportError: pass - return "%s-%s.%s" % (osname, version, release) + return "{}-{}.{}".format(osname, version, release) diff --git a/setuptools/_distutils/spawn.py b/setuptools/_distutils/spawn.py index acd2014..b18ba9d 100644 --- a/setuptools/_distutils/spawn.py +++ b/setuptools/_distutils/spawn.py @@ -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) ) diff --git a/setuptools/_distutils/sysconfig.py b/setuptools/_distutils/sysconfig.py index e41d51e..aae9c1b 100644 --- a/setuptools/_distutils/sysconfig.py +++ b/setuptools/_distutils/sysconfig.py @@ -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 diff --git a/setuptools/_distutils/tests/__init__.py b/setuptools/_distutils/tests/__init__.py index c7dcc7e..27e7339 100644 --- a/setuptools/_distutils/tests/__init__.py +++ b/setuptools/_distutils/tests/__init__.py @@ -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()) diff --git a/setuptools/_distutils/tests/py38compat.py b/setuptools/_distutils/tests/py38compat.py index c949f58..35ddbb5 100644 --- a/setuptools/_distutils/tests/py38compat.py +++ b/setuptools/_distutils/tests/py38compat.py @@ -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): diff --git a/setuptools/_distutils/tests/support.py b/setuptools/_distutils/tests/support.py index 3085468..5203ed1 100644 --- a/setuptools/_distutils/tests/support.py +++ b/setuptools/_distutils/tests/support.py @@ -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 diff --git a/setuptools/_distutils/tests/test_archive_util.py b/setuptools/_distutils/tests/test_archive_util.py index 8fb9574..72aa9d7 100644 --- a/setuptools/_distutils/tests/test_archive_util.py +++ b/setuptools/_distutils/tests/test_archive_util.py @@ -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()) diff --git a/setuptools/_distutils/tests/test_bdist.py b/setuptools/_distutils/tests/test_bdist.py index 2d0bb95..5b8774e 100644 --- a/setuptools/_distutils/tests/test_bdist.py +++ b/setuptools/_distutils/tests/test_bdist.py @@ -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 diff --git a/setuptools/_distutils/tests/test_bdist_dumb.py b/setuptools/_distutils/tests/test_bdist_dumb.py index 83ab217..8624a42 100644 --- a/setuptools/_distutils/tests/test_bdist_dumb.py +++ b/setuptools/_distutils/tests/test_bdist_dumb.py @@ -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) diff --git a/setuptools/_distutils/tests/test_bdist_msi.py b/setuptools/_distutils/tests/test_bdist_msi.py index a9f3dbb..f36b398 100644 --- a/setuptools/_distutils/tests/test_bdist_msi.py +++ b/setuptools/_distutils/tests/test_bdist_msi.py @@ -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()) diff --git a/setuptools/_distutils/tests/test_bdist_rpm.py b/setuptools/_distutils/tests/test_bdist_rpm.py index f60a582..2d14baf 100644 --- a/setuptools/_distutils/tests/test_bdist_rpm.py +++ b/setuptools/_distutils/tests/test_bdist_rpm.py @@ -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()) diff --git a/setuptools/_distutils/tests/test_bdist_wininst.py b/setuptools/_distutils/tests/test_bdist_wininst.py index c103a63..c432d24 100644 --- a/setuptools/_distutils/tests/test_bdist_wininst.py +++ b/setuptools/_distutils/tests/test_bdist_wininst.py @@ -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 diff --git a/setuptools/_distutils/tests/test_build.py b/setuptools/_distutils/tests/test_build.py index 190bbdf..8036760 100644 --- a/setuptools/_distutils/tests/test_build.py +++ b/setuptools/_distutils/tests/test_build.py @@ -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. 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) diff --git a/setuptools/_distutils/tests/test_build_clib.py b/setuptools/_distutils/tests/test_build_clib.py index 24c7478..c931c06 100644 --- a/setuptools/_distutils/tests/test_build_clib.py +++ b/setuptools/_distutils/tests/test_build_clib.py @@ -1,47 +1,44 @@ """Tests for distutils.command.build_clib.""" -import unittest import os -import sys -from test.support import 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) diff --git a/setuptools/_distutils/tests/test_build_ext.py b/setuptools/_distutils/tests/test_build_ext.py index c42ceab..e60814f 100644 --- a/setuptools/_distutils/tests/test_build_ext.py +++ b/setuptools/_distutils/tests/test_build_ext.py @@ -2,6 +2,12 @@ import sys import os from io import StringIO import textwrap +import site +import contextlib +import platform +import tempfile +import importlib +import shutil from distutils.core import Distribution from distutils.command.build_ext import build_ext @@ -20,53 +26,70 @@ from distutils.errors import ( UnknownFileError, ) -import unittest from test import support from . import py38compat as os_helper -from test.support.script_helper import assert_python_ok - -# http://bugs.python.org/issue4373 -# Don't load the xx module more than once. -ALREADY_TESTED = False - - -class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): - def setUp(self): - # Create a simple test environment - super(BuildExtTestCase, self).setUp() - self.tmp_dir = self.mkdtemp() - import site - - self.old_user_base = site.USER_BASE - site.USER_BASE = self.mkdtemp() - from distutils.command import build_ext - - build_ext.USER_BASE = site.USER_BASE - - # bpo-30132: On Windows, a .pdb file may be created in the current - # working directory. Create a temporary working directory to cleanup - # everything at the end of the test. - change_cwd = os_helper.change_cwd(self.tmp_dir) - change_cwd.__enter__() - self.addCleanup(change_cwd.__exit__, None, None, None) - - def tearDown(self): - import site - - site.USER_BASE = self.old_user_base - from distutils.command import build_ext - - build_ext.USER_BASE = self.old_user_base - super(BuildExtTestCase, self).tearDown() - +from . import py38compat as import_helper +import pytest +import re + + +@pytest.fixture() +def user_site_dir(request): + self = request.instance + self.tmp_dir = self.mkdtemp() + from distutils.command import build_ext + + orig_user_base = site.USER_BASE + + site.USER_BASE = self.mkdtemp() + build_ext.USER_BASE = site.USER_BASE + + # bpo-30132: On Windows, a .pdb file may be created in the current + # working directory. Create a temporary working directory to cleanup + # everything at the end of the test. + with os_helper.change_cwd(self.tmp_dir): + yield + + site.USER_BASE = orig_user_base + build_ext.USER_BASE = orig_user_base + + +@contextlib.contextmanager +def safe_extension_import(name, path): + with import_helper.CleanImport(name): + with extension_redirect(name, path) as new_path: + with import_helper.DirsOnSysPath(new_path): + yield + + +@contextlib.contextmanager +def extension_redirect(mod, path): + """ + Tests will fail to tear down an extension module if it's been imported. + + Before importing, copy the file to a temporary directory that won't + be cleaned up. Yield the new path. + """ + if platform.system() != "Windows" and sys.platform != "cygwin": + yield path + return + with import_helper.DirsOnSysPath(path): + spec = importlib.util.find_spec(mod) + filename = os.path.basename(spec.origin) + trash_dir = tempfile.mkdtemp(prefix='deleteme') + dest = os.path.join(trash_dir, os.path.basename(filename)) + shutil.copy(spec.origin, dest) + yield trash_dir + # TODO: can the file be scheduled for deletion? + + +@pytest.mark.usefixtures('user_site_dir') +class TestBuildExt(TempdirManager, LoggingSilencer): def build_ext(self, *args, **kwargs): return build_ext(*args, **kwargs) def test_build_ext(self): cmd = support.missing_compiler_executable() - if cmd is not None: - self.skipTest('The %r command is not found' % cmd) - global ALREADY_TESTED copy_xxmodule_c(self.tmp_dir) xx_c = os.path.join(self.tmp_dir, 'xxmodule.c') xx_ext = Extension('xx', [xx_c]) @@ -87,43 +110,24 @@ class BuildExtTestCase(TempdirManager, LoggingSilencer, unittest.TestCase): finally: sys.stdout = old_stdout - if ALREADY_TESTED: - self.skipTest('Already tested in %s' % ALREADY_TESTED) - else: - ALREADY_TESTED = type(self).__name__ + 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__) diff --git a/setuptools/_distutils/tests/test_build_py.py b/setuptools/_distutils/tests/test_build_py.py index eb01d81..63543dc 100644 --- a/setuptools/_distutils/tests/test_build_py.py +++ b/setuptools/_distutils/tests/test_build_py.py @@ -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()) diff --git a/setuptools/_distutils/tests/test_build_scripts.py b/setuptools/_distutils/tests/test_build_scripts.py index 8c7061d..00d7fc5 100644 --- a/setuptools/_distutils/tests/test_build_scripts.py +++ b/setuptools/_distutils/tests/test_build_scripts.py @@ -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 index 0000000..da1879f --- /dev/null +++ b/setuptools/_distutils/tests/test_ccompiler.py @@ -0,0 +1,55 @@ +import os +import sys +import platform +import textwrap +import sysconfig + +import pytest + +from distutils import ccompiler + + +def _make_strs(paths): + """ + Convert paths to strings for legacy compatibility. + """ + if sys.version_info > (3, 8) and platform.system() != "Windows": + return paths + return list(map(os.fspath, paths)) + + +@pytest.fixture +def c_file(tmp_path): + c_file = tmp_path / 'foo.c' + gen_headers = ('Python.h',) + is_windows = platform.system() == "Windows" + plat_headers = ('windows.h',) * is_windows + all_headers = gen_headers + plat_headers + headers = '\n'.join(f'#include <{header}>\n' for header in all_headers) + payload = ( + textwrap.dedent( + """ + #headers + void PyInit_foo(void) {} + """ + ) + .lstrip() + .replace('#headers', headers) + ) + c_file.write_text(payload) + return c_file + + +def test_set_include_dirs(c_file): + """ + Extensions should build even if set_include_dirs is invoked. + In particular, compiler-specific paths should not be overridden. + """ + compiler = ccompiler.new_compiler() + python = sysconfig.get_paths()['include'] + compiler.set_include_dirs([python]) + compiler.compile(_make_strs([c_file])) + + # do it again, setting include dirs after any initialization + compiler.set_include_dirs([python]) + compiler.compile(_make_strs([c_file])) diff --git a/setuptools/_distutils/tests/test_check.py b/setuptools/_distutils/tests/test_check.py index 424c5e0..3e5f603 100644 --- a/setuptools/_distutils/tests/test_check.py +++ b/setuptools/_distutils/tests/test_check.py @@ -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}) diff --git a/setuptools/_distutils/tests/test_clean.py b/setuptools/_distutils/tests/test_clean.py index 92e58f7..4166bb7 100644 --- a/setuptools/_distutils/tests/test_clean.py +++ b/setuptools/_distutils/tests/test_clean.py @@ -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()) diff --git a/setuptools/_distutils/tests/test_cmd.py b/setuptools/_distutils/tests/test_cmd.py index 12a8a20..e4d5bf3 100644 --- a/setuptools/_distutils/tests/test_cmd.py +++ b/setuptools/_distutils/tests/test_cmd.py @@ -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()) diff --git a/setuptools/_distutils/tests/test_config.py b/setuptools/_distutils/tests/test_config.py index a4b4850..43ba676 100644 --- a/setuptools/_distutils/tests/test_config.py +++ b/setuptools/_distutils/tests/test_config.py @@ -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 diff --git a/setuptools/_distutils/tests/test_config_cmd.py b/setuptools/_distutils/tests/test_config_cmd.py index 0c1a9d2..65c60f6 100644 --- a/setuptools/_distutils/tests/test_config_cmd.py +++ b/setuptools/_distutils/tests/test_config_cmd.py @@ -1,31 +1,28 @@ """Tests for distutils.command.config.""" -import unittest import os import sys -from test.support import 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) diff --git a/setuptools/_distutils/tests/test_core.py b/setuptools/_distutils/tests/test_core.py index 23402fb..86b0040 100644 --- a/setuptools/_distutils/tests/test_core.py +++ b/setuptools/_distutils/tests/test_core.py @@ -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 diff --git a/setuptools/_distutils/tests/test_cygwinccompiler.py b/setuptools/_distutils/tests/test_cygwinccompiler.py index 7760436..ef01ae2 100644 --- a/setuptools/_distutils/tests/test_cygwinccompiler.py +++ b/setuptools/_distutils/tests/test_cygwinccompiler.py @@ -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() diff --git a/setuptools/_distutils/tests/test_dep_util.py b/setuptools/_distutils/tests/test_dep_util.py index 89ae05d..2dcce1d 100644 --- a/setuptools/_distutils/tests/test_dep_util.py +++ b/setuptools/_distutils/tests/test_dep_util.py @@ -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') diff --git a/setuptools/_distutils/tests/test_dir_util.py b/setuptools/_distutils/tests/test_dir_util.py index a1f9a24..cd7e018 100644 --- a/setuptools/_distutils/tests/test_dir_util.py +++ b/setuptools/_distutils/tests/test_dir_util.py @@ -1,9 +1,7 @@ """Tests for distutils.dir_util.""" -import unittest import os import stat -import sys -from unittest.mock import patch +import unittest.mock as mock from distutils import dir_util, errors from distutils.dir_util import ( @@ -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()) diff --git a/setuptools/_distutils/tests/test_dist.py b/setuptools/_distutils/tests/test_dist.py index 6520a46..ddfaf92 100644 --- a/setuptools/_distutils/tests/test_dist.py +++ b/setuptools/_distutils/tests/test_dist.py @@ -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'] diff --git a/setuptools/_distutils/tests/test_extension.py b/setuptools/_distutils/tests/test_extension.py index 77fe3f8..f86af07 100644 --- a/setuptools/_distutils/tests/test_extension.py +++ b/setuptools/_distutils/tests/test_extension.py @@ -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'" diff --git a/setuptools/_distutils/tests/test_file_util.py b/setuptools/_distutils/tests/test_file_util.py index 22898b9..b2e83c5 100644 --- a/setuptools/_distutils/tests/test_file_util.py +++ b/setuptools/_distutils/tests/test_file_util.py @@ -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' diff --git a/setuptools/_distutils/tests/test_filelist.py b/setuptools/_distutils/tests/test_filelist.py index 71718a8..2607182 100644 --- a/setuptools/_distutils/tests/test_filelist.py +++ b/setuptools/_distutils/tests/test_filelist.py @@ -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()) diff --git a/setuptools/_distutils/tests/test_install.py b/setuptools/_distutils/tests/test_install.py index 5f0a64d..32a18b2 100644 --- a/setuptools/_distutils/tests/test_install.py +++ b/setuptools/_distutils/tests/test_install.py @@ -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 diff --git a/setuptools/_distutils/tests/test_install_data.py b/setuptools/_distutils/tests/test_install_data.py index a08168b..f77c790 100644 --- a/setuptools/_distutils/tests/test_install_data.py +++ b/setuptools/_distutils/tests/test_install_data.py @@ -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)) diff --git a/setuptools/_distutils/tests/test_install_headers.py b/setuptools/_distutils/tests/test_install_headers.py index db4f4db..7594f5a 100644 --- a/setuptools/_distutils/tests/test_install_headers.py +++ b/setuptools/_distutils/tests/test_install_headers.py @@ -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 diff --git a/setuptools/_distutils/tests/test_install_lib.py b/setuptools/_distutils/tests/test_install_lib.py index 1ef233a..a654a66 100644 --- a/setuptools/_distutils/tests/test_install_lib.py +++ b/setuptools/_distutils/tests/test_install_lib.py @@ -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] diff --git a/setuptools/_distutils/tests/test_install_scripts.py b/setuptools/_distutils/tests/test_install_scripts.py index bac7880..0d17f11 100644 --- a/setuptools/_distutils/tests/test_install_scripts.py +++ b/setuptools/_distutils/tests/test_install_scripts.py @@ -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 diff --git a/setuptools/_distutils/tests/test_log.py b/setuptools/_distutils/tests/test_log.py index 33f7f96..7aeee40 100644 --- a/setuptools/_distutils/tests/test_log.py +++ b/setuptools/_distutils/tests/test_log.py @@ -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' + ) diff --git a/setuptools/_distutils/tests/test_msvc9compiler.py b/setuptools/_distutils/tests/test_msvc9compiler.py index ec4781a..fe5693e 100644 --- a/setuptools/_distutils/tests/test_msvc9compiler.py +++ b/setuptools/_distutils/tests/test_msvc9compiler.py @@ -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 diff --git a/setuptools/_distutils/tests/test_msvccompiler.py b/setuptools/_distutils/tests/test_msvccompiler.py index 21fe57f..f63537b 100644 --- a/setuptools/_distutils/tests/test_msvccompiler.py +++ b/setuptools/_distutils/tests/test_msvccompiler.py @@ -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()) diff --git a/setuptools/_distutils/tests/test_register.py b/setuptools/_distutils/tests/test_register.py index 0f91ad3..0a5765f 100644 --- a/setuptools/_distutils/tests/test_register.py +++ b/setuptools/_distutils/tests/test_register.py @@ -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 * '-' diff --git a/setuptools/_distutils/tests/test_sdist.py b/setuptools/_distutils/tests/test_sdist.py index 3a6aea2..b11fe7c 100644 --- a/setuptools/_distutils/tests/test_sdist.py +++ b/setuptools/_distutils/tests/test_sdist.py @@ -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()) diff --git a/setuptools/_distutils/tests/test_spawn.py b/setuptools/_distutils/tests/test_spawn.py index a773256..d2a898e 100644 --- a/setuptools/_distutils/tests/test_spawn.py +++ b/setuptools/_distutils/tests/test_spawn.py @@ -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) diff --git a/setuptools/_distutils/tests/test_sysconfig.py b/setuptools/_distutils/tests/test_sysconfig.py index a033e07..f175983 100644 --- a/setuptools/_distutils/tests/test_sysconfig.py +++ b/setuptools/_distutils/tests/test_sysconfig.py @@ -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()) diff --git a/setuptools/_distutils/tests/test_text_file.py b/setuptools/_distutils/tests/test_text_file.py index 16de9ca..7c8dc5b 100644 --- a/setuptools/_distutils/tests/test_text_file.py +++ b/setuptools/_distutils/tests/test_text_file.py @@ -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()) diff --git a/setuptools/_distutils/tests/test_unixccompiler.py b/setuptools/_distutils/tests/test_unixccompiler.py index 879769f..3978c23 100644 --- a/setuptools/_distutils/tests/test_unixccompiler.py +++ b/setuptools/_distutils/tests/test_unixccompiler.py @@ -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()) diff --git a/setuptools/_distutils/tests/test_upload.py b/setuptools/_distutils/tests/test_upload.py index afba2fa..fb905b6 100644 --- a/setuptools/_distutils/tests/test_upload.py +++ b/setuptools/_distutils/tests/test_upload.py @@ -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() diff --git a/setuptools/_distutils/tests/test_util.py b/setuptools/_distutils/tests/test_util.py index cebd61c..605b0d4 100644 --- a/setuptools/_distutils/tests/test_util.py +++ b/setuptools/_distutils/tests/test_util.py @@ -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" diff --git a/setuptools/_distutils/tests/test_version.py b/setuptools/_distutils/tests/test_version.py index cecb279..ff52ea4 100644 --- a/setuptools/_distutils/tests/test_version.py +++ b/setuptools/_distutils/tests/test_version.py @@ -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) diff --git a/setuptools/_distutils/tests/test_versionpredicate.py b/setuptools/_distutils/tests/test_versionpredicate.py index ce3d0f4..e69de29 100644 --- a/setuptools/_distutils/tests/test_versionpredicate.py +++ b/setuptools/_distutils/tests/test_versionpredicate.py @@ -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()) diff --git a/setuptools/_distutils/tests/unix_compat.py b/setuptools/_distutils/tests/unix_compat.py index 8250b36..95fc8ee 100644 --- a/setuptools/_distutils/tests/unix_compat.py +++ b/setuptools/_distutils/tests/unix_compat.py @@ -1,5 +1,4 @@ import sys -import unittest try: import grp @@ -7,9 +6,13 @@ 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 index 0000000..0250031 --- /dev/null +++ b/setuptools/_distutils/tests/xxmodule-3.8.c @@ -0,0 +1,411 @@ + +/* Use this file as a template to start implementing a module that + also declares object types. All occurrences of 'Xxo' should be changed + to something reasonable for your objects. After that, all other + occurrences of 'xx' should be changed to something reasonable for your + module. If your module is named foo your sourcefile should be named + foomodule.c. + + You will probably want to delete all references to 'x_attr' and add + your own types of attributes instead. Maybe you want to name your + local variables other than 'self'. If your object type is needed in + other files, you'll have to create a file "foobarobject.h"; see + floatobject.h for an example. */ + +/* Xxo objects */ + +#include "Python.h" + +static PyObject *ErrorObject; + +typedef struct { + PyObject_HEAD + PyObject *x_attr; /* Attributes dictionary */ +} XxoObject; + +static PyTypeObject Xxo_Type; + +#define XxoObject_Check(v) (Py_TYPE(v) == &Xxo_Type) + +static XxoObject * +newXxoObject(PyObject *arg) +{ + XxoObject *self; + self = PyObject_New(XxoObject, &Xxo_Type); + if (self == NULL) + return NULL; + self->x_attr = NULL; + return self; +} + +/* Xxo methods */ + +static void +Xxo_dealloc(XxoObject *self) +{ + Py_XDECREF(self->x_attr); + PyObject_Del(self); +} + +static PyObject * +Xxo_demo(XxoObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":demo")) + return NULL; + Py_INCREF(Py_None); + return Py_None; +} + +static PyMethodDef Xxo_methods[] = { + {"demo", (PyCFunction)Xxo_demo, METH_VARARGS, + PyDoc_STR("demo() -> None")}, + {NULL, NULL} /* sentinel */ +}; + +static PyObject * +Xxo_getattro(XxoObject *self, PyObject *name) +{ + if (self->x_attr != NULL) { + PyObject *v = PyDict_GetItemWithError(self->x_attr, name); + if (v != NULL) { + Py_INCREF(v); + return v; + } + else if (PyErr_Occurred()) { + return NULL; + } + } + return PyObject_GenericGetAttr((PyObject *)self, name); +} + +static int +Xxo_setattr(XxoObject *self, const char *name, PyObject *v) +{ + if (self->x_attr == NULL) { + self->x_attr = PyDict_New(); + if (self->x_attr == NULL) + return -1; + } + if (v == NULL) { + int rv = PyDict_DelItemString(self->x_attr, name); + if (rv < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) + PyErr_SetString(PyExc_AttributeError, + "delete non-existing Xxo attribute"); + return rv; + } + else + return PyDict_SetItemString(self->x_attr, name, v); +} + +static PyTypeObject Xxo_Type = { + /* The ob_type field must be initialized in the module init function + * to be portable to Windows without using C++. */ + PyVarObject_HEAD_INIT(NULL, 0) + "xxmodule.Xxo", /*tp_name*/ + sizeof(XxoObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)Xxo_dealloc, /*tp_dealloc*/ + 0, /*tp_vectorcall_offset*/ + (getattrfunc)0, /*tp_getattr*/ + (setattrfunc)Xxo_setattr, /*tp_setattr*/ + 0, /*tp_as_async*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + (getattrofunc)Xxo_getattro, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + Xxo_methods, /*tp_methods*/ + 0, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + 0, /*tp_init*/ + 0, /*tp_alloc*/ + 0, /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ +}; +/* --------------------------------------------------------------------- */ + +/* Function of two integers returning integer */ + +PyDoc_STRVAR(xx_foo_doc, +"foo(i,j)\n\ +\n\ +Return the sum of i and j."); + +static PyObject * +xx_foo(PyObject *self, PyObject *args) +{ + long i, j; + long res; + if (!PyArg_ParseTuple(args, "ll:foo", &i, &j)) + return NULL; + res = i+j; /* XXX Do something here */ + return PyLong_FromLong(res); +} + + +/* Function of no arguments returning new Xxo object */ + +static PyObject * +xx_new(PyObject *self, PyObject *args) +{ + XxoObject *rv; + + if (!PyArg_ParseTuple(args, ":new")) + return NULL; + rv = newXxoObject(args); + if (rv == NULL) + return NULL; + return (PyObject *)rv; +} + +/* Example with subtle bug from extensions manual ("Thin Ice"). */ + +static PyObject * +xx_bug(PyObject *self, PyObject *args) +{ + PyObject *list, *item; + + if (!PyArg_ParseTuple(args, "O:bug", &list)) + return NULL; + + item = PyList_GetItem(list, 0); + /* Py_INCREF(item); */ + PyList_SetItem(list, 1, PyLong_FromLong(0L)); + PyObject_Print(item, stdout, 0); + printf("\n"); + /* Py_DECREF(item); */ + + Py_INCREF(Py_None); + return Py_None; +} + +/* Test bad format character */ + +static PyObject * +xx_roj(PyObject *self, PyObject *args) +{ + PyObject *a; + long b; + if (!PyArg_ParseTuple(args, "O#:roj", &a, &b)) + return NULL; + Py_INCREF(Py_None); + return Py_None; +} + + +/* ---------- */ + +static PyTypeObject Str_Type = { + /* The ob_type field must be initialized in the module init function + * to be portable to Windows without using C++. */ + PyVarObject_HEAD_INIT(NULL, 0) + "xxmodule.Str", /*tp_name*/ + 0, /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + 0, /*tp_dealloc*/ + 0, /*tp_vectorcall_offset*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_as_async*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + 0, /*tp_members*/ + 0, /*tp_getset*/ + 0, /* see PyInit_xx */ /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + 0, /*tp_init*/ + 0, /*tp_alloc*/ + 0, /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ +}; + +/* ---------- */ + +static PyObject * +null_richcompare(PyObject *self, PyObject *other, int op) +{ + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; +} + +static PyTypeObject Null_Type = { + /* The ob_type field must be initialized in the module init function + * to be portable to Windows without using C++. */ + PyVarObject_HEAD_INIT(NULL, 0) + "xxmodule.Null", /*tp_name*/ + 0, /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + 0, /*tp_dealloc*/ + 0, /*tp_vectorcall_offset*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_as_async*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + null_richcompare, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + 0, /*tp_members*/ + 0, /*tp_getset*/ + 0, /* see PyInit_xx */ /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + 0, /*tp_init*/ + 0, /*tp_alloc*/ + PyType_GenericNew, /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ +}; + + +/* ---------- */ + + +/* List of functions defined in the module */ + +static PyMethodDef xx_methods[] = { + {"roj", xx_roj, METH_VARARGS, + PyDoc_STR("roj(a,b) -> None")}, + {"foo", xx_foo, METH_VARARGS, + xx_foo_doc}, + {"new", xx_new, METH_VARARGS, + PyDoc_STR("new() -> new Xx object")}, + {"bug", xx_bug, METH_VARARGS, + PyDoc_STR("bug(o) -> None")}, + {NULL, NULL} /* sentinel */ +}; + +PyDoc_STRVAR(module_doc, +"This is a template module just for instruction."); + + +static int +xx_exec(PyObject *m) +{ + /* Slot initialization is subject to the rules of initializing globals. + C99 requires the initializers to be "address constants". Function + designators like 'PyType_GenericNew', with implicit conversion to + a pointer, are valid C99 address constants. + + However, the unary '&' operator applied to a non-static variable + like 'PyBaseObject_Type' is not required to produce an address + constant. Compilers may support this (gcc does), MSVC does not. + + Both compilers are strictly standard conforming in this particular + behavior. + */ + Null_Type.tp_base = &PyBaseObject_Type; + Str_Type.tp_base = &PyUnicode_Type; + + /* Finalize the type object including setting type of the new type + * object; doing it here is required for portability, too. */ + if (PyType_Ready(&Xxo_Type) < 0) + goto fail; + + /* Add some symbolic constants to the module */ + if (ErrorObject == NULL) { + ErrorObject = PyErr_NewException("xx.error", NULL, NULL); + if (ErrorObject == NULL) + goto fail; + } + Py_INCREF(ErrorObject); + PyModule_AddObject(m, "error", ErrorObject); + + /* Add Str */ + if (PyType_Ready(&Str_Type) < 0) + goto fail; + PyModule_AddObject(m, "Str", (PyObject *)&Str_Type); + + /* Add Null */ + if (PyType_Ready(&Null_Type) < 0) + goto fail; + PyModule_AddObject(m, "Null", (PyObject *)&Null_Type); + return 0; + fail: + Py_XDECREF(m); + return -1; +} + +static struct PyModuleDef_Slot xx_slots[] = { + {Py_mod_exec, xx_exec}, + {0, NULL}, +}; + +static struct PyModuleDef xxmodule = { + PyModuleDef_HEAD_INIT, + "xx", + module_doc, + 0, + xx_methods, + xx_slots, + NULL, + NULL, + NULL +}; + +/* Export function for the module (*must* be called PyInit_xx) */ + +PyMODINIT_FUNC +PyInit_xx(void) +{ + return PyModuleDef_Init(&xxmodule); +} diff --git a/setuptools/_distutils/tests/xxmodule.c b/setuptools/_distutils/tests/xxmodule.c new file mode 100644 index 0000000..a6e5071 --- /dev/null +++ b/setuptools/_distutils/tests/xxmodule.c @@ -0,0 +1,412 @@ + +/* Use this file as a template to start implementing a module that + also declares object types. All occurrences of 'Xxo' should be changed + to something reasonable for your objects. After that, all other + occurrences of 'xx' should be changed to something reasonable for your + module. If your module is named foo your sourcefile should be named + foomodule.c. + + You will probably want to delete all references to 'x_attr' and add + your own types of attributes instead. Maybe you want to name your + local variables other than 'self'. If your object type is needed in + other files, you'll have to create a file "foobarobject.h"; see + floatobject.h for an example. */ + +/* Xxo objects */ + +#include "Python.h" + +static PyObject *ErrorObject; + +typedef struct { + PyObject_HEAD + PyObject *x_attr; /* Attributes dictionary */ +} XxoObject; + +static PyTypeObject Xxo_Type; + +#define XxoObject_Check(v) Py_IS_TYPE(v, &Xxo_Type) + +static XxoObject * +newXxoObject(PyObject *arg) +{ + XxoObject *self; + self = PyObject_New(XxoObject, &Xxo_Type); + if (self == NULL) + return NULL; + self->x_attr = NULL; + return self; +} + +/* Xxo methods */ + +static void +Xxo_dealloc(XxoObject *self) +{ + Py_XDECREF(self->x_attr); + PyObject_Free(self); +} + +static PyObject * +Xxo_demo(XxoObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":demo")) + return NULL; + Py_INCREF(Py_None); + return Py_None; +} + +static PyMethodDef Xxo_methods[] = { + {"demo", (PyCFunction)Xxo_demo, METH_VARARGS, + PyDoc_STR("demo() -> None")}, + {NULL, NULL} /* sentinel */ +}; + +static PyObject * +Xxo_getattro(XxoObject *self, PyObject *name) +{ + if (self->x_attr != NULL) { + PyObject *v = PyDict_GetItemWithError(self->x_attr, name); + if (v != NULL) { + Py_INCREF(v); + return v; + } + else if (PyErr_Occurred()) { + return NULL; + } + } + return PyObject_GenericGetAttr((PyObject *)self, name); +} + +static int +Xxo_setattr(XxoObject *self, const char *name, PyObject *v) +{ + if (self->x_attr == NULL) { + self->x_attr = PyDict_New(); + if (self->x_attr == NULL) + return -1; + } + if (v == NULL) { + int rv = PyDict_DelItemString(self->x_attr, name); + if (rv < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) + PyErr_SetString(PyExc_AttributeError, + "delete non-existing Xxo attribute"); + return rv; + } + else + return PyDict_SetItemString(self->x_attr, name, v); +} + +static PyTypeObject Xxo_Type = { + /* The ob_type field must be initialized in the module init function + * to be portable to Windows without using C++. */ + PyVarObject_HEAD_INIT(NULL, 0) + "xxmodule.Xxo", /*tp_name*/ + sizeof(XxoObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)Xxo_dealloc, /*tp_dealloc*/ + 0, /*tp_vectorcall_offset*/ + (getattrfunc)0, /*tp_getattr*/ + (setattrfunc)Xxo_setattr, /*tp_setattr*/ + 0, /*tp_as_async*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + (getattrofunc)Xxo_getattro, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + Xxo_methods, /*tp_methods*/ + 0, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + 0, /*tp_init*/ + 0, /*tp_alloc*/ + 0, /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ +}; +/* --------------------------------------------------------------------- */ + +/* Function of two integers returning integer */ + +PyDoc_STRVAR(xx_foo_doc, +"foo(i,j)\n\ +\n\ +Return the sum of i and j."); + +static PyObject * +xx_foo(PyObject *self, PyObject *args) +{ + long i, j; + long res; + if (!PyArg_ParseTuple(args, "ll:foo", &i, &j)) + return NULL; + res = i+j; /* XXX Do something here */ + return PyLong_FromLong(res); +} + + +/* Function of no arguments returning new Xxo object */ + +static PyObject * +xx_new(PyObject *self, PyObject *args) +{ + XxoObject *rv; + + if (!PyArg_ParseTuple(args, ":new")) + return NULL; + rv = newXxoObject(args); + if (rv == NULL) + return NULL; + return (PyObject *)rv; +} + +/* Example with subtle bug from extensions manual ("Thin Ice"). */ + +static PyObject * +xx_bug(PyObject *self, PyObject *args) +{ + PyObject *list, *item; + + if (!PyArg_ParseTuple(args, "O:bug", &list)) + return NULL; + + item = PyList_GetItem(list, 0); + /* Py_INCREF(item); */ + PyList_SetItem(list, 1, PyLong_FromLong(0L)); + PyObject_Print(item, stdout, 0); + printf("\n"); + /* Py_DECREF(item); */ + + Py_INCREF(Py_None); + return Py_None; +} + +/* Test bad format character */ + +static PyObject * +xx_roj(PyObject *self, PyObject *args) +{ + PyObject *a; + long b; + if (!PyArg_ParseTuple(args, "O#:roj", &a, &b)) + return NULL; + Py_INCREF(Py_None); + return Py_None; +} + + +/* ---------- */ + +static PyTypeObject Str_Type = { + /* The ob_type field must be initialized in the module init function + * to be portable to Windows without using C++. */ + PyVarObject_HEAD_INIT(NULL, 0) + "xxmodule.Str", /*tp_name*/ + 0, /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + 0, /*tp_dealloc*/ + 0, /*tp_vectorcall_offset*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_as_async*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + 0, /*tp_members*/ + 0, /*tp_getset*/ + 0, /* see PyInit_xx */ /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + 0, /*tp_init*/ + 0, /*tp_alloc*/ + 0, /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ +}; + +/* ---------- */ + +static PyObject * +null_richcompare(PyObject *self, PyObject *other, int op) +{ + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; +} + +static PyTypeObject Null_Type = { + /* The ob_type field must be initialized in the module init function + * to be portable to Windows without using C++. */ + PyVarObject_HEAD_INIT(NULL, 0) + "xxmodule.Null", /*tp_name*/ + 0, /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + 0, /*tp_dealloc*/ + 0, /*tp_vectorcall_offset*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_as_async*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + null_richcompare, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + 0, /*tp_members*/ + 0, /*tp_getset*/ + 0, /* see PyInit_xx */ /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + 0, /*tp_init*/ + 0, /*tp_alloc*/ + PyType_GenericNew, /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ +}; + + +/* ---------- */ + + +/* List of functions defined in the module */ + +static PyMethodDef xx_methods[] = { + {"roj", xx_roj, METH_VARARGS, + PyDoc_STR("roj(a,b) -> None")}, + {"foo", xx_foo, METH_VARARGS, + xx_foo_doc}, + {"new", xx_new, METH_VARARGS, + PyDoc_STR("new() -> new Xx object")}, + {"bug", xx_bug, METH_VARARGS, + PyDoc_STR("bug(o) -> None")}, + {NULL, NULL} /* sentinel */ +}; + +PyDoc_STRVAR(module_doc, +"This is a template module just for instruction."); + + +static int +xx_exec(PyObject *m) +{ + /* Slot initialization is subject to the rules of initializing globals. + C99 requires the initializers to be "address constants". Function + designators like 'PyType_GenericNew', with implicit conversion to + a pointer, are valid C99 address constants. + + However, the unary '&' operator applied to a non-static variable + like 'PyBaseObject_Type' is not required to produce an address + constant. Compilers may support this (gcc does), MSVC does not. + + Both compilers are strictly standard conforming in this particular + behavior. + */ + Null_Type.tp_base = &PyBaseObject_Type; + Str_Type.tp_base = &PyUnicode_Type; + + /* Finalize the type object including setting type of the new type + * object; doing it here is required for portability, too. */ + if (PyType_Ready(&Xxo_Type) < 0) { + return -1; + } + + /* Add some symbolic constants to the module */ + if (ErrorObject == NULL) { + ErrorObject = PyErr_NewException("xx.error", NULL, NULL); + if (ErrorObject == NULL) { + return -1; + } + } + int rc = PyModule_AddType(m, (PyTypeObject *)ErrorObject); + Py_DECREF(ErrorObject); + if (rc < 0) { + return -1; + } + + /* Add Str and Null types */ + if (PyModule_AddType(m, &Str_Type) < 0) { + return -1; + } + if (PyModule_AddType(m, &Null_Type) < 0) { + return -1; + } + + return 0; +} + +static struct PyModuleDef_Slot xx_slots[] = { + {Py_mod_exec, xx_exec}, + {0, NULL}, +}; + +static struct PyModuleDef xxmodule = { + PyModuleDef_HEAD_INIT, + "xx", + module_doc, + 0, + xx_methods, + xx_slots, + NULL, + NULL, + NULL +}; + +/* Export function for the module (*must* be called PyInit_xx) */ + +PyMODINIT_FUNC +PyInit_xx(void) +{ + return PyModuleDef_Init(&xxmodule); +} diff --git a/setuptools/_distutils/text_file.py b/setuptools/_distutils/text_file.py index 015d680..7274d4b 100644 --- a/setuptools/_distutils/text_file.py +++ b/setuptools/_distutils/text_file.py @@ -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 diff --git a/setuptools/_distutils/unixccompiler.py b/setuptools/_distutils/unixccompiler.py index 4be74fd..4ab771a 100644 --- a/setuptools/_distutils/unixccompiler.py +++ b/setuptools/_distutils/unixccompiler.py @@ -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) diff --git a/setuptools/_distutils/util.py b/setuptools/_distutils/util.py index d59c362..d95992e 100644 --- a/setuptools/_distutils/util.py +++ b/setuptools/_distutils/util.py @@ -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, diff --git a/setuptools/_distutils/version.py b/setuptools/_distutils/version.py index a406a30..e29e265 100644 --- a/setuptools/_distutils/version.py +++ b/setuptools/_distutils/version.py @@ -60,7 +60,7 @@ class Version: ) def __repr__(self): - return "%s ('%s')" % (self.__class__.__name__, str(self)) + return "{} ('{}')".format(self.__class__.__name__, str(self)) def __eq__(self, other): c = self._cmp(other) @@ -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 index a1b589e..0000000 --- a/setuptools/_vendor/pyparsing-3.0.8.dist-info/INSTALLER +++ /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 index 1bf9852..0000000 --- a/setuptools/_vendor/pyparsing-3.0.8.dist-info/LICENSE +++ /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 index d6c8e9b..0000000 --- a/setuptools/_vendor/pyparsing-3.0.8.dist-info/METADATA +++ /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 -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 `__ -*.]* - -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 `__. Additional -documentation resources and project info are listed in the online -`GitHub wiki `__. An -entire directory of examples can be found `here `__. - -License -======= - -MIT License. See header of the `pyparsing.py `__ file. - -History -======= - -See `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 index 72947b0..0000000 --- a/setuptools/_vendor/pyparsing-3.0.8.dist-info/RECORD +++ /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 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 index c727d14..0000000 --- a/setuptools/_vendor/pyparsing-3.0.8.dist-info/WHEEL +++ /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 index 0000000..a1b589e --- /dev/null +++ b/setuptools/_vendor/pyparsing-3.0.9.dist-info/INSTALLER @@ -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 index 0000000..1bf9852 --- /dev/null +++ b/setuptools/_vendor/pyparsing-3.0.9.dist-info/LICENSE @@ -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 index 0000000..33e5194 --- /dev/null +++ b/setuptools/_vendor/pyparsing-3.0.9.dist-info/METADATA @@ -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 +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 `__ +*.]* + +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 `__. Additional +documentation resources and project info are listed in the online +`GitHub wiki `__. An +entire directory of examples can be found `here `__. + +License +======= + +MIT License. See header of the `pyparsing.py `__ file. + +History +======= + +See `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 index 0000000..7a4e49a --- /dev/null +++ b/setuptools/_vendor/pyparsing-3.0.9.dist-info/RECORD @@ -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 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 index 0000000..c727d14 --- /dev/null +++ b/setuptools/_vendor/pyparsing-3.0.9.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.6.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/setuptools/_vendor/pyparsing/__init__.py b/setuptools/_vendor/pyparsing/__init__.py index 45f334d..7802ff1 100644 --- a/setuptools/_vendor/pyparsing/__init__.py +++ b/setuptools/_vendor/pyparsing/__init__.py @@ -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 " diff --git a/setuptools/_vendor/pyparsing/actions.py b/setuptools/_vendor/pyparsing/actions.py index 2bcc550..f72c66e 100644 --- a/setuptools/_vendor/pyparsing/actions.py +++ b/setuptools/_vendor/pyparsing/actions.py @@ -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] diff --git a/setuptools/_vendor/pyparsing/core.py b/setuptools/_vendor/pyparsing/core.py index 454bd57..9acba3f 100644 --- a/setuptools/_vendor/pyparsing/core.py +++ b/setuptools/_vendor/pyparsing/core.py @@ -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, 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 diff --git a/setuptools/_vendor/pyparsing/diagram/__init__.py b/setuptools/_vendor/pyparsing/diagram/__init__.py index 2d0c587..8986447 100644 --- a/setuptools/_vendor/pyparsing/diagram/__init__.py +++ b/setuptools/_vendor/pyparsing/diagram/__init__.py @@ -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 = """\ + + + + {% if not head %} + + {% else %} + {{ head | safe }} + {% endif %} + + +{{ body | safe }} +{% for diagram in diagrams %} +
+

{{ diagram.title }}

+
{{ diagram.text }}
+
+ {{ diagram.svg }} +
+
+{% endfor %} + + +""" + +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 index d2219fb..0000000 --- a/setuptools/_vendor/pyparsing/diagram/template.jinja2 +++ /dev/null @@ -1,26 +0,0 @@ - - - - {% if not head %} - - {% else %} - {{ hear | safe }} - {% endif %} - - -{{ body | safe }} -{% for diagram in diagrams %} -
-

{{ diagram.title }}

-
{{ diagram.text }}
-
- {{ diagram.svg }} -
-
-{% endfor %} - - diff --git a/setuptools/_vendor/pyparsing/exceptions.py b/setuptools/_vendor/pyparsing/exceptions.py index e06513e..a38447b 100644 --- a/setuptools/_vendor/pyparsing/exceptions.py +++ b/setuptools/_vendor/pyparsing/exceptions.py @@ -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 diff --git a/setuptools/_vendor/pyparsing/helpers.py b/setuptools/_vendor/pyparsing/helpers.py index be8a365..9588b3b 100644 --- a/setuptools/_vendor/pyparsing/helpers.py +++ b/setuptools/_vendor/pyparsing/helpers.py @@ -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 diff --git a/setuptools/_vendor/pyparsing/results.py b/setuptools/_vendor/pyparsing/results.py index bb444df..00c9421 100644 --- a/setuptools/_vendor/pyparsing/results.py +++ b/setuptools/_vendor/pyparsing/results.py @@ -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) # -> ['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: diff --git a/setuptools/_vendor/pyparsing/testing.py b/setuptools/_vendor/pyparsing/testing.py index 991972f..84a0ef1 100644 --- a/setuptools/_vendor/pyparsing/testing.py +++ b/setuptools/_vendor/pyparsing/testing.py @@ -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. diff --git a/setuptools/_vendor/pyparsing/unicode.py b/setuptools/_vendor/pyparsing/unicode.py index 9226148..0652620 100644 --- a/setuptools/_vendor/pyparsing/unicode.py +++ b/setuptools/_vendor/pyparsing/unicode.py @@ -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 diff --git a/setuptools/_vendor/vendored.txt b/setuptools/_vendor/vendored.txt index 84c4006..e9d5bed 100644 --- a/setuptools/_vendor/vendored.txt +++ b/setuptools/_vendor/vendored.txt @@ -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 diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index 801ec30..98fb148 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -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']) diff --git a/setuptools/command/__init__.py b/setuptools/command/__init__.py index b966dce..5acd768 100644 --- a/setuptools/command/__init__.py +++ b/setuptools/command/__init__.py @@ -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 diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py index 923a323..8b1a332 100644 --- a/setuptools/command/build_py.py +++ b/setuptools/command/build_py.py @@ -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 diff --git a/setuptools/command/editable_wheel.py b/setuptools/command/editable_wheel.py index 5e205a4..1bb7ddf 100644 --- a/setuptools/command/editable_wheel.py +++ b/setuptools/command/editable_wheel.py @@ -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.""" diff --git a/setuptools/command/upload_docs.py b/setuptools/command/upload_docs.py index a548000..3263f07 100644 --- a/setuptools/command/upload_docs.py +++ b/setuptools/command/upload_docs.py @@ -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): diff --git a/setuptools/config/__init__.py b/setuptools/config/__init__.py index 35458d8..1a5153a 100644 --- a/setuptools/config/__init__.py +++ b/setuptools/config/__init__.py @@ -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) diff --git a/setuptools/config/pyprojecttoml.py b/setuptools/config/pyprojecttoml.py index 0e9e3c9..9ff0c87 100644 --- a/setuptools/config/pyprojecttoml.py +++ b/setuptools/config/pyprojecttoml.py @@ -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( diff --git a/setuptools/config/setupcfg.py b/setuptools/config/setupcfg.py index af12896..c2a974d 100644 --- a/setuptools/config/setupcfg.py +++ b/setuptools/config/setupcfg.py @@ -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 (";") + 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): diff --git a/setuptools/extension.py b/setuptools/extension.py index b9a2bad..58c023f 100644 --- a/setuptools/extension.py +++ b/setuptools/extension.py @@ -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 `. + + :raises setuptools.errors.PlatformError: if 'runtime_library_dirs' is + specified on Windows. (since v63) """ def __init__(self, name, sources, *args, **kw): diff --git a/setuptools/tests/config/test_pyprojecttoml.py b/setuptools/tests/config/test_pyprojecttoml.py index 200312b..811328f 100644 --- a/setuptools/tests/config/test_pyprojecttoml.py +++ b/setuptools/tests/config/test_pyprojecttoml.py @@ -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): diff --git a/setuptools/tests/config/test_setupcfg.py b/setuptools/tests/config/test_setupcfg.py index b2563a1..d2964fd 100644 --- a/setuptools/tests/config/test_setupcfg.py +++ b/setuptools/tests/config/test_setupcfg.py @@ -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') diff --git a/setuptools/tests/integration/test_pip_install_sdist.py b/setuptools/tests/integration/test_pip_install_sdist.py index 9d11047..b44e32f 100644 --- a/setuptools/tests/integration/test_pip_install_sdist.py +++ b/setuptools/tests/integration/test_pip_install_sdist.py @@ -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, "") diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py index 026c8ae..e70c71b 100644 --- a/setuptools/tests/test_build_meta.py +++ b/setuptools/tests/test_build_meta.py @@ -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 diff --git a/setuptools/tests/test_build_py.py b/setuptools/tests/test_build_py.py index 2b32edb..77738f2 100644 --- a/setuptools/tests/test_build_py.py +++ b/setuptools/tests/test_build_py.py @@ -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() diff --git a/setuptools/tests/test_editable_install.py b/setuptools/tests/test_editable_install.py index a76ab08..ea31cb4 100644 --- a/setuptools/tests/test_editable_install.py +++ b/setuptools/tests/test_editable_install.py @@ -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("//", "/") diff --git a/setuptools/tests/test_windows_wrappers.py b/setuptools/tests/test_windows_wrappers.py index 8ac9bd0..f8b82fc 100644 --- a/setuptools/tests/test_windows_wrappers.py +++ b/setuptools/tests/test_windows_wrappers.py @@ -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 bb2e7cb..13d944e 100644 --- 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]