diff --git a/.appveyor.yml b/.appveyor.yml index a0ffc795..9f176ee5 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -7,18 +7,13 @@ image: environment: matrix: - TOXENV: check - - TOXENV: 'py27-pytest46-xdist127-coverage55' - - TOXENV: 'py35-pytest46-xdist127-coverage55' - TOXENV: 'py36-pytest46-xdist127-coverage55,py36-pytest46-xdist133-coverage55,py36-pytest54-xdist133-coverage55,py36-pytest62-xdist202-coverage55' - TOXENV: 'py37-pytest46-xdist127-coverage55,py37-pytest46-xdist133-coverage55,py37-pytest54-xdist133-coverage55,py37-pytest62-xdist202-coverage55' - TOXENV: 'py38-pytest46-xdist133-coverage55,py38-pytest54-xdist133-coverage55,py38-pytest62-xdist202-coverage55' - TOXENV: 'py39-pytest62-xdist202-coverage55' - - TOXENV: 'pypy-pytest46-xdist127-coverage55' - TOXENV: 'pypy3-pytest46-xdist127-coverage55,pypy3-pytest46-xdist133-coverage55,pypy3-pytest54-xdist133-coverage55,pypy3-pytest62-xdist202-coverage55' matrix: exclude: - - image: Visual Studio 2019 - TOXENV: 'py27-pytest46-xdist127-coverage55' - image: Visual Studio 2015 TOXENV: 'py36-pytest46-xdist127-coverage55,py36-pytest46-xdist133-coverage55,py36-pytest54-xdist133-coverage55,py36-pytest62-xdist202-coverage55' - image: Visual Studio 2015 @@ -27,15 +22,12 @@ matrix: TOXENV: 'py38-pytest46-xdist133-coverage55,py38-pytest54-xdist133-coverage55,py38-pytest62-xdist202-coverage55' - image: Visual Studio 2015 TOXENV: 'py39-pytest62-xdist202-coverage55' - - image: Visual Studio 2015 - TOXENV: 'pypy-pytest46-xdist127-coverage55' - image: Visual Studio 2015 TOXENV: 'pypy3-pytest46-xdist127-coverage55,pypy3-pytest46-xdist133-coverage55,pypy3-pytest54-xdist133-coverage55,pypy3-pytest62-xdist202-coverage55' init: - ps: echo $env:TOXENV - ps: ls C:\Python* install: - - IF "%TOXENV:~0,5%" == "pypy-" choco install --no-progress python.pypy - IF "%TOXENV:~0,6%" == "pypy3-" choco install --no-progress pypy3 - SET PATH=C:\tools\pypy\pypy;%PATH% - C:\Python37\python -m pip install --progress-bar=off tox -rci/requirements.txt diff --git a/.bumpversion.cfg b/.bumpversion.cfg index a838695a..27a843fb 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.12.1 +current_version = 3.0.0 commit = True tag = True diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml new file mode 100644 index 00000000..dbd62a6c --- /dev/null +++ b/.github/workflows/examples.yml @@ -0,0 +1,49 @@ +name: Examples + +on: [push, pull_request, workflow_dispatch] + +env: + FORCE_COLOR: 1 + +jobs: + examples: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["pypy-3.7", "3.9"] + target: [ + "src-layout", + "adhoc-layout", + ] + include: + # Add new helper variables to existing jobs + - {python-version: "pypy-3.7", tox-python-version: "pypy3"} + - {python-version: "3.9", tox-python-version: "py39"} + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Cache + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: + examples-v1-${{ hashFiles('**/tox.ini') }} + restore-keys: | + examples-v1- + + - name: Install dependencies + run: | + python -m pip install -U pip + python -m pip install -U wheel + python -m pip install --progress-bar=off tox -rci/requirements.txt + + - name: Examples + run: | + cd examples/${{ matrix.target }} + tox -v -e ${{ matrix.tox-python-version }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..001ccfee --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,41 @@ +name: Lint + +on: [push, pull_request, workflow_dispatch] + +env: + FORCE_COLOR: 1 + +jobs: + lint: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + toxenv: ["check", "docs"] + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + + - name: Cache + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: + lint-v1-${{ hashFiles('**/tox.ini') }} + restore-keys: | + lint-v1- + + - name: Install dependencies + run: | + python -m pip install -U pip + python -m pip install -U wheel + python -m pip install --progress-bar=off tox -rci/requirements.txt + + - name: Lint ${{ matrix.toxenv }} + run: | + tox -v -e ${{ matrix.toxenv }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..b9f7c11c --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,81 @@ +name: Test + +on: [push, pull_request, workflow_dispatch] + +env: + FORCE_COLOR: 1 + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["pypy-3.6", "pypy-3.7", "3.6", "3.7", "3.8", "3.9", "3.10-dev"] + tox-extra-versions: [ + "pytest46-xdist127", + "pytest46-xdist133", + "pytest54-xdist133", + "pytest62-xdist202", + ] + include: + # Add new helper variables to existing jobs + - {python-version: "pypy-3.6", tox-python-version: "pypy3"} + - {python-version: "pypy-3.7", tox-python-version: "pypy3"} + - {python-version: "3.6", tox-python-version: "py36"} + - {python-version: "3.7", tox-python-version: "py37"} + - {python-version: "3.8", tox-python-version: "py38"} + - {python-version: "3.9", tox-python-version: "py39"} + - {python-version: "3.10-dev", tox-python-version: "py310"} + exclude: + # Remove some jobs from the matrix + - {tox-extra-versions: "pytest46-xdist127", python-version: "3.8"} + - {tox-extra-versions: "pytest46-xdist127", python-version: "3.9"} + - {tox-extra-versions: "pytest46-xdist133", python-version: "3.9"} + - {tox-extra-versions: "pytest54-xdist133", python-version: "3.9"} + - {tox-extra-versions: "pytest46-xdist127", python-version: "3.10-dev"} + - {tox-extra-versions: "pytest46-xdist133", python-version: "3.10-dev"} + - {tox-extra-versions: "pytest54-xdist133", python-version: "3.10-dev"} + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: Cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: + test-${{ matrix.python-version }}-v1-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + test-${{ matrix.python-version }}-v1- + + - name: Install dependencies + run: | + python -m pip install -U pip + python -m pip install -U wheel + python -m pip install --progress-bar=off tox -rci/requirements.txt + virtualenv --version + pip --version + tox --version + + - name: Tox tests + run: | + tox -v -e ${{ matrix.tox-python-version }}-${{ matrix.tox-extra-versions }}-coverage55 + + allgood: + needs: test + runs-on: ubuntu-latest + name: Test successful + steps: + - name: Success + run: echo Test successful diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f9942328..dc36fa2b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,19 +2,24 @@ # pre-commit install # To update the pre-commit hooks run: # pre-commit install-hooks -exclude: '^(src/.*\.pth|\.tox|ci/templates|\.bumpversion\.cfg)(/|$)' repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: master + rev: v4.0.1 hooks: - id: trailing-whitespace - id: end-of-file-fixer + exclude: '.*\.pth$' - id: debug-statements - - repo: https://github.com/timothycrosley/isort - rev: master + - repo: https://github.com/PyCQA/isort + rev: 5.9.3 hooks: - id: isort - - repo: https://gitlab.com/pycqa/flake8 - rev: master + - repo: https://github.com/PyCQA/flake8 + rev: 3.9.2 hooks: - id: flake8 + - repo: https://github.com/asottile/pyupgrade + rev: v2.23.3 + hooks: + - id: pyupgrade + args: [--py36-plus] diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a9cafa4e..00000000 --- a/.travis.yml +++ /dev/null @@ -1,89 +0,0 @@ -# NOTE: this file is auto-generated via ci/bootstrap.py (ci/templates/.travis.yml). -dist: xenial -language: python -cache: false -env: - global: - - LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so - - SEGFAULT_SIGNALS=all -stages: - - lint - - examples - - tests -jobs: - fast_finish: true - allow_failures: - - python: '3.8' - include: - - stage: lint - env: TOXENV=check - - env: TOXENV=docs - - - stage: tests - env: TOXENV=py27-pytest46-xdist127-coverage55 - python: '2.7' - - env: TOXENV=py35-pytest46-xdist127-coverage55 - python: '3.5' - - env: TOXENV=py36-pytest46-xdist127-coverage55 - python: '3.6' - - env: TOXENV=py37-pytest46-xdist127-coverage55 - python: '3.7' - - env: TOXENV=pypy-pytest46-xdist127-coverage55 - python: 'pypy' - - env: TOXENV=pypy3-pytest46-xdist127-coverage55 - python: 'pypy3' - - env: TOXENV=py36-pytest46-xdist133-coverage55 - python: '3.6' - - env: TOXENV=py36-pytest54-xdist133-coverage55 - python: '3.6' - - env: TOXENV=py37-pytest46-xdist133-coverage55 - python: '3.7' - - env: TOXENV=py37-pytest54-xdist133-coverage55 - python: '3.7' - - env: TOXENV=py38-pytest46-xdist133-coverage55 - python: '3.8' - - env: TOXENV=py38-pytest54-xdist133-coverage55 - python: '3.8' - - env: TOXENV=pypy3-pytest46-xdist133-coverage55 - python: 'pypy3' - - env: TOXENV=pypy3-pytest54-xdist133-coverage55 - python: 'pypy3' - - env: TOXENV=py36-pytest62-xdist202-coverage55 - python: '3.6' - - env: TOXENV=py37-pytest62-xdist202-coverage55 - python: '3.7' - - env: TOXENV=py38-pytest62-xdist202-coverage55 - python: '3.8' - - env: TOXENV=py39-pytest62-xdist202-coverage55 - python: '3.9' - - env: TOXENV=pypy3-pytest62-xdist202-coverage55 - python: 'pypy3' - - - stage: examples - python: '3.8' - script: cd $TARGET; tox -v - env: - - TARGET=examples/src-layout - - python: '3.8' - script: cd $TARGET; tox -v - env: - - TARGET=examples/adhoc-layout -before_install: - - python --version - - uname -a - - lsb_release -a -install: - - python -mpip install --progress-bar=off tox -rci/requirements.txt - - virtualenv --version - - easy_install --version - - pip --version - - tox --version -script: - - tox -v -after_failure: - - more .tox/log/* | cat - - more .tox/*/log/* | cat -notifications: - email: - on_success: never - on_failure: always diff --git a/AUTHORS.rst b/AUTHORS.rst index 14cb7781..6aa916a1 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -48,3 +48,5 @@ Authors * Chris Sreesangkom - https://github.com/csreesan * Sorin Sbarnea - https://github.com/ssbarnea * Brian Rutledge - https://github.com/bhrutledge +* Danilo Šegan - https://github.com/dsegan +* Michał Bielawski - https://github.com/D3X diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ef5ad741..e6e47d95 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,41 @@ Changelog ========= + +3.0.0 (2021-10-04) +------------------- + +**Note that this release drops support for Python 2.7 and Python 3.5.** + +* Added support for Python 3.10 and updated various test dependencies. + Contributed by Hugo van Kemenade in + `#500 `_. +* Switched from Travis CI to GitHub Actions. Contributed by Hugo van Kemenade in + `#494 `_ and + `#495 `_. +* Add a ``--cov-reset`` CLI option. + Contributed by Danilo Šegan in + `#459 `_. +* Improved validation of ``--cov-fail-under`` CLI option. + Contributed by ... Ronny Pfannschmidt's desire for skark in + `#480 `_. +* Dropped Python 2.7 support. + Contributed by Thomas Grainger in + `#488 `_. +* Updated trove classifiers. Contributed by Michał Bielawski in + `#481 `_. + + +2.13.0 (2021-06-01) +------------------- + +* Changed the `toml` requirement to be always be directly required (instead of being required through a coverage extra). + This fixes issues with pip-compile (`pip-tools#1300 `_). + Contributed by Sorin Sbarnea in `#472 `_. +* Documented ``show_contexts``. + Contributed by Brian Rutledge in `#473 `_. + + 2.12.1 (2021-06-01) ------------------- diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index f02562eb..e742be47 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -73,8 +73,8 @@ For merging, you should: 3. Add a note to ``CHANGELOG.rst`` about the changes. 4. Add yourself to ``AUTHORS.rst``. -.. [1] If you don't have all the necessary python versions available locally you can rely on Travis - it will - `run the tests `_ +.. [1] If you don't have all the necessary Python versions available locally you can rely on GitHub Actions - it will + `run the tests `_ for each change you add in the pull request. It will be slower though ... diff --git a/MANIFEST.in b/MANIFEST.in index 6db46114..af1581be 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,6 +6,7 @@ prune examples/*/*/htmlcov prune examples/adhoc-layout/*.egg-info prune examples/src-layout/src/*.egg-info +graft .github/workflows graft src graft ci graft tests @@ -21,6 +22,6 @@ include CONTRIBUTING.rst include LICENSE include README.rst -include tox.ini .travis.yml .appveyor.yml .readthedocs.yml .pre-commit-config.yaml +include tox.ini .appveyor.yml .readthedocs.yml .pre-commit-config.yaml global-exclude *.py[cod] __pycache__/* *.so *.dylib .coverage .coverage.* diff --git a/README.rst b/README.rst index 149594fd..508aff84 100644 --- a/README.rst +++ b/README.rst @@ -10,7 +10,7 @@ Overview * - docs - |docs| * - tests - - | |travis| |appveyor| |requires| + - | |github-actions| |appveyor| |requires| * - package - | |version| |conda-forge| |wheel| |supported-versions| |supported-implementations| | |commits-since| @@ -19,9 +19,9 @@ Overview :target: https://readthedocs.org/projects/pytest-cov :alt: Documentation Status -.. |travis| image:: https://api.travis-ci.com/pytest-dev/pytest-cov.svg?branch=master - :alt: Travis-CI Build Status - :target: https://travis-ci.com/github/pytest-dev/pytest-cov +.. |github-actions| image:: https://github.com/pytest-dev/pytest-cov/actions/workflows/test.yml/badge.svg + :alt: GitHub Actions Status + :target: https://github.com/pytest-dev/pytest-cov/actions .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/pytest-dev/pytest-cov?branch=master&svg=true :alt: AppVeyor Build Status @@ -38,9 +38,9 @@ Overview .. |conda-forge| image:: https://img.shields.io/conda/vn/conda-forge/pytest-cov.svg :target: https://anaconda.org/conda-forge/pytest-cov -.. |commits-since| image:: https://img.shields.io/github/commits-since/pytest-dev/pytest-cov/v2.12.1.svg +.. |commits-since| image:: https://img.shields.io/github/commits-since/pytest-dev/pytest-cov/v3.0.0.svg :alt: Commits since latest release - :target: https://github.com/pytest-dev/pytest-cov/compare/v2.12.1...master + :target: https://github.com/pytest-dev/pytest-cov/compare/v3.0.0...master .. |wheel| image:: https://img.shields.io/pypi/wheel/pytest-cov.svg :alt: PyPI Wheel diff --git a/ci/bootstrap.py b/ci/bootstrap.py index 61747a15..77daad5b 100755 --- a/ci/bootstrap.py +++ b/ci/bootstrap.py @@ -1,8 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import print_function -from __future__ import unicode_literals import os import subprocess @@ -30,7 +26,7 @@ def exec_in_env(): if not exists(env_path): import subprocess - print("Making bootstrap env in: {0} ...".format(env_path)) + print(f"Making bootstrap env in: {env_path} ...") try: check_call([sys.executable, "-m", "venv", env_path]) except subprocess.CalledProcessError: @@ -44,7 +40,7 @@ def exec_in_env(): if not os.path.exists(python_executable): python_executable += '.exe' - print("Re-executing with: {0}".format(python_executable)) + print(f"Re-executing with: {python_executable}") print("+ exec", python_executable, __file__, "--no-env") os.execv(python_executable, [python_executable, __file__, "--no-env"]) @@ -52,7 +48,7 @@ def exec_in_env(): def main(): import jinja2 - print("Project path: {0}".format(base_path)) + print(f"Project path: {base_path}") jinja = jinja2.Environment( loader=jinja2.FileSystemLoader(join(base_path, "ci", "templates")), @@ -78,7 +74,7 @@ def main(): with open(join(base_path, name), "w") as fh: fh.write('# NOTE: this file is auto-generated via ci/bootstrap.py (ci/templates/%s).\n' % name) fh.write(jinja.get_template(name).render(**template_vars)) - print("Wrote {}".format(name)) + print(f"Wrote {name}") print("DONE.") @@ -89,5 +85,5 @@ def main(): elif not args: exec_in_env() else: - print("Unexpected arguments {0}".format(args), file=sys.stderr) + print(f"Unexpected arguments {args}", file=sys.stderr) sys.exit(1) diff --git a/ci/templates/.travis.yml b/ci/templates/.travis.yml deleted file mode 100644 index 8c7e3b93..00000000 --- a/ci/templates/.travis.yml +++ /dev/null @@ -1,61 +0,0 @@ -dist: xenial -language: python -cache: false -env: - global: - - LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so - - SEGFAULT_SIGNALS=all -stages: - - lint - - examples - - tests -jobs: - fast_finish: true - allow_failures: - - python: '3.8' - include: - - stage: lint - env: TOXENV=check - - env: TOXENV=docs - - - stage: tests -{% for env in tox_environments %} - {%+ if not loop.first %}- {% else %} {% endif -%} - env: TOXENV={{ env }} - {% if env.startswith("pypy-") %} - python: 'pypy' - {% elif env.startswith("pypy3-") %} - python: 'pypy3' - {% else %} - python: '{{ "{0[2]}.{0[3]}".format(env) }}' - {% endif -%} -{% endfor %} - - - stage: examples -{%- for example in ['src', 'adhoc'] %}{{ '' }} - {%+ if not loop.first %}- {% else %} {% endif -%} - python: '3.8' - script: cd $TARGET; tox -v - env: - - TARGET=examples/{{ example }}-layout -{%- endfor %} - -before_install: - - python --version - - uname -a - - lsb_release -a -install: - - python -mpip install --progress-bar=off tox -rci/requirements.txt - - virtualenv --version - - easy_install --version - - pip --version - - tox --version -script: - - tox -v -after_failure: - - more .tox/log/* | cat - - more .tox/*/log/* | cat -notifications: - email: - on_success: never - on_failure: always diff --git a/docs/conf.py b/docs/conf.py index 0668fdc2..5dd4e74b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import os import sphinx_py3doc_enhanced_theme @@ -25,8 +22,8 @@ project = 'pytest-cov' year = '2016' author = 'pytest-cov contributors' -copyright = '{}, {}'.format(year, author) -version = release = '2.12.1' +copyright = f'{year}, {author}' +version = release = '3.0.0' pygments_style = 'trac' templates_path = ['.'] @@ -47,7 +44,7 @@ html_sidebars = { '**': ['searchbox.html', 'globaltoc.html', 'sourcelink.html'], } -html_short_title = '%s-%s' % (project, version) +html_short_title = f'{project}-{version}' napoleon_use_ivar = True napoleon_use_rtype = False diff --git a/docs/config.rst b/docs/config.rst index 0dfb17a0..0ead66e8 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -23,13 +23,13 @@ For full details refer to the `coverage config file`_ documentation. .. note:: Important Note - This plugin overrides the ``data_file`` and ``parallel`` options of coverage. Unless you also run coverage without + This plugin overrides the ``data_file`` and ``parallel`` options of coverage. Unless you also run coverage without pytest-cov it's pointless to set those options in your ``.coveragerc``. - - If you use the ``--cov=something`` option (with a value) then coverage's ``source`` option will also get overriden. - If you have multiple sources it might be easier to set those in ``.coveragerc`` and always use ``--cov`` (wihout a value) + + If you use the ``--cov=something`` option (with a value) then coverage's ``source`` option will also get overriden. + If you have multiple sources it might be easier to set those in ``.coveragerc`` and always use ``--cov`` (wihout a value) instead of having a long command line with ``--cov=pkg1 --cov=pkg2 --cov=pkg3 ...``. - + If you use the ``--cov-branch`` option then coverage's ``branch`` option will also get overriden. If you wish to always add pytest-cov with pytest, you can use ``addopts`` under ``pytest`` or ``tool:pytest`` section. @@ -66,6 +66,8 @@ The complete list of command line options is: False --no-cov Disable coverage report completely (useful for debuggers). Default: False + --cov-reset Reset cov sources accumulated in options so far. + Mostly useful for scripts and configuration files. --cov-fail-under=MIN Fail if the total coverage is less than MIN. --cov-append Do not delete coverage but append to current. Default: False diff --git a/docs/contexts.rst b/docs/contexts.rst index c502c1d6..cde920d5 100644 --- a/docs/contexts.rst +++ b/docs/contexts.rst @@ -6,7 +6,7 @@ Coverage.py 5.0 can record separate coverage data for `different contexts`_ duri one run of a test suite. Pytest-cov can use this feature to record coverage data for each test individually, with the ``--cov-context=test`` option. -.. _`different_contexts`: https://coverage.readthedocs.io/en/stable/contexts.html +.. _different contexts: https://coverage.readthedocs.io/en/stable/contexts.html The context name recorded in the coverage.py database is the pytest test id, and the phase of execution, one of "setup", "run", or "teardown". These two @@ -22,8 +22,8 @@ id, and each set of parameter values is recorded as a separate test. To view contexts when using ``--cov-report=html``, add this to your ``.coveragerc``:: [html] - show_contexts = True - + show_contexts = True + The HTML report will include an annotation on each covered line, indicating the -number of contexts that executed the line. Clicking the annotation displays a +number of contexts that executed the line. Clicking the annotation displays a list of the contexts. diff --git a/docs/releasing.rst b/docs/releasing.rst index 245dca54..ae78228d 100644 --- a/docs/releasing.rst +++ b/docs/releasing.rst @@ -16,7 +16,7 @@ The process for releasing should follow these steps: git push git push --tags #. Wait for `AppVeyor `_ - and `Travis `_ to give the green builds. + and `GitHub Actions `_ to give the green builds. #. Check that the docs on `ReadTheDocs `_ are built. #. Make sure you have a clean checkout, run ``git status`` to verify. #. Manually clean temporary files (that are ignored and won't show up in ``git status``):: diff --git a/docs/reporting.rst b/docs/reporting.rst index e9e4b06b..eaa99ad3 100644 --- a/docs/reporting.rst +++ b/docs/reporting.rst @@ -71,4 +71,4 @@ The final report option can also suppress printing to the terminal:: This mode can be especially useful on continuous integration servers, where a coverage file is needed for subsequent processing, but no local report needs to be viewed. For example, -tests run on Travis-CI could produce a .coverage file for use with Coveralls. +tests run on GitHub Actions could produce a .coverage file for use with Coveralls. diff --git a/examples/adhoc-layout/example/__init__.py b/examples/adhoc-layout/example/__init__.py index 18080ac5..684905a1 100644 --- a/examples/adhoc-layout/example/__init__.py +++ b/examples/adhoc-layout/example/__init__.py @@ -1,12 +1,10 @@ +import platform -import sys - -PY2 = sys.version_info[0] == 2 - - -if PY2: +# test merging multiple tox runs with a platform +# based branch +if platform.python_implementation() == "PyPy": def add(a, b): - return b + a + return a + b else: def add(a, b): diff --git a/examples/adhoc-layout/tox.ini b/examples/adhoc-layout/tox.ini index 6e299f24..e855c315 100644 --- a/examples/adhoc-layout/tox.ini +++ b/examples/adhoc-layout/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,py38,report +envlist = pypy3,py39,report [tool:pytest] addopts = @@ -7,7 +7,7 @@ addopts = [testenv] setenv = - py{27,38}: COVERAGE_FILE = .coverage.{envname} + py{py3,39}: COVERAGE_FILE = .coverage.{envname} commands = pytest --cov --cov-config={toxinidir}/.coveragerc {posargs:-vv} deps = pytest @@ -19,7 +19,7 @@ deps = ../.. depends = - report: py27,py38 + report: pypy3,py39 # note that this is necessary to prevent the tests importing the code from your badly laid project changedir = tests diff --git a/examples/src-layout/src/example/__init__.py b/examples/src-layout/src/example/__init__.py index 18080ac5..684905a1 100644 --- a/examples/src-layout/src/example/__init__.py +++ b/examples/src-layout/src/example/__init__.py @@ -1,12 +1,10 @@ +import platform -import sys - -PY2 = sys.version_info[0] == 2 - - -if PY2: +# test merging multiple tox runs with a platform +# based branch +if platform.python_implementation() == "PyPy": def add(a, b): - return b + a + return a + b else: def add(a, b): diff --git a/examples/src-layout/tox.ini b/examples/src-layout/tox.ini index 6be8e73d..94b72730 100644 --- a/examples/src-layout/tox.ini +++ b/examples/src-layout/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,py38,report +envlist = pypy3,py39,report [tool:pytest] testpaths = tests @@ -8,7 +8,7 @@ addopts = [testenv] setenv = - py{27,38}: COVERAGE_FILE = .coverage.{envname} + py{py3,39}: COVERAGE_FILE = .coverage.{envname} commands = pytest --cov {posargs:-vv} deps = pytest @@ -20,7 +20,7 @@ deps = ../.. depends = - report: py27,py38 + report: pypy3,py39 [testenv:report] skip_install = true diff --git a/setup.cfg b/setup.cfg index d6c36d12..360a416d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,3 @@ -[bdist_wheel] -universal = 1 - [flake8] max-line-length = 140 exclude = .tox,.eggs,ci/templates,build,dist diff --git a/setup.py b/setup.py index 143746ab..503b9aac 100755 --- a/setup.py +++ b/setup.py @@ -1,9 +1,5 @@ #!/usr/bin/env python -# -*- encoding: utf-8 -*- -from __future__ import absolute_import -from __future__ import print_function -import io import re from distutils.command.build import build from glob import glob @@ -22,7 +18,7 @@ def read(*names, **kwargs): - with io.open( + with open( join(dirname(__file__), *names), encoding=kwargs.get('encoding', 'utf8') ) as fh: @@ -85,10 +81,10 @@ def run(self): setup( name='pytest-cov', - version='2.12.1', + version='3.0.0', license='MIT', description='Pytest plugin for measuring coverage.', - long_description='%s\n%s' % (read('README.rst'), re.sub(':[a-z]+:`~?(.*?)`', r'``\1``', read('CHANGELOG.rst'))), + long_description='{}\n{}'.format(read('README.rst'), re.sub(':[a-z]+:`~?(.*?)`', r'``\1``', read('CHANGELOG.rst'))), author='Marc Schlaich', author_email='marc.schlaich@gmail.com', url='https://github.com/pytest-dev/pytest-cov', @@ -107,13 +103,13 @@ def run(self): 'Operating System :: POSIX', 'Operating System :: Unix', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Testing', @@ -124,10 +120,9 @@ def run(self): ], install_requires=[ 'pytest>=4.6', - 'coverage>=5.2.1', - 'toml' + 'coverage[toml]>=5.2.1' ], - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', + python_requires='>=3.6', extras_require={ 'testing': [ 'fields', diff --git a/src/pytest_cov/__init__.py b/src/pytest_cov/__init__.py index dad3b766..fba1d219 100644 --- a/src/pytest_cov/__init__.py +++ b/src/pytest_cov/__init__.py @@ -1,2 +1,2 @@ """pytest-cov: avoid already-imported warning: PYTEST_DONT_REWRITE.""" -__version__ = '2.12.1' +__version__ = '3.0.0' diff --git a/src/pytest_cov/compat.py b/src/pytest_cov/compat.py index 5b4a0bfb..f422f25c 100644 --- a/src/pytest_cov/compat.py +++ b/src/pytest_cov/compat.py @@ -14,7 +14,7 @@ hookwrapper = pytest.mark.hookwrapper -class SessionWrapper(object): +class SessionWrapper: def __init__(self, session): self._session = session if hasattr(session, 'testsfailed'): diff --git a/src/pytest_cov/engine.py b/src/pytest_cov/engine.py index 084e92ea..0303c2f1 100644 --- a/src/pytest_cov/engine.py +++ b/src/pytest_cov/engine.py @@ -14,7 +14,7 @@ from .embed import cleanup -class _NullFile(object): +class _NullFile: @staticmethod def write(v): pass @@ -49,7 +49,7 @@ def ensure_topdir_wrapper(self, *args, **kwargs): return ensure_topdir_wrapper -class CovController(object): +class CovController: """Base class for different plugin implementations.""" def __init__(self, cov_source, cov_report, cov_config, cov_append, cov_branch, config=None, nodeid=None): @@ -116,7 +116,7 @@ def unset_env(): def get_node_desc(platform, version_info): """Return a description of this node.""" - return 'platform %s, python %s' % (platform, '%s.%s.%s-%s-%s' % version_info[:5]) + return 'platform {}, python {}'.format(platform, '%s.%s.%s-%s-%s' % version_info[:5]) @staticmethod def sep(stream, s, txt): @@ -126,7 +126,7 @@ def sep(stream, s, txt): sep_total = max((70 - 2 - len(txt)), 2) sep_len = sep_total // 2 sep_extra = sep_total % 2 - out = '%s %s %s\n' % (s * sep_len, txt, s * (sep_len + sep_extra)) + out = f'{s * sep_len} {txt} {s * (sep_len + sep_extra)}\n' stream.write(out) @_ensure_topdir diff --git a/src/pytest_cov/plugin.py b/src/pytest_cov/plugin.py index b875f409..94a1e494 100644 --- a/src/pytest_cov/plugin.py +++ b/src/pytest_cov/plugin.py @@ -14,6 +14,20 @@ class CoverageError(Exception): """Indicates that our coverage is too low""" +class PytestCovWarning(pytest.PytestWarning): + """ + The base for all pytest-cov warnings, never raised directly + """ + + +class CovDisabledWarning(PytestCovWarning): + """Indicates that Coverage was manually disabled""" + + +class CovReportWarning(PytestCovWarning): + """Indicates that we failed to generate a report""" + + def validate_report(arg): file_choices = ['annotate', 'html', 'xml'] term_choices = ['term', 'term-missing'] @@ -22,7 +36,7 @@ def validate_report(arg): values = arg.split(":", 1) report_type = values[0] if report_type not in all_choices + ['']: - msg = 'invalid choice: "{}" (choose from "{}")'.format(arg, all_choices) + msg = f'invalid choice: "{arg}" (choose from "{all_choices}")' raise argparse.ArgumentTypeError(msg) if len(values) == 1: @@ -42,16 +56,23 @@ def validate_report(arg): def validate_fail_under(num_str): try: - return int(num_str) + value = int(num_str) except ValueError: - return float(num_str) + try: + value = float(num_str) + except ValueError: + raise argparse.ArgumentTypeError('An integer or float value is required.') + if value > 100: + raise argparse.ArgumentTypeError('Your desire for over-achievement is admirable but misplaced. ' + 'The maximum value is 100. Perhaps write more integration tests?') + return value def validate_context(arg): if coverage.version_info <= (5, 0): raise argparse.ArgumentTypeError('Contexts are only supported with coverage.py >= 5.x') if arg != "test": - raise argparse.ArgumentTypeError('--cov-context=test is the only supported value') + raise argparse.ArgumentTypeError('The only supported value is "test".') return arg @@ -70,6 +91,8 @@ def pytest_addoption(parser): nargs='?', const=True, dest='cov_source', help='Path or package name to measure during execution (multi-allowed). ' 'Use --cov= to not do any source filtering and record everything.') + group.addoption('--cov-reset', action='store_const', const=[], dest='cov_source', + help='Reset cov sources accumulated in options so far. ') group.addoption('--cov-report', action=StoreReport, default={}, metavar='TYPE', type=validate_report, help='Type of report to generate: term, term-missing, ' @@ -127,7 +150,7 @@ def pytest_load_initial_conftests(early_config, parser, args): early_config.pluginmanager.register(plugin, '_cov') -class CovPlugin(object): +class CovPlugin: """Use coverage package to produce code coverage reports. Delegates all work to a particular implementation based on whether @@ -182,7 +205,7 @@ def start(self, controller_cls, config=None, nodeid=None): if config is None: # fake config option for engine - class Config(object): + class Config: option = self.options config = Config() @@ -282,7 +305,7 @@ def pytest_runtestloop(self, session): message = 'Failed to generate report: %s\n' % exc session.config.pluginmanager.getplugin("terminalreporter").write( 'WARNING: %s\n' % message, red=True, bold=True) - warnings.warn(pytest.PytestWarning(message)) + warnings.warn(CovReportWarning(message)) self.cov_total = 0 assert self.cov_total is not None, 'Test coverage should never be `None`' if self._failed_cov_total(): @@ -294,7 +317,7 @@ def pytest_terminal_summary(self, terminalreporter): if self.options.no_cov_should_warn: message = 'Coverage disabled via --no-cov switch!' terminalreporter.write('WARNING: %s\n' % message, red=True, bold=True) - warnings.warn(pytest.PytestWarning(message)) + warnings.warn(CovDisabledWarning(message)) return if self.cov_controller is None: return @@ -340,7 +363,7 @@ def pytest_runtest_call(self, item): yield -class TestContextPlugin(object): +class TestContextPlugin: def __init__(self, cov): self.cov = cov @@ -354,7 +377,7 @@ def pytest_runtest_call(self, item): self.switch_context(item, 'run') def switch_context(self, item, when): - context = "{item.nodeid}|{when}".format(item=item, when=when) + context = f"{item.nodeid}|{when}" self.cov.switch_context(context) os.environ['COV_CORE_CONTEXT'] = context diff --git a/tests/test_pytest_cov.py b/tests/test_pytest_cov.py index 0d1b5a23..e7f90668 100644 --- a/tests/test_pytest_cov.py +++ b/tests/test_pytest_cov.py @@ -16,7 +16,6 @@ from process_tests import TestProcess as _TestProcess from process_tests import dump_on_error from process_tests import wait_for_strings -from six import exec_ import pytest_cov.plugin @@ -780,13 +779,13 @@ def test_dist_not_collocated_coveragerc_source(testdir, prop): dir2 = testdir.mkdir('dir2') testdir.tmpdir.join('.coveragerc').write(''' [run] -%s -source = %s +{} +source = {} [paths] source = . dir1 - dir2''' % (prop.conf, script.dirpath())) + dir2'''.format(prop.conf, script.dirpath())) result = testdir.runpytest('-v', '--cov', @@ -1964,7 +1963,7 @@ def bad_init(): monkeypatch.setattr(embed, 'init', bad_init) monkeypatch.setattr(sys, 'stderr', buff) monkeypatch.setitem(os.environ, 'COV_CORE_SOURCE', 'foobar') - exec_(payload) + exec(payload) assert buff.getvalue() == '''pytest-cov: Failed to setup subprocess coverage. Environ: {'COV_CORE_SOURCE': 'foobar'} Exception: SpecificError() ''' @@ -1999,6 +1998,33 @@ def test_double_cov2(testdir): assert result.ret == 0 +def test_cov_reset(testdir): + script = testdir.makepyfile(SCRIPT_SIMPLE) + result = testdir.runpytest('-v', + '--assert=plain', + '--cov=%s' % script.dirpath(), + '--cov-reset', + script) + + assert 'coverage: platform' not in result.stdout.str() + + +def test_cov_reset_then_set(testdir): + script = testdir.makepyfile(SCRIPT_SIMPLE) + result = testdir.runpytest('-v', + '--assert=plain', + '--cov=%s' % script.dirpath(), + '--cov-reset', + '--cov=%s' % script.dirpath(), + script) + + result.stdout.fnmatch_lines([ + '*- coverage: platform *, python * -*', + 'test_cov_reset_then_set* %s*' % SCRIPT_SIMPLE_RESULT, + '*1 passed*' + ]) + + @pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_cov_and_no_cov(testdir): script = testdir.makepyfile(SCRIPT_SIMPLE) @@ -2087,7 +2113,7 @@ def test_contexts(testdir, opts): continue data.set_query_context(context) actual = set(data.lines(test_context_path)) - assert line_data[label] == actual, "Wrong lines for context {!r}".format(context) + assert line_data[label] == actual, f"Wrong lines for context {context!r}" @pytest.mark.skipif("coverage.version_info >= (5, 0)") diff --git a/tox.ini b/tox.ini index 6be986e9..20a1561b 100644 --- a/tox.ini +++ b/tox.ini @@ -13,9 +13,9 @@ passenv = [tox] envlist = check - py{27,35,36,37,py,py3}-pytest46-xdist127-coverage{55} + py{36,37,py,py3}-pytest46-xdist127-coverage{55} py{36,37,38,py3}-pytest{46,54}-xdist133-coverage{55} - py{36,37,38,39,py3}-pytest{62}-xdist202-coverage{55} + py{36,37,38,39,310,py3}-pytest{62}-xdist202-coverage{55} docs [testenv] @@ -29,7 +29,7 @@ setenv = pytest54: _DEP_PYTEST=pytest==5.4.3 pytest60: _DEP_PYTEST=pytest==6.0.2 pytest61: _DEP_PYTEST=pytest==6.1.2 - pytest62: _DEP_PYTEST=pytest==6.2.2 + pytest62: _DEP_PYTEST=pytest==6.2.5 xdist127: _DEP_PYTESTXDIST=pytest-xdist==1.27.0 xdist129: _DEP_PYTESTXDIST=pytest-xdist==1.29.0