diff -Nru tox-3.15.1/CODE_OF_CONDUCT.md tox-3.21.4/CODE_OF_CONDUCT.md --- tox-3.15.1/CODE_OF_CONDUCT.md 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/CODE_OF_CONDUCT.md 2021-02-02 20:28:40.000000000 +0000 @@ -2,45 +2,59 @@ ## Our Pledge -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making +participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, +disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, +religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take +appropriate and fair corrective action in response to any instances of unacceptable behavior. -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, +issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any +contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the +project or its community. Examples of representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed representative at an online or offline +event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at tox-dev@python.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at +tox-dev@python.org. The project team will review and investigate all complaints, and will respond in a way that it deems +appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter +of an incident. Further details of specific enforcement policies may be posted separately. -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent +repercussions as determined by other members of the project's leadership. ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html][version] +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at +[https://www.contributor-covenant.org/version/1/4/code-of-conduct.html][version] [homepage]: https://www.contributor-covenant.org/ [version]: https://www.contributor-covenant.org/version/1/4/ diff -Nru tox-3.15.1/CONTRIBUTING.rst tox-3.21.4/CONTRIBUTING.rst --- tox-3.15.1/CONTRIBUTING.rst 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/CONTRIBUTING.rst 2021-02-02 20:28:40.000000000 +0000 @@ -31,12 +31,12 @@ * Any details about your local setup that might be helpful in troubleshooting, specifically the Python interpreter version, installed libraries, and tox version. -* Detailed steps to reproduce the bug, or - even better, a n xfaling test reproduces the bug +* Detailed steps to reproduce the bug, or - even better, an xfailing test reproduces the bug If you can write a demonstration test that currently fails but should pass (xfail), that is a very useful commit to make as well, even if you cannot fix the bug itself (e.g. something like this in -`test_config )`_ +`test_config `_). .. _fixbugs: diff -Nru tox-3.15.1/CONTRIBUTORS tox-3.21.4/CONTRIBUTORS --- tox-3.15.1/CONTRIBUTORS 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/CONTRIBUTORS 2021-02-02 20:28:40.000000000 +0000 @@ -14,8 +14,10 @@ Barney Gale Barry Warsaw Bartolome Sanchez Salado +Bastien Vallet Benoit Pierre Bernat Gabor +Brett Langdon Bruno Oliveira Carl Meyer Charles Brunet @@ -25,13 +27,16 @@ Cyril Roelandt Dane Hillard David Staheli +David Diaz Ederag Eli Collins Eugene Yunak Fernando L. Pereira +Florian Bruhin Florian Preinstorfer Florian Schulze George Alton +Gleb Nikonorov Gonéri Le Bouder Hazal Ozturk Henk-Jaap Wagenaar @@ -44,10 +49,13 @@ Jannis Leidel Joachim Brandon LeBlanc Johannes Christ +John Mark Vandenberg Jon Dufresne Josh Smeaton Josh Snyder +Joshua Hesketh Julian Krause +Jürgen Gmach Jurko Gospodnetić Krisztian Fekete Laszlo Vasko @@ -61,7 +69,9 @@ Mark Hirota Matt Good Matt Jeffery +Matthew Kenigsberg Mattieu Agopian +Mauricio Villegas Mehdi Abaakouk Michael Manganiello Mickaël Schoentgen @@ -72,13 +82,16 @@ Naveen S R Nick Douma Nick Prendergast +Nicolas Vivet Oliver Bestwalter Pablo Galindo Paul Moore Paweł Adamczak +Peter Kolbus Philip Thiem Pierre-Jean Campigotto Pierre-Luc Tessier Gagné +Prakhar Gurunani Ronald Evers Ronny Pfannschmidt Selim Belhaouane @@ -88,6 +101,9 @@ Sviatoslav Sydorenko Thomas Grainger Tim Laurence +Tyagraj Desigar +Usama Sadiq Ville Skyttä +Vlastimil Zíma Xander Johnson anatoly techtonik diff -Nru tox-3.15.1/debian/changelog tox-3.21.4/debian/changelog --- tox-3.15.1/debian/changelog 2020-05-25 16:20:36.000000000 +0000 +++ tox-3.21.4/debian/changelog 2021-02-06 21:36:50.000000000 +0000 @@ -1,3 +1,29 @@ +tox (3.21.4-1) unstable; urgency=medium + + * New upstream release. + + -- Faidon Liambotis Sat, 06 Feb 2021 23:36:50 +0200 + +tox (3.21.0-1) unstable; urgency=medium + + [ Faidon Liambotis ] + * New upstream version. + * General package hygiene: + - Update debian/watch to v4. + - Bump Standards-Version to 4.5.1, no changes needed. + - Bump debhelper compatibility level to 13. + * Remove Barry Warsaw from Uploaders (Closes: #970187) + * Remove versioned depends on package version before buster. + * Refresh Build-Depends/Depends to match upstream's requirements. + * Stop relying on setuptools_scm and use upstream's hardcoded version. + * Add Rules-Requires-Root: no. + + [ Ondřej Nový ] + * Update Maintainer field with new Debian Python Team contact address. + * Update Vcs-* fields with new Debian Python Team Salsa layout. + + -- Faidon Liambotis Sun, 10 Jan 2021 20:20:23 +0200 + tox (3.15.1-1) unstable; urgency=medium * Team upload. diff -Nru tox-3.15.1/debian/control tox-3.21.4/debian/control --- tox-3.15.1/debian/control 2020-05-25 09:01:34.000000000 +0000 +++ tox-3.21.4/debian/control 2021-02-06 21:35:16.000000000 +0000 @@ -1,42 +1,38 @@ Source: tox Section: python Priority: optional -Maintainer: Python Applications Packaging Team -Uploaders: Faidon Liambotis , Barry Warsaw +Maintainer: Debian Python Team +Uploaders: Faidon Liambotis Homepage: https://tox.readthedocs.io/ -Build-Depends: debhelper-compat (= 12), - dh-python, - python3-all, - python3-pip, - python3-filelock (>= 3.0.0-1~), - python3-pkg-resources, - python3-pluggy, - python3-py, - python3-pytest, - python3-pytest-timeout, - python3-setuptools, - python3-setuptools-scm (>= 2.0.0-1~), - python3-sphinx, - python3-sphinxcontrib.autoprogram, - python3-toml (>= 0.9.4-1~), - python3-virtualenv -Standards-Version: 4.5.0 -Vcs-Git: https://salsa.debian.org/python-team/applications/tox.git -Vcs-Browser: https://salsa.debian.org/python-team/applications/tox +Build-Depends: + debhelper-compat (= 13), + dh-python, + python3-all, + python3-filelock, + python3-packaging, + python3-pip, + python3-pluggy (>= 0.12.0-1~), + python3-py, + python3-setuptools, + python3-six, + python3-sphinx (>= 2.0.0-1~), + python3-sphinxcontrib.autoprogram, + python3-toml, + python3-virtualenv, +Standards-Version: 4.5.1 +Vcs-Git: https://salsa.debian.org/python-team/packages/tox.git +Vcs-Browser: https://salsa.debian.org/python-team/packages/tox +Rules-Requires-Root: no Package: tox Architecture: all Built-Using: ${sphinxdoc:Built-Using} -Depends: python3, - python3-pkg-resources, - python3-pluggy, - python3-py, - python3-setuptools, - python3-virtualenv, - virtualenv, - ${misc:Depends}, - ${python3:Depends}, - ${sphinxdoc:Depends} +Depends: + python3, + python3-setuptools, + ${misc:Depends}, + ${python3:Depends}, + ${sphinxdoc:Depends} Description: virtualenv-based automation of test activities Tox as is a generic virtualenv management and test command line tool you can use for: diff -Nru tox-3.15.1/debian/copyright tox-3.21.4/debian/copyright --- tox-3.15.1/debian/copyright 2020-05-25 08:50:32.000000000 +0000 +++ tox-3.21.4/debian/copyright 2021-02-06 21:35:16.000000000 +0000 @@ -13,7 +13,7 @@ Files: debian/* Copyright: 2012 Bradley Froehle 2012 Barry Warsaw - 2018 Faidon Liambotis + 2018-2021 Faidon Liambotis License: MIT License: MIT diff -Nru tox-3.15.1/debian/patches/alabaster-no-external-links tox-3.21.4/debian/patches/alabaster-no-external-links --- tox-3.15.1/debian/patches/alabaster-no-external-links 2020-05-25 08:50:49.000000000 +0000 +++ tox-3.21.4/debian/patches/alabaster-no-external-links 2021-02-06 21:35:16.000000000 +0000 @@ -10,10 +10,10 @@ 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py -index 8af499c..14786f1 100644 +index 05f1017..843a48f 100644 --- a/docs/conf.py +++ b/docs/conf.py -@@ -70,10 +70,10 @@ html_theme_options = { +@@ -72,10 +72,10 @@ html_theme_options = { "github_user": "tox-dev", "github_repo": "tox", "description": "standardise testing in Python", diff -Nru tox-3.15.1/debian/patches/disable-setuptools-scm tox-3.21.4/debian/patches/disable-setuptools-scm --- tox-3.15.1/debian/patches/disable-setuptools-scm 1970-01-01 00:00:00.000000000 +0000 +++ tox-3.21.4/debian/patches/disable-setuptools-scm 2021-02-06 21:35:16.000000000 +0000 @@ -0,0 +1,39 @@ +From: Faidon Liambotis +Date: Sun, 10 Jan 2021 20:13:53 +0200 +Subject: Don't guess version from git; use static version.py + +Upstream ships version.py, so use that instead of trying to regenerate +it using a git description (via setuptools_scm). +--- + setup.py | 18 +++++++----------- + 1 file changed, 7 insertions(+), 11 deletions(-) + +diff --git a/setup.py b/setup.py +index c59b4e9..bc18a38 100644 +--- a/setup.py ++++ b/setup.py +@@ -3,17 +3,13 @@ import textwrap + + from setuptools import setup + +-setup( +- use_scm_version={ +- "write_to": "src/tox/version.py", +- "write_to_template": textwrap.dedent( +- """ +- # coding: utf-8 +- from __future__ import unicode_literals + +- __version__ = {version!r} +- """, +- ).lstrip(), +- }, ++version = {} ++with open("src/tox/version.py") as fp: ++ exec(fp.read(), version) ++ ++ ++setup( ++ version=version["__version__"], + package_dir={"": "src"}, + ) diff -Nru tox-3.15.1/debian/patches/series tox-3.21.4/debian/patches/series --- tox-3.15.1/debian/patches/series 2020-05-25 08:50:49.000000000 +0000 +++ tox-3.21.4/debian/patches/series 2021-02-06 21:35:16.000000000 +0000 @@ -1 +1,2 @@ alabaster-no-external-links +disable-setuptools-scm diff -Nru tox-3.15.1/debian/rules tox-3.21.4/debian/rules --- tox-3.15.1/debian/rules 2020-05-25 08:50:32.000000000 +0000 +++ tox-3.21.4/debian/rules 2021-02-06 21:35:16.000000000 +0000 @@ -25,6 +25,4 @@ override_dh_auto_clean: dh_auto_clean - rm -rf build - rm -rf *.egg-info - rm -rf .tox + rm -f docs/_draft.rst diff -Nru tox-3.15.1/debian/watch tox-3.21.4/debian/watch --- tox-3.15.1/debian/watch 2020-05-25 08:50:32.000000000 +0000 +++ tox-3.21.4/debian/watch 2021-02-06 21:35:16.000000000 +0000 @@ -1,3 +1,4 @@ -version=3 +version=4 opts=uversionmangle=s/(rc|a|b|c)/~$1/ \ -https://pypi.debian.net/tox/tox-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz))) +https://pypi.python.org/packages/source/t/tox/ \ +tox-([\d\.]+)\.tar\.gz debian uupdate diff -Nru tox-3.15.1/docs/changelog.rst tox-3.21.4/docs/changelog.rst --- tox-3.15.1/docs/changelog.rst 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/docs/changelog.rst 2021-02-02 20:28:40.000000000 +0000 @@ -11,6 +11,273 @@ .. towncrier release notes start +v3.21.4 (2021-02-02) +-------------------- + +Bugfixes +^^^^^^^^ + +- Adapt tests not to assume the ``easy_install`` command exists, as it was removed from ``setuptools`` 52.0.0+ - by :user:`hroncok` + `#1893 `_ + + +v3.21.3 (2021-01-28) +-------------------- + +Bugfixes +^^^^^^^^ + +- Fix a killed tox (via SIGTERM) leaving the commands subprocesses running + by handling it as if it were a KeyboardInterrupt - by :user:`dajose` + `#1772 `_ + + +v3.21.2 (2021-01-19) +-------------------- + +Bugfixes +^^^^^^^^ + +- Newer coverage tools update the ``COV_CORE_CONTEXT`` environment variable, add it to the list of environment variables + that can change in our pytest plugin - by :user:`gaborbernat`. + `#1854 `_ + + +v3.21.1 (2021-01-13) +-------------------- + +Bugfixes +^^^^^^^^ + +- Fix regression that broke using install_command in config replacements - by :user:`jayvdb` + `#1777 `_ +- Fix regression parsing posargs default containing colon. - by :user:`jayvdb` + `#1785 `_ + + +Features +^^^^^^^^ + +- Prevent .tox in envlist - by :user:`jayvdb` + `#1684 `_ + + +Miscellaneous +^^^^^^^^^^^^^ + +- Enable building tox with ``setuptools_scm`` 4 and 5 by :user:`hroncok` + `#1799 `_ + + +v3.21.0 (2021-01-08) +-------------------- + +Bugfixes +^^^^^^^^ + +- Fix the false ``congratulations`` message that appears when a ``KeyboardInterrupt`` occurs during package installation. - by :user:`gnikonorov` + `#1453 `_ +- Fix ``platform`` support for ``install_command``. - by :user:`jayvdb` + `#1464 `_ +- Fixed regression in v3.20.0 that caused escaped curly braces in setenv + to break usage of the variable elsewhere in tox.ini. - by :user:`jayvdb` + `#1690 `_ +- Prevent ``{}`` and require ``{:`` is only followed by ``}``. - by :user:`jayvdb` + `#1711 `_ +- Raise ``MissingSubstitution`` on access of broken ini setting. - by :user:`jayvdb` + `#1716 `_ + + +Features +^^^^^^^^ + +- Allow \{ and \} in default of {env:key:default}. - by :user:`jayvdb` + `#1502 `_ +- Allow {posargs} in setenv. - by :user:`jayvdb` + `#1695 `_ +- Allow {/} to refer to os.sep. - by :user:`jayvdb` + `#1700 `_ +- Make parsing [testenv] sections in setup.cfg official. - by :user:`mauvilsa` + `#1727 `_ +- Relax importlib requirement to allow 3.0.0 or any newer version - by + :user:`pkolbus` + `#1763 `_ + + +Documentation +^^^^^^^^^^^^^ + +- Document more info about using ``platform`` setting. - by :user:`prakhargurunani` + `#1144 `_ +- Replace ``indexserver`` in documentation with environment variables - by :user:`ziima`. + `#1357 `_ +- Document that the ``passenv`` environment setting is case insensitive. - by :user:`gnikonorov` + `#1534 `_ + + +v3.20.1 (2020-10-09) +-------------------- + +Bugfixes +^^^^^^^^ + +- Relax importlib requirement to allow version<3 - by :user:`usamasadiq` + `#1682 `_ + + +v3.20.0 (2020-09-01) +-------------------- + +Bugfixes +^^^^^^^^ + +- Allow hyphens and empty factors in generative section name. - by :user:`tyagdit` + `#1636 `_ +- Support for PEP517 in-tree build backend-path key in ``get-build-requires``. - by :user:`nizox` + `#1654 `_ +- Allow escaping curly braces in setenv. - by :user:`mkenigs` + `#1656 `_ + + +Features +^^^^^^^^ + +- Support for comments within ``setenv`` and environment files via the ``files|`` prefix. - by :user:`gaborbernat` + `#1667 `_ + + +v3.19.0 (2020-08-06) +-------------------- + +Bugfixes +^^^^^^^^ + +- skip ``setup.cfg`` if it has no ``tox:tox`` namespace - by :user:`hroncok` + `#1045 `_ + + +Features +^^^^^^^^ + +- Implement support for building projects + having :pep:`517#in-tree-build-backends` ``backend-path`` setting - + by :user:`webknjaz` + `#1575 `_ +- Don't require a tox config file for ``tox --devenv`` - by :user:`hroncok` + `#1643 `_ + + +Documentation +^^^^^^^^^^^^^ + +- Fixed grammar in top-level documentation - by :user:`tfurf` + `#1631 `_ + + +v3.18.1 (2020-07-28) +-------------------- + +Bugfixes +^^^^^^^^ + +- Fix ``TypeError`` when using isolated_build with backends that are not submodules (e.g. ``maturin``) + `#1629 `_ + + +v3.18.0 (2020-07-23) +-------------------- + +Deprecations (removal in next major release) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Add allowlist_externals alias to whitelist_externals (whitelist_externals is now deprecated). - by :user:`dajose` + `#1491 `_ + + +v3.17.1 (2020-07-15) +-------------------- + +Bugfixes +^^^^^^^^ + +- Fix tests when the ``HOSTNAME`` environment variable is set, but empty string - by :user:`hroncok` + `#1616 `_ + + +v3.17.0 (2020-07-14) +-------------------- + +Features +^^^^^^^^ + +- The long arguments ``--verbose`` and ``--quiet`` (rather than only their short forms, ``-v`` and ``-q``) are now accepted. + `#1612 `_ +- The ``ResultLog`` now prefers ``HOSTNAME`` environment variable value (if set) over the full qualified domain name of localhost. + This makes it possible to disable an undesired DNS lookup, + which happened on all ``tox`` invocations, including trivial ones - by :user:`hroncok` + `#1615 `_ + + +Documentation +^^^^^^^^^^^^^ + +- Update packaging information for Flit. + `#1613 `_ + + +v3.16.1 (2020-06-29) +-------------------- + +Bugfixes +^^^^^^^^ + +- Fixed the support for using ``{temp_dir}`` in ``tox.ini`` - by :user:`webknjaz` + `#1609 `_ + + +v3.16.0 (2020-06-26) +-------------------- + +Features +^^^^^^^^ + +- Allow skipping the package and installation step when passing the ``--skip-pkg-install``. This should be used in pair with the ``--notest``, so you can separate environment setup and test run: + + .. code-block:: console + + tox -e py --notest + tox -e py --skip-pkg-install + + by :user:`gaborbernat`. + `#1605 `_ + + +Miscellaneous +^^^^^^^^^^^^^ + +- Improve config parsing performance by precompiling commonly used regular expressions - by :user:`brettlangdon` + `#1603 `_ + + +v3.15.2 (2020-06-06) +-------------------- + +Bugfixes +^^^^^^^^ + +- Add an option to allow a process to suicide before sending the SIGTERM. - by :user:`jhesketh` + `#1497 `_ +- PyPy 7.3.1 on Windows uses the ``Script`` folder instead of ``bin``. - by :user:`gaborbernat` + `#1597 `_ + + +Miscellaneous +^^^^^^^^^^^^^ + +- Allow to run the tests with pip 19.3.1 once again while preserving the ability to use pip 20.1 - by :user:`hroncok` + `#1594 `_ + + v3.15.1 (2020-05-20) -------------------- @@ -61,7 +328,7 @@ Bugfixes ^^^^^^^^ -- Exclude virtualenv depencency versions with known +- Exclude virtualenv dependency versions with known regressions (20.0.[0-7]) - by :user:`webknjaz`. `#1537 `_ - Fix ``tox -h`` and ``tox --hi`` shows an error when run outside a directory with tox support files by :user:`nkpro2000sr`. diff -Nru tox-3.15.1/docs/config.rst tox-3.21.4/docs/config.rst --- tox-3.15.1/docs/config.rst 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/docs/config.rst 2021-02-02 20:28:40.000000000 +0000 @@ -3,7 +3,7 @@ tox configuration specification =============================== -configuration discovery +Configuration discovery ----------------------- At the moment tox supports three configuration locations prioritized in the following order: @@ -15,8 +15,9 @@ As far as the configuration format at the moment we only support standard ConfigParser_ "ini-style" format (there is a plan to add a pure TOML one soon). ``tox.ini`` and ``setup.cfg`` are such files. Note that ``setup.cfg`` requires the content to be under the -``tox:tox`` section. ``pyproject.toml`` on the other hand is in TOML format. However, one can inline -the *ini-style* format under the ``tool.tox.legacy_tox_ini`` key as a multi-line string. +``tox:tox`` and ``testenv`` sections and is otherwise ignored. ``pyproject.toml`` on the other hand is +in TOML format. However, one can inline the *ini-style* format under the ``tool.tox.legacy_tox_ini`` key +as a multi-line string. Below you find the specification for the *ini-style* format, but you might want to skim some :doc:`examples` first and use this page as a reference. @@ -33,9 +34,9 @@ .. conf:: minversion - Define the minimal tox version required to run; if the host tox is less than this - the tool with create an environment and provision it with a tox that satisfies it - under :conf:`provision_tox_env`. + Define the minimal tox version required to run; if the host's tox version is less + than this the tool will create an environment and provision it with a version of + tox that satisfies this under :conf:`provision_tox_env`. .. conf:: requires ^ LIST of PEP-508 @@ -50,7 +51,7 @@ .. code-block:: ini [tox] - requires = tox-venv + requires = tox-pipenv setuptools >= 30.0.0 .. conf:: provision_tox_env ^ string ^ .tox @@ -108,7 +109,7 @@ .. versionadded:: 3.4.0 - What tox environments are ran during the tox invocation can be further filtered + Which tox environments are run during the tox invocation can be further filtered via the operating system environment variable ``TOX_SKIP_ENV`` regular expression (e.g. ``py27.*`` means **don't** evaluate environments that start with the key ``py27``). Skipped environments will be logged at level two verbosity level. @@ -117,9 +118,9 @@ .. versionadded:: 1.7.2 - When skip missing interpreters is ``true`` will force ``tox`` to return success even + Setting this to ``true`` will force ``tox`` to return success even if some of the specified environments were missing. This is useful for some CI - systems or running on a developer box, where you might only have a subset of + systems or when running on a developer box, where you might only have a subset of all your supported interpreters installed but don't want to mark the build as failed because of it. As expected, the command line switch always overrides this setting if passed on the invocation. Setting it to ``config`` @@ -160,27 +161,11 @@ Name of the virtual environment used to create a source distribution from the source tree. -.. conf:: interrupt_timeout ^ float ^ 0.3 - - .. versionadded:: 3.15.0 - - When tox is interrupted, it propagates the signal to the child process, - waits :conf:``interrupt_timeout`` seconds, and sends it a SIGTERM if it hasn't - exited. - -.. conf:: terminate_timeout ^ float ^ 0.2 - - .. versionadded:: 3.15.0 - - When tox is interrupted, it propagates the signal to the child process, - waits :conf:``interrupt_timeout`` seconds, sends it a SIGTERM, waits - :conf:``terminate_timeout`` seconds, and sends it a SIGKILL if it hasn't exited. - Jenkins override ++++++++++++++++ -It is possible to override global settings inside a Jenkins_ instance ( -detection is by checking for existence of the ``JENKINS_URL`` environment variable) +It is possible to override global settings inside a Jenkins_ instance (detection +is done by checking for existence of the ``JENKINS_URL`` environment variable) by using the ``tox:jenkins`` section: .. code-block:: ini @@ -222,13 +207,13 @@ - ``jython``: configures ``basepython = jython`` It is also possible to define what's know as *generative names*, where an -individual section maps to multiple environments; for example: -``py{37,38}-django{30,31}``, which would generate four environments, each -consisting of two factors a piece: ``py37-django30`` (``py37``, ``django30``), +individual section maps to multiple environments. For example, +``py{37,38}-django{30,31}`` would generate four environments, each +consisting of two factors: ``py37-django30`` (``py37``, ``django30``), ``py37-django31`` (``py37``, ``django31``), ``py38-django30`` (``py38``, ``django30``), and ``py38-django31`` (``py38``, ``django31``). Combined, these -features provide the ability to write very concise ``tox.ini`` files and is -discussed further :ref:`below `. +features provide the ability to write very concise ``tox.ini`` files. This is +discussed further in :ref:`below `. tox environment settings @@ -341,15 +326,22 @@ Don't set this option if your :conf:`install_command` does not use pip. -.. conf:: whitelist_externals ^ MULTI-LINE-LIST +.. conf:: allowlist_externals ^ MULTI-LINE-LIST + + .. versionadded:: 3.18 Each line specifies a command name (in glob-style pattern format) which can be used in the ``commands`` section without triggering a "not installed in virtualenv" warning. Example: if you use the - unix ``make`` for running tests you can list ``whitelist_externals=make`` - or ``whitelist_externals=/usr/bin/make`` if you want more precision. + unix ``make`` for running tests you can list ``allowlist_externals=make`` + or ``allowlist_externals=/usr/bin/make`` if you want more precision. If you don't want tox to issue a warning in any case, just use - ``whitelist_externals=*`` which will match all commands (not recommended). + ``allowlist_externals=*`` which will match all commands (not recommended). + + .. note:: + + ``whitelist_externals`` has the same meaning and usage as ``allowlist_externals`` + but it is now deprecated. .. conf:: changedir ^ PATH ^ {toxinidir} @@ -394,7 +386,9 @@ A testenv can define a new ``platform`` setting as a regular expression. If a non-empty expression is defined and does not match against the - ``sys.platform`` string the test environment will be skipped. + ``sys.platform`` string the entire test environment will be skipped and + none of the commands will be executed. Running ``tox -e `` + will run commands for a particular platform and skip the rest. .. conf:: setenv ^ MULTI-LINE-LIST @@ -413,6 +407,14 @@ setenv = PYTHONPATH = {env:PYTHONPATH}{:}{toxinidir} + .. versionadded:: 3.20 + + Support for comments. Lines starting with ``#`` are ignored. + + Support for environment files. Lines starting with the ``file|`` contain path to a environment + file to load. Rules within the environment file are the same as within the ``setenv`` + (same replacement and comment support). + .. conf:: passenv ^ SPACE-SEPARATED-GLOBNAMES .. versionadded:: 2.0 @@ -422,7 +424,9 @@ environment when executing test commands. If a specified environment variable doesn't exist in the tox invocation environment it is ignored. You can use ``*`` and ``?`` to match multiple environment variables with - one name. + one name. The list of environment variable names is not case sensitive, and + all variables that match when upper cased will be passed. For example, passing + ``A`` will pass both ``A`` and ``a``. Some variables are always passed through to ensure the basic functionality of standard library functions or tooling like pip: @@ -477,7 +481,7 @@ WARNING: test command found but not installed in testenv cmd: /path/to/parent/interpreter/bin/ env: /foo/bar/.tox/python - Maybe you forgot to specify a dependency? See also the whitelist_externals envconfig setting. + Maybe you forgot to specify a dependency? See also the allowlist_externals envconfig setting. .. conf:: alwayscopy ^ true|false ^ false @@ -493,9 +497,11 @@ .. versionadded:: 3.10 Set to ``true`` if you want virtualenv to upgrade pip/wheel/setuptools to - the latest version. + the latest version. If (and only if) you want to choose a specific version + (not necessarily the latest) then you can add e.g. ``VIRTUALENV_PIP=20.3.3`` + to your setenv. -.. conf:: args_are_paths ^ true|false ^ false +.. conf:: args_are_paths ^ true|false ^ true Treat positional arguments passed to ``tox`` as file system paths and - if they exist on the filesystem - rewrite them according @@ -597,6 +603,33 @@ via the ``-e`` tox will only run those three (even if ``coverage`` may specify as ``depends`` other targets too - such as ``py27, py35, py36, py37``). +.. conf:: suicide_timeout ^ float ^ 0.0 + + .. versionadded:: 3.15.2 + + When an interrupt is sent via Ctrl+C or the tox process is killed with a SIGTERM, + a SIGINT is sent to all foreground processes. The :conf:``suicide_timeout`` gives + the running process time to cleanup and exit before receiving (in some cases, a duplicate) SIGINT from + tox. + +.. conf:: interrupt_timeout ^ float ^ 0.3 + + .. versionadded:: 3.15.0 + + When tox is interrupted, it propagates the signal to the child process + after :conf:``suicide_timeout`` seconds. If the process still hasn't exited + after :conf:``interrupt_timeout`` seconds, its sends a SIGTERM. + +.. conf:: terminate_timeout ^ float ^ 0.2 + + .. versionadded:: 3.15.0 + + When tox is interrupted, after waiting :conf:``interrupt_timeout`` seconds, + it propagates the signal to the child process, waits + :conf:``interrupt_timeout`` seconds, sends it a SIGTERM, waits + :conf:``terminate_timeout`` seconds, and sends it a SIGKILL if it hasn't + exited. + Substitutions ------------- @@ -640,10 +673,16 @@ they may be accessed by other processes or tox runs. ``{:}`` - OS-specific path separator (``:`` os \*nix family, ``;`` on Windows). May be used in ``setenv``, + OS-specific path separator (``:`` on \*nix family, ``;`` on Windows). May be used in ``setenv``, when target variable is path variable (e.g. PATH or PYTHONPATH). -substitutions for virtualenv-related sections +``{/}`` + OS-specific directory separator (``/`` on \*nix family, ``\\`` on Windows). + Useful for deriving filenames from preset paths, as arguments for commands + that requires ``\\`` on Windows. e.g. ``{distdir}{/}file.txt``. + It is not usually needed when using commands written in Python. + +Substitutions for virtualenv-related sections +++++++++++++++++++++++++++++++++++++++++++++ ``{envname}`` @@ -663,7 +702,7 @@ the environment log directory -environment variable substitutions +Environment variable substitutions ++++++++++++++++++++++++++++++++++ If you specify a substitution string like this:: @@ -675,7 +714,7 @@ does not exist. -environment variable substitutions with default values +Environment variable substitutions with default values ++++++++++++++++++++++++++++++++++++++++++++++++++++++ If you specify a substitution string like this:: @@ -705,10 +744,10 @@ .. _`command positional substitution`: .. _`positional substitution`: -interactive shell substitution +Interactive shell substitution ++++++++++++++++++++++++++++++ -It's possible to inject a config value only when tox is running in interactive shell (standard input): +It's possible to inject a config value only when tox is running in interactive shell (standard input):: {tty:ON_VALUE:OFF_VALUE} @@ -716,7 +755,7 @@ the second value is the value to use when it's not. The later on is optional. A good use case for this is e.g. passing in the ``--pdb`` flag for pytest. -substitutions for positional arguments in commands +Substitutions for positional arguments in commands ++++++++++++++++++++++++++++++++++++++++++++++++++ .. versionadded:: 1.0 @@ -938,7 +977,7 @@ py{27,36}-sqlite: mock # mocking sqlite in python 2.x & 3.6 !py34-sqlite: mock # mocking sqlite, except in python 3.4 sqlite-!py34: mock # (same as the line above) - !py34,!py36: enum34 # use if neither py34 nor py36 are in the env name + !py34-!py36: enum34 # use if neither py34 nor py36 are in the env name Take a look at the first ``deps`` line. It shows how you can special case something for a combination of factors, by just hyphenating the combining @@ -1041,7 +1080,7 @@ it will be considered as relative to the ``toxinidir``, the directory where the configuration file resides. -cli +CLI === .. autoprogram:: tox.cli:cli diff -Nru tox-3.15.1/docs/conf.py tox-3.21.4/docs/conf.py --- tox-3.15.1/docs/conf.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/docs/conf.py 2021-02-02 20:28:40.000000000 +0000 @@ -35,7 +35,9 @@ [os.path.dirname(sys.executable)] + env["PATH"].split(os.pathsep), ) changelog = subprocess.check_output( - ["towncrier", "--draft", "--version", "DRAFT"], cwd=str(ROOT_SRC_TREE_DIR), env=env, + ["towncrier", "--draft", "--version", "DRAFT"], + cwd=str(ROOT_SRC_TREE_DIR), + env=env, ).decode("utf-8") if "No significant changes" in changelog: content = "" diff -Nru tox-3.15.1/docs/drafts/tox_conda_notes_niccodemus.md tox-3.21.4/docs/drafts/tox_conda_notes_niccodemus.md --- tox-3.15.1/docs/drafts/tox_conda_notes_niccodemus.md 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/docs/drafts/tox_conda_notes_niccodemus.md 2021-02-02 20:28:40.000000000 +0000 @@ -33,7 +33,7 @@ 1. Create a new ``create_env_command`` option. ;2. Create a new ``env_activate_command`` option (also consider how to make that platform dependent). -2. New substitution variable: {python_version} ('3.4', '2.7', etc') +2. New substitution variable: {python_version} ('3.5', '2.7', etc') 3. env type concept: different types change the default options. 1. tox_addoption can now add new "testenv" sections to tox.ini: diff -Nru tox-3.15.1/docs/example/basic.rst tox-3.21.4/docs/example/basic.rst --- tox-3.15.1/docs/example/basic.rst 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/docs/example/basic.rst 2021-02-02 20:28:40.000000000 +0000 @@ -1,7 +1,7 @@ Basic usage ============================================= -a simple tox.ini / default environments +A simple tox.ini / default environments ----------------------------------------------- Put basic information about your project and the test environments you @@ -38,27 +38,17 @@ you can restrict the test run to the python3.6 environment. -Available "default" test environments names are: +Tox currently understands the following patterns: .. code-block:: shell - py - py2 - py27 - py3 - py34 - py35 - py36 - py37 - py38 - jython - pypy - pypy2 - pypy27 - pypy3 - pypy35 - -The environment ``py`` uses the version of Python used to invoke tox. + py: The current Python version tox is using + pypy: Whatever available PyPy there is + jython: Whatever available Jython there is + pyN: Python of version N. for example py2 or py3 ... etc + pyNM: Python of version N.M. for example py27 or py38 ... etc + pypyN: PyPy of version N. for example pypy2 or pypy3 ... etc + pypyNM: PyPy version N.M. for example pypy27 or pypy35 ... etc However, you can also create your own test environment names, see some of the examples in :doc:`examples <../examples>`. @@ -86,9 +76,9 @@ commands = pytest """ -Note that when you define a ``pyproject.toml`` you must define the ``build-requires`` section per PEP-518. +Note that when you define a ``pyproject.toml`` you must define the ``build-system`` section per PEP-518. -specifying a platform +Specifying a platform ----------------------------------------------- .. versionadded:: 2.0 @@ -104,21 +94,21 @@ If the expression does not match against ``sys.platform`` the test environment will be skipped. -whitelisting non-virtualenv commands +Allowing non-virtualenv commands ----------------------------------------------- .. versionadded:: 1.5 Sometimes you may want to use tools not contained in your virtualenv such as ``make``, ``bash`` or others. To avoid -warnings you can use the ``whitelist_externals`` testenv +warnings you can use the ``allowlist_externals`` testenv configuration: .. code-block:: ini # content of tox.ini [testenv] - whitelist_externals = make + allowlist_externals = make /bin/bash @@ -126,7 +116,7 @@ .. _multiindex: -depending on requirements.txt or defining constraints +Depending on requirements.txt or defining constraints ----------------------------------------------------- .. versionadded:: 1.6.1 @@ -164,10 +154,8 @@ * https://pip.pypa.io/en/stable/user_guide/#requirements-files * https://pip.pypa.io/en/stable/user_guide/#constraints-files -using a different default PyPI url ------------------------------------------------ - -.. versionadded:: 0.9 +Using a different default PyPI URL +---------------------------------- To install dependencies and packages from a different default PyPI server you can type interactively: @@ -177,49 +165,49 @@ tox -i https://pypi.my-alternative-index.org This causes tox to install dependencies and the sdist install step -to use the specified url as the index server. +to use the specified URL as the index server. -You can cause the same effect by this ``tox.ini`` content: +You can cause the same effect by using a ``PIP_INDEX_URL`` environment variable. +This variable can be also set in ``tox.ini``: .. code-block:: ini [tox] - indexserver = - default = https://pypi.my-alternative-index.org + setenv = + PIP_INDEX_URL = https://pypi.my-alternative-index.org -installing dependencies from multiple PyPI servers ---------------------------------------------------- +Alternatively, a configuration where ``PIP_INDEX_URL`` could be overriden from environment: -.. versionadded:: 0.9 +.. code-block:: ini + + [tox] + setenv = + PIP_INDEX_URL = {env:PIP_INDEX_URL:https://pypi.my-alternative-index.org} + +Installing dependencies from multiple PyPI servers +-------------------------------------------------- You can instrument tox to install dependencies from -different PyPI servers, example: +multiple PyPI servers, using ``PIP_EXTRA_INDEX_URL`` environment variable: .. code-block:: ini [tox] - indexserver = - DEV = https://mypypiserver.org + setenv = + PIP_EXTRA_INDEX_URL = https://mypypiserver.org [testenv] deps = # docutils will be installed directly from PyPI docutils - # mypackage will be installed from custom "DEV" PyPI url - :DEV:mypackage + # mypackage missing at PyPI will be installed from custom PyPI URL + mypackage This configuration will install ``docutils`` from the default Python PYPI server and will install the ``mypackage`` from -our ``DEV`` indexserver, and the respective ``https://mypypiserver.org`` -url. You can override config file settings from the command line -like this: - -.. code-block:: shell - - tox -i DEV=https://pypi.org/simple # changes :DEV: package URLs - tox -i https://pypi.org/simple # changes default +our index server at ``https://mypypiserver.org`` URL. -further customizing installation +Further customizing installation --------------------------------- .. versionadded:: 1.6 @@ -238,7 +226,7 @@ .. _pip: https://pip.pypa.io/en/stable/ -forcing re-creation of virtual environments +Forcing re-creation of virtual environments ----------------------------------------------- .. versionadded:: 0.9 @@ -252,7 +240,7 @@ would trigger a complete reinstallation of the existing py27 environment (or create it afresh if it doesn't exist). -passing down environment variables +Passing down environment variables ------------------------------------------- .. versionadded:: 2.0 @@ -270,7 +258,7 @@ When your test commands execute they will execute with the same LANG setting as the one with which tox was invoked. -setting environment variables +Setting environment variables ------------------------------------------- .. versionadded:: 1.0 @@ -288,7 +276,7 @@ from the ``subdir`` below the directory where your ``tox.ini`` file resides. -special handling of PYTHONHASHSEED +Special handling of PYTHONHASHSEED ------------------------------------------- .. versionadded:: 1.6.2 @@ -319,16 +307,9 @@ .. warning:: - Integrating tox with ``setup.py test`` is as of October 2016 discouraged as - it breaks packaging/testing approaches used by downstream distributions - which expect ``setup.py test`` to run tests with the invocation interpreter - rather than setting up many virtualenvs and installing packages. If you need to - define ``setup.py test``, you can see how to integrate your eventual - test runner with it, here is an `example of setup.py test integration with pytest - `_. - As the python eco-system rather moves away from using ``setup.py`` as a tool entry - point it's maybe best to not go for any ``setup.py test`` integration. - + ``setup.py test`` is `deprecated + `_ + and will be removed in a future version. .. _`ignoring exit code`: @@ -359,18 +340,18 @@ .. code-block:: ini [tox] - envlist = py{27,34,36}-django{15,16}-{sqlite,mysql} + envlist = py{36,37,38}-django{22,30}-{sqlite,mysql} [testenv] deps = - django15: Django>=1.5,<1.6 - django16: Django>=1.6,<1.7 - # use PyMySQL if factors "py34" and "mysql" are present in env name - py34-mysql: PyMySQL - # use urllib3 if any of "py36" or "py27" are present in env name - py27,py36: urllib3 - # mocking sqlite on 2.7 and 3.6 if factor "sqlite" is present - py{27,36}-sqlite: mock + django22: Django>=2.2,<2.3 + django30: Django>=3.0,<3.1 + # use PyMySQL if factors "py37" and "mysql" are present in env name + py38-mysql: PyMySQL + # use urllib3 if any of "py36" or "py37" are present in env name + py36,py37: urllib3 + # mocking sqlite on 3.6 and 3.7 if factor "sqlite" is present + py{36,37}-sqlite: mock Using generative section names @@ -416,7 +397,7 @@ - Invoke by using the ``--parallel`` or ``-p`` flag. After the packaging phase completes tox will run in parallel processes tox environments (spins a new instance of the tox interpreter, but passes through all host flags and environment variables). -- ``-p`` takes an argument specifying the degree of parallelization: +- ``-p`` takes an argument specifying the degree of parallelization, defaulting to ``auto``: - ``all`` to run all invoked environments in parallel, - ``auto`` to limit it to CPU count, diff -Nru tox-3.15.1/docs/example/devenv.rst tox-3.21.4/docs/example/devenv.rst --- tox-3.15.1/docs/example/devenv.rst 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/docs/example/devenv.rst 2021-02-02 20:28:40.000000000 +0000 @@ -35,6 +35,9 @@ default to ``-e py`` -- usually taking the interpreter you're running ``tox`` with and the default ``[testenv]`` configuration. +It is possible to use the ``--devenv`` option without a tox configuration file, +however the configuration file is respected if present. + Creating development environments using configuration ===================================================== diff -Nru tox-3.15.1/docs/example/general.rst tox-3.21.4/docs/example/general.rst --- tox-3.15.1/docs/example/general.rst 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/docs/example/general.rst 2021-02-02 20:28:40.000000000 +0000 @@ -45,7 +45,7 @@ Dependency changes and tracking ------------------------------- -Creating virtual environments and installing dependencies is a expensive operation. +Creating virtual environments and installing dependencies is an expensive operation. Therefore tox tries to avoid it whenever possible, meaning it will never perform this unless it detects with absolute certainty that it needs to perform an update. A tox environment creation is made up of: @@ -160,7 +160,7 @@ ``pypy`` the respective ``jython`` and ``pypy-c`` names will be looked for. The executable must exist in order to successfully create *virtualenv* environments. On Windows a ``pythonX.Y`` named executable will be searched in -typical default locations using the ``C:\PythonX.Y\python.exe`` pattern. +typical default locations using the ``C:\PythonXY\python.exe`` pattern. All other targets will use the system ``python`` instead. You can override any of the default settings by defining the :conf:`basepython` variable in a diff -Nru tox-3.15.1/docs/example/package.rst tox-3.21.4/docs/example/package.rst --- tox-3.15.1/docs/example/package.rst 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/docs/example/package.rst 2021-02-02 20:28:40.000000000 +0000 @@ -1,4 +1,4 @@ -packaging +Packaging ========= Although one can use tox to develop and test applications one of its most popular @@ -43,8 +43,8 @@ .. code-block:: toml [build-system] - requires = ["flit >= 1.1"] - build-backend = "flit.buildapi" + requires = ["flit_core >=2,<4"] + build-backend = "flit_core.buildapi" [tool.flit.metadata] module = "package_toml_flit" @@ -58,13 +58,6 @@ [tox] isolated_build = True - [tox:.package] - # note tox will use the same python version as under what tox is installed to package - # so unless this is python 3 you can require a given python version for the packaging - # environment via the basepython key - basepython = python3 - - poetry ------ poetry_ requires ``Python 3``, however the generated source @@ -74,8 +67,8 @@ .. code-block:: toml [build-system] - requires = ["poetry >= 0.12, <1"] - build-backend = "poetry.masonry.api" + requires = ["poetry_core>=1.0.0"] + build-backend = "poetry.core.masonry.api" [tool.poetry] name = "package_toml_poetry" diff -Nru tox-3.15.1/docs/example/pytest.rst tox-3.21.4/docs/example/pytest.rst --- tox-3.15.1/docs/example/pytest.rst 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/docs/example/pytest.rst 2021-02-02 20:28:40.000000000 +0000 @@ -82,7 +82,7 @@ .. _`listed as a known issue`: -Known Issues and limitations +Known issues and limitations ----------------------------- **Too long filenames**. you may encounter "too long filenames" for temporarily diff -Nru tox-3.15.1/docs/example/result.rst tox-3.21.4/docs/example/result.rst --- tox-3.15.1/docs/example/result.rst 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/docs/example/result.rst 2021-02-02 20:28:40.000000000 +0000 @@ -1,4 +1,4 @@ -Writing a json result file +Writing a JSON result file -------------------------------------------------------- .. versionadded: 1.6 diff -Nru tox-3.15.1/docs/example/unittest.rst tox-3.21.4/docs/example/unittest.rst --- tox-3.15.1/docs/example/unittest.rst 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/docs/example/unittest.rst 2021-02-02 20:28:40.000000000 +0000 @@ -48,7 +48,7 @@ .. code-block:: ini [tox] - envlist = py27,py34,py35,py36 + envlist = py27,py35,py36,py37 [testenv] deps = unittest2 @@ -74,7 +74,7 @@ mock uses unittest2_ to run the tests. Invoking ``tox`` starts test discovery by executing the ``unit2 discover`` -commands on Python 2.7, 3.4, 3.5 and 3.6 respectively. Against +commands on Python 2.7, 3.5, 3.6 and 3.7 respectively. Against Python3.6 and Python2.7 it will additionally run sphinx-mediated doctests. If building the docs fails, due to a reST error, or any of the doctests fails, it will be reported by the tox run. diff -Nru tox-3.15.1/docs/examples.rst tox-3.21.4/docs/examples.rst --- tox-3.15.1/docs/examples.rst 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/docs/examples.rst 2021-02-02 20:28:40.000000000 +0000 @@ -1,4 +1,3 @@ - tox configuration and usage examples ============================================================================== diff -Nru tox-3.15.1/docs/index.rst tox-3.21.4/docs/index.rst --- tox-3.15.1/docs/index.rst 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/docs/index.rst 2021-02-02 20:28:40.000000000 +0000 @@ -1,7 +1,7 @@ Welcome to the tox automation project =============================================== -vision: standardize testing in Python +Vision: standardize testing in Python --------------------------------------------- ``tox`` aims to automate and standardize testing in Python. It is part @@ -13,7 +13,7 @@ tox is a generic virtualenv_ management and test command line tool you can use for: -* checking your package installs correctly with different Python versions and +* checking that your package installs correctly with different Python versions and interpreters * running your tests in each of the environments, configuring your test tool of choice @@ -50,7 +50,7 @@ tox -and watch things happening (you must have python2.7 and python3.6 installed in your +and watch things happen (you must have python2.7 and python3.6 installed in your environment otherwise you will see errors). When you run ``tox`` a second time you'll note that it runs much faster because it keeps track of virtualenv details and will not recreate or re-install dependencies. You also might want to @@ -104,7 +104,7 @@ any of them is not zero stop, and mark the environment failed. Note, starting a command with a single dash character means ignore exit code. -6. **report** print out a report of outcomes for each tox environment: +4. **report** print out a report of outcomes for each tox environment: .. code:: bash @@ -119,7 +119,7 @@ environment variables not specified via :conf:`passenv`. Furthermore, it will also alter the ``PATH`` variable so that your commands resolve first and foremost within the current active tox environment. In general all executables in the path are available in ``commands``, but tox will -emit a warning if it was not explicitly allowed via :conf:`whitelist_externals`. +emit a warning if it was not explicitly allowed via :conf:`allowlist_externals`. Current features ------------------- @@ -139,7 +139,7 @@ * uses pip_ and setuptools_ by default. Support for configuring the installer command through :conf:`install_command=ARGV`. -* **cross-Python compatible**: CPython-2.7, 3.4 and higher, Jython and pypy_. +* **cross-Python compatible**: CPython-2.7, 3.5 and higher, Jython and pypy_. * **cross-platform**: Windows and Unix style environments diff -Nru tox-3.15.1/docs/install.rst tox-3.21.4/docs/install.rst --- tox-3.15.1/docs/install.rst 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/docs/install.rst 2021-02-02 20:28:40.000000000 +0000 @@ -4,7 +4,7 @@ Install info in a nutshell ---------------------------------- -**Pythons**: CPython 2.7 and 3.4 or later, Jython-2.5.1, pypy-1.9ff +**Pythons**: CPython 2.7 and 3.5 or later, Jython-2.5.1, pypy-1.9ff **Operating systems**: Linux, Windows, OSX, Unix diff -Nru tox-3.15.1/docs/plugins.rst tox-3.21.4/docs/plugins.rst --- tox-3.15.1/docs/plugins.rst 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/docs/plugins.rst 2021-02-02 20:28:40.000000000 +0000 @@ -22,7 +22,7 @@ $ pip install tox-travis You can search for available plugins on PyPI by typing ``pip search tox`` and filter for -packages that are prefixed ``tox-`` or contain the "plugin" in the description. +packages that are prefixed ``tox-`` or contain the word "plugin" in the description. You will get some output similar to this:: tox-pipenv (1.4.1) - A pipenv plugin for tox @@ -51,7 +51,7 @@ There might also be some plugins not (yet) available from PyPI that could be installed directly -fom source hosters like Github or Bitbucket (or from a local clone). See the +from source hosters like Github or Bitbucket (or from a local clone). See the associated `pip documentation `_. To see what is installed you can call ``tox --version`` to get the version of the host and names and locations of all installed plugins:: @@ -90,7 +90,7 @@ successful tox run (we won't go into the details of how to display fireworks though). To create a working plugin you need at least a python project with a tox entry point and a python -module implementing one or more of the pluggy based hooks tox specifies (using the +module implementing one or more of the pluggy-based hooks tox specifies (using the ``@tox.hookimpl`` decorator as marker). minimal structure: @@ -157,7 +157,7 @@ Publish your plugin to PyPI --------------------------- -If you think the rest of the world could profit using your plugin you can publish it to PyPI. +If you think the rest of the world could profit using your plugin, you can publish it to PyPI. You need to add some more meta data to ``setup.py`` (see `cookiecutter-tox-plugin`_ for a complete example or consult the `setup.py docs `_). @@ -184,6 +184,7 @@ .. _toxHookSpecsApi: + Hook specifications and related API ----------------------------------- diff -Nru tox-3.15.1/docs/support.rst tox-3.21.4/docs/support.rst --- tox-3.15.1/docs/support.rst 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/docs/support.rst 2021-02-02 20:28:40.000000000 +0000 @@ -1,19 +1,19 @@ .. _support: -support and contact channels +Support and contact channels ===================================== Getting in contact: -* join the `tox-dev mailing list`_ for tox related questions and development discussions. +* join the `tox-dev mailing list`_ for tox related questions and development discussions * file a `report on the issue tracker`_ * hang out on the irc.freenode.net #pylib channel * `fork the github repository`_ and submit merge/pull requests (see the developers help page -- :ref:`developers`) -paid professional support +Paid professional support ---------------------------- -contact holger at `merlinux.eu`_, an association of +Contact holger at `merlinux.eu`_, an association of experienced well-known Python developers. .. _`Testing In Python (TIP) mailing list`: http://lists.idyll.org/listinfo/testing-in-python diff -Nru tox-3.15.1/PKG-INFO tox-3.21.4/PKG-INFO --- tox-3.15.1/PKG-INFO 2020-05-20 13:44:53.973059200 +0000 +++ tox-3.21.4/PKG-INFO 2021-02-02 20:28:59.911512400 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: tox -Version: 3.15.1 +Version: 3.21.4 Summary: tox is a generic virtualenv management and test command line tool Home-page: http://tox.readthedocs.org Author: Holger Krekel, Oliver Bestwalter, Bernát Gábor and others @@ -9,8 +9,7 @@ License: MIT Project-URL: Source, https://github.com/tox-dev/tox Project-URL: Tracker, https://github.com/tox-dev/tox/issues -Description: [![Latest version on - PyPi](https://badge.fury.io/py/tox.svg)](https://badge.fury.io/py/tox) +Description: ![PyPI](https://img.shields.io/pypi/v/tox?style=flat-square) [![Supported Python versions](https://img.shields.io/pypi/pyversions/tox.svg)](https://pypi.org/project/tox/) [![Azure Pipelines build @@ -32,61 +31,52 @@ **Command line driven CI frontend and development task automation tool** - At its core tox provides a convenient way to run arbitrary commands in - isolated environments to serve as a single entry point for build, test - and release activities. + At its core tox provides a convenient way to run arbitrary commands in isolated environments to serve as a single entry + point for build, test and release activities. - tox is highly - [configurable](https://tox.readthedocs.io/en/latest/config.html) and + tox is highly [configurable](https://tox.readthedocs.io/en/latest/config.html) and [pluggable](https://tox.readthedocs.io/en/latest/plugins.html). - ## Example: run tests with Python 2.7 and Python 3.7 + ## Example: run tests with Python 3.7 and Python 3.8 - tox is mainly used as a command line tool and needs a `tox.ini` or a - `tool.tox` section in `pyproject.toml` containing the configuration. + tox is mainly used as a command line tool and needs a `tox.ini` or a `tool.tox` section in `pyproject.toml` containing + the configuration. - To test a simple project that has some tests, here is an example with - a `tox.ini` in the root of the project: + To test a simple project that has some tests, here is an example with a `tox.ini` in the root of the project: - ``` {.sourceCode .ini} + ```{.sourceCode .ini} [tox] - envlist = py27,py37 + envlist = py37,py38 [testenv] deps = pytest commands = pytest ``` - ``` {.sourceCode .console} + ```{.sourceCode .console} $ tox [lots of output from what tox does] [lots of output from commands that were run] __________________ summary _________________ - py27: commands succeeded py37: commands succeeded + py38: commands succeeded congratulations :) ``` - tox created two ``testenvs`` - one based on Python2.7 and one based on - Python3.7, it installed pytest in them and ran the tests. The report at - the end summarizes which ``testenvs`` have failed and which have - succeeded. + tox created two `testenvs` - one based on Python3.7 and one based on Python3.8, it installed pytest in them and ran the + tests. The report at the end summarizes which `testenvs` have failed and which have succeeded. **Note:** To learn more about what you can do with tox, have a look at - [the collection of examples in the - documentation](https://tox.readthedocs.io/en/latest/examples.html) - or [existing projects using - tox](https://github.com/search?l=INI&q=tox.ini+in%3Apath&type=Code). + [the collection of examples in the documentation](https://tox.readthedocs.io/en/latest/examples.html) or + [existing projects using tox](https://github.com/search?l=INI&q=tox.ini+in%3Apath&type=Code). ### How it works - tox creates virtual environments for all configured so called - ``testenvs``, it then installs the project and other necessary - dependencies and runs the configured set of commands. See [system - overview](https://tox.readthedocs.io/en/latest/#system-overview) for - more details. + tox creates virtual environments for all configured so called `testenvs`, it then installs the project and other + necessary dependencies and runs the configured set of commands. See + [system overview](https://tox.readthedocs.io/en/latest/#system-overview) for more details. . - - We also have a [Gitter community](https://gitter.im/tox-dev/). + For the fastest and interactive feedback please join our + [![Discord](https://img.shields.io/discord/802911963368783933?style=flat-square)](https://discord.gg/edtj86wzBX) server. + If you have questions or suggestions you can first check if they have already been answered or discussed on our + [issue tracker](https://github.com/tox-dev/tox/issues?utf8=%E2%9C%93&q=is%3Aissue+sort%3Aupdated-desc+label%3A%22type%3Aquestion+%3Agrey_question%3A%22+). + On [Stack Overflow (tagged with `tox`)](https://stackoverflow.com/questions/tagged/tox). ### Contributing - Contributions are welcome. See - [contributing](https://github.com/tox-dev/tox/blob/master/CONTRIBUTING.rst) - and our [Contributor Covenant Code of - Conduct](https://github.com/tox-dev/tox/blob/master/CODE_OF_CONDUCT.md). + Contributions are welcome. See [contributing](https://github.com/tox-dev/tox/blob/master/CONTRIBUTING.rst) and our + [Contributor Covenant Code of Conduct](https://github.com/tox-dev/tox/blob/master/CODE_OF_CONDUCT.md). - Currently the [code](https://github.com/tox-dev/tox) and the - [issues](https://github.com/tox-dev/tox/issues) are hosted on Github. + Currently the [code](https://github.com/tox-dev/tox) and the [issues](https://github.com/tox-dev/tox/issues) are hosted + on Github. - The project is licensed under - [MIT](https://github.com/tox-dev/tox/blob/master/LICENSE). + The project is licensed under [MIT](https://github.com/tox-dev/tox/blob/master/LICENSE). Keywords: virtual,environments,isolated,testing Platform: any @@ -153,6 +135,9 @@ 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 :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: Testing Classifier: Topic :: Utilities diff -Nru tox-3.15.1/.pre-commit-config.yaml tox-3.21.4/.pre-commit-config.yaml --- tox-3.15.1/.pre-commit-config.yaml 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/.pre-commit-config.yaml 2021-02-02 20:28:40.000000000 +0000 @@ -1,77 +1,66 @@ default_language_version: python: python3 repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.0.1 - hooks: - - id: check-ast - - id: check-builtin-literals - - id: check-docstring-first - - id: check-merge-conflict - - id: check-yaml - - id: check-toml - - id: debug-statements - - id: end-of-file-fixer - - id: trailing-whitespace -- repo: https://github.com/asottile/add-trailing-comma - rev: v2.0.1 - hooks: - - id: add-trailing-comma -- repo: https://github.com/asottile/pyupgrade - rev: v2.4.3 - hooks: - - id: pyupgrade -- repo: https://github.com/asottile/seed-isort-config - rev: v2.1.1 - hooks: - - id: seed-isort-config - args: - - --application-directories - - .:src -- repo: https://github.com/pre-commit/mirrors-isort - rev: v4.3.21 - hooks: - - id: isort -- repo: https://github.com/ambv/black - rev: 19.10b0 - hooks: - - id: black - args: - - --safe - language_version: python3.8 -- repo: https://github.com/asottile/blacken-docs - rev: v1.7.0 - hooks: - - id: blacken-docs - additional_dependencies: - - black==19.10b0 - language_version: python3.8 -- repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.5.1 - hooks: - - id: rst-backticks -- repo: https://github.com/asottile/setup-cfg-fmt - rev: v1.9.0 - hooks: - - id: setup-cfg-fmt - args: - - --min-py3-version - - '3.4' -- repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.1 - hooks: - - id: flake8 - additional_dependencies: - - flake8-bugbear == 20.1.2 - language_version: python3.8 -- repo: local - hooks: - - id: changelogs-rst - name: changelog filenames - language: fail - entry: >- - changelog files must be named - ####.(bugfix|feature|deprecation|breaking|doc|misc).rst - exclude: >- - ^docs/changelog/(\d+\.(bugfix|feature|deprecation|breaking|doc|misc).rst|README.rst|template.jinja2) - files: ^docs/changelog/ + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.4.0 + hooks: + - id: check-ast + - id: check-builtin-literals + - id: check-docstring-first + - id: check-merge-conflict + - id: check-yaml + - id: check-toml + - id: debug-statements + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://github.com/asottile/pyupgrade + rev: v2.7.4 + hooks: + - id: pyupgrade + - repo: https://github.com/PyCQA/isort + rev: 5.7.0 + hooks: + - id: isort + - repo: https://github.com/psf/black + rev: 20.8b1 + hooks: + - id: black + args: + - --safe + language_version: python3.8 + - repo: https://github.com/asottile/blacken-docs + rev: v1.9.1 + hooks: + - id: blacken-docs + additional_dependencies: + - black==20.8b1 + language_version: python3.8 + - repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.7.0 + hooks: + - id: rst-backticks + - repo: https://github.com/asottile/setup-cfg-fmt + rev: v1.16.0 + hooks: + - id: setup-cfg-fmt + args: + - --min-py3-version + - "3.4" + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.8.4 + hooks: + - id: flake8 + additional_dependencies: + - flake8-bugbear == 20.11.1 + language_version: python3.8 + - repo: local + hooks: + - id: changelogs-rst + name: changelog filenames + language: fail + entry: >- + changelog files must be named + ####.(bugfix|feature|deprecation|breaking|doc|misc).rst + exclude: >- + ^docs/changelog/(\d+\.(bugfix|feature|deprecation|breaking|doc|misc).rst|README.rst|template.jinja2) + files: ^docs/changelog/ diff -Nru tox-3.15.1/pyproject.toml tox-3.21.4/pyproject.toml --- tox-3.15.1/pyproject.toml 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/pyproject.toml 2021-02-02 20:28:40.000000000 +0000 @@ -1,7 +1,7 @@ [build-system] requires = [ "setuptools >= 40.0.4", - "setuptools_scm >= 2.0.0, <4", + "setuptools_scm >= 2.0.0, <6", "wheel >= 0.29.0", ] build-backend = 'setuptools.build_meta' diff -Nru tox-3.15.1/README.md tox-3.21.4/README.md --- tox-3.15.1/README.md 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/README.md 2021-02-02 20:28:40.000000000 +0000 @@ -1,5 +1,4 @@ -[![Latest version on -PyPi](https://badge.fury.io/py/tox.svg)](https://badge.fury.io/py/tox) +![PyPI](https://img.shields.io/pypi/v/tox?style=flat-square) [![Supported Python versions](https://img.shields.io/pypi/pyversions/tox.svg)](https://pypi.org/project/tox/) [![Azure Pipelines build @@ -21,61 +20,52 @@ **Command line driven CI frontend and development task automation tool** -At its core tox provides a convenient way to run arbitrary commands in -isolated environments to serve as a single entry point for build, test -and release activities. +At its core tox provides a convenient way to run arbitrary commands in isolated environments to serve as a single entry +point for build, test and release activities. -tox is highly -[configurable](https://tox.readthedocs.io/en/latest/config.html) and +tox is highly [configurable](https://tox.readthedocs.io/en/latest/config.html) and [pluggable](https://tox.readthedocs.io/en/latest/plugins.html). -## Example: run tests with Python 2.7 and Python 3.7 +## Example: run tests with Python 3.7 and Python 3.8 -tox is mainly used as a command line tool and needs a `tox.ini` or a -`tool.tox` section in `pyproject.toml` containing the configuration. +tox is mainly used as a command line tool and needs a `tox.ini` or a `tool.tox` section in `pyproject.toml` containing +the configuration. -To test a simple project that has some tests, here is an example with -a `tox.ini` in the root of the project: +To test a simple project that has some tests, here is an example with a `tox.ini` in the root of the project: -``` {.sourceCode .ini} +```{.sourceCode .ini} [tox] -envlist = py27,py37 +envlist = py37,py38 [testenv] deps = pytest commands = pytest ``` -``` {.sourceCode .console} +```{.sourceCode .console} $ tox [lots of output from what tox does] [lots of output from commands that were run] __________________ summary _________________ - py27: commands succeeded py37: commands succeeded + py38: commands succeeded congratulations :) ``` -tox created two ``testenvs`` - one based on Python2.7 and one based on -Python3.7, it installed pytest in them and ran the tests. The report at -the end summarizes which ``testenvs`` have failed and which have -succeeded. +tox created two `testenvs` - one based on Python3.7 and one based on Python3.8, it installed pytest in them and ran the +tests. The report at the end summarizes which `testenvs` have failed and which have succeeded. **Note:** To learn more about what you can do with tox, have a look at -[the collection of examples in the -documentation](https://tox.readthedocs.io/en/latest/examples.html) -or [existing projects using -tox](https://github.com/search?l=INI&q=tox.ini+in%3Apath&type=Code). +[the collection of examples in the documentation](https://tox.readthedocs.io/en/latest/examples.html) or +[existing projects using tox](https://github.com/search?l=INI&q=tox.ini+in%3Apath&type=Code). ### How it works -tox creates virtual environments for all configured so called -``testenvs``, it then installs the project and other necessary -dependencies and runs the configured set of commands. See [system -overview](https://tox.readthedocs.io/en/latest/#system-overview) for -more details. +tox creates virtual environments for all configured so called `testenvs`, it then installs the project and other +necessary dependencies and runs the configured set of commands. See +[system overview](https://tox.readthedocs.io/en/latest/#system-overview) for more details. . - -We also have a [Gitter community](https://gitter.im/tox-dev/). +For the fastest and interactive feedback please join our +[![Discord](https://img.shields.io/discord/802911963368783933?style=flat-square)](https://discord.gg/edtj86wzBX) server. +If you have questions or suggestions you can first check if they have already been answered or discussed on our +[issue tracker](https://github.com/tox-dev/tox/issues?utf8=%E2%9C%93&q=is%3Aissue+sort%3Aupdated-desc+label%3A%22type%3Aquestion+%3Agrey_question%3A%22+). +On [Stack Overflow (tagged with `tox`)](https://stackoverflow.com/questions/tagged/tox). ### Contributing -Contributions are welcome. See -[contributing](https://github.com/tox-dev/tox/blob/master/CONTRIBUTING.rst) -and our [Contributor Covenant Code of -Conduct](https://github.com/tox-dev/tox/blob/master/CODE_OF_CONDUCT.md). +Contributions are welcome. See [contributing](https://github.com/tox-dev/tox/blob/master/CONTRIBUTING.rst) and our +[Contributor Covenant Code of Conduct](https://github.com/tox-dev/tox/blob/master/CODE_OF_CONDUCT.md). -Currently the [code](https://github.com/tox-dev/tox) and the -[issues](https://github.com/tox-dev/tox/issues) are hosted on Github. +Currently the [code](https://github.com/tox-dev/tox) and the [issues](https://github.com/tox-dev/tox/issues) are hosted +on Github. -The project is licensed under -[MIT](https://github.com/tox-dev/tox/blob/master/LICENSE). +The project is licensed under [MIT](https://github.com/tox-dev/tox/blob/master/LICENSE). diff -Nru tox-3.15.1/setup.cfg tox-3.21.4/setup.cfg --- tox-3.15.1/setup.cfg 2020-05-20 13:44:53.973059200 +0000 +++ tox-3.21.4/setup.cfg 2021-02-02 20:28:59.911512400 +0000 @@ -5,6 +5,8 @@ long_description_content_type = text/markdown url = http://tox.readthedocs.org author = Holger Krekel, Oliver Bestwalter, Bernát Gábor and others +maintainer = Bernat Gabor, Oliver Bestwalter, Anthony Asottile +maintainer_email = tox-dev@python.org license = MIT license_file = LICENSE platforms = any @@ -23,12 +25,13 @@ Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: Implementation :: CPython + Programming Language :: Python :: Implementation :: PyPy Topic :: Software Development :: Libraries Topic :: Software Development :: Testing Topic :: Utilities keywords = virtual, environments, isolated, testing -maintainer = Bernat Gabor, Oliver Bestwalter, Anthony Asottile -maintainer-email = tox-dev@python.org project_urls = Source=https://github.com/tox-dev/tox Tracker=https://github.com/tox-dev/tox/issues @@ -44,7 +47,7 @@ toml>=0.9.4 virtualenv!=20.0.0,!=20.0.1,!=20.0.2,!=20.0.3,!=20.0.4,!=20.0.5,!=20.0.6,!=20.0.7,>=16.0.0 colorama>=0.4.1 ;platform_system=="Windows" - importlib-metadata>=0.12,<2;python_version<"3.8" + importlib-metadata>=0.12;python_version<"3.8" python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* [options.entry_points] @@ -54,20 +57,20 @@ [options.extras_require] docs = - sphinx >= 2.0.0 - towncrier >= 18.5.0 - pygments-github-lexers >= 0.0.5 - sphinxcontrib-autoprogram >= 0.1.5 + pygments-github-lexers>=0.0.5 + sphinx>=2.0.0 + sphinxcontrib-autoprogram>=0.1.5 + towncrier>=18.5.0 testing = - freezegun >= 0.3.11 - pathlib2 >= 2.3.3 - pytest >= 4.0.0 - pytest-cov >= 2.5.1 - pytest-mock >= 1.10.0 - pytest-xdist >= 1.22.2 - pytest-randomly >= 1.0.0 - psutil >= 5.6.1; python_version != "3.4" - flaky >= 3.4.0 + flaky>=3.4.0 + freezegun>=0.3.11 + psutil>=5.6.1 + pytest>=4.0.0 + pytest-cov>=2.5.1 + pytest-mock>=1.10.0 + pytest-randomly>=1.0.0 + pytest-xdist>=1.22.2 + pathlib2>=2.3.3;python_version<"3.4" [options.packages.find] where = src diff -Nru tox-3.15.1/src/tox/action.py tox-3.21.4/src/tox/action.py --- tox-3.15.1/src/tox/action.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/action.py 2021-02-02 20:28:40.000000000 +0000 @@ -32,6 +32,7 @@ command_log, popen, python, + suicide_timeout, interrupt_timeout, terminate_timeout, ): @@ -45,8 +46,13 @@ self.command_log = command_log self._timed_report = None self.python = python + self.suicide_timeout = suicide_timeout self.interrupt_timeout = interrupt_timeout self.terminate_timeout = terminate_timeout + if is_main_thread(): + # python allows only main thread to install signal handlers + # see https://docs.python.org/3/library/signal.html#signals-and-threads + self._install_sigterm_handler() def __enter__(self): msg = "{} {}".format(self.msg, " ".join(map(str, self.args))) @@ -85,7 +91,11 @@ cmd_args = [str(x) for x in self._rewrite_args(cwd, args)] cmd_args_shell = " ".join(pipes.quote(i) for i in cmd_args) stream_getter = self._get_standard_streams( - capture_err, cmd_args_shell, redirect, returnout, cwd, + capture_err, + cmd_args_shell, + redirect, + returnout, + cwd, ) exit_code, output = None, None with stream_getter as (fin, out_path, stderr, stdout): @@ -188,7 +198,7 @@ def handle_interrupt(self, process): """A three level stop mechanism for children - INT -> TERM -> KILL""" msg = "from {} {{}} pid {}".format(os.getpid(), process.pid) - if process.poll() is None: + if self._wait(process, self.suicide_timeout) is None: self.info("KeyboardInterrupt", msg.format("SIGINT")) process.send_signal(signal.CTRL_C_EVENT if sys.platform == "win32" else signal.SIGINT) if self._wait(process, self.interrupt_timeout) is None: @@ -272,3 +282,12 @@ new_args.append(str(arg)) return new_args + + def _install_sigterm_handler(self): + """Handle sigterm as if it were a keyboardinterrupt""" + + def sigterm_handler(signum, frame): + reporter.error("Got SIGTERM, handling it as a KeyboardInterrupt") + raise KeyboardInterrupt() + + signal.signal(signal.SIGTERM, sigterm_handler) diff -Nru tox-3.15.1/src/tox/config/__init__.py tox-3.21.4/src/tox/config/__init__.py --- tox-3.15.1/src/tox/config/__init__.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/config/__init__.py 2021-02-02 20:28:40.000000000 +0000 @@ -17,9 +17,11 @@ import pluggy import py +import six import toml from packaging import requirements from packaging.utils import canonicalize_name +from packaging.version import Version import tox from tox.constants import INFO @@ -53,9 +55,15 @@ WITHIN_PROVISION = os.environ.get(str("TOX_PROVISION")) == "1" +SUICIDE_TIMEOUT = 0.0 INTERRUPT_TIMEOUT = 0.3 TERMINATE_TIMEOUT = 0.2 +_FACTOR_LINE_PATTERN = re.compile(r"^([\w{}\.!,-]+)\:\s+(.+)") +_ENVSTR_SPLIT_PATTERN = re.compile(r"((?:\{[^}]+\})+)|,") +_ENVSTR_EXPAND_PATTERN = re.compile(r"\{([^}]+)\}") +_WHITESPACE_PATTERN = re.compile(r"\s+") + def get_plugin_manager(plugins=()): # initialize plugin manager @@ -86,18 +94,21 @@ super(HelpFormatter, self).__init__(prog, max_help_position=35, width=190) self.argparser = argparse.ArgumentParser( - description="tox options", add_help=False, prog="tox", formatter_class=HelpFormatter, + description="tox options", + add_help=False, + prog="tox", + formatter_class=HelpFormatter, ) self._testenv_attr = [] def add_argument(self, *args, **kwargs): - """ add argument to command line parser. This takes the + """add argument to command line parser. This takes the same arguments that ``argparse.ArgumentParser.add_argument``. """ return self.argparser.add_argument(*args, **kwargs) def add_testenv_attribute(self, name, type, help, default=None, postprocess=None): - """ add an ini-file variable for "testenv" section. + """add an ini-file variable for "testenv" section. Types are specified as strings like "bool", "line-list", "string", "argv", "path", "argvlist". @@ -114,7 +125,7 @@ self._testenv_attr.append(VenvAttribute(name, type, default, help, postprocess)) def add_testenv_attribute_obj(self, obj): - """ add an ini-file variable as an object. + """add an ini-file variable as an object. This works as the ``add_testenv_attribute`` function but expects "name", "type", "help", and "postprocess" attributes on the object. @@ -231,8 +242,8 @@ class InstallcmdOption: name = "install_command" - type = "argv" - default = "python -m pip install {opts} {packages}" + type = "argv_install_command" + default = r"python -m pip install \{opts\} \{packages\}" help = "install command for dependencies and package under test." def postprocess(self, testenv_config, value): @@ -265,7 +276,10 @@ content = toml_content["tool"]["tox"]["legacy_tox_ini"] except KeyError: continue - ParseIni(config, config_file, content) + try: + ParseIni(config, config_file, content) + except SkipThisIni: + continue pm.hook.tox_configure(config=config) # post process config object break else: @@ -276,6 +290,11 @@ parser.parse_cli(args, strict=True) if option.help or option.helpini: return config + if option.devenv: + # To load defaults, we parse an empty config + ParseIni(config, py.path.local(), "") + pm.hook.tox_configure(config=config) + return config msg = "tox config file (either {}) not found" candidates = ", ".join(INFO.CONFIG_CANDIDATES) feedback(msg.format(candidates), sysexit=not (option.help or option.helpini)) @@ -298,7 +317,8 @@ from_folder = py.path.local(cli_config_file) else: print( - "ERROR: {} is neither file or directory".format(cli_config_file), file=sys.stderr, + "ERROR: {} is neither file or directory".format(cli_config_file), + file=sys.stderr, ) return for basename in INFO.CONFIG_CANDIDATES: @@ -319,7 +339,11 @@ raise SystemExit(0) interpreters = Interpreters(hook=pm.hook) config = Config( - pluginmanager=pm, option=option, interpreters=interpreters, parser=parser, args=args, + pluginmanager=pm, + option=option, + interpreters=interpreters, + parser=parser, + args=args, ) return config, option @@ -368,7 +392,7 @@ return os.environ.get(name, default) self._lookupstack.append(name) try: - self.resolved[name] = res = self.reader._replace(val) + self.resolved[name] = res = self.reader._replace(val, name="setenv") finally: self._lookupstack.pop() return res @@ -386,11 +410,26 @@ self.definitions[name] = value self.resolved[name] = value + def items(self): + return ((name, self[name]) for name in self.definitions) + + def export(self): + # post-process items to avoid internal syntax/semantics + # such as {} being escaped using \{\}, suitable for use with + # os.environ . + return { + name: Replacer._unescape(value) + for name, value in self.items() + if value is not self._DUMMY + } + @tox.hookimpl def tox_addoption(parser): parser.add_argument( - "--version", action="store_true", help="report version information to stdout.", + "--version", + action="store_true", + help="report version information to stdout.", ) parser.add_argument("-h", "--help", action="store_true", help="show help about options") parser.add_argument( @@ -420,7 +459,9 @@ help="show list of all defined environments (with description if verbose)", ) parser.add_argument( - "-c", dest="configfile", help="config file name or directory with 'tox.ini' file.", + "-c", + dest="configfile", + help="config file name or directory with 'tox.ini' file.", ) parser.add_argument( "-e", @@ -439,7 +480,14 @@ ) parser.add_argument("--notest", action="store_true", help="skip invoking test commands.") parser.add_argument( - "--sdistonly", action="store_true", help="only perform the sdist packaging activity.", + "--sdistonly", + action="store_true", + help="only perform the sdist packaging activity.", + ) + parser.add_argument( + "--skip-pkg-install", + action="store_true", + help="skip package installation for this run", ) add_parallel_flags(parser) parser.add_argument( @@ -476,7 +524,10 @@ "(pip by default).", ) parser.add_argument( - "-r", "--recreate", action="store_true", help="force recreation of virtual environments", + "-r", + "--recreate", + action="store_true", + help="force recreation of virtual environments", ) parser.add_argument( "--result-json", @@ -604,7 +655,9 @@ warnings.warn( "conflicting basepython version (set {}, should be {}) for env '{}';" "resolve conflict or set ignore_basepython_conflict".format( - proposed_version, implied_version, testenv_config.envname, + proposed_version, + implied_version, + testenv_config.envname, ), ) @@ -620,7 +673,7 @@ def merge_description(testenv_config, value): """the reader by default joins generated description with new line, - replace new line with space""" + replace new line with space""" return value.replace("\n", " ") parser.add_testenv_attribute( @@ -632,11 +685,17 @@ ) parser.add_testenv_attribute( - name="envtmpdir", type="path", default="{envdir}/tmp", help="venv temporary directory", + name="envtmpdir", + type="path", + default="{envdir}/tmp", + help="venv temporary directory", ) parser.add_testenv_attribute( - name="envlogdir", type="path", default="{envdir}/log", help="venv log directory", + name="envlogdir", + type="path", + default="{envdir}/log", + help="venv log directory", ) parser.add_testenv_attribute( @@ -655,10 +714,14 @@ parser.add_testenv_attribute_obj(PosargsOption()) + def skip_install_default(testenv_config, value): + return value is True or testenv_config.config.option.skip_pkg_install is True + parser.add_testenv_attribute( name="skip_install", type="bool", default=False, + postprocess=skip_install_default, help="Do not install the current package. This can be used when you need the virtualenv " "management but do not want to install the current package", ) @@ -749,6 +812,12 @@ parser.add_testenv_attribute( name="whitelist_externals", type="line-list", + help="DEPRECATED: use allowlist_externals", + ) + + parser.add_testenv_attribute( + name="allowlist_externals", + type="line-list", help="each lines specifies a path or basename for which tox will not warn " "about it coming from outside the test environment.", ) @@ -828,6 +897,13 @@ parser.add_testenv_attribute_obj(DepOption()) parser.add_testenv_attribute( + name="suicide_timeout", + type="float", + default=SUICIDE_TIMEOUT, + help="timeout to allow process to exit before sending SIGINT", + ) + + parser.add_testenv_attribute( name="interrupt_timeout", type="float", default=INTERRUPT_TIMEOUT, @@ -937,7 +1013,7 @@ #: set of factors self.factors = factors self._reader = reader - self._missing_subs = [] + self._missing_subs = {} """Holds substitutions that could not be resolved. Pre 2.8.1 missing substitutions crashed with a ConfigError although this would not be a @@ -945,12 +1021,33 @@ check later when the testenv is actually run and crash only then. """ + # Python 3 only, as __getattribute__ is ignored for old-style types on Python 2 + def __getattribute__(self, name): + rv = object.__getattribute__(self, name) + if isinstance(rv, Exception): + raise rv + return rv + + if six.PY2: + + def __getattr__(self, name): + if name in self._missing_subs: + raise self._missing_subs[name] + raise AttributeError(name) + def get_envbindir(self): """Path to directory where scripts/binaries reside.""" - if tox.INFO.IS_WIN and "jython" not in self.basepython and "pypy" not in self.basepython: - return self.envdir.join("Scripts") - else: - return self.envdir.join("bin") + is_bin = ( + isinstance(self.python_info, NoInterpreterInfo) + or tox.INFO.IS_WIN is False + or self.python_info.implementation == "Jython" + or ( + tox.INFO.IS_WIN + and self.python_info.implementation == "PyPy" + and self.python_info.extra_version_info < (7, 3, 1) + ) + ) + return self.envdir.join("bin" if is_bin else "Scripts") @property def envbindir(self): @@ -1014,13 +1111,22 @@ return str(random.randint(1, max_seed)) +class SkipThisIni(Exception): + """Internal exception to indicate the parsed ini file should be skipped""" + + class ParseIni(object): def __init__(self, config, ini_path, ini_data): # noqa config.toxinipath = ini_path using("tox.ini: {} (pid {})".format(config.toxinipath, os.getpid())) - config.toxinidir = config.toxinipath.dirpath() + config.toxinidir = config.toxinipath.dirpath() if ini_path.check(file=True) else ini_path self._cfg = py.iniconfig.IniConfig(config.toxinipath, ini_data) + + if ini_path.basename == "setup.cfg" and "tox:tox" not in self._cfg: + verbosity1("Found no [tox:tox] section in setup.cfg, skipping.") + raise SkipThisIni() + previous_line_of = self._cfg.lineof self.expand_section_names(self._cfg) @@ -1041,7 +1147,10 @@ context_name = getcontextname() if context_name == "jenkins": reader = SectionReader( - "tox:jenkins", self._cfg, prefix=prefix, fallbacksections=[fallbacksection], + "tox:jenkins", + self._cfg, + prefix=prefix, + fallbacksections=[fallbacksection], ) dist_share_default = "{toxworkdir}/distshare" elif not context_name: @@ -1075,8 +1184,9 @@ reader.addsubstitutions(distdir=config.distdir) config.distshare = reader.getpath("distshare", dist_share_default) - config.temp_dir = reader.getpath("temp_dir", "{toxworkdir}/.tmp") reader.addsubstitutions(distshare=config.distshare) + config.temp_dir = reader.getpath("temp_dir", "{toxworkdir}/.tmp") + reader.addsubstitutions(temp_dir=config.temp_dir) config.sdistsrc = reader.getpath("sdistsrc", None) config.setupdir = reader.getpath("setupdir", "{toxinidir}") config.logdir = config.toxworkdir.join("log") @@ -1188,7 +1298,7 @@ requires_list = reader.getlist("requires") config.minversion = reader.getstring("minversion", None) config.provision_tox_env = name = reader.getstring("provision_tox_env", ".tox") - min_version = "tox >= {}".format(config.minversion or tox.__version__) + min_version = "tox >= {}".format(config.minversion or Version(tox.__version__).public) deps = self.ensure_requires_satisfied(config, requires_list, min_version) if config.run_provision: section_name = "testenv:{}".format(name) @@ -1196,7 +1306,10 @@ self._cfg.sections[section_name] = {} self._cfg.sections[section_name]["description"] = "meta tox" env_config = self.make_envconfig( - name, "{}{}".format(testenvprefix, name), reader._subs, config, + name, + "{}{}".format(testenvprefix, name), + reader._subs, + config, ) env_config.deps = deps config.envconfigs[config.provision_tox_env] = env_config @@ -1251,7 +1364,10 @@ self._cfg.sections[section_name]["sitepackages"] = "False" self._cfg.sections[section_name]["description"] = "isolated packaging environment" config.envconfigs[name] = self.make_envconfig( - name, "{}{}".format(testenvprefix, name), reader._subs, config, + name, + "{}{}".format(testenvprefix, name), + reader._subs, + config, ) def _list_section_factors(self, section): @@ -1285,13 +1401,17 @@ "dict_setenv", "argv", "argvlist", + "argv_install_command", ): meth = getattr(reader, "get{}".format(atype)) res = meth(env_attr.name, env_attr.default, replace=replace) elif atype == "basepython": no_fallback = name in (config.provision_tox_env,) res = reader.getstring( - env_attr.name, env_attr.default, replace=replace, no_fallback=no_fallback, + env_attr.name, + env_attr.default, + replace=replace, + no_fallback=no_fallback, ) elif atype == "space-separated-list": res = reader.getlist(env_attr.name, sep=" ") @@ -1305,9 +1425,10 @@ if env_attr.postprocess: res = env_attr.postprocess(testenv_config=tc, value=res) except tox.exception.MissingSubstitution as e: - tc._missing_subs.append(e.name) - res = e.FLAG - setattr(tc, env_attr.name, res) + tc._missing_subs[env_attr.name] = res = e + # On Python 2, exceptions are handled in __getattr__ + if not six.PY2 or not isinstance(res, Exception): + setattr(tc, env_attr.name, res) if atype in ("path", "string", "basepython"): reader.addsubstitutions(**{env_attr.name: res}) return tc @@ -1354,6 +1475,11 @@ if not env_list: env_list = all_envs + provision_tox_env = config.provision_tox_env + if config.provision_tox_env in env_list: + msg = "provision_tox_env {} cannot be part of envlist".format(provision_tox_env) + raise tox.exception.ConfigError(msg) + package_env = config.isolated_build_env if config.isolated_build is True and package_env in all_envs: all_envs.remove(package_env) @@ -1361,6 +1487,7 @@ if config.isolated_build is True and package_env in env_list: msg = "isolated_build_env {} cannot be part of envlist".format(package_env) raise tox.exception.ConfigError(msg) + return env_list, all_envs, _split_env(from_config), envlist_explicit @staticmethod @@ -1371,7 +1498,7 @@ The parser will see it as two different sections: [testenv:py36-cov], [testenv:py37-cov] """ - factor_re = re.compile(r"\{\s*([\w\s,]+)\s*\}") + factor_re = re.compile(r"\{\s*([\w\s,-]+)\s*\}") split_re = re.compile(r"\s*,\s*") to_remove = set() for section in list(config.sections): @@ -1423,12 +1550,12 @@ def _expand_envstr(envstr): # split by commas not in groups - tokens = re.split(r"((?:\{[^}]+\})+)|,", envstr) + tokens = _ENVSTR_SPLIT_PATTERN.split(envstr) envlist = ["".join(g).strip() for k, g in itertools.groupby(tokens, key=bool) if k] def expand(env): - tokens = re.split(r"\{([^}]+)\}", env) - parts = [re.sub(r"\s+", "", token).split(",") for token in tokens] + tokens = _ENVSTR_EXPAND_PATTERN.split(env) + parts = [_WHITESPACE_PATTERN.sub("", token).split(",") for token in tokens] return ["".join(variant) for variant in itertools.product(*parts)] return mapcat(expand, envlist) @@ -1466,7 +1593,15 @@ class SectionReader: - def __init__(self, section_name, cfgparser, fallbacksections=None, factors=(), prefix=None): + def __init__( + self, + section_name, + cfgparser, + fallbacksections=None, + factors=(), + prefix=None, + posargs="", + ): if prefix is None: self.section_name = section_name else: @@ -1477,6 +1612,7 @@ self._subs = {} self._subststack = [] self._setenv = None + self.posargs = posargs def get_environ_value(self, name): if self._setenv is None: @@ -1514,13 +1650,21 @@ if value is None or not replace: return default or {} - d = {} + env_values = {} for line in value.split(sep): if line.strip(): - name, rest = line.split("=", 1) - d[name.strip()] = rest.strip() - - return d + if line.startswith("#"): # comment lines are ignored + pass + elif line.startswith("file|"): # file markers contain paths to env files + file_path = line[5:].strip() + if os.path.exists(file_path): + with open(file_path, "rt") as file_handler: + content = file_handler.read() + env_values.update(self._getdict(content, "", sep, replace)) + else: + name, value = line.split("=", 1) + env_values[name.strip()] = value.strip() + return env_values def getfloat(self, name, default=None, replace=True): s = self.getstring(name, default, replace=replace) @@ -1556,11 +1700,27 @@ def getargvlist(self, name, default="", replace=True): s = self.getstring(name, default, replace=False) - return _ArgvlistReader.getargvlist(self, s, replace=replace) + return _ArgvlistReader.getargvlist(self, s, replace=replace, name=name) def getargv(self, name, default="", replace=True): return self.getargvlist(name, default, replace=replace)[0] + def getargv_install_command(self, name, default="", replace=True): + s = self.getstring(name, default, replace=False) + if not s: + # This occurs when factors are used, and a testenv doesnt have + # a factorised value for install_command, most commonly occurring + # if setting platform is also used. + # An empty value causes error install_command must contain '{packages}'. + s = default + + if "{packages}" in s: + s = s.replace("{packages}", r"\{packages\}") + if "{opts}" in s: + s = s.replace("{opts}", r"\{opts\}") + + return _ArgvlistReader.getargvlist(self, s, replace=replace, name=name)[0] + def getstring(self, name, default=None, replace=True, crossonly=False, no_fallback=False): x = None sections = [self.section_name] + ([] if no_fallback else self.fallbacksections) @@ -1585,6 +1745,17 @@ x = self._replace_if_needed(x, name, replace, crossonly) return x + def getposargs(self, default=None): + if self.posargs: + posargs = self.posargs + if sys.platform.startswith("win"): + posargs_string = list2cmdline([x for x in posargs if x]) + else: + posargs_string = " ".join([shlex_quote(x) for x in posargs if x]) + return posargs_string + else: + return default or "" + def _replace_if_needed(self, x, name, replace, crossonly): if replace and x and hasattr(x, "replace"): x = self._replace(x, name=name, crossonly=crossonly) @@ -1592,7 +1763,7 @@ def _apply_factors(self, s): def factor_line(line): - m = re.search(r"^([\w{}\.!,-]+)\:\s+(.+)", line) + m = _FACTOR_LINE_PATTERN.search(line) if not m: return line @@ -1611,6 +1782,7 @@ return value section_name = section_name if section_name else self.section_name + assert name self._subststack.append((section_name, name)) try: replaced = Replacer(self, crossonly=crossonly).do_replace(value) @@ -1631,7 +1803,7 @@ (?[^[:{}]+):)? # optional sub_type for special rules (?P(?:\[[^,{}]*\])?[^:,{}]*) # substitution key - (?::(?P[^{}]*))? # default value + (?::(?P([^{}]|\\{|\\})*))? # default value [}] """, re.VERBOSE, @@ -1657,6 +1829,10 @@ return expanded + @staticmethod + def _unescape(s): + return s.replace("\\{", "{").replace("\\}", "}") + def _replace_match(self, match): g = match.groupdict() sub_value = g["substitution_value"] @@ -1667,40 +1843,60 @@ start, end = match.span() return match.string[start:end] - # special case: all empty values means ":" which is os.pathsep - if not any(g.values()): + full_match = match.group(0) + # ":" is swallowed by the regex, so the raw matched string is checked + if full_match.startswith("{:"): + if full_match != "{:}": + raise tox.exception.ConfigError( + "Malformed substitution with prefix ':': {}".format(full_match), + ) + return os.pathsep + default_value = g["default_value"] # special case: opts and packages. Leave {opts} and # {packages} intact, they are replaced manually in # _venv.VirtualEnv.run_install_command. if sub_value in ("opts", "packages"): return "{{{}}}".format(sub_value) - try: - sub_type = g["sub_type"] - except KeyError: + if sub_value == "posargs": + return self.reader.getposargs(default_value) + + sub_type = g["sub_type"] + if sub_type == "posargs": + if default_value: + value = "{}:{}".format(sub_value, default_value) + else: + value = sub_value + return self.reader.getposargs(value) + + if not sub_type and not sub_value: raise tox.exception.ConfigError( - "Malformed substitution; no substitution type provided", + "Malformed substitution; no substitution type provided. " + "If you were using `{}` for `os.pathsep`, please use `{:}`.", ) + if not sub_type and not default_value and sub_value == "/": + return os.sep + if sub_type == "env": - return self._replace_env(match) + return self._replace_env(sub_value, default_value) if sub_type == "tty": if is_interactive(): return match.group("substitution_value") return match.group("default_value") + if sub_type == "posargs": + return self.reader.getposargs(sub_value) if sub_type is not None: raise tox.exception.ConfigError( "No support for the {} substitution type".format(sub_type), ) - return self._replace_substitution(match) + return self._replace_substitution(sub_value) - def _replace_env(self, match): - key = match.group("substitution_value") + def _replace_env(self, key, default): if not key: raise tox.exception.ConfigError("env: requires an environment variable name") - default = match.group("default_value") value = self.reader.get_environ_value(key) if value is not None: return value @@ -1715,18 +1911,20 @@ cfg = self.reader._cfg if section in cfg and item in cfg[section]: if (section, item) in self.reader._subststack: - raise ValueError( + raise tox.exception.SubstitutionStackError( "{} already in {}".format((section, item), self.reader._subststack), ) x = str(cfg[section][item]) return self.reader._replace( - x, name=item, section_name=section, crossonly=self.crossonly, + x, + name=item, + section_name=section, + crossonly=self.crossonly, ) raise tox.exception.ConfigError("substitution key {!r} not found".format(key)) - def _replace_substitution(self, match): - sub_key = match.group("substitution_value") + def _replace_substitution(self, sub_key): val = self.reader._subs.get(sub_key, None) if val is None: val = self._substitute_from_other_section(sub_key) @@ -1741,7 +1939,7 @@ class _ArgvlistReader: @classmethod - def getargvlist(cls, reader, value, replace=True): + def getargvlist(cls, reader, value, replace=True, name=None): """Parse ``commands`` argvlist multiline string. :param SectionReader reader: reader to be used. @@ -1763,28 +1961,23 @@ current_command += line if is_section_substitution(current_command): - replaced = reader._replace(current_command, crossonly=True) - commands.extend(cls.getargvlist(reader, replaced)) + replaced = reader._replace(current_command, crossonly=True, name=name) + commands.extend(cls.getargvlist(reader, replaced, name=name)) else: - commands.append(cls.processcommand(reader, current_command, replace)) + commands.append(cls.processcommand(reader, current_command, replace, name=name)) current_command = "" else: if current_command: raise tox.exception.ConfigError( "line-continuation ends nowhere while resolving for [{}] {}".format( - reader.section_name, "commands", + reader.section_name, + "commands", ), ) return commands @classmethod - def processcommand(cls, reader, command, replace=True): - posargs = getattr(reader, "posargs", "") - if sys.platform.startswith("win"): - posargs_string = list2cmdline([x for x in posargs if x]) - else: - posargs_string = " ".join([shlex_quote(x) for x in posargs if x]) - + def processcommand(cls, reader, command, replace=True, name=None): # Iterate through each word of the command substituting as # appropriate to construct the new command string. This # string is then broken up into exec argv components using @@ -1792,19 +1985,14 @@ if replace: newcommand = "" for word in CommandParser(command).words(): - if word == "{posargs}" or word == "[]": - newcommand += posargs_string + if word == "[]": + newcommand += reader.getposargs() continue - elif word.startswith("{posargs:") and word.endswith("}"): - if posargs: - newcommand += posargs_string - continue - else: - word = word[9:-1] + new_arg = "" - new_word = reader._replace(word) - new_word = reader._replace(new_word) - new_word = new_word.replace("\\{", "{").replace("\\}", "}") + new_word = reader._replace(word, name=name) + new_word = reader._replace(new_word, name=name) + new_word = Replacer._unescape(new_word) new_arg += new_word newcommand += new_arg else: diff -Nru tox-3.15.1/src/tox/config/reporter.py tox-3.21.4/src/tox/config/reporter.py --- tox-3.15.1/src/tox/config/reporter.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/config/reporter.py 2021-02-02 20:28:40.000000000 +0000 @@ -4,6 +4,7 @@ def add_verbosity_commands(parser): parser.add_argument( "-v", + "--verbose", action="count", dest="verbose_level", default=0, @@ -13,6 +14,7 @@ ) parser.add_argument( "-q", + "--quiet", action="count", dest="quiet_level", default=0, diff -Nru tox-3.15.1/src/tox/exception.py tox-3.21.4/src/tox/exception.py --- tox-3.15.1/src/tox/exception.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/exception.py 2021-02-02 20:28:40.000000000 +0000 @@ -49,12 +49,17 @@ def __init__(self, name): self.name = name + super(Error, self).__init__(name) class ConfigError(Error): """Error in tox configuration.""" +class SubstitutionStackError(ConfigError, ValueError): + """Error in tox configuration recursive substitution.""" + + class UnsupportedInterpreter(Error): """Signals an unsupported Interpreter.""" diff -Nru tox-3.15.1/src/tox/helper/build_isolated.py tox-3.21.4/src/tox/helper/build_isolated.py --- tox-3.15.1/src/tox/helper/build_isolated.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/helper/build_isolated.py 2021-02-02 20:28:40.000000000 +0000 @@ -1,10 +1,40 @@ +"""PEP 517 build backend invocation script. + +It accepts externally parsed build configuration from `[build-system]` +in `pyproject.toml` and invokes an API endpoint for building an sdist +tarball. +""" + +import os import sys + +def _ensure_module_in_paths(module, paths): + """Verify that the imported backend belongs in-tree.""" + if not paths: + return + + module_path = os.path.normcase(os.path.abspath(module.__file__)) + normalized_paths = (os.path.normcase(os.path.abspath(path)) for path in paths) + + if any(os.path.commonprefix((module_path, path)) == path for path in normalized_paths): + return + + raise SystemExit( + "build-backend ({!r}) must exist in one of the paths " + "specified by backend-path ({!r})".format(module, paths), + ) + + dist_folder = sys.argv[1] backend_spec = sys.argv[2] backend_obj = sys.argv[3] if len(sys.argv) >= 4 else None +backend_paths = sys.argv[4].split(os.path.pathsep) if sys.argv[4] else [] + +sys.path[:0] = backend_paths -backend = __import__(backend_spec, fromlist=[None]) +backend = __import__(backend_spec, fromlist=["_trash"]) +_ensure_module_in_paths(backend, backend_paths) if backend_obj: backend = getattr(backend, backend_obj) diff -Nru tox-3.15.1/src/tox/helper/build_requires.py tox-3.21.4/src/tox/helper/build_requires.py --- tox-3.15.1/src/tox/helper/build_requires.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/helper/build_requires.py 2021-02-02 20:28:40.000000000 +0000 @@ -1,10 +1,14 @@ import json +import os import sys backend_spec = sys.argv[1] backend_obj = sys.argv[2] if len(sys.argv) >= 3 else None +backend_paths = sys.argv[3].split(os.path.pathsep) if len(sys.argv) >= 4 else [] -backend = __import__(backend_spec, fromlist=[None]) +sys.path[:0] = backend_paths + +backend = __import__(backend_spec, fromlist=["_trash"]) if backend_obj: backend = getattr(backend, backend_obj) diff -Nru tox-3.15.1/src/tox/helper/get_version.py tox-3.21.4/src/tox/helper/get_version.py --- tox-3.15.1/src/tox/helper/get_version.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/helper/get_version.py 2021-02-02 20:28:40.000000000 +0000 @@ -1,15 +1,17 @@ from __future__ import unicode_literals import json +import platform import sys info = { "executable": sys.executable, - "name": "pypy" if hasattr(sys, "pypy_version_info") else "python", + "implementation": platform.python_implementation(), "version_info": list(sys.version_info), "version": sys.version, "is_64": sys.maxsize > 2 ** 32, "sysplatform": sys.platform, + "extra_version_info": getattr(sys, "pypy_version_info", None), } info_as_dump = json.dumps(info) print(info_as_dump) diff -Nru tox-3.15.1/src/tox/interpreters/__init__.py tox-3.21.4/src/tox/interpreters/__init__.py --- tox-3.15.1/src/tox/interpreters/__init__.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/interpreters/__init__.py 2021-02-02 20:28:40.000000000 +0000 @@ -16,7 +16,7 @@ self.hook = hook def get_executable(self, envconfig): - """ return path object to the executable for the given + """return path object to the executable for the given name (e.g. python2.7, python3.6, python etc.) if name is already an existing path, return name. If an interpreter cannot be found, return None. @@ -59,17 +59,20 @@ try: result = get_python_info(str(executable)) result["version_info"] = tuple(result["version_info"]) # fix json dump transformation - del result["name"] + if result["extra_version_info"] is not None: + result["extra_version_info"] = tuple( + result["extra_version_info"], + ) # fix json dump transformation del result["version"] result["executable"] = str(executable) except ExecFailed as e: return NoInterpreterInfo(name, executable=e.executable, out=e.out, err=e.err) else: - return InterpreterInfo(name, **result) + return InterpreterInfo(**result) def exec_on_interpreter(*args): - from subprocess import Popen, PIPE + from subprocess import PIPE, Popen popen = Popen(args, stdout=PIPE, stderr=PIPE, universal_newlines=True) out, err = popen.communicate() @@ -93,12 +96,22 @@ class InterpreterInfo: - def __init__(self, name, executable, version_info, sysplatform, is_64): - self.name = name + def __init__( + self, + implementation, + executable, + version_info, + sysplatform, + is_64, + extra_version_info, + ): + self.implementation = implementation self.executable = executable + self.version_info = version_info self.sysplatform = sysplatform self.is_64 = is_64 + self.extra_version_info = extra_version_info def __str__(self): return "".format(self.executable, self.version_info) diff -Nru tox-3.15.1/src/tox/interpreters/via_path.py tox-3.21.4/src/tox/interpreters/via_path.py --- tox-3.15.1/src/tox/interpreters/via_path.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/interpreters/via_path.py 2021-02-02 20:28:40.000000000 +0000 @@ -38,7 +38,7 @@ info = get_python_info(python_exe) if info is not None: found = PythonSpec( - info["name"], + "pypy" if info["implementation"] == "PyPy" else "python", info["version_info"][0], info["version_info"][1], 64 if info["is_64"] else 32, diff -Nru tox-3.15.1/src/tox/logs/result.py tox-3.21.4/src/tox/logs/result.py --- tox-3.15.1/src/tox/logs/result.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/logs/result.py 2021-02-02 20:28:40.000000000 +0000 @@ -2,6 +2,7 @@ from __future__ import absolute_import, unicode_literals import json +import os import socket import sys @@ -21,7 +22,7 @@ "reportversion": "1", "toxversion": __version__, "platform": sys.platform, - "host": socket.getfqdn(), + "host": os.getenv(str("HOSTNAME")) or socket.getfqdn(), "commands": command_log, } @@ -33,7 +34,7 @@ return result def get_envlog(self, name): - """Return the env log of a environment (create on first call)""" + """Return the env log of an environment (create on first call)""" test_envs = self.dict.setdefault("testenvs", {}) env_data = test_envs.setdefault(name, {}) return EnvLog(self, name, env_data) diff -Nru tox-3.15.1/src/tox/package/builder/isolated.py tox-3.21.4/src/tox/package/builder/isolated.py --- tox-3.15.1/src/tox/package/builder/isolated.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/package/builder/isolated.py 2021-02-02 20:28:40.000000000 +0000 @@ -1,6 +1,7 @@ from __future__ import unicode_literals import json +import os from collections import namedtuple import six @@ -11,7 +12,10 @@ from tox.config import DepConfig, get_py_project_toml from tox.constants import BUILD_ISOLATED, BUILD_REQUIRE_SCRIPT -BuildInfo = namedtuple("BuildInfo", ["requires", "backend_module", "backend_object"]) +BuildInfo = namedtuple( + "BuildInfo", + ["requires", "backend_module", "backend_object", "backend_paths"], +) def build(config, session): @@ -84,12 +88,27 @@ module = args[0] obj = args[1] if len(args) > 1 else "" - return BuildInfo(requires, module, obj) + backend_paths = build_system.get("backend-path", []) + if not isinstance(backend_paths, list): + abort("backend-path key at build-system section must be a list, if specified") + backend_paths = [folder.join(p) for p in backend_paths] + + normalized_folder = os.path.normcase(str(folder.realpath())) + normalized_paths = (os.path.normcase(str(path.realpath())) for path in backend_paths) + + if not all( + os.path.commonprefix((normalized_folder, path)) == normalized_folder + for path in normalized_paths + ): + abort("backend-path must exist in the project root") + + return BuildInfo(requires, module, obj, backend_paths) def perform_isolated_build(build_info, package_venv, dist_dir, setup_dir): with package_venv.new_action( - "perform-isolated-build", package_venv.envconfig.envdir, + "perform-isolated-build", + package_venv.envconfig.envdir, ) as action: # need to start with an empty (but existing) source distribution folder if dist_dir.exists(): @@ -103,6 +122,7 @@ str(dist_dir), build_info.backend_module, build_info.backend_object, + os.path.pathsep.join(str(p) for p in build_info.backend_paths), ], returnout=True, action=action, @@ -120,6 +140,7 @@ BUILD_REQUIRE_SCRIPT, build_info.backend_module, build_info.backend_object, + os.path.pathsep.join(str(p) for p in build_info.backend_paths), ], returnout=True, action=action, diff -Nru tox-3.15.1/src/tox/package/view.py tox-3.21.4/src/tox/package/view.py --- tox-3.15.1/src/tox/package/view.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/package/view.py 2021-02-02 20:28:40.000000000 +0000 @@ -41,7 +41,10 @@ common = session_package.common(package) verbosity1( "package {} {} to {} ({})".format( - common.bestrelpath(session_package), operation, common.bestrelpath(package), common, + common.bestrelpath(session_package), + operation, + common.bestrelpath(package), + common, ), ) return session_package diff -Nru tox-3.15.1/src/tox/_pytestplugin.py tox-3.21.4/src/tox/_pytestplugin.py --- tox-3.15.1/src/tox/_pytestplugin.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/_pytestplugin.py 2021-02-02 20:28:40.000000000 +0000 @@ -87,7 +87,7 @@ diff = { "{} = {} vs {}".format(k, old[k], new[k]) for k in set(old) & set(new) - if old[k] != new[k] and not k.startswith("PYTEST_") + if old[k] != new[k] and not (k.startswith("PYTEST_") or k.startswith("COV_")) } if extra or miss or diff: msg = "test changed environ" @@ -183,7 +183,10 @@ def __repr__(self): res = "RunResult(ret={}, args={!r}, out=\n{}\n, err=\n{})".format( - self.ret, self.args, self.out, self.err, + self.ret, + self.args, + self.out, + self.err, ) if six.PY2: return res.encode("UTF-8") @@ -233,7 +236,9 @@ newindex += 1 raise LookupError( "looking for {!r}, no reports found at >={:d} in {!r}".format( - cat, self._index + 1, self.instance.reported_lines, + cat, + self._index + 1, + self.instance.reported_lines, ), ) @@ -257,7 +262,10 @@ if not invert: raise AssertionError( "looking for {}({!r}), no reports found at >={:d} in {!r}".format( - cat, messagepattern, self._index + 1, self.instance.reported_lines, + cat, + messagepattern, + self._index + 1, + self.instance.reported_lines, ), ) @@ -490,7 +498,7 @@ def mock_venv(monkeypatch): """This creates a mock virtual environment (e.g. will inherit the current interpreter). Note: because we inherit, to keep things sane you must call the py environment and only that; - and cannot install any packages. """ + and cannot install any packages.""" # first ensure we have a clean python path monkeypatch.delenv(str("PYTHONPATH"), raising=False) diff -Nru tox-3.15.1/src/tox/_quickstart.py tox-3.21.4/src/tox/_quickstart.py --- tox-3.15.1/src/tox/_quickstart.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/_quickstart.py 2021-02-02 20:28:40.000000000 +0000 @@ -262,7 +262,9 @@ help="Custom root directory to write config to. Defaults to current directory.", ) parser.add_argument( - "--version", action="version", version="%(prog)s {}".format(tox.__version__), + "--version", + action="version", + version="%(prog)s {}".format(tox.__version__), ) return parser.parse_args() diff -Nru tox-3.15.1/src/tox/reporter.py tox-3.21.4/src/tox/reporter.py --- tox-3.15.1/src/tox/reporter.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/reporter.py 2021-02-02 20:28:40.000000000 +0000 @@ -61,7 +61,8 @@ yield duration = time.time() - start self.verbosity2( - "{} finish: {} after {:.2f} seconds".format(name, msg, duration), bold=True, + "{} finish: {} after {:.2f} seconds".format(name, msg, duration), + bold=True, ) def separator(self, of, msg, level): diff -Nru tox-3.15.1/src/tox/session/commands/help_ini.py tox-3.21.4/src/tox/session/commands/help_ini.py --- tox-3.15.1/src/tox/session/commands/help_ini.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/session/commands/help_ini.py 2021-02-02 20:28:40.000000000 +0000 @@ -6,7 +6,9 @@ for env_attr in config._testenv_attr: reporter.line( "{:<15} {:<8} default: {}".format( - env_attr.name, "<{}>".format(env_attr.type), env_attr.default, + env_attr.name, + "<{}>".format(env_attr.type), + env_attr.default, ), bold=True, ) diff -Nru tox-3.15.1/src/tox/session/commands/run/parallel.py tox-3.21.4/src/tox/session/commands/run/parallel.py --- tox-3.15.1/src/tox/session/commands/run/parallel.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/session/commands/run/parallel.py 2021-02-02 20:28:40.000000000 +0000 @@ -95,7 +95,8 @@ semaphore.acquire(blocking=True) spinner.add(name) thread = Thread( - target=run_in_thread, args=(venv, os.environ.copy(), processes), + target=run_in_thread, + args=(venv, os.environ.copy(), processes), ) thread.daemon = True thread.start() diff -Nru tox-3.15.1/src/tox/session/__init__.py tox-3.21.4/src/tox/session/__init__.py --- tox-3.15.1/src/tox/session/__init__.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/session/__init__.py 2021-02-02 20:28:40.000000000 +0000 @@ -19,7 +19,7 @@ import tox from tox import reporter from tox.action import Action -from tox.config import INTERRUPT_TIMEOUT, TERMINATE_TIMEOUT, parseconfig +from tox.config import INTERRUPT_TIMEOUT, SUICIDE_TIMEOUT, TERMINATE_TIMEOUT, parseconfig from tox.config.parallel import ENV_VAR_KEY_PRIVATE as PARALLEL_ENV_VAR_KEY_PRIVATE from tox.config.parallel import OFF_VALUE as PARALLEL_OFF from tox.logs.result import ResultLog @@ -46,6 +46,7 @@ def setup_reporter(args): from argparse import ArgumentParser + from tox.config.reporter import add_verbosity_commands parser = ArgumentParser(add_help=False) @@ -150,7 +151,8 @@ visited.add(name) if tox_env_filter_re is not None and tox_env_filter_re.match(name): msg = "skip environment {}, matches filter {!r}".format( - name, tox_env_filter_re.pattern, + name, + tox_env_filter_re.pattern, ) reporter.verbosity1(msg) continue @@ -170,6 +172,7 @@ self.resultlog.command_log, self.popen, sys.executable, + SUICIDE_TIMEOUT, INTERRUPT_TIMEOUT, TERMINATE_TIMEOUT, ) @@ -259,7 +262,10 @@ report = reporter.error elif status == "platform mismatch": msg = " {}: {} ({!r} does not match {!r})".format( - venv.envconfig.envname, str(status), sys.platform, venv.envconfig.platform, + venv.envconfig.envname, + str(status), + sys.platform, + venv.envconfig.platform, ) report = reporter.skip elif status and status == "ignored failed command": diff -Nru tox-3.15.1/src/tox/util/spinner.py tox-3.21.4/src/tox/util/spinner.py --- tox-3.15.1/src/tox/util/spinner.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/util/spinner.py 2021-02-02 20:28:40.000000000 +0000 @@ -120,7 +120,10 @@ self.clear() self.stream.write( "{} {} in {}{}".format( - status, key, td_human_readable(datetime.now() - start_at), os.linesep, + status, + key, + td_human_readable(datetime.now() - start_at), + os.linesep, ), **kwargs ) diff -Nru tox-3.15.1/src/tox/util/stdlib.py tox-3.21.4/src/tox/util/stdlib.py --- tox-3.15.1/src/tox/util/stdlib.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/util/stdlib.py 2021-02-02 20:28:40.000000000 +0000 @@ -24,7 +24,7 @@ def suppress_output(): """suppress both stdout and stderr outputs""" if sys.version_info >= (3, 5): - from contextlib import redirect_stdout, redirect_stderr + from contextlib import redirect_stderr, redirect_stdout else: class _RedirectStream(object): diff -Nru tox-3.15.1/src/tox/venv.py tox-3.21.4/src/tox/venv.py --- tox-3.15.1/src/tox/venv.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/src/tox/venv.py 2021-02-02 20:28:40.000000000 +0000 @@ -43,7 +43,10 @@ lines = [ "{} {}".format(self.base_resolved_python_sha256, self.base_resolved_python_path), "{} {:d} {:d} {:d}".format( - self.tox_version, self.sitepackages, self.usedevelop, self.alwayscopy, + self.tox_version, + self.sitepackages, + self.usedevelop, + self.alwayscopy, ), ] for dep in self.deps: @@ -130,6 +133,7 @@ command_log, self.popen, self.envconfig.envpython, + self.envconfig.suicide_timeout, self.envconfig.interrupt_timeout, self.envconfig.terminate_timeout, ) @@ -138,7 +142,9 @@ if self._result_json_path is None: if self.envconfig.config.option.resultjson: self._result_json_path = get_unique_file( - self.path, PARALLEL_RESULT_JSON_PREFIX, PARALLEL_RESULT_JSON_SUFFIX, + self.path, + PARALLEL_RESULT_JSON_PREFIX, + PARALLEL_RESULT_JSON_SUFFIX, ) return self._result_json_path @@ -164,11 +170,11 @@ return "".format(self.path) def getcommandpath(self, name, venv=True, cwd=None): - """ Return absolute path (str or localpath) for specified command name. + """Return absolute path (str or localpath) for specified command name. - If it's a local path we will rewrite it as as a relative path. - If venv is True we will check if the command is coming from the venv - or is whitelisted to come from external. + or is allowed to come from external. """ name = str(name) if os.path.isabs(name): @@ -179,7 +185,7 @@ return str(path) if venv: - path = self._venv_lookup_and_check_external_whitelist(name) + path = self._venv_lookup_and_check_external_allowlist(name) else: path = self._normal_lookup(name) @@ -190,7 +196,7 @@ return str(path) # will not be rewritten for reporting - def _venv_lookup_and_check_external_whitelist(self, name): + def _venv_lookup_and_check_external_allowlist(self, name): path = self._venv_lookup(name) if path is None: path = self._normal_lookup(name) @@ -211,9 +217,10 @@ " cmd: {}\n" " env: {}\n" "Maybe you forgot to specify a dependency? " - "See also the whitelist_externals envconfig setting.\n\n" + "See also the allowlist_externals envconfig setting.\n\n" "DEPRECATION WARNING: this will be an error in tox 4 and above!".format( - path, self.envconfig.envdir, + path, + self.envconfig.envdir, ), ) @@ -222,15 +229,24 @@ if tox.INFO.IS_WIN: tryadd += [os.path.normcase(x) for x in os.environ["PATHEXT"].split(os.pathsep)] p = py.path.local(os.path.normcase(str(p))) - for x in self.envconfig.whitelist_externals: + + if self.envconfig.allowlist_externals and self.envconfig.whitelist_externals: + raise tox.exception.ConfigError( + "Either whitelist_externals or allowlist_externals might be specified, not both", + ) + + allowed_externals = ( + self.envconfig.whitelist_externals or self.envconfig.allowlist_externals + ) + for x in allowed_externals: for add in tryadd: if p.fnmatch(x + add): return True return False def update(self, action): - """ return status string for updating actual venv to match configuration. - if status string is empty, all is ok. + """return status string for updating actual venv to match configuration. + if status string is empty, all is ok. """ rconfig = CreationConfig.readconfig(self.path_config) if self.envconfig.recreate: @@ -310,10 +326,16 @@ args = [self.envconfig.envpython, str(setup_py), "--name"] env = self._get_os_environ() output = action.popen( - args, cwd=setupdir, redirect=False, returnout=True, env=env, capture_err=False, + args, + cwd=setupdir, + redirect=False, + returnout=True, + env=env, + capture_err=False, ) name = next( - (i for i in output.split("\n") if i and not i.startswith("pydev debugger:")), "", + (i for i in output.split("\n") if i and not i.startswith("pydev debugger:")), + "", ) args = [ self.envconfig.envpython, @@ -408,6 +430,9 @@ redirect=reporter.verbosity() < reporter.Verbosity.DEBUG, env=env, ) + except KeyboardInterrupt: + self.status = "keyboardinterrupt" + raise finally: sys.stdout = old_stdout @@ -471,7 +496,7 @@ env = os.environ.copy() # in any case we honor per-testenv setenv configuration - env.update(self.envconfig.setenv) + env.update(self.envconfig.setenv.export()) env["VIRTUAL_ENV"] = str(self.path) return env @@ -501,7 +526,8 @@ # have to make strings as _pcall changes argv[0] to a local() # happens if the same environment is invoked twice message = "commands[{}] | {}".format( - i, " ".join([pipes.quote(str(x)) for x in argv]), + i, + " ".join([pipes.quote(str(x)) for x in argv]), ) action.setactivity(name, message) # check to see if we need to ignore the return code @@ -580,9 +606,14 @@ def setupenv(self): if self.envconfig._missing_subs: self.status = ( - "unresolvable substitution(s): {}. " + "unresolvable substitution(s):\n {}\n" "Environment variables are missing or defined recursively.".format( - ",".join(["'{}'".format(m) for m in self.envconfig._missing_subs]), + "\n ".join( + [ + "{}: '{}'".format(section_key, exc.name) + for section_key, exc in sorted(self.envconfig._missing_subs.items()) + ], + ), ) ) return @@ -608,6 +639,9 @@ status = e if self.envconfig.config.option.skip_missing_interpreters == "true": default_ret_code = 0 + except KeyboardInterrupt: + self.status = "keyboardinterrupt" + raise if status: str_status = str(status) command_log = envlog.get_commandlog("setup") @@ -672,6 +706,8 @@ args.append("--always-copy") if not venv.envconfig.download: args.append("--no-download") + else: + args.append("--download") # add interpreter explicitly, to prevent using default (virtualenv.ini) args.extend(["--python", str(config_interpreter)]) diff -Nru tox-3.15.1/src/tox/version.py tox-3.21.4/src/tox/version.py --- tox-3.15.1/src/tox/version.py 2020-05-20 13:44:53.000000000 +0000 +++ tox-3.21.4/src/tox/version.py 2021-02-02 20:28:59.000000000 +0000 @@ -1,4 +1,4 @@ # coding: utf-8 from __future__ import unicode_literals -__version__ = '3.15.1' +__version__ = '3.21.4' diff -Nru tox-3.15.1/src/tox.egg-info/PKG-INFO tox-3.21.4/src/tox.egg-info/PKG-INFO --- tox-3.15.1/src/tox.egg-info/PKG-INFO 2020-05-20 13:44:53.000000000 +0000 +++ tox-3.21.4/src/tox.egg-info/PKG-INFO 2021-02-02 20:28:59.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: tox -Version: 3.15.1 +Version: 3.21.4 Summary: tox is a generic virtualenv management and test command line tool Home-page: http://tox.readthedocs.org Author: Holger Krekel, Oliver Bestwalter, Bernát Gábor and others @@ -9,8 +9,7 @@ License: MIT Project-URL: Source, https://github.com/tox-dev/tox Project-URL: Tracker, https://github.com/tox-dev/tox/issues -Description: [![Latest version on - PyPi](https://badge.fury.io/py/tox.svg)](https://badge.fury.io/py/tox) +Description: ![PyPI](https://img.shields.io/pypi/v/tox?style=flat-square) [![Supported Python versions](https://img.shields.io/pypi/pyversions/tox.svg)](https://pypi.org/project/tox/) [![Azure Pipelines build @@ -32,61 +31,52 @@ **Command line driven CI frontend and development task automation tool** - At its core tox provides a convenient way to run arbitrary commands in - isolated environments to serve as a single entry point for build, test - and release activities. + At its core tox provides a convenient way to run arbitrary commands in isolated environments to serve as a single entry + point for build, test and release activities. - tox is highly - [configurable](https://tox.readthedocs.io/en/latest/config.html) and + tox is highly [configurable](https://tox.readthedocs.io/en/latest/config.html) and [pluggable](https://tox.readthedocs.io/en/latest/plugins.html). - ## Example: run tests with Python 2.7 and Python 3.7 + ## Example: run tests with Python 3.7 and Python 3.8 - tox is mainly used as a command line tool and needs a `tox.ini` or a - `tool.tox` section in `pyproject.toml` containing the configuration. + tox is mainly used as a command line tool and needs a `tox.ini` or a `tool.tox` section in `pyproject.toml` containing + the configuration. - To test a simple project that has some tests, here is an example with - a `tox.ini` in the root of the project: + To test a simple project that has some tests, here is an example with a `tox.ini` in the root of the project: - ``` {.sourceCode .ini} + ```{.sourceCode .ini} [tox] - envlist = py27,py37 + envlist = py37,py38 [testenv] deps = pytest commands = pytest ``` - ``` {.sourceCode .console} + ```{.sourceCode .console} $ tox [lots of output from what tox does] [lots of output from commands that were run] __________________ summary _________________ - py27: commands succeeded py37: commands succeeded + py38: commands succeeded congratulations :) ``` - tox created two ``testenvs`` - one based on Python2.7 and one based on - Python3.7, it installed pytest in them and ran the tests. The report at - the end summarizes which ``testenvs`` have failed and which have - succeeded. + tox created two `testenvs` - one based on Python3.7 and one based on Python3.8, it installed pytest in them and ran the + tests. The report at the end summarizes which `testenvs` have failed and which have succeeded. **Note:** To learn more about what you can do with tox, have a look at - [the collection of examples in the - documentation](https://tox.readthedocs.io/en/latest/examples.html) - or [existing projects using - tox](https://github.com/search?l=INI&q=tox.ini+in%3Apath&type=Code). + [the collection of examples in the documentation](https://tox.readthedocs.io/en/latest/examples.html) or + [existing projects using tox](https://github.com/search?l=INI&q=tox.ini+in%3Apath&type=Code). ### How it works - tox creates virtual environments for all configured so called - ``testenvs``, it then installs the project and other necessary - dependencies and runs the configured set of commands. See [system - overview](https://tox.readthedocs.io/en/latest/#system-overview) for - more details. + tox creates virtual environments for all configured so called `testenvs`, it then installs the project and other + necessary dependencies and runs the configured set of commands. See + [system overview](https://tox.readthedocs.io/en/latest/#system-overview) for more details. . - - We also have a [Gitter community](https://gitter.im/tox-dev/). + For the fastest and interactive feedback please join our + [![Discord](https://img.shields.io/discord/802911963368783933?style=flat-square)](https://discord.gg/edtj86wzBX) server. + If you have questions or suggestions you can first check if they have already been answered or discussed on our + [issue tracker](https://github.com/tox-dev/tox/issues?utf8=%E2%9C%93&q=is%3Aissue+sort%3Aupdated-desc+label%3A%22type%3Aquestion+%3Agrey_question%3A%22+). + On [Stack Overflow (tagged with `tox`)](https://stackoverflow.com/questions/tagged/tox). ### Contributing - Contributions are welcome. See - [contributing](https://github.com/tox-dev/tox/blob/master/CONTRIBUTING.rst) - and our [Contributor Covenant Code of - Conduct](https://github.com/tox-dev/tox/blob/master/CODE_OF_CONDUCT.md). + Contributions are welcome. See [contributing](https://github.com/tox-dev/tox/blob/master/CONTRIBUTING.rst) and our + [Contributor Covenant Code of Conduct](https://github.com/tox-dev/tox/blob/master/CODE_OF_CONDUCT.md). - Currently the [code](https://github.com/tox-dev/tox) and the - [issues](https://github.com/tox-dev/tox/issues) are hosted on Github. + Currently the [code](https://github.com/tox-dev/tox) and the [issues](https://github.com/tox-dev/tox/issues) are hosted + on Github. - The project is licensed under - [MIT](https://github.com/tox-dev/tox/blob/master/LICENSE). + The project is licensed under [MIT](https://github.com/tox-dev/tox/blob/master/LICENSE). Keywords: virtual,environments,isolated,testing Platform: any @@ -153,6 +135,9 @@ 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 :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: Testing Classifier: Topic :: Utilities diff -Nru tox-3.15.1/src/tox.egg-info/requires.txt tox-3.21.4/src/tox.egg-info/requires.txt --- tox-3.15.1/src/tox.egg-info/requires.txt 2020-05-20 13:44:53.000000000 +0000 +++ tox-3.21.4/src/tox.egg-info/requires.txt 2021-02-02 20:28:59.000000000 +0000 @@ -10,23 +10,23 @@ colorama>=0.4.1 [:python_version < "3.8"] -importlib-metadata<2,>=0.12 +importlib-metadata>=0.12 [docs] -sphinx>=2.0.0 -towncrier>=18.5.0 pygments-github-lexers>=0.0.5 +sphinx>=2.0.0 sphinxcontrib-autoprogram>=0.1.5 +towncrier>=18.5.0 [testing] +flaky>=3.4.0 freezegun>=0.3.11 -pathlib2>=2.3.3 +psutil>=5.6.1 pytest>=4.0.0 pytest-cov>=2.5.1 pytest-mock>=1.10.0 -pytest-xdist>=1.22.2 pytest-randomly>=1.0.0 -flaky>=3.4.0 +pytest-xdist>=1.22.2 -[testing:python_version != "3.4"] -psutil>=5.6.1 +[testing:python_version < "3.4"] +pathlib2>=2.3.3 diff -Nru tox-3.15.1/tasks/notify.py tox-3.21.4/tasks/notify.py --- tox-3.15.1/tasks/notify.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tasks/notify.py 2021-02-02 20:28:40.000000000 +0000 @@ -85,7 +85,8 @@ print("get latest release version") commit_to_tag = {tag.commit.hexsha: tag for tag in repo.tags} _, release_tag = sorted( - [(tag.commit.committed_datetime, tag) for tag in repo.tags], reverse=True, + [(tag.commit.committed_datetime, tag) for tag in repo.tags], + reverse=True, )[0] for commit in release_tag.commit.iter_parents(): if commit.hexsha in commit_to_tag: @@ -123,7 +124,8 @@ json.dump(client_secret_json, temp_filename) temp_filename.flush() flow = client.flow_from_clientsecrets( - filename=temp_filename.name, scope="https://www.googleapis.com/auth/gmail.send", + filename=temp_filename.name, + scope="https://www.googleapis.com/auth/gmail.send", ) credentials = tools.run_flow(flow, store) service = discovery.build("gmail", "v1", http=credentials.authorize(httplib2.Http())) diff -Nru tox-3.15.1/tests/integration/test_package_int.py tox-3.21.4/tests/integration/test_package_int.py --- tox-3.15.1/tests/integration/test_package_int.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tests/integration/test_package_int.py 2021-02-02 20:28:40.000000000 +0000 @@ -4,7 +4,11 @@ import sys import pytest -from pathlib2 import Path + +if sys.version_info[:2] >= (3, 4): + from pathlib import Path +else: + from pathlib2 import Path from tests.lib import need_git diff -Nru tox-3.15.1/tests/integration/test_parallel_interrupt.py tox-3.21.4/tests/integration/test_parallel_interrupt.py --- tox-3.15.1/tests/integration/test_parallel_interrupt.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tests/integration/test_parallel_interrupt.py 2021-02-02 20:28:40.000000000 +0000 @@ -7,7 +7,11 @@ import pytest from flaky import flaky -from pathlib2 import Path + +if sys.version_info[:2] >= (3, 4): + from pathlib import Path +else: + from pathlib2 import Path from tox.constants import INFO from tox.util.main import MAIN_FILE @@ -16,7 +20,8 @@ @flaky(max_runs=3) @pytest.mark.skipif(INFO.IS_PYPY, reason="TODO: process numbers work differently on pypy") @pytest.mark.skipif( - "sys.platform == 'win32'", reason="triggering SIGINT reliably on Windows is hard", + "sys.platform == 'win32'", + reason="triggering SIGINT reliably on Windows is hard", ) def test_parallel_interrupt(initproj, monkeypatch, capfd): monkeypatch.setenv(str("_TOX_SKIP_ENV_CREATION_TEST"), str("1")) @@ -33,7 +38,7 @@ skip_install = True commands = python -c "open('{{envname}}', 'w').write('done'); \ import time; time.sleep(100)" - whitelist_externals = {} + allowlist_externals = {} """.format( sys.executable, diff -Nru tox-3.15.1/tests/integration/test_provision_int.py tox-3.21.4/tests/integration/test_provision_int.py --- tox-3.15.1/tests/integration/test_provision_int.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tests/integration/test_provision_int.py 2021-02-02 20:28:40.000000000 +0000 @@ -4,7 +4,11 @@ import time import pytest -from pathlib2 import Path + +if sys.version_info[:2] >= (3, 4): + from pathlib import Path +else: + from pathlib2 import Path from tox.constants import INFO from tox.util.main import MAIN_FILE @@ -66,9 +70,11 @@ @pytest.mark.skipif(INFO.IS_PYPY, reason="TODO: process numbers work differently on pypy") @pytest.mark.skipif( - "sys.platform == 'win32'", reason="triggering SIGINT reliably on Windows is hard", + "sys.platform == 'win32'", + reason="triggering SIGINT reliably on Windows is hard", ) -def test_provision_interrupt_child(initproj, monkeypatch, capfd): +@pytest.mark.parametrize("signal_type", [signal.SIGINT, signal.SIGTERM]) +def test_provision_interrupt_child(initproj, monkeypatch, capfd, signal_type): monkeypatch.delenv(str("PYTHONPATH"), raising=False) monkeypatch.setenv(str("TOX_REPORTER_TIMESTAMP"), str("1")) initproj( @@ -118,12 +124,13 @@ # 1 process for the host tox, 1 for the provisioned assert len(all_process) >= 2, all_process - process.send_signal(signal.CTRL_C_EVENT if sys.platform == "win32" else signal.SIGINT) + process.send_signal(signal.CTRL_C_EVENT if sys.platform == "win32" else signal_type) process.communicate() out, err = capfd.readouterr() assert ".tox KeyboardInterrupt: from" in out, out for process in all_process: assert not process.is_running(), "{}{}".format( - out, "\n".join(repr(i) for i in all_process), + out, + "\n".join(repr(i) for i in all_process), ) diff -Nru tox-3.15.1/tests/unit/config/test_config.py tox-3.21.4/tests/unit/config/test_config.py --- tox-3.15.1/tests/unit/config/test_config.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tests/unit/config/test_config.py 2021-02-02 20:28:40.000000000 +0000 @@ -6,6 +6,7 @@ import py import pytest from pluggy import PluginManager +from six import PY2 import tox from tox.config import ( @@ -90,6 +91,24 @@ envconfig = config.envconfigs["dev"] assert envconfig.envdir == config.toxworkdir.join("foobar") + def test_envdir_set_manually_setup_cfg(self, tmpdir, newconfig): + config = newconfig( + [], + """ + [tox:tox] + envlist = py36,py37 + [testenv] + envdir = dev + [testenv:py36] + envdir = dev36 + """, + filename="setup.cfg", + ) + envconfig = config.envconfigs["py36"] + assert envconfig.envdir == tmpdir.join("dev36") + envconfig = config.envconfigs["py37"] + assert envconfig.envdir == tmpdir.join("dev") + def test_force_dep_version(self, initproj): """ Make sure we can override dependencies configured in tox.ini when using the command line @@ -175,11 +194,12 @@ assert DepOption._is_same_dep("pkg_hello-world3==1.0", "pkg_hello-world3<=2.0") assert not DepOption._is_same_dep("pkg_hello-world3==1.0", "otherpkg>=2.0") - def test_interrupt_terminate_timeout_set_manually(self, newconfig): + def test_suicide_interrupt_terminate_timeout_set_manually(self, newconfig): config = newconfig( [], """ [testenv:dev] + suicide_timeout = 30.0 interrupt_timeout = 5.0 terminate_timeout = 10.0 @@ -187,10 +207,12 @@ """, ) envconfig = config.envconfigs["other"] + assert 0.0 == envconfig.suicide_timeout assert 0.3 == envconfig.interrupt_timeout assert 0.2 == envconfig.terminate_timeout envconfig = config.envconfigs["dev"] + assert 30.0 == envconfig.suicide_timeout assert 5.0 == envconfig.interrupt_timeout assert 10.0 == envconfig.terminate_timeout @@ -244,6 +266,97 @@ expected = {"win": "win32", "lin": "linux2", "osx": ""}.get(plat) assert platform == expected + def test_platform_install_command(self, newconfig, mocksession, monkeypatch): + # Expanded from docs/example/platform.html + config = newconfig( + [], + """ + [tox] + envlist = py{27,36}-{mylinux,mymacos,mywindows} + + [testenv] + platform = + mylinux: linux + mymacos: darwin + mywindows: win32 + + deps = + mylinux,mymacos: py==1.4.32 + mywindows: py==1.4.30 + + install_command = + mylinux: python -m pip install {packages} distro + mywindows: python -m pip install {packages} pywin32 + + commands= + mylinux: echo Linus + mymacos: echo Steve + mywindows: echo Bill + """, + ) + mocksession.config = config + assert len(config.envconfigs) == 6 + + monkeypatch.setattr(sys, "platform", "linux") + + venv = mocksession.getvenv("py27-mylinux") + assert venv.envconfig._reader.factors == {"py27", "mylinux"} + assert venv.matching_platform() + assert str(venv.envconfig.deps[0]) == "py==1.4.32" + assert venv.envconfig.install_command == [ + "python", + "-m", + "pip", + "install", + "{packages}", + "distro", + ] + assert venv.envconfig.commands[0] == ["echo", "Linus"] + + venv = mocksession.getvenv("py27-mymacos") + assert venv.envconfig._reader.factors == {"py27", "mymacos"} + assert not venv.matching_platform() + assert str(venv.envconfig.deps[0]) == "py==1.4.32" + assert venv.envconfig.install_command == [ + "python", + "-m", + "pip", + "install", + "{opts}", + "{packages}", + ] + assert venv.envconfig.commands[0] == ["echo", "Steve"] + + venv = mocksession.getvenv("py27-mywindows") + assert venv.envconfig._reader.factors == {"py27", "mywindows"} + assert not venv.matching_platform() + assert str(venv.envconfig.deps[0]) == "py==1.4.30" + assert venv.envconfig.install_command == [ + "python", + "-m", + "pip", + "install", + "{packages}", + "pywin32", + ] + assert venv.envconfig.commands[0] == ["echo", "Bill"] + + monkeypatch.undo() + + monkeypatch.setattr(sys, "platform", "darwin") + + venv = mocksession.getvenv("py27-mymacos") + assert venv.envconfig.install_command == [ + "python", + "-m", + "pip", + "install", + "{opts}", + "{packages}", + ] + + monkeypatch.undo() + class TestConfigPackage: def test_defaults(self, tmpdir, newconfig): @@ -342,6 +455,176 @@ class TestIniParserAgainstCommandsKey: """Test parsing commands with substitutions""" + def test_command_substitution_recursion_error_same_section(self, newconfig): + expected = r"\('testenv:a', 'commands'\) already in \[\('testenv:a', 'commands'\)\]" + with pytest.raises(tox.exception.ConfigError, match=expected): + newconfig( + """ + [testenv:a] + commands = {[testenv:a]commands} + """, + ) + + def test_command_substitution_recursion_error_other_section(self, newconfig): + expected = ( + r"\('testenv:py27', 'commands'\) already in " + r"\[\('testenv:py27', 'commands'\), " + r"\('testenv:base', 'foo'\)\]" + ) + with pytest.raises(tox.exception.ConfigError, match=expected): + newconfig( + """ + [testenv:base] + foo = {[testenv:py27]commands} + + [testenv:py27] + commands = {[testenv:base]foo} + """, + ) + + def test_command_substitution_recursion_error_unnecessary(self, newconfig): + # TODO: There is no reason for this recursion error to occur, so it + # could be optimised away, or emit a warning, or give a custom error + expected = ( + r"\('testenv:base', 'foo'\) already in " + r"\[\('testenv:py27', 'commands'\), \('testenv:base', 'foo'\)\]" + ) + with pytest.raises(tox.exception.ConfigError, match=expected): + newconfig( + """ + [testenv:base] + foo = {[testenv:base]foo} + + [testenv:py27] + bar = {[testenv:base]foo} + setenv = + FOO = foo + commands = {env:FOO:{[testenv:base]foo}} + """, + ) + + def test_command_missing_substitution_simple(self, newconfig): + config = newconfig( + """ + [testenv:py27] + commands = {env:{env:FOO}} + """, + ) + envconfig = config.envconfigs["py27"] + + expected = "MissingSubstitution: FOO" + + with pytest.raises(tox.exception.MissingSubstitution, match=expected): + envconfig.commands + + def test_command_missing_substitution_setenv(self, newconfig): + config = newconfig( + """ + [testenv:py27] + setenv = + FOO = {env:{env:FOO}} + commands = {env:FOO} + """, + ) + envconfig = config.envconfigs["py27"] + + expected = "MissingSubstitution: FOO" + + with pytest.raises(tox.exception.MissingSubstitution, match=expected): + envconfig.setenv["FOO"] + + with pytest.raises(tox.exception.MissingSubstitution, match=expected): + envconfig.commands + + def test_command_missing_substitution_inherit(self, newconfig): + config = newconfig( + """ + [testenv] + setenv = + FOO = {[testenv:py27]commands} + + [testenv:py27] + commands = {env:FOO} + """, + ) + envconfig = config.envconfigs["py27"] + + expected = "MissingSubstitution: FOO" + + with pytest.raises(tox.exception.MissingSubstitution, match=expected): + envconfig.commands + + with pytest.raises(tox.exception.MissingSubstitution, match=expected): + envconfig.setenv["FOO"] + + def test_command_missing_substitution_other_section(self, newconfig): + config = newconfig( + """ + [testenv:base] + bar = {[testenv:py27]foo} + + [testenv:py27] + foo = {env:FOO} + setenv = + FOO = {[testenv:base]bar} + commands = {env:FOO} + """, + ) + envconfig = config.envconfigs["py27"] + + expected = "MissingSubstitution: FOO" + + with pytest.raises(tox.exception.MissingSubstitution, match=expected): + envconfig.commands + + with pytest.raises(tox.exception.MissingSubstitution, match=expected): + envconfig.setenv["FOO"] + + def test_command_missing_substitution_multi_env(self, newconfig): + config = newconfig( + """ + [testenv:py27] + setenv = + FOO = {env:BAR} + BAR = {env:FOO} + commands = {env:BAR} + """, + ) + envconfig = config.envconfigs["py27"] + + expected = "MissingSubstitution: BAR" + with pytest.raises(tox.exception.MissingSubstitution, match=expected): + envconfig.commands + + expected = "MissingSubstitution: FOO" + with pytest.raises(tox.exception.MissingSubstitution, match=expected): + envconfig.setenv["FOO"] + + def test_command_missing_substitution_complex(self, newconfig): + config = newconfig( + """ + [testenv:base] + bar = {env:BAR} + setenv = + BAR = {[testenv:py27]foo} + + [testenv:py27] + foo = {env:FOO} + setenv = + FOO = {[testenv:base]bar} + commands = {env:FOO} + """, + ) + envconfig = config.envconfigs["py27"] + + expected = "MissingSubstitution: BAR" + + with pytest.raises(tox.exception.MissingSubstitution, match=expected): + envconfig.setenv["FOO"] + + with pytest.raises(tox.exception.MissingSubstitution, match=expected): + envconfig.commands + def test_command_substitution_from_other_section(self, newconfig): config = newconfig( """ @@ -402,7 +685,7 @@ """, ) reader = SectionReader("testenv", config._cfg) - reader.addsubstitutions([r"argpos"]) + reader.addsubstitutions(["argpos"]) x = reader.getargvlist("commands") assert x == [["thing", "argpos", "arg2"]] @@ -418,10 +701,38 @@ """, ) reader = SectionReader("testenv", config._cfg) - reader.addsubstitutions([r"argpos"]) + reader.addsubstitutions(["argpos"]) x = reader.getargvlist("commands") assert x == [["thing", "arg1", "argpos", "endarg"]] + def test_command_posargs_with_colon(self, newconfig): + """Ensure posargs with default containing : succeeds""" + config = newconfig( + r""" + [testenv] + commands = + pytest {posargs:default with : colon after} + """, + ) + reader = SectionReader("testenv", config._cfg) + x = reader.getargvlist("commands") + assert x[0] == ["pytest", "default", "with", ":", "colon", "after"] + + def test_command_missing_substitution(self, newconfig): + config = newconfig( + """ + [testenv:a] + setenv = + FOO = foo + commands = {env:FOO} + """, + ) + reader = SectionReader("testenv:a", config._cfg) + + expected = "MissingSubstitution: FOO" + with pytest.raises(tox.exception.MissingSubstitution, match=expected): + reader.getargvlist("commands") + def test_command_env_substitution(self, newconfig): """Ensure referenced {env:key:default} values are substituted correctly.""" config = newconfig( @@ -437,6 +748,51 @@ assert envconfig.commands == [["ls", "testvalue"]] assert envconfig.setenv["TEST"] == "testvalue" + def test_command_env_substitution_posargs(self, newconfig): + """Ensure {posargs} values are substituted correctly.""" + config = newconfig( + """ + [testenv:py27] + setenv = + TEST={posargs:default} + commands = + ls {env:TEST} + """, + ) + envconfig = config.envconfigs["py27"] + assert envconfig.setenv["TEST"] == "default" + assert envconfig.commands == [["ls", "default"]] + + def test_command_env_substitution_posargs_with_colon(self, newconfig): + """Ensure {posargs} values are substituted correctly.""" + config = newconfig( + """ + [testenv:py27] + setenv = + TEST=pytest {posargs:default with:colon after} + commands = + ls {env:TEST} + """, + ) + envconfig = config.envconfigs["py27"] + assert envconfig.setenv["TEST"] == "pytest default with:colon after" + assert envconfig.commands == [["ls", "pytest", "default", "with:colon", "after"]] + + def test_command_env_substitution_posargs_with_spaced_colon(self, newconfig): + """Ensure {posargs} values are substituted correctly.""" + config = newconfig( + """ + [testenv:py27] + setenv = + TEST=pytest {posargs:default with : colon after} + commands = + ls {env:TEST} + """, + ) + envconfig = config.envconfigs["py27"] + assert envconfig.setenv["TEST"] == "pytest default with : colon after" + assert envconfig.commands == [["ls", "pytest", "default", "with", ":", "colon", "after"]] + def test_command_env_substitution_global(self, newconfig): """Ensure referenced {env:key:default} values are substituted correctly.""" config = newconfig( @@ -449,6 +805,17 @@ envconfig = config.envconfigs["python"] assert envconfig.commands == [["echo", "bar"]] + def test_command_env_substitution_default_escape(self, newconfig): + """Ensure literal { and } in default of {env:key:default} values.""" + config = newconfig( + r""" + [testenv] + commands = echo {env:FOO:\{bar\}} + """, + ) + envconfig = config.envconfigs["python"] + assert envconfig.commands == [["echo", "{bar}"]] + def test_regression_issue595(self, newconfig): config = newconfig( """ @@ -482,6 +849,33 @@ x = reader.getstring("hello", "world") assert x == "world" + def test_substitution_empty(self, newconfig): + config = newconfig( + """ + [mydefault] + key2={} + """, + ) + reader = SectionReader("mydefault", config._cfg, fallbacksections=["mydefault"]) + assert reader is not None + with pytest.raises(tox.exception.ConfigError, match="no substitution type provided"): + reader.getstring("key2") + + def test_substitution_colon_prefix(self, newconfig): + config = newconfig( + """ + [mydefault] + key2={:abc} + """, + ) + reader = SectionReader("mydefault", config._cfg, fallbacksections=["mydefault"]) + assert reader is not None + with pytest.raises( + tox.exception.ConfigError, + match="Malformed substitution with prefix ':'", + ): + reader.getstring("key2") + def test_missing_substitution(self, newconfig): config = newconfig( """ @@ -491,7 +885,7 @@ ) reader = SectionReader("mydefault", config._cfg, fallbacksections=["mydefault"]) assert reader is not None - with pytest.raises(tox.exception.ConfigError): + with pytest.raises(tox.exception.ConfigError, match="substitution key '.*' not found"): reader.getstring("key2") def test_getstring_fallback_sections(self, newconfig): @@ -576,7 +970,10 @@ def test_missing_env_sub_populates_missing_subs(self, newconfig): config = newconfig("[testenv:foo]\ncommands={env:VAR}") print(SectionReader("section", config._cfg).getstring("commands")) - assert config.envconfigs["foo"]._missing_subs == ["VAR"] + + assert "commands" in config.envconfigs["foo"]._missing_subs + missing_exception = config.envconfigs["foo"]._missing_subs["commands"] + assert missing_exception.name == "VAR" def test_getstring_environment_substitution_with_default(self, monkeypatch, newconfig): monkeypatch.setenv("KEY1", "hello") @@ -862,10 +1259,11 @@ def test_expand_section_name(self, newconfig): config = newconfig( """ - [testenv:custom-{one,two,three}-{four,five}-six] + [testenv:custom{,-one,-two,-three}-{four,five}-six] """, ) assert "testenv:custom-one-five-six" in config._cfg.sections + assert "testenv:custom-four-six" in config._cfg.sections assert "testenv:custom-{one,two,three}-{four,five}-six" not in config._cfg.sections @@ -894,7 +1292,10 @@ """, ) reader = SectionReader( - "section", config._cfg, prefix="p", fallbacksections=["p:mydefault"], + "section", + config._cfg, + prefix="p", + fallbacksections=["p:mydefault"], ) x = reader.getstring("key2") assert x == "value2" @@ -996,25 +1397,25 @@ envconfig = config.envconfigs["py"] assert envconfig.commands == [["abc"]] - def test_whitelist_externals(self, newconfig): + def test_allowlist_externals(self, newconfig): config = newconfig( """ [testenv] - whitelist_externals = xyz + allowlist_externals = xyz commands=xyz [testenv:x] [testenv:py] - whitelist_externals = xyz2 + allowlist_externals = xyz2 commands=abc """, ) assert len(config.envconfigs) == 2 envconfig = config.envconfigs["py"] assert envconfig.commands == [["abc"]] - assert envconfig.whitelist_externals == ["xyz2"] + assert envconfig.allowlist_externals == ["xyz2"] envconfig = config.envconfigs["x"] - assert envconfig.whitelist_externals == ["xyz"] + assert envconfig.allowlist_externals == ["xyz"] def test_changedir(self, newconfig): config = newconfig( @@ -1269,6 +1670,26 @@ ] assert envconfig.install_command == expected_deps + def test_install_command_substitutions_other_section(self, newconfig): + config = newconfig( + """ + [base] + install_command=some_install --arg={toxinidir}/foo \ + {envname} {opts} {packages} + [testenv] + install_command={[base]install_command} + """, + ) + envconfig = config.envconfigs["python"] + expected_deps = [ + "some_install", + "--arg={}/foo".format(config.toxinidir), + "python", + "{opts}", + "{packages}", + ] + assert envconfig.install_command == expected_deps + def test_pip_pre(self, newconfig): config = newconfig( """ @@ -1516,7 +1937,8 @@ fun frob{{env:ENV_VAR:>1.0,<2.0}} """.format( - envlist=",".join(envlist), deps="\n" + "\n".join([" " * 17 + d for d in deps]), + envlist=",".join(envlist), + deps="\n" + "\n".join([" " * 17 + d for d in deps]), ) conf = newconfig([], inisource).envconfigs["py27"] packages = [dep.name for dep in conf.deps] @@ -1955,7 +2377,8 @@ [testenv:{}] commands = python --version """.format( - exe, env, + exe, + env, ), ) assert len(config.envconfigs) == 1 @@ -1963,7 +2386,12 @@ assert envconfig.basepython == exe def test_default_factors_conflict_lying_name( - self, newconfig, capsys, tmpdir, recwarn, monkeypatch, + self, + newconfig, + capsys, + tmpdir, + recwarn, + monkeypatch, ): # we first need to create a lying Python here, let's mock out here from tox.interpreters import Interpreters @@ -1980,7 +2408,9 @@ basepython=python{0}.{2} commands = python --version """.format( - major, minor, minor - 1, + major, + minor, + minor - 1, ), ) env_config = config.envconfigs["py{}{}".format(major, minor)] @@ -2004,7 +2434,8 @@ basepython=python{0}.{1} commands = python --version """.format( - major, minor - 1, + major, + minor - 1, ), ) @@ -2019,7 +2450,8 @@ basepython=python{0}.{1} commands = python --version """.format( - major, minor, + major, + minor, ), ) @@ -2070,6 +2502,18 @@ assert configs["py27"].setenv["X"] == "1" assert "X" not in configs["py36"].setenv + def test_curly_braces_in_setenv(self, newconfig): + inisource = r""" + [testenv] + setenv = + VAR = \{val\} + commands = + {env:VAR} + """ + configs = newconfig([], inisource).envconfigs + assert configs["python"].setenv["VAR"] == r"\{val\}" + assert configs["python"].commands[0] == ["{val}"] + def test_factor_use_not_checked(self, newconfig): inisource = """ [tox] @@ -2123,15 +2567,18 @@ config = newconfig(["--notest"], "") assert config.option.notest - def test_verbosity(self, newconfig): - config = newconfig([], "") - assert config.option.verbose_level == 0 - config = newconfig(["-v"], "") - assert config.option.verbose_level == 1 - config = newconfig(["-vv"], "") - assert config.option.verbose_level == 2 + @pytest.mark.parametrize( + "args, expected", + [([], 0), (["-v"], 1), (["-vv"], 2), (["--verbose", "--verbose"], 2), (["-vvv"], 3)], + ) + def test_verbosity(self, args, expected, newconfig): + config = newconfig(args, "") + assert config.option.verbose_level == expected - @pytest.mark.parametrize("args, expected", [([], 0), (["-q"], 1), (["-qq"], 2), (["-qqq"], 3)]) + @pytest.mark.parametrize( + "args, expected", + [([], 0), (["-q"], 1), (["-qq"], 2), (["--quiet", "--quiet"], 2), (["-qqq"], 3)], + ) def test_quiet(self, args, expected, newconfig): config = newconfig(args, "") assert config.option.quiet_level == expected @@ -2532,7 +2979,7 @@ ) assert config.envconfigs["env1"].setenv["X"] == "5" - def test_setenv_recursive_direct(self, newconfig): + def test_setenv_recursive_direct_with_default(self, newconfig): config = newconfig( """ [testenv:env1] @@ -2542,6 +2989,27 @@ ) assert config.envconfigs["env1"].setenv["X"] == "3" + def test_setenv_recursive_direct_with_default_nested(self, newconfig): + config = newconfig( + """ + [testenv:env1] + setenv = + X = {env:X:{env:X:3}} + """, + ) + assert config.envconfigs["env1"].setenv["X"] == "3" + + def test_setenv_recursive_direct_without_default(self, newconfig): + config = newconfig( + """ + [testenv:env1] + setenv = + X = {env:X} + """, + ) + with pytest.raises(tox.exception.MissingSubstitution): + config.envconfigs["env1"].setenv["X"] + def test_setenv_overrides(self, newconfig): config = newconfig( """ @@ -2638,6 +3106,58 @@ assert envconfig.setenv["NOT_TEST"] == "defaultvalue" assert envconfig.setenv["y"] == "7" + def test_setenv_comment(self, newconfig): + """Check that setenv ignores comments.""" + envconfig = newconfig( + """ + [testenv] + setenv = + # MAGIC = yes + """, + ).envconfigs["python"] + assert "MAGIC" not in envconfig.setenv + + @pytest.mark.parametrize( + "content, has_magic", + [ + (None, False), + ("\n", False), + ("#MAGIC = yes", False), + ("MAGIC=yes", True), + ("\nMAGIC = yes", True), + ], + ) + def test_setenv_env_file(self, newconfig, content, has_magic, tmp_path): + """Check that setenv handles env files.""" + env_path = tmp_path / ".env" if content else None + if content: + env_path.write_text(content.decode() if PY2 else content) + env_config = newconfig( + """ + [testenv] + setenv = + ALPHA = 1 + file| {} + """.format( + env_path, + ), + ).envconfigs["python"] + + envs = env_config.setenv.definitions + + assert envs["ALPHA"] == "1" + if has_magic: + assert envs["MAGIC"] == "yes" + else: + assert "MAGIC" not in envs + + expected_vars = ["ALPHA", "PYTHONHASHSEED", "TOX_ENV_DIR", "TOX_ENV_NAME"] + if has_magic: + expected_vars = sorted(expected_vars + ["MAGIC"]) + + exported = env_config.setenv.export() + assert sorted(exported) == expected_vars + class TestIndexServer: def test_indexserver(self, newconfig): @@ -2692,7 +3212,8 @@ class TestConfigConstSubstitutions: @pytest.mark.parametrize("pathsep", [":", ";"]) - def test_replace_pathsep_unix(self, monkeypatch, newconfig, pathsep): + def test_replace_pathsep(self, monkeypatch, newconfig, pathsep): + """Replace {:} with OS path separator.""" monkeypatch.setattr("os.pathsep", pathsep) config = newconfig( """ @@ -2713,6 +3234,29 @@ assert mdict["substitution_value"] == "" assert mdict["default_value"] == "" + @pytest.mark.parametrize("dirsep", ["\\", "\\\\"]) + def test_dirsep_replace(self, monkeypatch, newconfig, dirsep): + """Replace {/} with OS directory separator.""" + monkeypatch.setattr("os.sep", dirsep) + config = newconfig( + """ + [testenv] + setenv = + VAR = dira{/}subdirb{/}subdirc + """, + ) + envconfig = config.envconfigs["python"] + assert envconfig.setenv["VAR"] == dirsep.join(["dira", "subdirb", "subdirc"]) + + def test_dirsep_regex(self): + """Sanity check for regex behavior for directory separator.""" + regex = tox.config.Replacer.RE_ITEM_REF + match = next(regex.finditer("{/}")) + mdict = match.groupdict() + assert mdict["sub_type"] is None + assert mdict["substitution_value"] == "/" + assert mdict["default_value"] is None + class TestParseEnv: def test_parse_recreate(self, newconfig): @@ -2935,6 +3479,22 @@ assert envconfig.commands[0] == ["some", r"hello\world"] +def test_provision_tox_env_cannot_be_in_envlist(newconfig, capsys): + inisource = """ + [tox] + envlist = py36,.tox + """ + with pytest.raises( + tox.exception.ConfigError, + match="provision_tox_env .tox cannot be part of envlist", + ): + newconfig([], inisource) + + out, err = capsys.readouterr() + assert not err + assert not out + + def test_isolated_build_env_cannot_be_in_envlist(newconfig, capsys): inisource = """ [tox] @@ -2943,7 +3503,8 @@ isolated_build_env = package """ with pytest.raises( - tox.exception.ConfigError, match="isolated_build_env package cannot be part of envlist", + tox.exception.ConfigError, + match="isolated_build_env package cannot be part of envlist", ): newconfig([], inisource) @@ -2969,7 +3530,8 @@ @pytest.mark.parametrize( - "key, set_value, default", [("deps", "crazy", []), ("sitepackages", "True", False)], + "key, set_value, default", + [("deps", "crazy", []), ("sitepackages", "True", False)], ) def test_isolated_build_ignores(newconfig, capsys, key, set_value, default): config = newconfig( @@ -2981,7 +3543,8 @@ [testenv] {} = {} """.format( - key, set_value, + key, + set_value, ), ) package_env = config.envconfigs.get(".package") @@ -3017,6 +3580,32 @@ assert "ERROR:" not in out +def test_config_setup_cfg_no_tox_section(initproj, capsys): + setup_cfg = """ + [nope:nope] + envlist = py37 + """ + initproj("setup_cfg_no_tox-0.1", filedefs={"setup.cfg": setup_cfg}) + with pytest.raises(SystemExit): + parseconfig([]) + + out, err = capsys.readouterr() + msg = "ERROR: tox config file (either pyproject.toml, tox.ini, setup.cfg) not found\n" + assert err == msg + assert "ERROR:" not in out + + +def test_config_file_not_required_with_devenv(initproj, capsys): + initproj("no_tox_config-0.7") + config = parseconfig(["--devenv", "myenv"]) + + out, err = capsys.readouterr() + assert "ERROR:" not in err + assert "ERROR:" not in out + assert config.option.devenv == "myenv" + assert config.option.notest is True + + @pytest.mark.skipif(sys.platform == "win32", reason="no named pipes on Windows") def test_config_bad_config_type_specified(monkeypatch, tmpdir, capsys): monkeypatch.chdir(tmpdir) @@ -3118,3 +3707,19 @@ out, err = capsys.readouterr() assert not out assert not err + + +def test_overwrite_skip_install_override(newconfig): + source = """ + [tox] + envlist = py, skip + [testenv:skip] + skip_install = True + """ + config = newconfig(args=[], source=source) + assert config.envconfigs["py"].skip_install is False # by default do not skip + assert config.envconfigs["skip"].skip_install is True + + config = newconfig(args=["--skip-pkg-install"], source=source) + assert config.envconfigs["py"].skip_install is True # skip if the flag is passed + assert config.envconfigs["skip"].skip_install is True diff -Nru tox-3.15.1/tests/unit/interpreters/test_interpreters.py tox-3.21.4/tests/unit/interpreters/test_interpreters.py --- tox-3.15.1/tests/unit/interpreters/test_interpreters.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tests/unit/interpreters/test_interpreters.py 2021-02-02 20:28:40.000000000 +0000 @@ -1,6 +1,7 @@ from __future__ import unicode_literals import os +import platform import stat import subprocess import sys @@ -101,7 +102,7 @@ name = os.path.basename(sys.executable) info = run_and_get_interpreter_info(name, sys.executable) assert info.version_info == tuple(sys.version_info) - assert info.name == name + assert info.implementation == platform.python_implementation() assert info.executable == sys.executable @@ -186,16 +187,16 @@ class TestInterpreterInfo: @staticmethod def info( - name="my-name", + implementation="CPython", executable="my-executable", version_info="my-version-info", sysplatform="my-sys-platform", ): - return InterpreterInfo(name, executable, version_info, sysplatform, True) + return InterpreterInfo(implementation, executable, version_info, sysplatform, True, None) def test_data(self): x = self.info("larry", "moe", "shemp", "curly") - assert x.name == "larry" + assert x.implementation == "larry" assert x.executable == "moe" assert x.version_info == "shemp" assert x.sysplatform == "curly" diff -Nru tox-3.15.1/tests/unit/interpreters/windows/test_pep514.py tox-3.21.4/tests/unit/interpreters/windows/test_pep514.py --- tox-3.15.1/tests/unit/interpreters/windows/test_pep514.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tests/unit/interpreters/windows/test_pep514.py 2021-02-02 20:28:40.000000000 +0000 @@ -20,6 +20,7 @@ import tox.interpreters.windows.pep514 as pep514 out = subprocess.check_output( - [sys.executable, inspect.getsourcefile(pep514)], universal_newlines=True, + [sys.executable, inspect.getsourcefile(pep514)], + universal_newlines=True, ) assert "PEP-514 violation in Windows Registry " not in out, out diff -Nru tox-3.15.1/tests/unit/interpreters/windows/test_windows.py tox-3.21.4/tests/unit/interpreters/windows/test_windows.py --- tox-3.15.1/tests/unit/interpreters/windows/test_windows.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tests/unit/interpreters/windows/test_windows.py 2021-02-02 20:28:40.000000000 +0000 @@ -3,8 +3,8 @@ @mark_dont_run_on_posix def test_locate_via_pep514(monkeypatch): - from tox.interpreters.py_spec import CURRENT import tox.interpreters.windows + from tox.interpreters.py_spec import CURRENT del tox.interpreters.windows._PY_AVAILABLE[:] exe = tox.interpreters.windows.locate_via_pep514(CURRENT) diff -Nru tox-3.15.1/tests/unit/package/builder/test_package_builder_isolated.py tox-3.21.4/tests/unit/package/builder/test_package_builder_isolated.py --- tox-3.15.1/tests/unit/package/builder/test_package_builder_isolated.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tests/unit/package/builder/test_package_builder_isolated.py 2021-02-02 20:28:40.000000000 +0000 @@ -138,3 +138,58 @@ build-backend = [] """, ) + + +def test_package_isolated_toml_bad_backend_path(initproj): + """Verify that a non-list 'backend-path' is forbidden.""" + toml_file_check( + initproj, + 6, + "backend-path key at build-system section must be a list, if specified", + """ + [build-system] + requires = [] + build-backend = 'setuptools.build_meta' + backend-path = 42 + """, + ) + + +def test_package_isolated_toml_backend_path_outside_root(initproj): + """Verify that a 'backend-path' outside the project root is forbidden.""" + toml_file_check( + initproj, + 6, + "backend-path must exist in the project root", + """ + [build-system] + requires = [] + build-backend = 'setuptools.build_meta' + backend-path = ['..'] + """, + ) + + +def test_verbose_isolated_build_in_tree(initproj, mock_venv, cmd): + initproj( + "example123-0.5", + filedefs={ + "tox.ini": """ + [tox] + isolated_build = true + """, + "build.py": """ + from setuptools.build_meta import * + """, + "pyproject.toml": """ + [build-system] + requires = ["setuptools >= 35.0.2"] + build-backend = 'build' + backend-path = ['.'] + """, + }, + ) + result = cmd("--sdistonly", "-v", "-v", "-v", "-e", "py") + assert "running sdist" in result.out, result.out + assert "running egg_info" in result.out, result.out + assert "Writing example123-0.5{}setup.cfg".format(os.sep) in result.out, result.out diff -Nru tox-3.15.1/tests/unit/package/test_package_parallel.py tox-3.21.4/tests/unit/package/test_package_parallel.py --- tox-3.15.1/tests/unit/package/test_package_parallel.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tests/unit/package/test_package_parallel.py 2021-02-02 20:28:40.000000000 +0000 @@ -28,9 +28,10 @@ # t1 is artificially blocked to run test command until t2 finishes build # (parallel build package present) # t2 package build finishes both t1 and t2 can now finish and clean up their build packages - import tox.package import threading + import tox.package + t1_build_started = threading.Event() t1_build_blocker = threading.Event() t2_build_started = threading.Event() diff -Nru tox-3.15.1/tests/unit/package/test_package.py tox-3.21.4/tests/unit/package/test_package.py --- tox-3.15.1/tests/unit/package/test_package.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tests/unit/package/test_package.py 2021-02-02 20:28:40.000000000 +0000 @@ -1,4 +1,5 @@ import re +import sys from tox.config import parseconfig from tox.package import get_package @@ -106,6 +107,53 @@ assert sdist_new.stat().size > 10 +def test_build_backend_without_submodule(initproj, cmd): + # The important part of this test is that the build backend + # "inline_backend" is just a base package without a submodule. + # (Regression test for #1344) + initproj( + "magic-0.1", + filedefs={ + "tox.ini": """\ + [tox] + isolated_build = true + [testenv:.package] + basepython = {} + [testenv] + setenv = PYTHONPATH = {{toxinidir}} + """.format( + sys.executable, + ), + "pyproject.toml": """\ + [build-system] + requires = [] + build-backend = "inline_backend" + """, + # To trigger original bug, must be package with __init__.py + "inline_backend": { + "__init__.py": """\ + import sys + def get_requires_for_build_sdist(*args, **kwargs): + return ["pathlib2;python_version<'3.4'"] + + def build_sdist(sdist_directory, config_settings=None): + if sys.version_info[:2] >= (3, 4): + import pathlib + else: + import pathlib2 as pathlib + + (pathlib.Path(sdist_directory) / "magic-0.1.0.tar.gz").touch() + return "magic-0.1.0.tar.gz" + """, + }, + ".gitignore": ".tox", + }, + add_missing_setup_py=False, + ) + result = cmd("--sdistonly", "-e", "py", "-v", "-v") + result.assert_success(is_run_test_env=False) + + def test_package_inject(initproj, cmd, monkeypatch, tmp_path): monkeypatch.delenv(str("PYTHONPATH"), raising=False) initproj( diff -Nru tox-3.15.1/tests/unit/session/test_list_env.py tox-3.21.4/tests/unit/session/test_list_env.py --- tox-3.15.1/tests/unit/session/test_list_env.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tests/unit/session/test_list_env.py 2021-02-02 20:28:40.000000000 +0000 @@ -5,9 +5,9 @@ filedefs={ "tox.ini": """ [tox] - envlist=py36,py27,py34,pypi,docs + envlist=py36,py27,py37,pypi,docs description= py27: run pytest on Python 2.7 - py34: run pytest on Python 3.6 + py37: run pytest on Python 3.6 pypi: publish to PyPI docs: document stuff notincluded: random extra @@ -22,18 +22,18 @@ ) result = cmd("-l") - assert result.outlines == ["py36", "py27", "py34", "pypi", "docs"] + assert result.outlines == ["py36", "py27", "py37", "pypi", "docs"] result = cmd("-l", "-e", "py") - assert result.outlines == ["py36", "py27", "py34", "pypi", "docs"] + assert result.outlines == ["py36", "py27", "py37", "pypi", "docs"] monkeypatch.setenv(str("TOXENV"), str("py")) result = cmd("-l") - assert result.outlines == ["py36", "py27", "py34", "pypi", "docs"] + assert result.outlines == ["py36", "py27", "py37", "pypi", "docs"] monkeypatch.setenv(str("TOXENV"), str("py36")) result = cmd("-l") - assert result.outlines == ["py36", "py27", "py34", "pypi", "docs"] + assert result.outlines == ["py36", "py27", "py37", "pypi", "docs"] def test_listenvs_verbose_description(cmd, initproj): @@ -42,11 +42,11 @@ filedefs={ "tox.ini": """ [tox] - envlist=py36,py27,py34,pypi,docs + envlist=py36,py27,py37,pypi,docs [testenv] description= py36: run pytest on Python 3.6 py27: run pytest on Python 2.7 - py34: run pytest on Python 3.4 + py37: run pytest on Python 3.7 pypi: publish to PyPI docs: document stuff notincluded: random extra @@ -65,7 +65,7 @@ "default environments:", "py36 -> run pytest on Python 3.6", "py27 -> run pytest on Python 2.7", - "py34 -> run pytest on Python 3.4", + "py37 -> run pytest on Python 3.7", "pypi -> publish to PyPI", "docs -> let me overwrite that", ] @@ -78,7 +78,7 @@ filedefs={ "tox.ini": """ [tox] - envlist=py36,py27,py34,pypi,docs + envlist=py36,py27,py37,pypi,docs [testenv:notincluded] changedir = whatever @@ -89,19 +89,19 @@ }, ) result = cmd("-a") - expected = ["py36", "py27", "py34", "pypi", "docs", "notincluded"] + expected = ["py36", "py27", "py37", "pypi", "docs", "notincluded"] assert result.outlines == expected result = cmd("-a", "-e", "py") - assert result.outlines == ["py36", "py27", "py34", "pypi", "docs", "py", "notincluded"] + assert result.outlines == ["py36", "py27", "py37", "pypi", "docs", "py", "notincluded"] monkeypatch.setenv(str("TOXENV"), str("py")) result = cmd("-a") - assert result.outlines == ["py36", "py27", "py34", "pypi", "docs", "py", "notincluded"] + assert result.outlines == ["py36", "py27", "py37", "pypi", "docs", "py", "notincluded"] monkeypatch.setenv(str("TOXENV"), str("py36")) result = cmd("-a") - assert result.outlines == ["py36", "py27", "py34", "pypi", "docs", "notincluded"] + assert result.outlines == ["py36", "py27", "py37", "pypi", "docs", "notincluded"] def test_listenvs_all_verbose_description(cmd, initproj): @@ -160,7 +160,7 @@ filedefs={ "tox.ini": """ [tox] - envlist = py36,py27,py34,pypi,docs + envlist = py36,py27,py37,pypi,docs isolated_build = True [testenv:notincluded] @@ -172,7 +172,7 @@ }, ) result = cmd("-a") - expected = ["py36", "py27", "py34", "pypi", "docs", "notincluded"] + expected = ["py36", "py27", "py37", "pypi", "docs", "notincluded"] assert result.outlines == expected, result.outlines @@ -221,7 +221,8 @@ def test_listenvs_without_default_envs(cmd, initproj): """When running tox -l without any default envirinments, nothing happens.""" initproj( - "logsnada", filedefs={"tox.ini": ""}, + "logsnada", + filedefs={"tox.ini": ""}, ) result = cmd("-l") assert result.ret == 0 diff -Nru tox-3.15.1/tests/unit/session/test_parallel.py tox-3.21.4/tests/unit/session/test_parallel.py --- tox-3.15.1/tests/unit/session/test_parallel.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tests/unit/session/test_parallel.py 2021-02-02 20:28:40.000000000 +0000 @@ -98,7 +98,7 @@ skip_install = true commands=python -c "import sys, os; sys.stderr.write(str(12345) + os.linesep);\ raise SystemExit(17)" - whitelist_externals = {} + allowlist_externals = {} """.format( sys.executable, ), @@ -129,7 +129,7 @@ skipsdist = true [testenv] -whitelist_externals = {} +allowlist_externals = {} commands = python -c '[print("hello world") for _ in range(5000)]' """.format( @@ -148,7 +148,7 @@ skipsdist = true [testenv] -whitelist_externals = {} +allowlist_externals = {} commands = python -c '[print("hello world") for _ in range(1)]' """.format( @@ -177,7 +177,7 @@ skipsdist = true [testenv] -whitelist_externals = {} +allowlist_externals = {} commands = python -c 'import sys; sys.stderr.write("stderr env"); sys.stdout.write("stdout env")' @@ -255,7 +255,8 @@ for env in ("a", "b"): for key in ("setup", "test"): assert key in serial_data["testenvs"][env], json.dumps( - serial_data["testenvs"], indent=2, + serial_data["testenvs"], + indent=2, ) diff -Nru tox-3.15.1/tests/unit/session/test_provision.py tox-3.21.4/tests/unit/session/test_provision.py --- tox-3.15.1/tests/unit/session/test_provision.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tests/unit/session/test_provision.py 2021-02-02 20:28:40.000000000 +0000 @@ -7,7 +7,12 @@ import py import pytest -from pathlib2 import Path + +if sys.version_info[:2] >= (3, 4): + from pathlib import Path +else: + from pathlib2 import Path + from six.moves.urllib.parse import urljoin from six.moves.urllib.request import pathname2url @@ -73,7 +78,7 @@ def test_provision_basepython_local(newconfig, next_tox_major): - """however adhere to basepython when explicilty set""" + """however adhere to basepython when explicitly set""" with pytest.raises(MissingRequirement) as context: newconfig( [], @@ -126,7 +131,9 @@ return result monkeypatch.setattr( - tox.config.ParseIni, "ensure_requires_satisfied", ensure_requires_satisfied, + tox.config.ParseIni, + "ensure_requires_satisfied", + ensure_requires_satisfied, ) prev_get_venv = tox.session.Session.getvenv @@ -203,7 +210,11 @@ def test_provision_non_canonical_dep( - cmd, initproj, monkeypatch, tox_wheel, magic_non_canonical_wheel, + cmd, + initproj, + monkeypatch, + tox_wheel, + magic_non_canonical_wheel, ): initproj( "w-0.1", diff -Nru tox-3.15.1/tests/unit/session/test_session.py tox-3.21.4/tests/unit/session/test_session.py --- tox-3.15.1/tests/unit/session/test_session.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tests/unit/session/test_session.py 2021-02-02 20:28:40.000000000 +0000 @@ -186,7 +186,8 @@ [testenv:{}] commands = python -c "print('ok')" """.format( - "True" if isolated_build else "False", tox_env, + "True" if isolated_build else "False", + tox_env, ), } if isolated_build: @@ -208,8 +209,8 @@ class EnvironmentTestRun(Thread): """we wrap this invocation into a thread to avoid modifying in any way the - current threads environment variable (e.g. on failure of this test incorrect teardown) - """ + current threads environment variable (e.g. on failure of this test incorrect teardown) + """ def run(self): prev_build = tox.session.build_session @@ -354,7 +355,8 @@ ___________________________________ summary ___________________________________{} ERROR: py: commands failed """.format( - pipes.quote(sys.executable), "_" if sys.platform != "win32" else "", + pipes.quote(sys.executable), + "_" if sys.platform != "win32" else "", ), ) have = result.out.replace(os.linesep, "\n") diff -Nru tox-3.15.1/tests/unit/test_pytest_plugins.py tox-3.21.4/tests/unit/test_pytest_plugins.py --- tox-3.15.1/tests/unit/test_pytest_plugins.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tests/unit/test_pytest_plugins.py 2021-02-02 20:28:40.000000000 +0000 @@ -14,7 +14,8 @@ class TestInitProj: @pytest.mark.parametrize( - "kwargs", ({}, {"src_root": None}, {"src_root": ""}, {"src_root": "."}), + "kwargs", + ({}, {"src_root": None}, {"src_root": ""}, {"src_root": "."}), ) def test_no_src_root(self, kwargs, tmpdir, initproj): initproj("black_knight-42", **kwargs) diff -Nru tox-3.15.1/tests/unit/test_result.py tox-3.21.4/tests/unit/test_result.py --- tox-3.15.1/tests/unit/test_result.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tests/unit/test_result.py 2021-02-02 20:28:40.000000000 +0000 @@ -1,3 +1,4 @@ +import functools import os import signal import socket @@ -17,7 +18,13 @@ return pkg -def test_pre_set_header(): +@pytest.fixture() +def clean_hostname_envvar(monkeypatch): + monkeypatch.delenv("HOSTNAME", raising=False) + return functools.partial(monkeypatch.setenv, "HOSTNAME") + + +def test_pre_set_header(clean_hostname_envvar): replog = ResultLog() d = replog.dict assert replog.dict == d @@ -30,7 +37,7 @@ assert replog2.dict == replog.dict -def test_set_header(pkg): +def test_set_header(pkg, clean_hostname_envvar): replog = ResultLog() d = replog.dict assert replog.dict == d @@ -48,6 +55,12 @@ assert replog2.dict == replog.dict +def test_hosname_via_envvar(clean_hostname_envvar): + clean_hostname_envvar("toxicity") + replog = ResultLog() + assert replog.dict["host"] == "toxicity" + + def test_addenv_setpython(pkg): replog = ResultLog() envlog = replog.get_envlog("py36") diff -Nru tox-3.15.1/tests/unit/test_venv.py tox-3.21.4/tests/unit/test_venv.py --- tox-3.15.1/tests/unit/test_venv.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tests/unit/test_venv.py 2021-02-02 20:28:40.000000000 +0000 @@ -3,6 +3,7 @@ import py import pytest +from six import PY2 import tox from tox.interpreters import NoInterpreterInfo @@ -77,22 +78,24 @@ our_sys_path = py.path.local(sys.executable).realpath() assert our_sys_path == py.path.local(args[0]).realpath() # assert Envconfig.toxworkdir in args - assert venv.getcommandpath("easy_install", cwd=py.path.local()) + assert venv.getcommandpath("pip", cwd=py.path.local()) interp = venv._getliveconfig().base_resolved_python_path assert interp == venv.envconfig.python_info.executable assert venv.path_config.check(exists=False) -def test_create_KeyboardInterrupt(mocksession, newconfig, mocker): +@pytest.mark.parametrize("patched_venv_methodname", ["_pcall", "update"]) +def test_create_KeyboardInterrupt(mocksession, newconfig, mocker, patched_venv_methodname): config = newconfig( [], """\ [testenv:py123] + deps = pip >= 19.3.1 """, ) mocksession.new_config(config) venv = mocksession.getvenv("py123") - mocker.patch.object(venv, "_pcall", side_effect=KeyboardInterrupt) + mocker.patch.object(venv, patched_venv_methodname, side_effect=KeyboardInterrupt) with pytest.raises(KeyboardInterrupt): venv.setupenv() @@ -109,10 +112,10 @@ mocksession.new_config(config) venv = mocksession.getvenv("py123") envconfig = venv.envconfig - tmpdir.ensure("easy_install") + tmpdir.ensure("pip") monkeypatch.setenv("PATH", str(tmpdir), prepend=os.pathsep) - envconfig.envbindir.ensure("easy_install") - p = venv.getcommandpath("easy_install") + envconfig.envbindir.ensure("pip") + p = venv.getcommandpath("pip") assert py.path.local(p).relto(envconfig.envbindir), p @@ -450,6 +453,41 @@ assert venv.status == "commands failed" +def test_install_command_allowlisted(newmocksession): + mocksession = newmocksession( + ["--recreate"], + """\ + [testenv] + allowlist_externals = pytest + xy* + commands= + pytest + xyz + """, + ) + venv = mocksession.getvenv("python") + venv.test() + mocksession.report.expect("warning", "*test command found but not*", invert=True) + assert venv.status == "commands failed" + + +def test_install_command_allowlisted_exclusive(newmocksession): + mocksession = newmocksession( + ["--recreate"], + """\ + [testenv] + allowlist_externals = pytest + whitelist_externals = xy* + commands= + pytest + xyz + """, + ) + venv = mocksession.getvenv("python") + with pytest.raises(tox.exception.ConfigError): + venv.test() + + def test_install_command_not_installed_bash(newmocksession): mocksession = newmocksession( ["--recreate"], @@ -735,21 +773,36 @@ assert "PYTHONPATH" not in pcalls[0].env -def test_env_variables_added_to_pcall(tmpdir, mocksession, newconfig, monkeypatch): +def test_env_variables_added_to_pcall(tmpdir, mocksession, newconfig, monkeypatch, tmp_path): monkeypatch.delenv("PYTHONPATH", raising=False) pkg = tmpdir.ensure("package.tar.gz") monkeypatch.setenv("X123", "123") monkeypatch.setenv("YY", "456") + env_path = tmp_path / ".env" + env_file_content = "ENV_FILE_VAR = file_value" + env_path.write_text(env_file_content.decode() if PY2 else env_file_content) + config = newconfig( [], - """\ + r""" + [base] + base_var = base_value + [testenv:python] commands=python -V passenv = x123 setenv = ENV_VAR = value + ESCAPED_VAR = \{value\} + ESCAPED_VAR2 = \\{value\\} + BASE_VAR = {[base]base_var} PYTHONPATH = value - """, + TTY_VAR = {tty:ON_VALUE:OFF_VALUE} + COLON = {:} + REUSED_FILE_VAR = reused {env:ENV_FILE_VAR} + file| %s + """ + % env_path, ) mocksession._clearmocks() mocksession.new_config(config) @@ -764,10 +817,18 @@ assert env is not None assert "ENV_VAR" in env assert env["ENV_VAR"] == "value" + assert env["ESCAPED_VAR"] == "{value}" + assert env["ESCAPED_VAR2"] == r"\{value\}" + assert env["COLON"] == ";" if sys.platform == "win32" else ":" + assert env["TTY_VAR"] == "OFF_VALUE" + assert env["ENV_FILE_VAR"] == "file_value" + assert env["REUSED_FILE_VAR"] == "reused file_value" + assert env["BASE_VAR"] == "base_value" assert env["VIRTUAL_ENV"] == str(venv.path) assert env["X123"] == "123" assert "PYTHONPATH" in env assert env["PYTHONPATH"] == "value" + # all env variables are passed for installation assert pcalls[0].env["YY"] == "456" assert "YY" not in pcalls[1].env @@ -843,20 +904,35 @@ [], """\ [testenv] - install_command=easy_install {opts} {packages} + install_command=cool-installer {opts} {packages} """, ) venv = mocksession.getvenv("python") venv.just_created = True venv.envconfig.envdir.ensure(dir=1) + venv.envconfig.envbindir.ensure("cool-installer") with mocksession.newaction(venv.name, "hello") as action: venv.run_install_command(packages=["whatever"], action=action) pcalls = mocksession._pcalls assert len(pcalls) == 1 - assert "easy_install" in pcalls[0].args[0] + assert "cool-installer" in pcalls[0].args[0] assert pcalls[0].args[1:] == ["whatever"] +def test_run_install_command_handles_KeyboardInterrupt(newmocksession, mocker): + mocksession = newmocksession([], "") + venv = mocksession.getvenv("python") + venv.just_created = True + venv.envconfig.envdir.ensure(dir=1) + + mocker.patch.object(venv, "_pcall", side_effect=KeyboardInterrupt) + with mocksession.newaction(venv.name, "hello") as action: + with pytest.raises(KeyboardInterrupt): + venv.run_install_command(packages=["whatever"], action=action) + + assert venv.status == "keyboardinterrupt" + + def test_command_relative_issue36(newmocksession, tmpdir, monkeypatch): mocksession = newmocksession( [], diff -Nru tox-3.15.1/tests/unit/test_z_cmdline.py tox-3.21.4/tests/unit/test_z_cmdline.py --- tox-3.15.1/tests/unit/test_z_cmdline.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tests/unit/test_z_cmdline.py 2021-02-02 20:28:40.000000000 +0000 @@ -6,7 +6,10 @@ import sys import tempfile -import pathlib2 +if sys.version_info[:2] >= (3, 4): + import pathlib +else: + import pathlib2 as pathlib import py import pytest @@ -142,7 +145,8 @@ result = cmd() assert result.outlines[1] == "ERROR: ConfigError: envdir must not equal toxinidir" assert re.match( - r"ERROR: venv \'python\' in .* would delete project", result.outlines[0], + r"ERROR: venv \'python\' in .* would delete project", + result.outlines[0], ), result.outlines[0] result.assert_fail() @@ -337,7 +341,7 @@ ) result = cmd() result.assert_success() - pattern = re.compile("special&&1 installed: .*pkg123 @ .*-0.7.zip.*") + pattern = re.compile(r"special&&1 installed: .*pkg123( @ .*-|==)0\.7(\.zip)?.*") assert any(pattern.match(line) for line in result.outlines), "\n".join(result.outlines) @@ -461,7 +465,7 @@ }, ) os.remove("setup.py") - pathlib2.Path("pyproject.toml").touch() + pathlib.Path("pyproject.toml").touch() result = cmd() result.assert_fail() assert any( @@ -524,7 +528,9 @@ result = cmd() result.assert_success() assert re.match( - r".*generated\W+xml\W+file.*junit-python\.xml" r".*\W+1\W+passed.*", result.out, re.DOTALL, + r".*generated\W+xml\W+file.*junit-python\.xml" r".*\W+1\W+passed.*", + result.out, + re.DOTALL, ) result = cmd("-epython") result.assert_success() @@ -675,7 +681,9 @@ result = cmd("-v") result.assert_success() assert re.match( - r".*generated\W+xml\W+file.*junit-python\.xml" r".*\W+1\W+passed.*", result.out, re.DOTALL, + r".*generated\W+xml\W+file.*junit-python\.xml" r".*\W+1\W+passed.*", + result.out, + re.DOTALL, ) assert "sdist-make" not in result.out result = cmd("-epython") @@ -706,7 +714,9 @@ result.assert_fail() assert "develop-inst-noop" in result.out assert re.match( - r".*\W+1\W+failed.*" r"summary.*" r"python:\W+commands\W+failed.*", result.out, re.DOTALL, + r".*\W+1\W+failed.*" r"summary.*" r"python:\W+commands\W+failed.*", + result.out, + re.DOTALL, ) # test develop is called if setup.py changes @@ -821,7 +831,7 @@ [testenv] list_dependencies_command=echo commands={envpython} --version - whitelist_externals = echo + allowlist_externals = echo """, }, ) @@ -882,15 +892,18 @@ assert re.search("ERROR:.*InvocationError", result.out) -def test_devenv(initproj, cmd): - initproj( - "example123", - filedefs={ - "setup.py": """\ - from setuptools import setup - setup(name='x') - """, - "tox.ini": """\ +@pytest.mark.parametrize("has_config", [True, False]) +def test_devenv(initproj, cmd, has_config): + filedefs = { + "setup.py": """\ + from setuptools import setup + setup(name='x') + """, + } + if has_config: + filedefs[ + "tox.ini" + ] = """\ [tox] # envlist is ignored for --devenv envlist = foo,bar,baz @@ -898,8 +911,10 @@ [testenv] # --devenv implies --notest commands = python -c "exit(1)" - """, - }, + """ + initproj( + "example123", + filedefs=filedefs, ) result = cmd("--devenv", "venv") result.assert_success() @@ -1068,12 +1083,19 @@ def test_missing_env_fails(initproj, cmd): - initproj("foo", filedefs={"tox.ini": "[testenv:foo]\ncommands={env:VAR}"}) + ini = """ + [testenv:foo] + install_command={env:FOO} + commands={env:VAR} + """ + initproj("foo", filedefs={"tox.ini": ini}) result = cmd() result.assert_fail() assert result.out.endswith( - "foo: unresolvable substitution(s): 'VAR'." - " Environment variables are missing or defined recursively.\n", + "foo: unresolvable substitution(s):\n" + " commands: 'VAR'\n" + " install_command: 'FOO'\n" + "Environment variables are missing or defined recursively.\n", ) @@ -1104,8 +1126,8 @@ @pytest.mark.parametrize("exit_code", [0, 6]) def test_exit_code(initproj, cmd, exit_code, mocker): - """ Check for correct InvocationError, with exit code, - except for zero exit code """ + """Check for correct InvocationError, with exit code, + except for zero exit code""" import tox.exception mocker.spy(tox.exception, "exit_code_str") diff -Nru tox-3.15.1/tests/unit/util/test_spinner.py tox-3.21.4/tests/unit/util/test_spinner.py --- tox-3.15.1/tests/unit/util/test_spinner.py 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tests/unit/util/test_spinner.py 2021-02-02 20:28:40.000000000 +0000 @@ -100,14 +100,17 @@ assert lines == expected -def test_spinner_stdout_not_unicode(capfd, monkeypatch): - monkeypatch.setattr(sys.stdout, "encoding", "ascii") +def test_spinner_stdout_not_unicode(mocker, capfd): + stdout = mocker.patch("tox.util.spinner.sys.stdout") + stdout.encoding = "ascii" with spinner.Spinner(refresh_rate=100) as spin: for _ in range(len(spin.frames)): spin.render_frame() out, err = capfd.readouterr() assert not err - assert all(f in out for f in spin.frames) + assert not out + written = "".join({i[0][0] for i in stdout.write.call_args_list}) + assert all(f in written for f in spin.frames) @pytest.mark.parametrize( diff -Nru tox-3.15.1/tox.ini tox-3.21.4/tox.ini --- tox-3.15.1/tox.ini 2020-05-20 13:44:42.000000000 +0000 +++ tox-3.21.4/tox.ini 2021-02-02 20:28:40.000000000 +0000 @@ -1,6 +1,5 @@ [tox] envlist = py27, - py34, py35, py36, py37, @@ -31,7 +30,7 @@ PYTEST_* PIP_CACHE_DIR deps = - pip >= 20.1 + pip >= 19.3.1 extras = testing commands = pytest \ --cov "{envsitepackagesdir}/tox" \ @@ -151,12 +150,9 @@ network [isort] -multi_line_output = 3 -include_trailing_comma = True -force_grid_wrap = 0 +profile = black line_length = 99 known_first_party = tox,tests -known_third_party = apiclient,docutils,filelock,flaky,freezegun,git,httplib2,oauth2client,packaging,pathlib2,pluggy,py,pytest,setuptools,six,sphinx,toml [testenv:release] description = do a release, required posarg of the version number