--- /dev/null
+# NOTE: this file is auto-generated via ci/bootstrap.py (ci/templates/.appveyor.yml).
+version: '{branch}-{build}'
+build: off
+environment:
+ matrix:
+ - TOXENV: check
+ - TOXENV: 'py27-pytest46-xdist27-coverage45,py27-pytest46-xdist27-coverage52'
+ - TOXENV: 'py35-pytest46-xdist27-coverage45,py35-pytest46-xdist27-coverage52'
+ - TOXENV: 'py36-pytest46-xdist27-coverage45,py36-pytest46-xdist27-coverage52,py36-pytest46-xdist33-coverage45,py36-pytest46-xdist33-coverage52,py36-pytest54-xdist33-coverage45,py36-pytest54-xdist33-coverage52,py36-pytest60-xdist200-coverage52'
+ - TOXENV: 'py37-pytest46-xdist27-coverage45,py37-pytest46-xdist27-coverage52,py37-pytest46-xdist33-coverage45,py37-pytest46-xdist33-coverage52,py37-pytest54-xdist33-coverage45,py37-pytest54-xdist33-coverage52,py37-pytest60-xdist200-coverage52'
+ - TOXENV: 'pypy-pytest46-xdist27-coverage45,pypy-pytest46-xdist27-coverage52'
+
+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
+
+test_script:
+ - cmd /E:ON /V:ON /C .\ci\appveyor-with-compiler.cmd C:\Python37\python -m tox
+
+on_failure:
+ - ps: dir "env:"
+ - ps: get-content .tox\*\log\*
+artifacts:
+ - path: dist\*
+
+### To enable remote debugging uncomment this (also, see: http://www.appveyor.com/docs/how-to/rdp-to-build-worker):
+# on_finish:
+# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
--- /dev/null
+[bumpversion]
+current_version = 2.10.1
+commit = True
+tag = True
+
+[bumpversion:file:setup.py]
+search = version='{current_version}'
+replace = version='{new_version}'
+
+[bumpversion:file:README.rst]
+search = v{current_version}.
+replace = v{new_version}.
+
+[bumpversion:file:docs/conf.py]
+search = version = release = '{current_version}'
+replace = version = release = '{new_version}'
+
+[bumpversion:file:src/pytest_cov/__init__.py]
+search = __version__ = '{current_version}'
+replace = __version__ = '{new_version}'
+
--- /dev/null
+# Generated by cookiepatcher, a small shim around cookiecutter (pip install cookiepatcher)
+
+cookiecutter:
+ _extensions:
+ - jinja2_time.TimeExtension
+ _template: /home/ionel/open-source/cookiecutter-pylibrary
+ allow_tests_inside_package: no
+ appveyor: yes
+ c_extension_function: '-'
+ c_extension_module: '-'
+ c_extension_optional: no
+ c_extension_support: no
+ c_extension_test_pypi: no
+ c_extension_test_pypi_username: '-'
+ codacy: no
+ codacy_projectid: '[Get ID from https://app.codacy.com/app/ionelmc/pytest-cov/settings]'
+ codeclimate: no
+ codecov: no
+ command_line_interface: no
+ command_line_interface_bin_name: '-'
+ coveralls: no
+ coveralls_token: '[Required for Appveyor, take it from https://coveralls.io/github/ionelmc/pytest-cov]'
+ distribution_name: pytest-cov
+ email: contact@ionelmc.ro
+ full_name: Ionel Cristian Mărieș
+ landscape: no
+ license: MIT license
+ linter: flake8
+ package_name: pytest_cov
+ pre_commit: yes
+ project_name: pytest-cov
+ project_short_description: This plugin produces coverage reports. It supports centralised testing and distributed testing in both load and each modes. It also supports coverage of subprocesses.
+ pypi_badge: yes
+ pypi_disable_upload: no
+ release_date: '2020-06-12'
+ repo_hosting: github.com
+ repo_hosting_domain: github.com
+ repo_name: pytest-cov
+ repo_username: pytest-dev
+ requiresio: yes
+ scrutinizer: no
+ setup_py_uses_setuptools_scm: no
+ setup_py_uses_test_runner: no
+ sphinx_docs: yes
+ sphinx_docs_hosting: https://pytest-cov.readthedocs.io/
+ sphinx_doctest: no
+ sphinx_theme: sphinx-py3doc-enhanced-theme
+ test_matrix_configurator: no
+ test_matrix_separate_coverage: no
+ test_runner: pytest
+ travis: yes
+ travis_osx: no
+ version: 2.10.0
+ website: http://blog.ionelmc.ro
+ year_from: '2010'
+ year_to: '2020'
--- /dev/null
+# see https://editorconfig.org/
+root = true
+
+[*]
+end_of_line = lf
+trim_trailing_whitespace = true
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+charset = utf-8
+
+[*.{bat,cmd,ps1}]
+end_of_line = crlf
+
+[*.{yml,yaml}]
+indent_size = 2
--- /dev/null
+---
+name: 🐞 Bug report
+about: There a problem with how pytest-cov or coverage works
+---
+
+# Summary
+
+## Expected vs actual result
+
+# Reproducer
+
+## Versions
+
+Output of relevant packages `pip list`, `python --version`, `pytest --version` etc.
+
+Make sure you include complete output of `tox` if you use it (it will show versions of various things).
+
+## Config
+
+Include your `tox.ini`, `pytest.ini`, `.coveragerc`, `setup.cfg` or any relevant configuration.
+
+## Code
+
+Link to your repository, gist, pastebin or just paste raw code that illustrates the issue.
+
+If you paste raw code make sure you quote it, eg:
+
+```python
+def foobar():
+ pass
+```
--- /dev/null
+---
+name: ✈ Feature request
+about: Proposal for a new feature in pytest-cov
+---
+
+Before proposing please consider:
+
+* the maintenance cost of the feature
+* implementing it externally (like a shell/python script,
+ pytest plugin or something else)
+
+# Summary
+
+These questions should be answered:
+
+* why is the feature needed?
+* what problem does it solve?
+* how it is better compared to past solutions to the problem?
--- /dev/null
+---
+name: 🤔 Support request
+about: Request help with setting up pytest-cov in your project
+---
+
+Please go over all the sections and search
+https://pytest-cov.readthedocs.io/en/latest/ or
+https://coverage.readthedocs.io/en/latest/
+before opening the issue.
+
+# Summary
+
+## Expected vs actual result
+
+# Reproducer
+
+## Versions
+
+Output of relevant packages `pip list`, `python --version`, `pytest --version` etc.
+
+Make sure you include complete output of `tox` if you use it (it will show versions of various things).
+
+## Config
+
+Include your `tox.ini`, `pytest.ini`, `.coveragerc`, `setup.cfg` or any relevant configuration.
+
+## Code
+
+Link to your repository, gist, pastebin or just paste raw code that illustrates the issue.
+
+If you paste raw code make sure you quote it, eg:
+
+```python
+def foobar():
+ pass
+```
+
+# What has been tried to solve the problem
+
+You should outline the things you tried to solve the problem but didn't work.
--- /dev/null
+# To install the git pre-commit hook run:
+# 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
+ hooks:
+ - id: trailing-whitespace
+ - id: end-of-file-fixer
+ - id: debug-statements
+ - repo: https://github.com/timothycrosley/isort
+ rev: master
+ hooks:
+ - id: isort
+ - repo: https://gitlab.com/pycqa/flake8
+ rev: master
+ hooks:
+ - id: flake8
--- /dev/null
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+version: 2
+sphinx:
+ configuration: docs/conf.py
+formats: all
+python:
+ version: 3
+ install:
+ - requirements: docs/requirements.txt
+ - method: pip
+ path: .
--- /dev/null
+# 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-xdist27-coverage45
+ python: '2.7'
+ - env: TOXENV=py27-pytest46-xdist27-coverage52
+ python: '2.7'
+ - env: TOXENV=py35-pytest46-xdist27-coverage45
+ python: '3.5'
+ - env: TOXENV=py35-pytest46-xdist27-coverage52
+ python: '3.5'
+ - env: TOXENV=py36-pytest46-xdist27-coverage45
+ python: '3.6'
+ - env: TOXENV=py36-pytest46-xdist27-coverage52
+ python: '3.6'
+ - env: TOXENV=py37-pytest46-xdist27-coverage45
+ python: '3.7'
+ - env: TOXENV=py37-pytest46-xdist27-coverage52
+ python: '3.7'
+ - env: TOXENV=pypy-pytest46-xdist27-coverage45
+ python: 'pypy'
+ - env: TOXENV=pypy-pytest46-xdist27-coverage52
+ python: 'pypy'
+ - env: TOXENV=pypy3-pytest46-xdist27-coverage45
+ python: 'pypy3'
+ - env: TOXENV=pypy3-pytest46-xdist27-coverage52
+ python: 'pypy3'
+ - env: TOXENV=py36-pytest46-xdist33-coverage45
+ python: '3.6'
+ - env: TOXENV=py36-pytest46-xdist33-coverage52
+ python: '3.6'
+ - env: TOXENV=py36-pytest54-xdist33-coverage45
+ python: '3.6'
+ - env: TOXENV=py36-pytest54-xdist33-coverage52
+ python: '3.6'
+ - env: TOXENV=py37-pytest46-xdist33-coverage45
+ python: '3.7'
+ - env: TOXENV=py37-pytest46-xdist33-coverage52
+ python: '3.7'
+ - env: TOXENV=py37-pytest54-xdist33-coverage45
+ python: '3.7'
+ - env: TOXENV=py37-pytest54-xdist33-coverage52
+ python: '3.7'
+ - env: TOXENV=py38-pytest46-xdist33-coverage45
+ python: '3.8'
+ - env: TOXENV=py38-pytest46-xdist33-coverage52
+ python: '3.8'
+ - env: TOXENV=py38-pytest54-xdist33-coverage45
+ python: '3.8'
+ - env: TOXENV=py38-pytest54-xdist33-coverage52
+ python: '3.8'
+ - env: TOXENV=pypy3-pytest46-xdist33-coverage45
+ python: 'pypy3'
+ - env: TOXENV=pypy3-pytest46-xdist33-coverage52
+ python: 'pypy3'
+ - env: TOXENV=pypy3-pytest54-xdist33-coverage45
+ python: 'pypy3'
+ - env: TOXENV=pypy3-pytest54-xdist33-coverage52
+ python: 'pypy3'
+ - env: TOXENV=py36-pytest60-xdist200-coverage52
+ python: '3.6'
+ - env: TOXENV=py37-pytest60-xdist200-coverage52
+ python: '3.7'
+ - env: TOXENV=py38-pytest60-xdist200-coverage52
+ python: '3.8'
+ - env: TOXENV=pypy3-pytest60-xdist200-coverage52
+ 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
--- /dev/null
+Authors
+=======
+
+* Marc Schlaich - http://www.schlamar.org
+* Rick van Hattem - http://wol.ph
+* Buck Evan - https://github.com/bukzor
+* Eric Larson - http://larsoner.com
+* Marc Abramowitz - http://marc-abramowitz.com
+* Thomas Kluyver - https://github.com/takluyver
+* Guillaume Ayoub - http://www.yabz.fr
+* Federico Ceratto - http://firelet.net
+* Josh Kalderimis - http://blog.cookiestack.com
+* Ionel Cristian Mărieș - https://blog.ionelmc.ro
+* Christian Ledermann - https://github.com/cleder
+* Alec Nikolas Reiter - https://github.com/justanr
+* Patrick Lannigan - https://github.com/plannigan
+* David Szotten - https://github.com/davidszotten
+* Michael Elovskikh - https://github.com/wronglink
+* Saurabh Kumar - https://github.com/theskumar
+* Michael Elovskikh - https://github.com/wronglink
+* Daniel Hahler - https://daniel.hahler.de
+* Florian Bruhin - http://www.the-compiler.org
+* Zoltan Kozma - https://github.com/kozmaz87
+* Francis Niu - https://flniu.github.io
+* Jannis Leidel - https://github.com/jezdez
+* Ryan Hiebert - http://ryanhiebert.com/
+* Terence Honles - https://github.com/terencehonles
+* Jeremy Bowman - https://github.com/jmbowman
+* Samuel Giffard - https://github.com/Mulugruntz
+* Семён Марьясин - https://github.com/MarSoft
+* Alexander Shadchin - https://github.com/shadchin
+* Thomas Grainger - https://graingert.co.uk
+* Juanjo Bazán - https://github.com/xuanxu
+* Andrew Murray - https://github.com/radarhere
+* Ned Batchelder - https://nedbatchelder.com/
+* Albert Tugushev - https://github.com/atugushev
+* Martín Gaitán - https://github.com/mgaitan
+* Hugo van Kemenade - https://github.com/hugovk
+* Michael Manganiello - https://github.com/adamantike
+* Anders Hovmöller - https://github.com/boxed
+* Zac Hatfield-Dodds - https://zhd.dev
--- /dev/null
+Changelog
+=========
+
+2.10.1 (2020-08-14)
+-------------------
+
+* Support for ``pytest-xdist`` 2.0, which breaks compatibility with ``pytest-xdist`` before 1.22.3 (from 2017).
+ Contributed by Zac Hatfield-Dodds in `#412 <https://github.com/pytest-dev/pytest-cov/pull/412>`_.
+* Fixed the ``LocalPath has no attribute startswith`` failure that occurred when using the ``pytester`` plugin
+ in inline mode.
+
+2.10.0 (2020-06-12)
+-------------------
+
+* Improved the ``--no-cov`` warning. Now it's only shown if ``--no-cov`` is present before ``--cov``.
+* Removed legacy pytest support. Changed ``setup.py`` so that ``pytest>=4.6`` is required.
+
+2.9.0 (2020-05-22)
+------------------
+
+* Fixed ``RemovedInPytest4Warning`` when using Pytest 3.10.
+ Contributed by Michael Manganiello in `#354 <https://github.com/pytest-dev/pytest-cov/pull/354>`_.
+* Made pytest startup faster when plugin not active by lazy-importing.
+ Contributed by Anders Hovmöller in `#339 <https://github.com/pytest-dev/pytest-cov/pull/339>`_.
+* Various CI improvements.
+ Contributed by Daniel Hahler in `#363 <https://github.com/pytest-dev/pytest-cov/pull/>`_ and
+ `#364 <https://github.com/pytest-dev/pytest-cov/pull/364>`_.
+* Various Python support updates (drop EOL 3.4, test against 3.8 final).
+ Contributed by Hugo van Kemenade in
+ `#336 <https://github.com/pytest-dev/pytest-cov/pull/336>`_ and
+ `#367 <https://github.com/pytest-dev/pytest-cov/pull/367>`_.
+* Changed ``--cov-append`` to always enable ``data_suffix`` (a coverage setting).
+ Contributed by Harm Geerts in
+ `#387 <https://github.com/pytest-dev/pytest-cov/pull/387>`_.
+* Changed ``--cov-append`` to handle loading previous data better
+ (fixes various path aliasing issues).
+* Various other testing improvements, github issue templates, example updates.
+* Fixed internal failures that are caused by tests that change the current working directory by
+ ensuring a consistent working directory when coverage is called.
+ See `#306 <https://github.com/pytest-dev/pytest-cov/issues/306>`_ and
+ `coveragepy#881 <https://github.com/nedbat/coveragepy/issues/881>`_
+
+2.8.1 (2019-10-05)
+------------------
+
+* Fixed `#348 <https://github.com/pytest-dev/pytest-cov/issues/348>`_ -
+ regression when only certain reports (html or xml) are used then ``--cov-fail-under`` always fails.
+
+2.8.0 (2019-10-04)
+------------------
+
+* Fixed ``RecursionError`` that can occur when using
+ `cleanup_on_signal <https://pytest-cov.readthedocs.io/en/latest/subprocess-support.html#if-you-got-custom-signal-handling>`__ or
+ `cleanup_on_sigterm <https://pytest-cov.readthedocs.io/en/latest/subprocess-support.html#if-you-got-custom-signal-handling>`__.
+ See: `#294 <https://github.com/pytest-dev/pytest-cov/issues/294>`_.
+ The 2.7.x releases of pytest-cov should be considered broken regarding aforementioned cleanup API.
+* Added compatibility with future xdist release that deprecates some internals
+ (match pytest-xdist master/worker terminology).
+ Contributed by Thomas Grainger in `#321 <https://github.com/pytest-dev/pytest-cov/pull/321>`_
+* Fixed breakage that occurs when multiple reporting options are used.
+ Contributed by Thomas Grainger in `#338 <https://github.com/pytest-dev/pytest-cov/pull/338>`_.
+* Changed internals to use a stub instead of ``os.devnull``.
+ Contributed by Thomas Grainger in `#332 <https://github.com/pytest-dev/pytest-cov/pull/332>`_.
+* Added support for Coverage 5.0.
+ Contributed by Ned Batchelder in `#319 <https://github.com/pytest-dev/pytest-cov/pull/319>`_.
+* Added support for float values in ``--cov-fail-under``.
+ Contributed by Martín Gaitán in `#311 <https://github.com/pytest-dev/pytest-cov/pull/311>`_.
+* Various documentation fixes. Contributed by
+ Juanjo Bazán,
+ Andrew Murray and
+ Albert Tugushev in
+ `#298 <https://github.com/pytest-dev/pytest-cov/pull/298>`_,
+ `#299 <https://github.com/pytest-dev/pytest-cov/pull/299>`_ and
+ `#307 <https://github.com/pytest-dev/pytest-cov/pull/307>`_.
+* Various testing improvements. Contributed by
+ Ned Batchelder,
+ Daniel Hahler,
+ Ionel Cristian Mărieș and
+ Hugo van Kemenade in
+ `#313 <https://github.com/pytest-dev/pytest-cov/pull/313>`_,
+ `#314 <https://github.com/pytest-dev/pytest-cov/pull/314>`_,
+ `#315 <https://github.com/pytest-dev/pytest-cov/pull/315>`_,
+ `#316 <https://github.com/pytest-dev/pytest-cov/pull/316>`_,
+ `#325 <https://github.com/pytest-dev/pytest-cov/pull/325>`_,
+ `#326 <https://github.com/pytest-dev/pytest-cov/pull/326>`_,
+ `#334 <https://github.com/pytest-dev/pytest-cov/pull/334>`_ and
+ `#335 <https://github.com/pytest-dev/pytest-cov/pull/335>`_.
+* Added the ``--cov-context`` CLI options that enables coverage contexts. Only works with coverage 5.0+.
+ Contributed by Ned Batchelder in `#345 <https://github.com/pytest-dev/pytest-cov/pull/345>`_.
+
+2.7.1 (2019-05-03)
+------------------
+
+* Fixed source distribution manifest so that garbage ain't included in the tarball.
+
+2.7.0 (2019-05-03)
+------------------
+
+* Fixed ``AttributeError: 'NoneType' object has no attribute 'configure_node'`` error when ``--no-cov`` is used.
+ Contributed by Alexander Shadchin in `#263 <https://github.com/pytest-dev/pytest-cov/pull/263>`_.
+* Various testing and CI improvements. Contributed by Daniel Hahler in
+ `#255 <https://github.com/pytest-dev/pytest-cov/pull/255>`_,
+ `#266 <https://github.com/pytest-dev/pytest-cov/pull/266>`_,
+ `#272 <https://github.com/pytest-dev/pytest-cov/pull/272>`_,
+ `#271 <https://github.com/pytest-dev/pytest-cov/pull/271>`_ and
+ `#269 <https://github.com/pytest-dev/pytest-cov/pull/269>`_.
+* Improved documentation regarding subprocess and multiprocessing.
+ Contributed in `#265 <https://github.com/pytest-dev/pytest-cov/pull/265>`_.
+* Improved ``pytest_cov.embed.cleanup_on_sigterm`` to be reentrant (signal deliveries while signal handling is
+ running won't break stuff).
+* Added ``pytest_cov.embed.cleanup_on_signal`` for customized cleanup.
+* Improved cleanup code and fixed various issues with leftover data files. All contributed in
+ `#265 <https://github.com/pytest-dev/pytest-cov/pull/265>`_ or
+ `#262 <https://github.com/pytest-dev/pytest-cov/pull/262>`_.
+* Improved examples. Now there are two examples for the common project layouts, complete with working coverage
+ configuration. The examples have CI testing. Contributed in
+ `#267 <https://github.com/pytest-dev/pytest-cov/pull/267>`_.
+* Improved help text for CLI options.
+
+2.6.1 (2019-01-07)
+------------------
+
+* Added support for Pytest 4.1. Contributed by Daniel Hahler and Семён Марьясин in
+ `#253 <https://github.com/pytest-dev/pytest-cov/pull/253>`_ and
+ `#230 <https://github.com/pytest-dev/pytest-cov/pull/230>`_.
+* Various test and docs fixes. Contributed by Daniel Hahler in
+ `#224 <https://github.com/pytest-dev/pytest-cov/pull/224>`_ and
+ `#223 <https://github.com/pytest-dev/pytest-cov/pull/223>`_.
+* Fixed the "Module already imported" issue (`#211 <https://github.com/pytest-dev/pytest-cov/issues/211>`_).
+ Contributed by Daniel Hahler in `#228 <https://github.com/pytest-dev/pytest-cov/pull/228>`_.
+
+2.6.0 (2018-09-03)
+------------------
+
+* Dropped support for Python 3 < 3.4, Pytest < 3.5 and Coverage < 4.4.
+* Fixed some documentation formatting. Contributed by Jean Jordaan and Julian.
+* Added an example with ``addopts`` in documentation. Contributed by Samuel Giffard in
+ `#195 <https://github.com/pytest-dev/pytest-cov/pull/195>`_.
+* Fixed ``TypeError: 'NoneType' object is not iterable`` in certain xdist configurations. Contributed by Jeremy Bowman in
+ `#213 <https://github.com/pytest-dev/pytest-cov/pull/213>`_.
+* Added a ``no_cover`` marker and fixture. Fixes
+ `#78 <https://github.com/pytest-dev/pytest-cov/issues/78>`_.
+* Fixed broken ``no_cover`` check when running doctests. Contributed by Terence Honles in
+ `#200 <https://github.com/pytest-dev/pytest-cov/pull/200>`_.
+* Fixed various issues with path normalization in reports (when combining coverage data from parallel mode). Fixes
+ `#130 <https://github.com/pytest-dev/pytest-cov/issues/161>`_.
+ Contributed by Ryan Hiebert & Ionel Cristian Mărieș in
+ `#178 <https://github.com/pytest-dev/pytest-cov/pull/178>`_.
+* Report generation failures don't raise exceptions anymore. A warning will be logged instead. Fixes
+ `#161 <https://github.com/pytest-dev/pytest-cov/issues/161>`_.
+* Fixed multiprocessing issue on Windows (empty env vars are not passed). Fixes
+ `#165 <https://github.com/pytest-dev/pytest-cov/issues/165>`_.
+
+2.5.1 (2017-05-11)
+------------------
+
+* Fixed xdist breakage (regression in ``2.5.0``).
+ Fixes `#157 <https://github.com/pytest-dev/pytest-cov/issues/157>`_.
+* Allow setting custom ``data_file`` name in ``.coveragerc``.
+ Fixes `#145 <https://github.com/pytest-dev/pytest-cov/issues/145>`_.
+ Contributed by Jannis Leidel & Ionel Cristian Mărieș in
+ `#156 <https://github.com/pytest-dev/pytest-cov/pull/156>`_.
+
+2.5.0 (2017-05-09)
+------------------
+
+* Always show a summary when ``--cov-fail-under`` is used. Contributed by Francis Niu in `PR#141
+ <https://github.com/pytest-dev/pytest-cov/pull/141>`_.
+* Added ``--cov-branch`` option. Fixes `#85 <https://github.com/pytest-dev/pytest-cov/issues/85>`_.
+* Improve exception handling in subprocess setup. Fixes `#144 <https://github.com/pytest-dev/pytest-cov/issues/144>`_.
+* Fixed handling when ``--cov`` is used multiple times. Fixes `#151 <https://github.com/pytest-dev/pytest-cov/issues/151>`_.
+
+2.4.0 (2016-10-10)
+------------------
+
+* Added a "disarm" option: ``--no-cov``. It will disable coverage measurements. Contributed by Zoltan Kozma in
+ `PR#135 <https://github.com/pytest-dev/pytest-cov/pull/135>`_.
+
+ **WARNING: Do not put this in your configuration files, it's meant to be an one-off for situations where you want to
+ disable coverage from command line.**
+* Fixed broken exception handling on ``.pth`` file. See `#136 <https://github.com/pytest-dev/pytest-cov/issues/136>`_.
+
+2.3.1 (2016-08-07)
+------------------
+
+* Fixed regression causing spurious errors when xdist was used. See `#124
+ <https://github.com/pytest-dev/pytest-cov/issues/124>`_.
+* Fixed DeprecationWarning about incorrect `addoption` use. Contributed by Florian Bruhin in `PR#127
+ <https://github.com/pytest-dev/pytest-cov/pull/127>`_.
+* Fixed deprecated use of funcarg fixture API. Contributed by Daniel Hahler in `PR#125
+ <https://github.com/pytest-dev/pytest-cov/pull/125>`_.
+
+2.3.0 (2016-07-05)
+------------------
+
+* Add support for specifying output location for html, xml, and annotate report.
+ Contributed by Patrick Lannigan in `PR#113 <https://github.com/pytest-dev/pytest-cov/pull/113>`_.
+* Fix bug hiding test failure when cov-fail-under failed.
+* For coverage >= 4.0, match the default behaviour of `coverage report` and
+ error if coverage fails to find the source instead of just printing a warning.
+ Contributed by David Szotten in `PR#116 <https://github.com/pytest-dev/pytest-cov/pull/116>`_.
+* Fixed bug occurred when bare ``--cov`` parameter was used with xdist.
+ Contributed by Michael Elovskikh in `PR#120 <https://github.com/pytest-dev/pytest-cov/pull/120>`_.
+* Add support for ``skip_covered`` and added ``--cov-report=term-skip-covered`` command
+ line options. Contributed by Saurabh Kumar in `PR#115 <https://github.com/pytest-dev/pytest-cov/pull/115>`_.
+
+2.2.1 (2016-01-30)
+------------------
+
+* Fixed incorrect merging of coverage data when xdist was used and coverage was ``>= 4.0``.
+
+2.2.0 (2015-10-04)
+------------------
+
+* Added support for changing working directory in tests. Previously changing working
+ directory would disable coverage measurements in suprocesses.
+* Fixed broken handling for ``--cov-report=annotate``.
+
+2.1.0 (2015-08-23)
+------------------
+
+* Added support for `coverage 4.0b2`.
+* Added the ``--cov-append`` command line options. Contributed by Christian Ledermann
+ in `PR#80 <https://github.com/pytest-dev/pytest-cov/pull/80>`_.
+
+2.0.0 (2015-07-28)
+------------------
+
+* Added ``--cov-fail-under``, akin to the new ``fail_under`` option in `coverage-4.0`
+ (automatically activated if there's a ``[report] fail_under = ...`` in ``.coveragerc``).
+* Changed ``--cov-report=term`` to automatically upgrade to ``--cov-report=term-missing``
+ if there's ``[run] show_missing = True`` in ``.coveragerc``.
+* Changed ``--cov`` so it can be used with no path argument (in which case the source
+ settings from ``.coveragerc`` will be used instead).
+* Fixed `.pth` installation to work in all cases (install, easy_install, wheels, develop etc).
+* Fixed `.pth` uninstallation to work for wheel installs.
+* Support for coverage 4.0.
+* Data file suffixing changed to use coverage's ``data_suffix=True`` option (instead of the
+ custom suffixing).
+* Avoid warning about missing coverage data (just like ``coverage.control.process_startup``).
+* Fixed a race condition when running with xdist (all the workers tried to combine the files).
+ It's possible that this issue is not present in `pytest-cov 1.8.X`.
+
+1.8.2 (2014-11-06)
+------------------
+
+* N/A
--- /dev/null
+============
+Contributing
+============
+
+Contributions are welcome, and they are greatly appreciated! Every
+little bit helps, and credit will always be given.
+
+Bug reports
+===========
+
+When `reporting a bug <https://github.com/pytest-dev/pytest-cov/issues>`_ please include:
+
+ * Your operating system name and version.
+ * Any details about your local setup that might be helpful in troubleshooting.
+ * Detailed steps to reproduce the bug.
+
+Documentation improvements
+==========================
+
+pytest-cov could always use more documentation, whether as part of the
+official pytest-cov docs, in docstrings, or even on the web in blog posts,
+articles, and such.
+
+Feature requests and feedback
+=============================
+
+The best way to send feedback is to file an issue at https://github.com/pytest-dev/pytest-cov/issues.
+
+If you are proposing a feature:
+
+* Explain in detail how it would work.
+* Keep the scope as narrow as possible, to make it easier to implement.
+* Remember that this is a volunteer-driven project, and that code contributions are welcome :)
+
+Development
+===========
+
+To set up `pytest-cov` for local development:
+
+1. Fork `pytest-cov <https://github.com/pytest-dev/pytest-cov>`_
+ (look for the "Fork" button).
+2. Clone your fork locally::
+
+ git clone git@github.com:YOURGITHUBNAME/pytest-cov.git
+
+3. Create a branch for local development::
+
+ git checkout -b name-of-your-bugfix-or-feature
+
+ Now you can make your changes locally.
+
+4. When you're done making changes run all the checks and docs builder with `tox <https://tox.readthedocs.io/en/latest/install.html>`_ one command::
+
+ tox
+
+5. Commit your changes and push your branch to GitHub::
+
+ git add .
+ git commit -m "Your detailed description of your changes."
+ git push origin name-of-your-bugfix-or-feature
+
+6. Submit a pull request through the GitHub website.
+
+Pull Request Guidelines
+-----------------------
+
+If you need some code review or feedback while you're developing the code just make the pull request.
+
+For merging, you should:
+
+1. Include passing tests (run ``tox``) [1]_.
+2. Update documentation when there's new API, functionality etc.
+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 <https://travis-ci.org/pytest-dev/pytest-cov/pull_requests>`_ for each change you add in the pull request.
+
+ It will be slower though ...
+
+Tips
+----
+
+To run a subset of tests::
+
+ tox -e envname -- pytest -k test_myfeature
+
+To run the test environments in *parallel*::
+
+ tox -p auto
--- /dev/null
+The MIT License
+
+Copyright (c) 2010 Meme Dough
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
--- /dev/null
+graft docs
+graft examples
+prune examples/*/.tox
+prune examples/*/htmlcov
+prune examples/*/*/htmlcov
+prune examples/adhoc-layout/*.egg-info
+prune examples/src-layout/src/*.egg-info
+
+graft src
+graft ci
+graft tests
+
+include .bumpversion.cfg
+include .coveragerc
+include .cookiecutterrc
+include .editorconfig
+
+include AUTHORS.rst
+include CHANGELOG.rst
+include CONTRIBUTING.rst
+include LICENSE
+include README.rst
+
+include tox.ini .travis.yml .appveyor.yml .readthedocs.yml .pre-commit-config.yaml
+
+global-exclude *.py[cod] __pycache__/* *.so *.dylib .coverage .coverage.*
--- /dev/null
+Metadata-Version: 2.1
+Name: pytest-cov
+Version: 2.10.1
+Summary: Pytest plugin for measuring coverage.
+Home-page: https://github.com/pytest-dev/pytest-cov
+Author: Marc Schlaich
+Author-email: marc.schlaich@gmail.com
+License: MIT
+Description: ========
+ Overview
+ ========
+
+ .. start-badges
+
+ .. list-table::
+ :stub-columns: 1
+
+ * - docs
+ - |docs|
+ * - tests
+ - | |travis| |appveyor| |requires|
+ * - package
+ - | |version| |conda-forge| |wheel| |supported-versions| |supported-implementations|
+ | |commits-since|
+
+ .. |docs| image:: https://readthedocs.org/projects/pytest-cov/badge/?style=flat
+ :target: https://readthedocs.org/projects/pytest-cov
+ :alt: Documentation Status
+
+ .. |travis| image:: https://api.travis-ci.org/pytest-dev/pytest-cov.svg?branch=master
+ :alt: Travis-CI Build Status
+ :target: https://travis-ci.org/pytest-dev/pytest-cov
+
+ .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/pytest-dev/pytest-cov?branch=master&svg=true
+ :alt: AppVeyor Build Status
+ :target: https://ci.appveyor.com/project/pytestbot/pytest-cov
+
+ .. |requires| image:: https://requires.io/github/pytest-dev/pytest-cov/requirements.svg?branch=master
+ :alt: Requirements Status
+ :target: https://requires.io/github/pytest-dev/pytest-cov/requirements/?branch=master
+
+ .. |version| image:: https://img.shields.io/pypi/v/pytest-cov.svg
+ :alt: PyPI Package latest release
+ :target: https://pypi.org/project/pytest-cov
+
+ .. |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.10.1.svg
+ :alt: Commits since latest release
+ :target: https://github.com/pytest-dev/pytest-cov/compare/v2.10.1...master
+
+ .. |wheel| image:: https://img.shields.io/pypi/wheel/pytest-cov.svg
+ :alt: PyPI Wheel
+ :target: https://pypi.org/project/pytest-cov
+
+ .. |supported-versions| image:: https://img.shields.io/pypi/pyversions/pytest-cov.svg
+ :alt: Supported versions
+ :target: https://pypi.org/project/pytest-cov
+
+ .. |supported-implementations| image:: https://img.shields.io/pypi/implementation/pytest-cov.svg
+ :alt: Supported implementations
+ :target: https://pypi.org/project/pytest-cov
+
+ .. end-badges
+
+ This plugin produces coverage reports. Compared to just using ``coverage run`` this plugin does some extras:
+
+ * Subprocess support: you can fork or run stuff in a subprocess and will get covered without any fuss.
+ * Xdist support: you can use all of pytest-xdist's features and still get coverage.
+ * Consistent pytest behavior. If you run ``coverage run -m pytest`` you will have slightly different ``sys.path`` (CWD will be
+ in it, unlike when running ``pytest``).
+
+ All features offered by the coverage package should work, either through pytest-cov's command line options or
+ through coverage's config file.
+
+ * Free software: MIT license
+
+ Installation
+ ============
+
+ Install with pip::
+
+ pip install pytest-cov
+
+ For distributed testing support install pytest-xdist::
+
+ pip install pytest-xdist
+
+ Upgrading from ancient pytest-cov
+ ---------------------------------
+
+ `pytest-cov 2.0` is using a new ``.pth`` file (``pytest-cov.pth``). You may want to manually remove the older
+ ``init_cov_core.pth`` from site-packages as it's not automatically removed.
+
+ Uninstalling
+ ------------
+
+ Uninstall with pip::
+
+ pip uninstall pytest-cov
+
+ Under certain scenarios a stray ``.pth`` file may be left around in site-packages.
+
+ * `pytest-cov 2.0` may leave a ``pytest-cov.pth`` if you installed without wheels
+ (``easy_install``, ``setup.py install`` etc).
+ * `pytest-cov 1.8 or older` will leave a ``init_cov_core.pth``.
+
+ Usage
+ =====
+
+ ::
+
+ pytest --cov=myproj tests/
+
+ Would produce a report like::
+
+ -------------------- coverage: ... ---------------------
+ Name Stmts Miss Cover
+ ----------------------------------------
+ myproj/__init__ 2 0 100%
+ myproj/myproj 257 13 94%
+ myproj/feature4286 94 7 92%
+ ----------------------------------------
+ TOTAL 353 20 94%
+
+ Documentation
+ =============
+
+ http://pytest-cov.rtfd.org/
+
+
+
+
+
+
+ Coverage Data File
+ ==================
+
+ The data file is erased at the beginning of testing to ensure clean data for each test run. If you
+ need to combine the coverage of several test runs you can use the ``--cov-append`` option to append
+ this coverage data to coverage data from previous test runs.
+
+ The data file is left at the end of testing so that it is possible to use normal coverage tools to
+ examine it.
+
+ Limitations
+ ===========
+
+ For distributed testing the workers must have the pytest-cov package installed. This is needed since
+ the plugin must be registered through setuptools for pytest to start the plugin on the
+ worker.
+
+ For subprocess measurement environment variables must make it from the main process to the
+ subprocess. The python used by the subprocess must have pytest-cov installed. The subprocess must
+ do normal site initialisation so that the environment variables can be detected and coverage
+ started.
+
+
+ Acknowledgements
+ ================
+
+ Whilst this plugin has been built fresh from the ground up it has been influenced by the work done
+ on pytest-coverage (Ross Lawley, James Mills, Holger Krekel) and nose-cover (Jason Pellerin) which are
+ other coverage plugins.
+
+ Ned Batchelder for coverage and its ability to combine the coverage results of parallel runs.
+
+ Holger Krekel for pytest with its distributed testing support.
+
+ Jason Pellerin for nose.
+
+ Michael Foord for unittest2.
+
+ No doubt others have contributed to these tools as well.
+
+ Changelog
+ =========
+
+ 2.10.1 (2020-08-14)
+ -------------------
+
+ * Support for ``pytest-xdist`` 2.0, which breaks compatibility with ``pytest-xdist`` before 1.22.3 (from 2017).
+ Contributed by Zac Hatfield-Dodds in `#412 <https://github.com/pytest-dev/pytest-cov/pull/412>`_.
+ * Fixed the ``LocalPath has no attribute startswith`` failure that occurred when using the ``pytester`` plugin
+ in inline mode.
+
+ 2.10.0 (2020-06-12)
+ -------------------
+
+ * Improved the ``--no-cov`` warning. Now it's only shown if ``--no-cov`` is present before ``--cov``.
+ * Removed legacy pytest support. Changed ``setup.py`` so that ``pytest>=4.6`` is required.
+
+ 2.9.0 (2020-05-22)
+ ------------------
+
+ * Fixed ``RemovedInPytest4Warning`` when using Pytest 3.10.
+ Contributed by Michael Manganiello in `#354 <https://github.com/pytest-dev/pytest-cov/pull/354>`_.
+ * Made pytest startup faster when plugin not active by lazy-importing.
+ Contributed by Anders Hovmöller in `#339 <https://github.com/pytest-dev/pytest-cov/pull/339>`_.
+ * Various CI improvements.
+ Contributed by Daniel Hahler in `#363 <https://github.com/pytest-dev/pytest-cov/pull/>`_ and
+ `#364 <https://github.com/pytest-dev/pytest-cov/pull/364>`_.
+ * Various Python support updates (drop EOL 3.4, test against 3.8 final).
+ Contributed by Hugo van Kemenade in
+ `#336 <https://github.com/pytest-dev/pytest-cov/pull/336>`_ and
+ `#367 <https://github.com/pytest-dev/pytest-cov/pull/367>`_.
+ * Changed ``--cov-append`` to always enable ``data_suffix`` (a coverage setting).
+ Contributed by Harm Geerts in
+ `#387 <https://github.com/pytest-dev/pytest-cov/pull/387>`_.
+ * Changed ``--cov-append`` to handle loading previous data better
+ (fixes various path aliasing issues).
+ * Various other testing improvements, github issue templates, example updates.
+ * Fixed internal failures that are caused by tests that change the current working directory by
+ ensuring a consistent working directory when coverage is called.
+ See `#306 <https://github.com/pytest-dev/pytest-cov/issues/306>`_ and
+ `coveragepy#881 <https://github.com/nedbat/coveragepy/issues/881>`_
+
+ 2.8.1 (2019-10-05)
+ ------------------
+
+ * Fixed `#348 <https://github.com/pytest-dev/pytest-cov/issues/348>`_ -
+ regression when only certain reports (html or xml) are used then ``--cov-fail-under`` always fails.
+
+ 2.8.0 (2019-10-04)
+ ------------------
+
+ * Fixed ``RecursionError`` that can occur when using
+ `cleanup_on_signal <https://pytest-cov.readthedocs.io/en/latest/subprocess-support.html#if-you-got-custom-signal-handling>`__ or
+ `cleanup_on_sigterm <https://pytest-cov.readthedocs.io/en/latest/subprocess-support.html#if-you-got-custom-signal-handling>`__.
+ See: `#294 <https://github.com/pytest-dev/pytest-cov/issues/294>`_.
+ The 2.7.x releases of pytest-cov should be considered broken regarding aforementioned cleanup API.
+ * Added compatibility with future xdist release that deprecates some internals
+ (match pytest-xdist master/worker terminology).
+ Contributed by Thomas Grainger in `#321 <https://github.com/pytest-dev/pytest-cov/pull/321>`_
+ * Fixed breakage that occurs when multiple reporting options are used.
+ Contributed by Thomas Grainger in `#338 <https://github.com/pytest-dev/pytest-cov/pull/338>`_.
+ * Changed internals to use a stub instead of ``os.devnull``.
+ Contributed by Thomas Grainger in `#332 <https://github.com/pytest-dev/pytest-cov/pull/332>`_.
+ * Added support for Coverage 5.0.
+ Contributed by Ned Batchelder in `#319 <https://github.com/pytest-dev/pytest-cov/pull/319>`_.
+ * Added support for float values in ``--cov-fail-under``.
+ Contributed by Martín Gaitán in `#311 <https://github.com/pytest-dev/pytest-cov/pull/311>`_.
+ * Various documentation fixes. Contributed by
+ Juanjo Bazán,
+ Andrew Murray and
+ Albert Tugushev in
+ `#298 <https://github.com/pytest-dev/pytest-cov/pull/298>`_,
+ `#299 <https://github.com/pytest-dev/pytest-cov/pull/299>`_ and
+ `#307 <https://github.com/pytest-dev/pytest-cov/pull/307>`_.
+ * Various testing improvements. Contributed by
+ Ned Batchelder,
+ Daniel Hahler,
+ Ionel Cristian Mărieș and
+ Hugo van Kemenade in
+ `#313 <https://github.com/pytest-dev/pytest-cov/pull/313>`_,
+ `#314 <https://github.com/pytest-dev/pytest-cov/pull/314>`_,
+ `#315 <https://github.com/pytest-dev/pytest-cov/pull/315>`_,
+ `#316 <https://github.com/pytest-dev/pytest-cov/pull/316>`_,
+ `#325 <https://github.com/pytest-dev/pytest-cov/pull/325>`_,
+ `#326 <https://github.com/pytest-dev/pytest-cov/pull/326>`_,
+ `#334 <https://github.com/pytest-dev/pytest-cov/pull/334>`_ and
+ `#335 <https://github.com/pytest-dev/pytest-cov/pull/335>`_.
+ * Added the ``--cov-context`` CLI options that enables coverage contexts. Only works with coverage 5.0+.
+ Contributed by Ned Batchelder in `#345 <https://github.com/pytest-dev/pytest-cov/pull/345>`_.
+
+ 2.7.1 (2019-05-03)
+ ------------------
+
+ * Fixed source distribution manifest so that garbage ain't included in the tarball.
+
+ 2.7.0 (2019-05-03)
+ ------------------
+
+ * Fixed ``AttributeError: 'NoneType' object has no attribute 'configure_node'`` error when ``--no-cov`` is used.
+ Contributed by Alexander Shadchin in `#263 <https://github.com/pytest-dev/pytest-cov/pull/263>`_.
+ * Various testing and CI improvements. Contributed by Daniel Hahler in
+ `#255 <https://github.com/pytest-dev/pytest-cov/pull/255>`_,
+ `#266 <https://github.com/pytest-dev/pytest-cov/pull/266>`_,
+ `#272 <https://github.com/pytest-dev/pytest-cov/pull/272>`_,
+ `#271 <https://github.com/pytest-dev/pytest-cov/pull/271>`_ and
+ `#269 <https://github.com/pytest-dev/pytest-cov/pull/269>`_.
+ * Improved documentation regarding subprocess and multiprocessing.
+ Contributed in `#265 <https://github.com/pytest-dev/pytest-cov/pull/265>`_.
+ * Improved ``pytest_cov.embed.cleanup_on_sigterm`` to be reentrant (signal deliveries while signal handling is
+ running won't break stuff).
+ * Added ``pytest_cov.embed.cleanup_on_signal`` for customized cleanup.
+ * Improved cleanup code and fixed various issues with leftover data files. All contributed in
+ `#265 <https://github.com/pytest-dev/pytest-cov/pull/265>`_ or
+ `#262 <https://github.com/pytest-dev/pytest-cov/pull/262>`_.
+ * Improved examples. Now there are two examples for the common project layouts, complete with working coverage
+ configuration. The examples have CI testing. Contributed in
+ `#267 <https://github.com/pytest-dev/pytest-cov/pull/267>`_.
+ * Improved help text for CLI options.
+
+ 2.6.1 (2019-01-07)
+ ------------------
+
+ * Added support for Pytest 4.1. Contributed by Daniel Hahler and Семён Марьясин in
+ `#253 <https://github.com/pytest-dev/pytest-cov/pull/253>`_ and
+ `#230 <https://github.com/pytest-dev/pytest-cov/pull/230>`_.
+ * Various test and docs fixes. Contributed by Daniel Hahler in
+ `#224 <https://github.com/pytest-dev/pytest-cov/pull/224>`_ and
+ `#223 <https://github.com/pytest-dev/pytest-cov/pull/223>`_.
+ * Fixed the "Module already imported" issue (`#211 <https://github.com/pytest-dev/pytest-cov/issues/211>`_).
+ Contributed by Daniel Hahler in `#228 <https://github.com/pytest-dev/pytest-cov/pull/228>`_.
+
+ 2.6.0 (2018-09-03)
+ ------------------
+
+ * Dropped support for Python 3 < 3.4, Pytest < 3.5 and Coverage < 4.4.
+ * Fixed some documentation formatting. Contributed by Jean Jordaan and Julian.
+ * Added an example with ``addopts`` in documentation. Contributed by Samuel Giffard in
+ `#195 <https://github.com/pytest-dev/pytest-cov/pull/195>`_.
+ * Fixed ``TypeError: 'NoneType' object is not iterable`` in certain xdist configurations. Contributed by Jeremy Bowman in
+ `#213 <https://github.com/pytest-dev/pytest-cov/pull/213>`_.
+ * Added a ``no_cover`` marker and fixture. Fixes
+ `#78 <https://github.com/pytest-dev/pytest-cov/issues/78>`_.
+ * Fixed broken ``no_cover`` check when running doctests. Contributed by Terence Honles in
+ `#200 <https://github.com/pytest-dev/pytest-cov/pull/200>`_.
+ * Fixed various issues with path normalization in reports (when combining coverage data from parallel mode). Fixes
+ `#130 <https://github.com/pytest-dev/pytest-cov/issues/161>`_.
+ Contributed by Ryan Hiebert & Ionel Cristian Mărieș in
+ `#178 <https://github.com/pytest-dev/pytest-cov/pull/178>`_.
+ * Report generation failures don't raise exceptions anymore. A warning will be logged instead. Fixes
+ `#161 <https://github.com/pytest-dev/pytest-cov/issues/161>`_.
+ * Fixed multiprocessing issue on Windows (empty env vars are not passed). Fixes
+ `#165 <https://github.com/pytest-dev/pytest-cov/issues/165>`_.
+
+ 2.5.1 (2017-05-11)
+ ------------------
+
+ * Fixed xdist breakage (regression in ``2.5.0``).
+ Fixes `#157 <https://github.com/pytest-dev/pytest-cov/issues/157>`_.
+ * Allow setting custom ``data_file`` name in ``.coveragerc``.
+ Fixes `#145 <https://github.com/pytest-dev/pytest-cov/issues/145>`_.
+ Contributed by Jannis Leidel & Ionel Cristian Mărieș in
+ `#156 <https://github.com/pytest-dev/pytest-cov/pull/156>`_.
+
+ 2.5.0 (2017-05-09)
+ ------------------
+
+ * Always show a summary when ``--cov-fail-under`` is used. Contributed by Francis Niu in `PR#141
+ <https://github.com/pytest-dev/pytest-cov/pull/141>`_.
+ * Added ``--cov-branch`` option. Fixes `#85 <https://github.com/pytest-dev/pytest-cov/issues/85>`_.
+ * Improve exception handling in subprocess setup. Fixes `#144 <https://github.com/pytest-dev/pytest-cov/issues/144>`_.
+ * Fixed handling when ``--cov`` is used multiple times. Fixes `#151 <https://github.com/pytest-dev/pytest-cov/issues/151>`_.
+
+ 2.4.0 (2016-10-10)
+ ------------------
+
+ * Added a "disarm" option: ``--no-cov``. It will disable coverage measurements. Contributed by Zoltan Kozma in
+ `PR#135 <https://github.com/pytest-dev/pytest-cov/pull/135>`_.
+
+ **WARNING: Do not put this in your configuration files, it's meant to be an one-off for situations where you want to
+ disable coverage from command line.**
+ * Fixed broken exception handling on ``.pth`` file. See `#136 <https://github.com/pytest-dev/pytest-cov/issues/136>`_.
+
+ 2.3.1 (2016-08-07)
+ ------------------
+
+ * Fixed regression causing spurious errors when xdist was used. See `#124
+ <https://github.com/pytest-dev/pytest-cov/issues/124>`_.
+ * Fixed DeprecationWarning about incorrect `addoption` use. Contributed by Florian Bruhin in `PR#127
+ <https://github.com/pytest-dev/pytest-cov/pull/127>`_.
+ * Fixed deprecated use of funcarg fixture API. Contributed by Daniel Hahler in `PR#125
+ <https://github.com/pytest-dev/pytest-cov/pull/125>`_.
+
+ 2.3.0 (2016-07-05)
+ ------------------
+
+ * Add support for specifying output location for html, xml, and annotate report.
+ Contributed by Patrick Lannigan in `PR#113 <https://github.com/pytest-dev/pytest-cov/pull/113>`_.
+ * Fix bug hiding test failure when cov-fail-under failed.
+ * For coverage >= 4.0, match the default behaviour of `coverage report` and
+ error if coverage fails to find the source instead of just printing a warning.
+ Contributed by David Szotten in `PR#116 <https://github.com/pytest-dev/pytest-cov/pull/116>`_.
+ * Fixed bug occurred when bare ``--cov`` parameter was used with xdist.
+ Contributed by Michael Elovskikh in `PR#120 <https://github.com/pytest-dev/pytest-cov/pull/120>`_.
+ * Add support for ``skip_covered`` and added ``--cov-report=term-skip-covered`` command
+ line options. Contributed by Saurabh Kumar in `PR#115 <https://github.com/pytest-dev/pytest-cov/pull/115>`_.
+
+ 2.2.1 (2016-01-30)
+ ------------------
+
+ * Fixed incorrect merging of coverage data when xdist was used and coverage was ``>= 4.0``.
+
+ 2.2.0 (2015-10-04)
+ ------------------
+
+ * Added support for changing working directory in tests. Previously changing working
+ directory would disable coverage measurements in suprocesses.
+ * Fixed broken handling for ``--cov-report=annotate``.
+
+ 2.1.0 (2015-08-23)
+ ------------------
+
+ * Added support for `coverage 4.0b2`.
+ * Added the ``--cov-append`` command line options. Contributed by Christian Ledermann
+ in `PR#80 <https://github.com/pytest-dev/pytest-cov/pull/80>`_.
+
+ 2.0.0 (2015-07-28)
+ ------------------
+
+ * Added ``--cov-fail-under``, akin to the new ``fail_under`` option in `coverage-4.0`
+ (automatically activated if there's a ``[report] fail_under = ...`` in ``.coveragerc``).
+ * Changed ``--cov-report=term`` to automatically upgrade to ``--cov-report=term-missing``
+ if there's ``[run] show_missing = True`` in ``.coveragerc``.
+ * Changed ``--cov`` so it can be used with no path argument (in which case the source
+ settings from ``.coveragerc`` will be used instead).
+ * Fixed `.pth` installation to work in all cases (install, easy_install, wheels, develop etc).
+ * Fixed `.pth` uninstallation to work for wheel installs.
+ * Support for coverage 4.0.
+ * Data file suffixing changed to use coverage's ``data_suffix=True`` option (instead of the
+ custom suffixing).
+ * Avoid warning about missing coverage data (just like ``coverage.control.process_startup``).
+ * Fixed a race condition when running with xdist (all the workers tried to combine the files).
+ It's possible that this issue is not present in `pytest-cov 1.8.X`.
+
+ 1.8.2 (2014-11-06)
+ ------------------
+
+ * N/A
+
+Keywords: cover,coverage,pytest,py.test,distributed,parallel
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Framework :: Pytest
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: Unix
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Topic :: Software Development :: Testing
+Classifier: Topic :: Utilities
+Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
+Provides-Extra: testing
--- /dev/null
+========
+Overview
+========
+
+.. start-badges
+
+.. list-table::
+ :stub-columns: 1
+
+ * - docs
+ - |docs|
+ * - tests
+ - | |travis| |appveyor| |requires|
+ * - package
+ - | |version| |conda-forge| |wheel| |supported-versions| |supported-implementations|
+ | |commits-since|
+
+.. |docs| image:: https://readthedocs.org/projects/pytest-cov/badge/?style=flat
+ :target: https://readthedocs.org/projects/pytest-cov
+ :alt: Documentation Status
+
+.. |travis| image:: https://api.travis-ci.org/pytest-dev/pytest-cov.svg?branch=master
+ :alt: Travis-CI Build Status
+ :target: https://travis-ci.org/pytest-dev/pytest-cov
+
+.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/pytest-dev/pytest-cov?branch=master&svg=true
+ :alt: AppVeyor Build Status
+ :target: https://ci.appveyor.com/project/pytestbot/pytest-cov
+
+.. |requires| image:: https://requires.io/github/pytest-dev/pytest-cov/requirements.svg?branch=master
+ :alt: Requirements Status
+ :target: https://requires.io/github/pytest-dev/pytest-cov/requirements/?branch=master
+
+.. |version| image:: https://img.shields.io/pypi/v/pytest-cov.svg
+ :alt: PyPI Package latest release
+ :target: https://pypi.org/project/pytest-cov
+
+.. |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.10.1.svg
+ :alt: Commits since latest release
+ :target: https://github.com/pytest-dev/pytest-cov/compare/v2.10.1...master
+
+.. |wheel| image:: https://img.shields.io/pypi/wheel/pytest-cov.svg
+ :alt: PyPI Wheel
+ :target: https://pypi.org/project/pytest-cov
+
+.. |supported-versions| image:: https://img.shields.io/pypi/pyversions/pytest-cov.svg
+ :alt: Supported versions
+ :target: https://pypi.org/project/pytest-cov
+
+.. |supported-implementations| image:: https://img.shields.io/pypi/implementation/pytest-cov.svg
+ :alt: Supported implementations
+ :target: https://pypi.org/project/pytest-cov
+
+.. end-badges
+
+This plugin produces coverage reports. Compared to just using ``coverage run`` this plugin does some extras:
+
+* Subprocess support: you can fork or run stuff in a subprocess and will get covered without any fuss.
+* Xdist support: you can use all of pytest-xdist's features and still get coverage.
+* Consistent pytest behavior. If you run ``coverage run -m pytest`` you will have slightly different ``sys.path`` (CWD will be
+ in it, unlike when running ``pytest``).
+
+All features offered by the coverage package should work, either through pytest-cov's command line options or
+through coverage's config file.
+
+* Free software: MIT license
+
+Installation
+============
+
+Install with pip::
+
+ pip install pytest-cov
+
+For distributed testing support install pytest-xdist::
+
+ pip install pytest-xdist
+
+Upgrading from ancient pytest-cov
+---------------------------------
+
+`pytest-cov 2.0` is using a new ``.pth`` file (``pytest-cov.pth``). You may want to manually remove the older
+``init_cov_core.pth`` from site-packages as it's not automatically removed.
+
+Uninstalling
+------------
+
+Uninstall with pip::
+
+ pip uninstall pytest-cov
+
+Under certain scenarios a stray ``.pth`` file may be left around in site-packages.
+
+* `pytest-cov 2.0` may leave a ``pytest-cov.pth`` if you installed without wheels
+ (``easy_install``, ``setup.py install`` etc).
+* `pytest-cov 1.8 or older` will leave a ``init_cov_core.pth``.
+
+Usage
+=====
+
+::
+
+ pytest --cov=myproj tests/
+
+Would produce a report like::
+
+ -------------------- coverage: ... ---------------------
+ Name Stmts Miss Cover
+ ----------------------------------------
+ myproj/__init__ 2 0 100%
+ myproj/myproj 257 13 94%
+ myproj/feature4286 94 7 92%
+ ----------------------------------------
+ TOTAL 353 20 94%
+
+Documentation
+=============
+
+ http://pytest-cov.rtfd.org/
+
+
+
+
+
+
+Coverage Data File
+==================
+
+The data file is erased at the beginning of testing to ensure clean data for each test run. If you
+need to combine the coverage of several test runs you can use the ``--cov-append`` option to append
+this coverage data to coverage data from previous test runs.
+
+The data file is left at the end of testing so that it is possible to use normal coverage tools to
+examine it.
+
+Limitations
+===========
+
+For distributed testing the workers must have the pytest-cov package installed. This is needed since
+the plugin must be registered through setuptools for pytest to start the plugin on the
+worker.
+
+For subprocess measurement environment variables must make it from the main process to the
+subprocess. The python used by the subprocess must have pytest-cov installed. The subprocess must
+do normal site initialisation so that the environment variables can be detected and coverage
+started.
+
+
+Acknowledgements
+================
+
+Whilst this plugin has been built fresh from the ground up it has been influenced by the work done
+on pytest-coverage (Ross Lawley, James Mills, Holger Krekel) and nose-cover (Jason Pellerin) which are
+other coverage plugins.
+
+Ned Batchelder for coverage and its ability to combine the coverage results of parallel runs.
+
+Holger Krekel for pytest with its distributed testing support.
+
+Jason Pellerin for nose.
+
+Michael Foord for unittest2.
+
+No doubt others have contributed to these tools as well.
--- /dev/null
+:: Very simple setup:
+:: - if WINDOWS_SDK_VERSION is set then activate the SDK.
+:: - disable the WDK if it's around.
+
+SET COMMAND_TO_RUN=%*
+SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows
+SET WIN_WDK="c:\Program Files (x86)\Windows Kits\10\Include\wdf"
+ECHO SDK: %WINDOWS_SDK_VERSION% ARCH: %PYTHON_ARCH%
+
+IF EXIST %WIN_WDK% (
+ REM See: https://connect.microsoft.com/VisualStudio/feedback/details/1610302/
+ REN %WIN_WDK% 0wdf
+)
+IF "%WINDOWS_SDK_VERSION%"=="" GOTO main
+
+SET DISTUTILS_USE_SDK=1
+SET MSSdk=1
+"%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION%
+CALL "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release
+
+:main
+ECHO Executing: %COMMAND_TO_RUN%
+CALL %COMMAND_TO_RUN% || EXIT 1
--- /dev/null
+#!/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
+import sys
+from collections import defaultdict
+from os.path import abspath
+from os.path import dirname
+from os.path import exists
+from os.path import join
+
+base_path = dirname(dirname(abspath(__file__)))
+
+
+def check_call(args):
+ print("+", *args)
+ subprocess.check_call(args)
+
+
+def exec_in_env():
+ env_path = join(base_path, ".tox", "bootstrap")
+ if sys.platform == "win32":
+ bin_path = join(env_path, "Scripts")
+ else:
+ bin_path = join(env_path, "bin")
+ if not exists(env_path):
+ import subprocess
+
+ print("Making bootstrap env in: {0} ...".format(env_path))
+ try:
+ check_call([sys.executable, "-m", "venv", env_path])
+ except subprocess.CalledProcessError:
+ try:
+ check_call([sys.executable, "-m", "virtualenv", env_path])
+ except subprocess.CalledProcessError:
+ check_call(["virtualenv", env_path])
+ print("Installing `jinja2` into bootstrap environment...")
+ check_call([join(bin_path, "pip"), "install", "jinja2", "tox"])
+ python_executable = join(bin_path, "python")
+ if not os.path.exists(python_executable):
+ python_executable += '.exe'
+
+ print("Re-executing with: {0}".format(python_executable))
+ print("+ exec", python_executable, __file__, "--no-env")
+ os.execv(python_executable, [python_executable, __file__, "--no-env"])
+
+
+def main():
+ import jinja2
+
+ print("Project path: {0}".format(base_path))
+
+ jinja = jinja2.Environment(
+ loader=jinja2.FileSystemLoader(join(base_path, "ci", "templates")),
+ trim_blocks=True,
+ lstrip_blocks=True,
+ keep_trailing_newline=True
+ )
+
+ tox_environments = [
+ line.strip()
+ # WARNING: 'tox' must be installed globally or in the project's virtualenv
+ for line in subprocess.check_output(['tox', '--listenvs'], universal_newlines=True).splitlines()
+ ]
+ tox_environments = [line for line in tox_environments if line not in ['clean', 'report', 'docs', 'check']]
+
+ template_vars = defaultdict(list)
+ template_vars['tox_environments'] = tox_environments
+ for env in tox_environments:
+ first, _ = env.split('-', 1)
+ template_vars['%s_environments' % first].append(env)
+
+ for name in os.listdir(join("ci", "templates")):
+ 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("DONE.")
+
+
+if __name__ == "__main__":
+ args = sys.argv[1:]
+ if args == ["--no-env"]:
+ main()
+ elif not args:
+ exec_in_env()
+ else:
+ print("Unexpected arguments {0}".format(args), file=sys.stderr)
+ sys.exit(1)
--- /dev/null
+virtualenv>=16.6.0
+pip>=19.1.1
+setuptools>=18.0.1
+six>=1.14.0
--- /dev/null
+version: '{branch}-{build}'
+build: off
+environment:
+ matrix:
+ - TOXENV: check
+ - TOXENV: '{{ py27_environments|join(",") }}'
+ - TOXENV: '{{ py35_environments|join(",") }}'
+ - TOXENV: '{{ py36_environments|join(",") }}'
+ - TOXENV: '{{ py37_environments|join(",") }}'
+ - TOXENV: '{{ pypy_environments|join(",") }}'
+
+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
+
+test_script:
+ - cmd /E:ON /V:ON /C .\ci\appveyor-with-compiler.cmd C:\Python37\python -m tox
+
+on_failure:
+ - ps: dir "env:"
+ - ps: get-content .tox\*\log\*
+artifacts:
+ - path: dist\*
+
+### To enable remote debugging uncomment this (also, see: http://www.appveyor.com/docs/how-to/rdp-to-build-worker):
+# on_finish:
+# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
--- /dev/null
+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
--- /dev/null
+.. include:: ../AUTHORS.rst
--- /dev/null
+.. include:: ../CHANGELOG.rst
--- /dev/null
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import os
+
+import sphinx_py3doc_enhanced_theme
+
+extensions = [
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.autosummary',
+ 'sphinx.ext.todo',
+ 'sphinx.ext.coverage',
+ 'sphinx.ext.ifconfig',
+ 'sphinx.ext.viewcode',
+ 'sphinx.ext.napoleon',
+ 'sphinx.ext.extlinks',
+]
+if os.getenv('SPELLCHECK'):
+ extensions += 'sphinxcontrib.spelling',
+ spelling_show_suggestions = True
+ spelling_lang = 'en_US'
+
+source_suffix = '.rst'
+master_doc = 'index'
+project = 'pytest-cov'
+year = '2016'
+author = 'pytest-cov contributors'
+copyright = '{}, {}'.format(year, author)
+version = release = '2.10.1'
+
+pygments_style = 'trac'
+templates_path = ['.']
+extlinks = {
+ 'issue': ('https://github.com/pytest-dev/pytest-cov/issues/%s', '#'),
+ 'pr': ('https://github.com/pytest-dev/pytest-cov/pull/%s', 'PR #'),
+}
+
+html_theme = "sphinx_py3doc_enhanced_theme"
+html_theme_path = [sphinx_py3doc_enhanced_theme.get_html_theme_path()]
+html_theme_options = {
+ 'githuburl': 'https://github.com/pytest-dev/pytest-cov/'
+}
+
+html_use_smartypants = True
+html_last_updated_fmt = '%b %d, %Y'
+html_split_index = True
+html_sidebars = {
+ '**': ['searchbox.html', 'globaltoc.html', 'sourcelink.html'],
+}
+html_short_title = '%s-%s' % (project, version)
+
+napoleon_use_ivar = True
+napoleon_use_rtype = False
+napoleon_use_param = False
--- /dev/null
+=============
+Configuration
+=============
+
+This plugin provides a clean minimal set of command line options that are added to pytest. For
+further control of coverage use a coverage config file.
+
+For example if tests are contained within the directory tree being measured the tests may be
+excluded if desired by using a .coveragerc file with the omit option set::
+
+ pytest --cov-config=.coveragerc
+ --cov=myproj
+ myproj/tests/
+
+Where the .coveragerc file contains file globs::
+
+ [run]
+ omit = tests/*
+
+For full details refer to the `coverage config file`_ documentation.
+
+.. _`coverage config file`: https://coverage.readthedocs.io/en/latest/config.html
+
+Note that this plugin controls some options and setting the option in the config file will have no
+effect. These include specifying source to be measured (source option) and all data file handling
+(data_file and parallel options).
+
+If you wish to always add pytest-cov with pytest, you can use ``addopts`` under ``pytest`` or ``tool:pytest`` section.
+For example: ::
+
+ [tool:pytest]
+ addopts = --cov=<project-name> --cov-report html
+
+Caveats
+=======
+
+A unfortunate consequence of coverage.py's history is that ``.coveragerc`` is a magic name: it's the default file but it also
+means "try to also lookup coverage configuration in ``tox.ini`` or ``setup.cfg``".
+
+In practical terms this means that if you have your coverage configuration in ``tox.ini`` or ``setup.cfg`` it is paramount
+that you also use ``--cov-config=tox.ini`` or ``--cov-config=setup.cfg``.
+
+You might not be affected but it's unlikely that you won't ever use ``chdir`` in a test.
+
+Reference
+=========
+
+The complete list of command line options is:
+
+ --cov=PATH Measure coverage for filesystem path. (multi-allowed)
+ --cov-report=type Type of report to generate: term, term-missing,
+ annotate, html, xml (multi-allowed). term, term-
+ missing may be followed by ":skip-covered". annotate,
+ html and xml may be followed by ":DEST" where DEST
+ specifies the output location. Use --cov-report= to
+ not generate any output.
+ --cov-config=path Config file for coverage. Default: .coveragerc
+ --no-cov-on-fail Do not report coverage if test run fails. Default:
+ False
+ --no-cov Disable coverage report completely (useful for
+ debuggers). Default: False
+ --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
+ --cov-branch Enable branch coverage.
+ --cov-context Choose the method for setting the dynamic context.
--- /dev/null
+========
+Contexts
+========
+
+Coverage.py 5.0 can record separate coverage data for different contexts during
+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.
+
+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
+are separated with a pipe symbol. You might see contexts like::
+
+ test_functions.py::test_addition|run
+ test_fancy.py::test_parametrized[1-101]|setup
+ test_oldschool.py::RegressionTests::test_error|run
+
+Note that parameterized tests include the values of the parameters in the test
+id, and each set of parameter values is recorded as a separate test.
--- /dev/null
+.. include:: ../CONTRIBUTING.rst
--- /dev/null
+=====================
+Debuggers and PyCharm
+=====================
+
+(or other IDEs)
+
+When it comes to TDD one obviously would like to debug tests. Debuggers in Python use mostly the sys.settrace function
+to gain access to context. Coverage uses the same technique to get access to the lines executed. Coverage does not play
+well with other tracers simultaneously running. This manifests itself in behaviour that PyCharm might not hit a
+breakpoint no matter what the user does. Since it is common practice to have coverage configuration in the pytest.ini
+file and pytest does not support removeopts or similar the `--no-cov` flag can disable coverage completely.
+
+At the reporting part a warning message will show on screen::
+
+ Coverage disabled via --no-cov switch!
--- /dev/null
+Welcome to pytest-cov's documentation!
+======================================
+
+Contents:
+
+.. toctree::
+ :maxdepth: 2
+
+ readme
+ config
+ reporting
+ debuggers
+ xdist
+ subprocess-support
+ contexts
+ tox
+ plugins
+ markers-fixtures
+ changelog
+ authors
+ releasing
+ contributing
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
--- /dev/null
+====================
+Markers and fixtures
+====================
+
+There are some builtin markers and fixtures in ``pytest-cov``.
+
+Markers
+=======
+
+``no_cover``
+------------
+
+Eg:
+
+.. code-block:: python
+
+ @pytest.mark.no_cover
+ def test_foobar():
+ # do some stuff that needs coverage disabled
+
+.. warning:: Caveat
+
+ Note that subprocess coverage will also be disabled.
+
+Fixtures
+========
+
+``no_cover``
+------------
+
+Eg:
+
+.. code-block:: python
+
+ def test_foobar(no_cover):
+ # same as the marker ...
+
+``cov``
+-------
+
+For reasons that no one can remember there is a ``cov`` fixture that provides access to the underlying Coverage instance.
+Some say this is a disguised foot-gun and should be removed, and some think mysteries make life more interesting and it should
+be left alone.
--- /dev/null
+===============
+Plugin coverage
+===============
+
+Getting coverage on pytest plugins is a very particular situation. Because how pytest implements plugins (using setuptools
+entrypoints) it doesn't allow controlling the order in which the plugins load.
+See `pytest/issues/935 <https://github.com/pytest-dev/pytest/issues/935#issuecomment-245107960>`_ for technical details.
+
+The current way of dealing with this problem is using the append feature and manually starting ``pytest-cov``'s engine, eg::
+
+ COV_CORE_SOURCE=src COV_CORE_CONFIG=.coveragerc COV_CORE_DATAFILE=.coverage.eager pytest --cov=src --cov-append
+
+Alternatively you can have this in ``tox.ini`` (if you're using `Tox <https://tox.readthedocs.io/en/latest/>`_ of course)::
+
+ [testenv]
+ setenv =
+ COV_CORE_SOURCE=
+ COV_CORE_CONFIG={toxinidir}/.coveragerc
+ COV_CORE_DATAFILE={toxinidir}/.coverage
+
+And in ``pytest.ini`` / ``tox.ini`` / ``setup.cfg``::
+
+ [tool:pytest]
+ addopts = --cov --cov-append
--- /dev/null
+.. include:: ../README.rst
--- /dev/null
+=========
+Releasing
+=========
+
+The process for releasing should follow these steps:
+
+#. Test that docs build and render properly by running ``tox -e docs,spell``.
+
+ If there are bogus spelling issues add the words in ``spelling_wordlist.txt``.
+#. Update ``CHANGELOG.rst`` and ``AUTHORS.rst`` to be up to date.
+#. Bump the version by running ``bumpversion [ major | minor | patch ]``. This will automatically add a tag.
+
+ Alternatively, you can manually edit the files and run ``git tag v1.2.3`` yourself.
+#. Push changes and tags with::
+
+ git push
+ git push --tags
+#. Wait for `AppVeyor <https://ci.appveyor.com/project/pytestbot/pytest-cov>`_
+ and `Travis <https://travis-ci.org/schlamar/pytest-cov>`_ to give the green builds.
+#. Check that the docs on `ReadTheDocs <https://readthedocs.org/projects/pytest-cov>`_ 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``)::
+
+ rm -rf dist build src/*.egg-info
+
+ These files need to be removed to force distutils/setuptools to rebuild everything and recreate the egg-info metadata.
+#. Build the dists::
+
+ python3 setup.py clean --all sdist bdist_wheel
+
+#. Verify that the resulting archives (found in ``dist/``) are good.
+#. Upload the sdist and wheel with twine::
+
+ twine upload dist/*
--- /dev/null
+Reporting
+=========
+
+It is possible to generate any combination of the reports for a single test run.
+
+The available reports are terminal (with or without missing line numbers shown), HTML, XML and
+annotated source code.
+
+The terminal report without line numbers (default)::
+
+ pytest --cov-report term --cov=myproj tests/
+
+ -------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
+ Name Stmts Miss Cover
+ ----------------------------------------
+ myproj/__init__ 2 0 100%
+ myproj/myproj 257 13 94%
+ myproj/feature4286 94 7 92%
+ ----------------------------------------
+ TOTAL 353 20 94%
+
+
+The terminal report with line numbers::
+
+ pytest --cov-report term-missing --cov=myproj tests/
+
+ -------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
+ Name Stmts Miss Cover Missing
+ --------------------------------------------------
+ myproj/__init__ 2 0 100%
+ myproj/myproj 257 13 94% 24-26, 99, 149, 233-236, 297-298, 369-370
+ myproj/feature4286 94 7 92% 183-188, 197
+ --------------------------------------------------
+ TOTAL 353 20 94%
+
+The terminal report with skip covered::
+
+ pytest --cov-report term:skip-covered --cov=myproj tests/
+
+ -------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
+ Name Stmts Miss Cover
+ ----------------------------------------
+ myproj/myproj 257 13 94%
+ myproj/feature4286 94 7 92%
+ ----------------------------------------
+ TOTAL 353 20 94%
+
+ 1 files skipped due to complete coverage.
+
+You can use ``skip-covered`` with ``term-missing`` as well. e.g. ``--cov-report term-missing:skip-covered``
+
+These three report options output to files without showing anything on the terminal::
+
+ pytest --cov-report html
+ --cov-report xml
+ --cov-report annotate
+ --cov=myproj tests/
+
+The output location for each of these reports can be specified. The output location for the XML
+report is a file. Where as the output location for the HTML and annotated source code reports are
+directories::
+
+ pytest --cov-report html:cov_html
+ --cov-report xml:cov.xml
+ --cov-report annotate:cov_annotate
+ --cov=myproj tests/
+
+The final report option can also suppress printing to the terminal::
+
+ pytest --cov-report= --cov=myproj tests/
+
+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.
--- /dev/null
+sphinx==3.0.3
+sphinx-py3doc-enhanced-theme==2.4.0
+docutils==0.16
+-e .
--- /dev/null
+builtin
+builtins
+classmethod
+staticmethod
+classmethods
+staticmethods
+args
+kwargs
+callstack
+Changelog
+Indices
--- /dev/null
+==================
+Subprocess support
+==================
+
+Normally coverage writes the data via a pretty standard atexit handler. However, if the subprocess doesn't exit on its
+own then the atexit handler might not run. Why that happens is best left to the adventurous to discover by waddling
+through the Python bug tracker.
+
+pytest-cov supports subprocesses and multiprocessing, and works around these atexit limitations. However, there are a
+few pitfalls that need to be explained.
+
+If you use ``multiprocessing.Pool``
+===================================
+
+**pytest-cov** automatically registers a multiprocessing finalizer. The finalizer will only run reliably if the pool is
+closed. Closing the pool basically signals the workers that there will be no more work, and they will eventually exit.
+Thus one also needs to call `join` on the pool.
+
+If you use ``multiprocessing.Pool.terminate`` or the context manager API (``__exit__``
+will just call ``terminate``) then the workers can get SIGTERM and then the finalizers won't run or complete in time.
+Thus you need to make sure your ``multiprocessing.Pool`` gets a nice and clean exit:
+
+.. code-block:: python
+
+ from multiprocessing import Pool
+
+ def f(x):
+ return x*x
+
+ if __name__ == '__main__':
+ p = Pool(5)
+ try:
+ print(p.map(f, [1, 2, 3]))
+ finally:
+ p.close() # Marks the pool as closed.
+ p.join() # Waits for workers to exit.
+
+
+If you must use the context manager API (e.g.: the pool is managed in third party code you can't change) then you can
+register a cleaning SIGTERM handler like so:
+
+.. warning::
+
+ **This technique cannot be used on Python 3.8** (registering signal handlers will cause deadlocks in the pool,
+ see: https://bugs.python.org/issue38227).
+
+.. code-block:: python
+
+ from multiprocessing import Pool
+
+ def f(x):
+ return x*x
+
+ if __name__ == '__main__':
+ try:
+ from pytest_cov.embed import cleanup_on_sigterm
+ except ImportError:
+ pass
+ else:
+ cleanup_on_sigterm()
+
+ with Pool(5) as p:
+ print(p.map(f, [1, 2, 3]))
+
+If you use ``multiprocessing.Process``
+======================================
+
+There's similar issue when using the ``Process`` objects. Don't forget to use ``.join()``:
+
+.. code-block:: python
+
+ from multiprocessing import Process
+
+ def f(name):
+ print('hello', name)
+
+ if __name__ == '__main__':
+ try:
+ from pytest_cov.embed import cleanup_on_sigterm
+ except ImportError:
+ pass
+ else:
+ cleanup_on_sigterm()
+
+ p = Process(target=f, args=('bob',))
+ try:
+ p.start()
+ finally:
+ p.join() # necessary so that the Process exists before the test suite exits (thus coverage is collected)
+
+.. _cleanup_on_sigterm:
+
+If you got custom signal handling
+=================================
+
+**pytest-cov 2.6** has a rudimentary ``pytest_cov.embed.cleanup_on_sigterm`` you can use to register a SIGTERM handler
+that flushes the coverage data.
+
+**pytest-cov 2.7** adds a ``pytest_cov.embed.cleanup_on_signal`` function and changes the implementation to be more
+robust: the handler will call the previous handler (if you had previously registered any), and is re-entrant (will
+defer extra signals if delivered while the handler runs).
+
+For example, if you reload on SIGHUP you should have something like this:
+
+.. code-block:: python
+
+ import os
+ import signal
+
+ def restart_service(frame, signum):
+ os.exec( ... ) # or whatever your custom signal would do
+ signal.signal(signal.SIGHUP, restart_service)
+
+ try:
+ from pytest_cov.embed import cleanup_on_signal
+ except ImportError:
+ pass
+ else:
+ cleanup_on_signal(signal.SIGHUP)
+
+Note that both ``cleanup_on_signal`` and ``cleanup_on_sigterm`` will run the previous signal handler.
+
+Alternatively you can do this:
+
+.. code-block:: python
+
+ import os
+ import signal
+
+ try:
+ from pytest_cov.embed import cleanup
+ except ImportError:
+ cleanup = None
+
+ def restart_service(frame, signum):
+ if cleanup is not None:
+ cleanup()
+
+ os.exec( ... ) # or whatever your custom signal would do
+ signal.signal(signal.SIGHUP, restart_service)
+
+If you use Windows
+==================
+
+On Windows you can register a handler for SIGTERM but it doesn't actually work. It will work if you
+`os.kill(os.getpid(), signal.SIGTERM)` (send SIGTERM to the current process) but for most intents and purposes that's
+completely useless.
+
+Consequently this means that if you use multiprocessing you got no choice but to use the close/join pattern as described
+above. Using the context manager API or `terminate` won't work as it relies on SIGTERM.
+
+However you can have a working handler for SIGBREAK (with some caveats):
+
+.. code-block:: python
+
+ import os
+ import signal
+
+ def shutdown(frame, signum):
+ # your app's shutdown or whatever
+ signal.signal(signal.SIGBREAK, shutdown)
+
+ try:
+ from pytest_cov.embed import cleanup_on_signal
+ except ImportError:
+ pass
+ else:
+ cleanup_on_signal(signal.SIGBREAK)
+
+The `caveats <https://stefan.sofa-rockers.org/2013/08/15/handling-sub-process-hierarchies-python-linux-os-x/>`_ being
+roughly:
+
+* you need to deliver ``signal.CTRL_BREAK_EVENT``
+* it gets delivered to the whole process group, and that can have unforeseen consequences
--- /dev/null
+===
+Tox
+===
+
+When using `tox <https://tox.readthedocs.io/>`_ you can have ultra-compact configuration - you can have all of it in
+``tox.ini``::
+
+ [tox]
+ envlist = ...
+
+ [tool:pytest]
+ ...
+
+ [coverage:paths]
+ ...
+
+ [coverage:run]
+ ...
+
+ [coverage:report]
+ ..
+
+ [testenv]
+ commands = ...
+
+An usual problem users have is that pytest-cov will erase the previous coverage data by default, thus if you run tox
+with multiple environments you'll get incomplete coverage at the end.
+
+To prevent this problem you need to use ``--cov-append``. It's still recommended to clean the previous coverage data to
+have consistent output. A ``tox.ini`` like this should be enough for sequential runs::
+
+ [tox]
+ envlist = clean,py27,py36,...
+
+ [testenv]
+ commands = pytest --cov --cov-append --cov-report=term-missing ...
+ deps =
+ pytest
+ pytest-cov
+
+ [testenv:clean]
+ deps = coverage
+ skip_install = true
+ commands = coverage erase
+
+For parallel runs we need to set some dependencies and have an extra report env like so::
+
+ [tox]
+ envlist = clean,py27,py36,report
+
+ [testenv]
+ commands = pytest --cov --cov-append --cov-report=term-missing
+ deps =
+ pytest
+ pytest-cov
+ depends =
+ {py27,py36}: clean
+ report: py27,py36
+
+ [testenv:report]
+ deps = coverage
+ skip_install = true
+ commands =
+ coverage report
+ coverage html
+
+ [testenv:clean]
+ deps = coverage
+ skip_install = true
+ commands = coverage erase
+
+Depending on your project layout you might need extra configuration, see the working examples at
+https://github.com/pytest-dev/pytest-cov/tree/master/examples for two common layouts.
--- /dev/null
+===========================
+Distributed testing (xdist)
+===========================
+
+"load" mode
+===========
+
+Distributed testing with dist mode set to "load" will report on the combined coverage of all workers.
+The workers may be spread out over any number of hosts and each worker may be located anywhere on the
+file system. Each worker will have its subprocesses measured.
+
+Running distributed testing with dist mode set to load::
+
+ pytest --cov=myproj -n 2 tests/
+
+Shows a terminal report::
+
+ -------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
+ Name Stmts Miss Cover
+ ----------------------------------------
+ myproj/__init__ 2 0 100%
+ myproj/myproj 257 13 94%
+ myproj/feature4286 94 7 92%
+ ----------------------------------------
+ TOTAL 353 20 94%
+
+
+Again but spread over different hosts and different directories::
+
+ pytest --cov=myproj --dist load
+ --tx ssh=memedough@host1//chdir=testenv1
+ --tx ssh=memedough@host2//chdir=/tmp/testenv2//python=/tmp/env1/bin/python
+ --rsyncdir myproj --rsyncdir tests --rsync examples
+ tests/
+
+Shows a terminal report::
+
+ -------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
+ Name Stmts Miss Cover
+ ----------------------------------------
+ myproj/__init__ 2 0 100%
+ myproj/myproj 257 13 94%
+ myproj/feature4286 94 7 92%
+ ----------------------------------------
+ TOTAL 353 20 94%
+
+
+"each" mode
+===========
+
+Distributed testing with dist mode set to each will report on the combined coverage of all workers.
+Since each worker is running all tests this allows generating a combined coverage report for multiple
+environments.
+
+Running distributed testing with dist mode set to each::
+
+ pytest --cov=myproj --dist each
+ --tx popen//chdir=/tmp/testenv3//python=/usr/local/python27/bin/python
+ --tx ssh=memedough@host2//chdir=/tmp/testenv4//python=/tmp/env2/bin/python
+ --rsyncdir myproj --rsyncdir tests --rsync examples
+ tests/
+
+Shows a terminal report::
+
+ ---------------------------------------- coverage ----------------------------------------
+ platform linux2, python 2.6.5-final-0
+ platform linux2, python 2.7.0-final-0
+ Name Stmts Miss Cover
+ ----------------------------------------
+ myproj/__init__ 2 0 100%
+ myproj/myproj 257 13 94%
+ myproj/feature4286 94 7 92%
+ ----------------------------------------
+ TOTAL 353 20 94%
--- /dev/null
+Simple examples with ``tox.ini``
+================================
+
+These examples provide necessary configuration to:
+
+* aggregate coverage from multiple interpreters
+* support tox parallel mode
+* run tests on installed code
+
+The `adhoc` layout is the old and problematic layout where you can mix up the installed code
+with the source code. However, these examples will provide correct configuration even for
+the `adhoc` layout.
+
+The `src` layout configuration is less complicated, have that in mind when picking a layout
+for your project.
--- /dev/null
+[paths]
+source =
+ ../example
+ */site-packages/example
+
+[run]
+branch = true
+parallel = true
+source =
+ example
+ .
+
+[report]
+show_missing = true
+precision = 2
--- /dev/null
+
+import sys
+
+PY2 = sys.version_info[0] == 2
+
+
+if PY2:
+ def add(a, b):
+ return b + a
+
+else:
+ def add(a, b):
+ return a + b
--- /dev/null
+from setuptools import find_packages
+from setuptools import setup
+
+setup(
+ name='example',
+ packages=find_packages(include=['example'])
+)
--- /dev/null
+import example
+
+
+def test_add():
+ assert example.add(1, 1) == 2
+ assert not example.add(0, 1) == 2
--- /dev/null
+[tox]
+envlist = clean,py27,py38,report
+
+[tool:pytest]
+addopts =
+ --cov-report=term-missing
+
+[testenv]
+commands = pytest --cov --cov-append --cov-config={toxinidir}/.coveragerc {posargs:-vv}
+deps =
+ pytest
+ coverage
+# Note:
+# This is here just to allow examples to be tested against
+# the current code of pytest-cov. If you copy this then
+# use "pytest-cov" instead of "../.."
+ ../..
+
+depends =
+ {py27,py38}: clean
+ report: py27,py38
+
+# note that this is necessary to prevent the tests importing the code from your badly laid project
+changedir = tests
+
+[testenv:report]
+skip_install = true
+deps = coverage
+commands =
+ coverage html
+ coverage report --fail-under=100
+
+[testenv:clean]
+skip_install = true
+deps = coverage
+commands = coverage erase
--- /dev/null
+[paths]
+source =
+ src
+ */site-packages
+
+[run]
+branch = true
+parallel = true
+source =
+ example
+ tests
+
+[report]
+show_missing = true
+precision = 2
--- /dev/null
+from setuptools import find_packages
+from setuptools import setup
+
+setup(
+ name='example',
+ packages=find_packages('src'),
+ package_dir={'': 'src'},
+)
--- /dev/null
+
+import sys
+
+PY2 = sys.version_info[0] == 2
+
+
+if PY2:
+ def add(a, b):
+ return b + a
+
+else:
+ def add(a, b):
+ return a + b
--- /dev/null
+import example
+
+
+def test_add():
+ assert example.add(1, 1) == 2
+ assert not example.add(0, 1) == 2
--- /dev/null
+[tox]
+envlist = clean,py27,py38,report
+
+[tool:pytest]
+testpaths = tests
+addopts =
+ --cov-report=term-missing
+
+[testenv]
+commands = pytest --cov --cov-append {posargs:-vv}
+deps =
+ pytest
+ coverage
+# Note:
+# This is here just to allow examples to be tested against
+# the current code of pytest-cov. If you copy this then
+# use "pytest-cov" instead of "../.."
+ ../..
+
+depends =
+ {py27,py38}: clean
+ report: py27,py38
+
+[testenv:report]
+skip_install = true
+deps = coverage
+commands =
+ coverage html
+ coverage report --fail-under=100
+
+[testenv:clean]
+skip_install = true
+deps = coverage
+commands = coverage erase
--- /dev/null
+[bdist_wheel]
+universal = 1
+
+[flake8]
+max-line-length = 140
+exclude = .tox,.eggs,ci/templates,build,dist
+
+[tool:pytest]
+testpaths = tests
+python_files = test_*.py
+addopts =
+ -ra
+ --strict
+ -p pytester
+
+[tool:isort]
+force_single_line = True
+line_length = 120
+known_first_party = pytest_cov
+default_section = THIRDPARTY
+forced_separate = test_pytest_cov
+skip = .tox,.eggs,ci/templates,build,dist
+
+[egg_info]
+tag_build =
+tag_date = 0
+
--- /dev/null
+#!/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
+from itertools import chain
+from os.path import basename
+from os.path import dirname
+from os.path import join
+from os.path import splitext
+
+from setuptools import Command
+from setuptools import find_packages
+from setuptools import setup
+from setuptools.command.develop import develop
+from setuptools.command.easy_install import easy_install
+from setuptools.command.install_lib import install_lib
+
+
+def read(*names, **kwargs):
+ with io.open(
+ join(dirname(__file__), *names),
+ encoding=kwargs.get('encoding', 'utf8')
+ ) as fh:
+ return fh.read()
+
+
+class BuildWithPTH(build):
+ def run(self):
+ build.run(self)
+ path = join(dirname(__file__), 'src', 'pytest-cov.pth')
+ dest = join(self.build_lib, basename(path))
+ self.copy_file(path, dest)
+
+
+class EasyInstallWithPTH(easy_install):
+ def run(self):
+ easy_install.run(self)
+ path = join(dirname(__file__), 'src', 'pytest-cov.pth')
+ dest = join(self.install_dir, basename(path))
+ self.copy_file(path, dest)
+
+
+class InstallLibWithPTH(install_lib):
+ def run(self):
+ install_lib.run(self)
+ path = join(dirname(__file__), 'src', 'pytest-cov.pth')
+ dest = join(self.install_dir, basename(path))
+ self.copy_file(path, dest)
+ self.outputs = [dest]
+
+ def get_outputs(self):
+ return chain(install_lib.get_outputs(self), self.outputs)
+
+
+class DevelopWithPTH(develop):
+ def run(self):
+ develop.run(self)
+ path = join(dirname(__file__), 'src', 'pytest-cov.pth')
+ dest = join(self.install_dir, basename(path))
+ self.copy_file(path, dest)
+
+
+class GeneratePTH(Command):
+ user_options = []
+
+ def initialize_options(self):
+ pass
+
+ def finalize_options(self):
+ pass
+
+ def run(self):
+ with open(join(dirname(__file__), 'src', 'pytest-cov.pth'), 'w') as fh:
+ with open(join(dirname(__file__), 'src', 'pytest-cov.embed')) as sh:
+ fh.write(
+ 'import os, sys;'
+ 'exec(%r)' % sh.read().replace(' ', ' ')
+ )
+
+
+setup(
+ name='pytest-cov',
+ version='2.10.1',
+ 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'))),
+ author='Marc Schlaich',
+ author_email='marc.schlaich@gmail.com',
+ url='https://github.com/pytest-dev/pytest-cov',
+ packages=find_packages('src'),
+ package_dir={'': 'src'},
+ py_modules=[splitext(basename(path))[0] for path in glob('src/*.py')],
+ include_package_data=True,
+ zip_safe=False,
+ classifiers=[
+ # complete classifier list: http://pypi.python.org/pypi?%3Aaction=list_classifiers
+ 'Development Status :: 5 - Production/Stable',
+ 'Framework :: Pytest',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: Microsoft :: Windows',
+ '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.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: Implementation :: CPython',
+ 'Programming Language :: Python :: Implementation :: PyPy',
+ 'Topic :: Software Development :: Testing',
+ 'Topic :: Utilities',
+ ],
+ keywords=[
+ 'cover', 'coverage', 'pytest', 'py.test', 'distributed', 'parallel',
+ ],
+ install_requires=[
+ 'pytest>=4.6',
+ 'coverage>=4.4'
+ ],
+ python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
+ extras_require={
+ 'testing': [
+ 'fields',
+ 'hunter',
+ 'process-tests==2.0.2',
+ 'six',
+ 'pytest-xdist',
+ 'virtualenv',
+ ]
+ },
+ entry_points={
+ 'pytest11': [
+ 'pytest_cov = pytest_cov.plugin',
+ ],
+ 'console_scripts': [
+ ]
+ },
+ cmdclass={
+ 'build': BuildWithPTH,
+ 'easy_install': EasyInstallWithPTH,
+ 'install_lib': InstallLibWithPTH,
+ 'develop': DevelopWithPTH,
+ 'genpth': GeneratePTH,
+ },
+)
--- /dev/null
+if 'COV_CORE_SOURCE' in os.environ:
+ try:
+ from pytest_cov.embed import init
+ init()
+ except Exception as exc:
+ sys.stderr.write(
+ "pytest-cov: Failed to setup subprocess coverage. "
+ "Environ: {0!r} "
+ "Exception: {1!r}\n".format(
+ dict((k, v) for k, v in os.environ.items() if k.startswith('COV_CORE')),
+ exc
+ )
+ )
--- /dev/null
+import os, sys;exec('if \'COV_CORE_SOURCE\' in os.environ:\n try:\n from pytest_cov.embed import init\n init()\n except Exception as exc:\n sys.stderr.write(\n "pytest-cov: Failed to setup subprocess coverage. "\n "Environ: {0!r} "\n "Exception: {1!r}\\n".format(\n dict((k, v) for k, v in os.environ.items() if k.startswith(\'COV_CORE\')),\n exc\n )\n )\n')
\ No newline at end of file
--- /dev/null
+Metadata-Version: 2.1
+Name: pytest-cov
+Version: 2.10.1
+Summary: Pytest plugin for measuring coverage.
+Home-page: https://github.com/pytest-dev/pytest-cov
+Author: Marc Schlaich
+Author-email: marc.schlaich@gmail.com
+License: MIT
+Description: ========
+ Overview
+ ========
+
+ .. start-badges
+
+ .. list-table::
+ :stub-columns: 1
+
+ * - docs
+ - |docs|
+ * - tests
+ - | |travis| |appveyor| |requires|
+ * - package
+ - | |version| |conda-forge| |wheel| |supported-versions| |supported-implementations|
+ | |commits-since|
+
+ .. |docs| image:: https://readthedocs.org/projects/pytest-cov/badge/?style=flat
+ :target: https://readthedocs.org/projects/pytest-cov
+ :alt: Documentation Status
+
+ .. |travis| image:: https://api.travis-ci.org/pytest-dev/pytest-cov.svg?branch=master
+ :alt: Travis-CI Build Status
+ :target: https://travis-ci.org/pytest-dev/pytest-cov
+
+ .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/pytest-dev/pytest-cov?branch=master&svg=true
+ :alt: AppVeyor Build Status
+ :target: https://ci.appveyor.com/project/pytestbot/pytest-cov
+
+ .. |requires| image:: https://requires.io/github/pytest-dev/pytest-cov/requirements.svg?branch=master
+ :alt: Requirements Status
+ :target: https://requires.io/github/pytest-dev/pytest-cov/requirements/?branch=master
+
+ .. |version| image:: https://img.shields.io/pypi/v/pytest-cov.svg
+ :alt: PyPI Package latest release
+ :target: https://pypi.org/project/pytest-cov
+
+ .. |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.10.1.svg
+ :alt: Commits since latest release
+ :target: https://github.com/pytest-dev/pytest-cov/compare/v2.10.1...master
+
+ .. |wheel| image:: https://img.shields.io/pypi/wheel/pytest-cov.svg
+ :alt: PyPI Wheel
+ :target: https://pypi.org/project/pytest-cov
+
+ .. |supported-versions| image:: https://img.shields.io/pypi/pyversions/pytest-cov.svg
+ :alt: Supported versions
+ :target: https://pypi.org/project/pytest-cov
+
+ .. |supported-implementations| image:: https://img.shields.io/pypi/implementation/pytest-cov.svg
+ :alt: Supported implementations
+ :target: https://pypi.org/project/pytest-cov
+
+ .. end-badges
+
+ This plugin produces coverage reports. Compared to just using ``coverage run`` this plugin does some extras:
+
+ * Subprocess support: you can fork or run stuff in a subprocess and will get covered without any fuss.
+ * Xdist support: you can use all of pytest-xdist's features and still get coverage.
+ * Consistent pytest behavior. If you run ``coverage run -m pytest`` you will have slightly different ``sys.path`` (CWD will be
+ in it, unlike when running ``pytest``).
+
+ All features offered by the coverage package should work, either through pytest-cov's command line options or
+ through coverage's config file.
+
+ * Free software: MIT license
+
+ Installation
+ ============
+
+ Install with pip::
+
+ pip install pytest-cov
+
+ For distributed testing support install pytest-xdist::
+
+ pip install pytest-xdist
+
+ Upgrading from ancient pytest-cov
+ ---------------------------------
+
+ `pytest-cov 2.0` is using a new ``.pth`` file (``pytest-cov.pth``). You may want to manually remove the older
+ ``init_cov_core.pth`` from site-packages as it's not automatically removed.
+
+ Uninstalling
+ ------------
+
+ Uninstall with pip::
+
+ pip uninstall pytest-cov
+
+ Under certain scenarios a stray ``.pth`` file may be left around in site-packages.
+
+ * `pytest-cov 2.0` may leave a ``pytest-cov.pth`` if you installed without wheels
+ (``easy_install``, ``setup.py install`` etc).
+ * `pytest-cov 1.8 or older` will leave a ``init_cov_core.pth``.
+
+ Usage
+ =====
+
+ ::
+
+ pytest --cov=myproj tests/
+
+ Would produce a report like::
+
+ -------------------- coverage: ... ---------------------
+ Name Stmts Miss Cover
+ ----------------------------------------
+ myproj/__init__ 2 0 100%
+ myproj/myproj 257 13 94%
+ myproj/feature4286 94 7 92%
+ ----------------------------------------
+ TOTAL 353 20 94%
+
+ Documentation
+ =============
+
+ http://pytest-cov.rtfd.org/
+
+
+
+
+
+
+ Coverage Data File
+ ==================
+
+ The data file is erased at the beginning of testing to ensure clean data for each test run. If you
+ need to combine the coverage of several test runs you can use the ``--cov-append`` option to append
+ this coverage data to coverage data from previous test runs.
+
+ The data file is left at the end of testing so that it is possible to use normal coverage tools to
+ examine it.
+
+ Limitations
+ ===========
+
+ For distributed testing the workers must have the pytest-cov package installed. This is needed since
+ the plugin must be registered through setuptools for pytest to start the plugin on the
+ worker.
+
+ For subprocess measurement environment variables must make it from the main process to the
+ subprocess. The python used by the subprocess must have pytest-cov installed. The subprocess must
+ do normal site initialisation so that the environment variables can be detected and coverage
+ started.
+
+
+ Acknowledgements
+ ================
+
+ Whilst this plugin has been built fresh from the ground up it has been influenced by the work done
+ on pytest-coverage (Ross Lawley, James Mills, Holger Krekel) and nose-cover (Jason Pellerin) which are
+ other coverage plugins.
+
+ Ned Batchelder for coverage and its ability to combine the coverage results of parallel runs.
+
+ Holger Krekel for pytest with its distributed testing support.
+
+ Jason Pellerin for nose.
+
+ Michael Foord for unittest2.
+
+ No doubt others have contributed to these tools as well.
+
+ Changelog
+ =========
+
+ 2.10.1 (2020-08-14)
+ -------------------
+
+ * Support for ``pytest-xdist`` 2.0, which breaks compatibility with ``pytest-xdist`` before 1.22.3 (from 2017).
+ Contributed by Zac Hatfield-Dodds in `#412 <https://github.com/pytest-dev/pytest-cov/pull/412>`_.
+ * Fixed the ``LocalPath has no attribute startswith`` failure that occurred when using the ``pytester`` plugin
+ in inline mode.
+
+ 2.10.0 (2020-06-12)
+ -------------------
+
+ * Improved the ``--no-cov`` warning. Now it's only shown if ``--no-cov`` is present before ``--cov``.
+ * Removed legacy pytest support. Changed ``setup.py`` so that ``pytest>=4.6`` is required.
+
+ 2.9.0 (2020-05-22)
+ ------------------
+
+ * Fixed ``RemovedInPytest4Warning`` when using Pytest 3.10.
+ Contributed by Michael Manganiello in `#354 <https://github.com/pytest-dev/pytest-cov/pull/354>`_.
+ * Made pytest startup faster when plugin not active by lazy-importing.
+ Contributed by Anders Hovmöller in `#339 <https://github.com/pytest-dev/pytest-cov/pull/339>`_.
+ * Various CI improvements.
+ Contributed by Daniel Hahler in `#363 <https://github.com/pytest-dev/pytest-cov/pull/>`_ and
+ `#364 <https://github.com/pytest-dev/pytest-cov/pull/364>`_.
+ * Various Python support updates (drop EOL 3.4, test against 3.8 final).
+ Contributed by Hugo van Kemenade in
+ `#336 <https://github.com/pytest-dev/pytest-cov/pull/336>`_ and
+ `#367 <https://github.com/pytest-dev/pytest-cov/pull/367>`_.
+ * Changed ``--cov-append`` to always enable ``data_suffix`` (a coverage setting).
+ Contributed by Harm Geerts in
+ `#387 <https://github.com/pytest-dev/pytest-cov/pull/387>`_.
+ * Changed ``--cov-append`` to handle loading previous data better
+ (fixes various path aliasing issues).
+ * Various other testing improvements, github issue templates, example updates.
+ * Fixed internal failures that are caused by tests that change the current working directory by
+ ensuring a consistent working directory when coverage is called.
+ See `#306 <https://github.com/pytest-dev/pytest-cov/issues/306>`_ and
+ `coveragepy#881 <https://github.com/nedbat/coveragepy/issues/881>`_
+
+ 2.8.1 (2019-10-05)
+ ------------------
+
+ * Fixed `#348 <https://github.com/pytest-dev/pytest-cov/issues/348>`_ -
+ regression when only certain reports (html or xml) are used then ``--cov-fail-under`` always fails.
+
+ 2.8.0 (2019-10-04)
+ ------------------
+
+ * Fixed ``RecursionError`` that can occur when using
+ `cleanup_on_signal <https://pytest-cov.readthedocs.io/en/latest/subprocess-support.html#if-you-got-custom-signal-handling>`__ or
+ `cleanup_on_sigterm <https://pytest-cov.readthedocs.io/en/latest/subprocess-support.html#if-you-got-custom-signal-handling>`__.
+ See: `#294 <https://github.com/pytest-dev/pytest-cov/issues/294>`_.
+ The 2.7.x releases of pytest-cov should be considered broken regarding aforementioned cleanup API.
+ * Added compatibility with future xdist release that deprecates some internals
+ (match pytest-xdist master/worker terminology).
+ Contributed by Thomas Grainger in `#321 <https://github.com/pytest-dev/pytest-cov/pull/321>`_
+ * Fixed breakage that occurs when multiple reporting options are used.
+ Contributed by Thomas Grainger in `#338 <https://github.com/pytest-dev/pytest-cov/pull/338>`_.
+ * Changed internals to use a stub instead of ``os.devnull``.
+ Contributed by Thomas Grainger in `#332 <https://github.com/pytest-dev/pytest-cov/pull/332>`_.
+ * Added support for Coverage 5.0.
+ Contributed by Ned Batchelder in `#319 <https://github.com/pytest-dev/pytest-cov/pull/319>`_.
+ * Added support for float values in ``--cov-fail-under``.
+ Contributed by Martín Gaitán in `#311 <https://github.com/pytest-dev/pytest-cov/pull/311>`_.
+ * Various documentation fixes. Contributed by
+ Juanjo Bazán,
+ Andrew Murray and
+ Albert Tugushev in
+ `#298 <https://github.com/pytest-dev/pytest-cov/pull/298>`_,
+ `#299 <https://github.com/pytest-dev/pytest-cov/pull/299>`_ and
+ `#307 <https://github.com/pytest-dev/pytest-cov/pull/307>`_.
+ * Various testing improvements. Contributed by
+ Ned Batchelder,
+ Daniel Hahler,
+ Ionel Cristian Mărieș and
+ Hugo van Kemenade in
+ `#313 <https://github.com/pytest-dev/pytest-cov/pull/313>`_,
+ `#314 <https://github.com/pytest-dev/pytest-cov/pull/314>`_,
+ `#315 <https://github.com/pytest-dev/pytest-cov/pull/315>`_,
+ `#316 <https://github.com/pytest-dev/pytest-cov/pull/316>`_,
+ `#325 <https://github.com/pytest-dev/pytest-cov/pull/325>`_,
+ `#326 <https://github.com/pytest-dev/pytest-cov/pull/326>`_,
+ `#334 <https://github.com/pytest-dev/pytest-cov/pull/334>`_ and
+ `#335 <https://github.com/pytest-dev/pytest-cov/pull/335>`_.
+ * Added the ``--cov-context`` CLI options that enables coverage contexts. Only works with coverage 5.0+.
+ Contributed by Ned Batchelder in `#345 <https://github.com/pytest-dev/pytest-cov/pull/345>`_.
+
+ 2.7.1 (2019-05-03)
+ ------------------
+
+ * Fixed source distribution manifest so that garbage ain't included in the tarball.
+
+ 2.7.0 (2019-05-03)
+ ------------------
+
+ * Fixed ``AttributeError: 'NoneType' object has no attribute 'configure_node'`` error when ``--no-cov`` is used.
+ Contributed by Alexander Shadchin in `#263 <https://github.com/pytest-dev/pytest-cov/pull/263>`_.
+ * Various testing and CI improvements. Contributed by Daniel Hahler in
+ `#255 <https://github.com/pytest-dev/pytest-cov/pull/255>`_,
+ `#266 <https://github.com/pytest-dev/pytest-cov/pull/266>`_,
+ `#272 <https://github.com/pytest-dev/pytest-cov/pull/272>`_,
+ `#271 <https://github.com/pytest-dev/pytest-cov/pull/271>`_ and
+ `#269 <https://github.com/pytest-dev/pytest-cov/pull/269>`_.
+ * Improved documentation regarding subprocess and multiprocessing.
+ Contributed in `#265 <https://github.com/pytest-dev/pytest-cov/pull/265>`_.
+ * Improved ``pytest_cov.embed.cleanup_on_sigterm`` to be reentrant (signal deliveries while signal handling is
+ running won't break stuff).
+ * Added ``pytest_cov.embed.cleanup_on_signal`` for customized cleanup.
+ * Improved cleanup code and fixed various issues with leftover data files. All contributed in
+ `#265 <https://github.com/pytest-dev/pytest-cov/pull/265>`_ or
+ `#262 <https://github.com/pytest-dev/pytest-cov/pull/262>`_.
+ * Improved examples. Now there are two examples for the common project layouts, complete with working coverage
+ configuration. The examples have CI testing. Contributed in
+ `#267 <https://github.com/pytest-dev/pytest-cov/pull/267>`_.
+ * Improved help text for CLI options.
+
+ 2.6.1 (2019-01-07)
+ ------------------
+
+ * Added support for Pytest 4.1. Contributed by Daniel Hahler and Семён Марьясин in
+ `#253 <https://github.com/pytest-dev/pytest-cov/pull/253>`_ and
+ `#230 <https://github.com/pytest-dev/pytest-cov/pull/230>`_.
+ * Various test and docs fixes. Contributed by Daniel Hahler in
+ `#224 <https://github.com/pytest-dev/pytest-cov/pull/224>`_ and
+ `#223 <https://github.com/pytest-dev/pytest-cov/pull/223>`_.
+ * Fixed the "Module already imported" issue (`#211 <https://github.com/pytest-dev/pytest-cov/issues/211>`_).
+ Contributed by Daniel Hahler in `#228 <https://github.com/pytest-dev/pytest-cov/pull/228>`_.
+
+ 2.6.0 (2018-09-03)
+ ------------------
+
+ * Dropped support for Python 3 < 3.4, Pytest < 3.5 and Coverage < 4.4.
+ * Fixed some documentation formatting. Contributed by Jean Jordaan and Julian.
+ * Added an example with ``addopts`` in documentation. Contributed by Samuel Giffard in
+ `#195 <https://github.com/pytest-dev/pytest-cov/pull/195>`_.
+ * Fixed ``TypeError: 'NoneType' object is not iterable`` in certain xdist configurations. Contributed by Jeremy Bowman in
+ `#213 <https://github.com/pytest-dev/pytest-cov/pull/213>`_.
+ * Added a ``no_cover`` marker and fixture. Fixes
+ `#78 <https://github.com/pytest-dev/pytest-cov/issues/78>`_.
+ * Fixed broken ``no_cover`` check when running doctests. Contributed by Terence Honles in
+ `#200 <https://github.com/pytest-dev/pytest-cov/pull/200>`_.
+ * Fixed various issues with path normalization in reports (when combining coverage data from parallel mode). Fixes
+ `#130 <https://github.com/pytest-dev/pytest-cov/issues/161>`_.
+ Contributed by Ryan Hiebert & Ionel Cristian Mărieș in
+ `#178 <https://github.com/pytest-dev/pytest-cov/pull/178>`_.
+ * Report generation failures don't raise exceptions anymore. A warning will be logged instead. Fixes
+ `#161 <https://github.com/pytest-dev/pytest-cov/issues/161>`_.
+ * Fixed multiprocessing issue on Windows (empty env vars are not passed). Fixes
+ `#165 <https://github.com/pytest-dev/pytest-cov/issues/165>`_.
+
+ 2.5.1 (2017-05-11)
+ ------------------
+
+ * Fixed xdist breakage (regression in ``2.5.0``).
+ Fixes `#157 <https://github.com/pytest-dev/pytest-cov/issues/157>`_.
+ * Allow setting custom ``data_file`` name in ``.coveragerc``.
+ Fixes `#145 <https://github.com/pytest-dev/pytest-cov/issues/145>`_.
+ Contributed by Jannis Leidel & Ionel Cristian Mărieș in
+ `#156 <https://github.com/pytest-dev/pytest-cov/pull/156>`_.
+
+ 2.5.0 (2017-05-09)
+ ------------------
+
+ * Always show a summary when ``--cov-fail-under`` is used. Contributed by Francis Niu in `PR#141
+ <https://github.com/pytest-dev/pytest-cov/pull/141>`_.
+ * Added ``--cov-branch`` option. Fixes `#85 <https://github.com/pytest-dev/pytest-cov/issues/85>`_.
+ * Improve exception handling in subprocess setup. Fixes `#144 <https://github.com/pytest-dev/pytest-cov/issues/144>`_.
+ * Fixed handling when ``--cov`` is used multiple times. Fixes `#151 <https://github.com/pytest-dev/pytest-cov/issues/151>`_.
+
+ 2.4.0 (2016-10-10)
+ ------------------
+
+ * Added a "disarm" option: ``--no-cov``. It will disable coverage measurements. Contributed by Zoltan Kozma in
+ `PR#135 <https://github.com/pytest-dev/pytest-cov/pull/135>`_.
+
+ **WARNING: Do not put this in your configuration files, it's meant to be an one-off for situations where you want to
+ disable coverage from command line.**
+ * Fixed broken exception handling on ``.pth`` file. See `#136 <https://github.com/pytest-dev/pytest-cov/issues/136>`_.
+
+ 2.3.1 (2016-08-07)
+ ------------------
+
+ * Fixed regression causing spurious errors when xdist was used. See `#124
+ <https://github.com/pytest-dev/pytest-cov/issues/124>`_.
+ * Fixed DeprecationWarning about incorrect `addoption` use. Contributed by Florian Bruhin in `PR#127
+ <https://github.com/pytest-dev/pytest-cov/pull/127>`_.
+ * Fixed deprecated use of funcarg fixture API. Contributed by Daniel Hahler in `PR#125
+ <https://github.com/pytest-dev/pytest-cov/pull/125>`_.
+
+ 2.3.0 (2016-07-05)
+ ------------------
+
+ * Add support for specifying output location for html, xml, and annotate report.
+ Contributed by Patrick Lannigan in `PR#113 <https://github.com/pytest-dev/pytest-cov/pull/113>`_.
+ * Fix bug hiding test failure when cov-fail-under failed.
+ * For coverage >= 4.0, match the default behaviour of `coverage report` and
+ error if coverage fails to find the source instead of just printing a warning.
+ Contributed by David Szotten in `PR#116 <https://github.com/pytest-dev/pytest-cov/pull/116>`_.
+ * Fixed bug occurred when bare ``--cov`` parameter was used with xdist.
+ Contributed by Michael Elovskikh in `PR#120 <https://github.com/pytest-dev/pytest-cov/pull/120>`_.
+ * Add support for ``skip_covered`` and added ``--cov-report=term-skip-covered`` command
+ line options. Contributed by Saurabh Kumar in `PR#115 <https://github.com/pytest-dev/pytest-cov/pull/115>`_.
+
+ 2.2.1 (2016-01-30)
+ ------------------
+
+ * Fixed incorrect merging of coverage data when xdist was used and coverage was ``>= 4.0``.
+
+ 2.2.0 (2015-10-04)
+ ------------------
+
+ * Added support for changing working directory in tests. Previously changing working
+ directory would disable coverage measurements in suprocesses.
+ * Fixed broken handling for ``--cov-report=annotate``.
+
+ 2.1.0 (2015-08-23)
+ ------------------
+
+ * Added support for `coverage 4.0b2`.
+ * Added the ``--cov-append`` command line options. Contributed by Christian Ledermann
+ in `PR#80 <https://github.com/pytest-dev/pytest-cov/pull/80>`_.
+
+ 2.0.0 (2015-07-28)
+ ------------------
+
+ * Added ``--cov-fail-under``, akin to the new ``fail_under`` option in `coverage-4.0`
+ (automatically activated if there's a ``[report] fail_under = ...`` in ``.coveragerc``).
+ * Changed ``--cov-report=term`` to automatically upgrade to ``--cov-report=term-missing``
+ if there's ``[run] show_missing = True`` in ``.coveragerc``.
+ * Changed ``--cov`` so it can be used with no path argument (in which case the source
+ settings from ``.coveragerc`` will be used instead).
+ * Fixed `.pth` installation to work in all cases (install, easy_install, wheels, develop etc).
+ * Fixed `.pth` uninstallation to work for wheel installs.
+ * Support for coverage 4.0.
+ * Data file suffixing changed to use coverage's ``data_suffix=True`` option (instead of the
+ custom suffixing).
+ * Avoid warning about missing coverage data (just like ``coverage.control.process_startup``).
+ * Fixed a race condition when running with xdist (all the workers tried to combine the files).
+ It's possible that this issue is not present in `pytest-cov 1.8.X`.
+
+ 1.8.2 (2014-11-06)
+ ------------------
+
+ * N/A
+
+Keywords: cover,coverage,pytest,py.test,distributed,parallel
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Framework :: Pytest
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: Unix
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Topic :: Software Development :: Testing
+Classifier: Topic :: Utilities
+Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
+Provides-Extra: testing
--- /dev/null
+.appveyor.yml
+.bumpversion.cfg
+.cookiecutterrc
+.editorconfig
+.gitignore
+.pre-commit-config.yaml
+.readthedocs.yml
+.travis.yml
+AUTHORS.rst
+CHANGELOG.rst
+CONTRIBUTING.rst
+LICENSE
+MANIFEST.in
+README.rst
+setup.cfg
+setup.py
+tox.ini
+.github/ISSUE_TEMPLATE/bug_report.md
+.github/ISSUE_TEMPLATE/feature_request.md
+.github/ISSUE_TEMPLATE/support_request.md
+ci/appveyor-with-compiler.cmd
+ci/bootstrap.py
+ci/requirements.txt
+ci/templates/.appveyor.yml
+ci/templates/.travis.yml
+docs/authors.rst
+docs/changelog.rst
+docs/conf.py
+docs/config.rst
+docs/contexts.rst
+docs/contributing.rst
+docs/debuggers.rst
+docs/index.rst
+docs/markers-fixtures.rst
+docs/plugins.rst
+docs/readme.rst
+docs/releasing.rst
+docs/reporting.rst
+docs/requirements.txt
+docs/spelling_wordlist.txt
+docs/subprocess-support.rst
+docs/tox.rst
+docs/xdist.rst
+examples/README.rst
+examples/adhoc-layout/.coveragerc
+examples/adhoc-layout/setup.py
+examples/adhoc-layout/tox.ini
+examples/adhoc-layout/example/__init__.py
+examples/adhoc-layout/tests/test_example.py
+examples/src-layout/.coveragerc
+examples/src-layout/setup.py
+examples/src-layout/tox.ini
+examples/src-layout/src/example/__init__.py
+examples/src-layout/tests/test_example.py
+src/pytest-cov.embed
+src/pytest-cov.pth
+src/pytest_cov/__init__.py
+src/pytest_cov/compat.py
+src/pytest_cov/embed.py
+src/pytest_cov/engine.py
+src/pytest_cov/plugin.py
+src/pytest_cov.egg-info/PKG-INFO
+src/pytest_cov.egg-info/SOURCES.txt
+src/pytest_cov.egg-info/dependency_links.txt
+src/pytest_cov.egg-info/entry_points.txt
+src/pytest_cov.egg-info/not-zip-safe
+src/pytest_cov.egg-info/requires.txt
+src/pytest_cov.egg-info/top_level.txt
+tests/conftest.py
+tests/contextful.py
+tests/helper.py
+tests/test_pytest_cov.py
\ No newline at end of file
--- /dev/null
+[console_scripts]
+
+
+[pytest11]
+pytest_cov = pytest_cov.plugin
+
--- /dev/null
+pytest>=4.6
+coverage>=4.4
+
+[testing]
+fields
+hunter
+process-tests==2.0.2
+six
+pytest-xdist
+virtualenv
--- /dev/null
+pytest_cov
--- /dev/null
+"""pytest-cov: avoid already-imported warning: PYTEST_DONT_REWRITE."""
+__version__ = "__version__ = '2.10.1'"
--- /dev/null
+try:
+ from StringIO import StringIO
+except ImportError:
+ from io import StringIO
+
+import pytest
+
+StringIO # pyflakes, this is for re-export
+
+
+if hasattr(pytest, 'hookimpl'):
+ hookwrapper = pytest.hookimpl(hookwrapper=True)
+else:
+ hookwrapper = pytest.mark.hookwrapper
+
+
+class SessionWrapper(object):
+ def __init__(self, session):
+ self._session = session
+ if hasattr(session, 'testsfailed'):
+ self._attr = 'testsfailed'
+ else:
+ self._attr = '_testsfailed'
+
+ @property
+ def testsfailed(self):
+ return getattr(self._session, self._attr)
+
+ @testsfailed.setter
+ def testsfailed(self, value):
+ setattr(self._session, self._attr, value)
--- /dev/null
+"""Activate coverage at python startup if appropriate.
+
+The python site initialisation will ensure that anything we import
+will be removed and not visible at the end of python startup. However
+we minimise all work by putting these init actions in this separate
+module and only importing what is needed when needed.
+
+For normal python startup when coverage should not be activated the pth
+file checks a single env var and does not import or call the init fn
+here.
+
+For python startup when an ancestor process has set the env indicating
+that code coverage is being collected we activate coverage based on
+info passed via env vars.
+"""
+import atexit
+import os
+import signal
+
+_active_cov = None
+
+
+def multiprocessing_start(_):
+ global _active_cov
+ cov = init()
+ if cov:
+ _active_cov = cov
+ multiprocessing.util.Finalize(None, cleanup, exitpriority=1000)
+
+
+try:
+ import multiprocessing.util
+except ImportError:
+ pass
+else:
+ multiprocessing.util.register_after_fork(multiprocessing_start, multiprocessing_start)
+
+
+def init():
+ # Only continue if ancestor process has set everything needed in
+ # the env.
+ global _active_cov
+
+ cov_source = os.environ.get('COV_CORE_SOURCE')
+ cov_config = os.environ.get('COV_CORE_CONFIG')
+ cov_datafile = os.environ.get('COV_CORE_DATAFILE')
+ cov_branch = True if os.environ.get('COV_CORE_BRANCH') == 'enabled' else None
+
+ if cov_datafile:
+ if _active_cov:
+ cleanup()
+ # Import what we need to activate coverage.
+ import coverage
+
+ # Determine all source roots.
+ if cov_source in os.pathsep:
+ cov_source = None
+ else:
+ cov_source = cov_source.split(os.pathsep)
+ if cov_config == os.pathsep:
+ cov_config = True
+
+ # Activate coverage for this process.
+ cov = _active_cov = coverage.Coverage(
+ source=cov_source,
+ branch=cov_branch,
+ data_suffix=True,
+ config_file=cov_config,
+ auto_data=True,
+ data_file=cov_datafile
+ )
+ cov.load()
+ cov.start()
+ cov._warn_no_data = False
+ cov._warn_unimported_source = False
+ return cov
+
+
+def _cleanup(cov):
+ if cov is not None:
+ cov.stop()
+ cov.save()
+ cov._auto_save = False # prevent autosaving from cov._atexit in case the interpreter lacks atexit.unregister
+ try:
+ atexit.unregister(cov._atexit)
+ except Exception:
+ pass
+
+
+def cleanup():
+ global _active_cov
+ global _cleanup_in_progress
+ global _pending_signal
+
+ _cleanup_in_progress = True
+ _cleanup(_active_cov)
+ _active_cov = None
+ _cleanup_in_progress = False
+ if _pending_signal:
+ pending_singal = _pending_signal
+ _pending_signal = None
+ _signal_cleanup_handler(*pending_singal)
+
+
+multiprocessing_finish = cleanup # in case someone dared to use this internal
+
+_previous_handlers = {}
+_pending_signal = None
+_cleanup_in_progress = False
+
+
+def _signal_cleanup_handler(signum, frame):
+ global _pending_signal
+ if _cleanup_in_progress:
+ _pending_signal = signum, frame
+ return
+ cleanup()
+ _previous_handler = _previous_handlers.get(signum)
+ if _previous_handler == signal.SIG_IGN:
+ return
+ elif _previous_handler and _previous_handler is not _signal_cleanup_handler:
+ _previous_handler(signum, frame)
+ elif signum == signal.SIGTERM:
+ os._exit(128 + signum)
+ elif signum == signal.SIGINT:
+ raise KeyboardInterrupt()
+
+
+def cleanup_on_signal(signum):
+ previous = signal.getsignal(signum)
+ if previous is not _signal_cleanup_handler:
+ _previous_handlers[signum] = previous
+ signal.signal(signum, _signal_cleanup_handler)
+
+
+def cleanup_on_sigterm():
+ cleanup_on_signal(signal.SIGTERM)
--- /dev/null
+"""Coverage controllers for use by pytest-cov and nose-cov."""
+import contextlib
+import copy
+import functools
+import os
+import random
+import socket
+import sys
+
+import coverage
+from coverage.data import CoverageData
+
+from .compat import StringIO
+from .embed import cleanup
+
+
+class _NullFile(object):
+ @staticmethod
+ def write(v):
+ pass
+
+
+@contextlib.contextmanager
+def _backup(obj, attr):
+ backup = getattr(obj, attr)
+ try:
+ setattr(obj, attr, copy.copy(backup))
+ yield
+ finally:
+ setattr(obj, attr, backup)
+
+
+def _ensure_topdir(meth):
+ @functools.wraps(meth)
+ def ensure_topdir_wrapper(self, *args, **kwargs):
+ try:
+ original_cwd = os.getcwd()
+ except OSError:
+ # Looks like it's gone, this is non-ideal because a side-effect will
+ # be introduced in the tests here but we can't do anything about it.
+ original_cwd = None
+ os.chdir(self.topdir)
+ try:
+ return meth(self, *args, **kwargs)
+ finally:
+ if original_cwd is not None:
+ os.chdir(original_cwd)
+
+ return ensure_topdir_wrapper
+
+
+class CovController(object):
+ """Base class for different plugin implementations."""
+
+ def __init__(self, cov_source, cov_report, cov_config, cov_append, cov_branch, config=None, nodeid=None):
+ """Get some common config used by multiple derived classes."""
+ self.cov_source = cov_source
+ self.cov_report = cov_report
+ self.cov_config = cov_config
+ self.cov_append = cov_append
+ self.cov_branch = cov_branch
+ self.config = config
+ self.nodeid = nodeid
+
+ self.cov = None
+ self.combining_cov = None
+ self.data_file = None
+ self.node_descs = set()
+ self.failed_workers = []
+ self.topdir = os.getcwd()
+ self.is_collocated = None
+
+ @contextlib.contextmanager
+ def ensure_topdir(self):
+ original_cwd = os.getcwd()
+ os.chdir(self.topdir)
+ yield
+ os.chdir(original_cwd)
+
+ @_ensure_topdir
+ def pause(self):
+ self.cov.stop()
+ self.unset_env()
+
+ @_ensure_topdir
+ def resume(self):
+ self.cov.start()
+ self.set_env()
+
+ @_ensure_topdir
+ def set_env(self):
+ """Put info about coverage into the env so that subprocesses can activate coverage."""
+ if self.cov_source is None:
+ os.environ['COV_CORE_SOURCE'] = os.pathsep
+ else:
+ os.environ['COV_CORE_SOURCE'] = os.pathsep.join(self.cov_source)
+ config_file = os.path.abspath(self.cov_config)
+ if os.path.exists(config_file):
+ os.environ['COV_CORE_CONFIG'] = config_file
+ else:
+ os.environ['COV_CORE_CONFIG'] = os.pathsep
+ os.environ['COV_CORE_DATAFILE'] = os.path.abspath(self.cov.config.data_file)
+ if self.cov_branch:
+ os.environ['COV_CORE_BRANCH'] = 'enabled'
+
+ @staticmethod
+ def unset_env():
+ """Remove coverage info from env."""
+ os.environ.pop('COV_CORE_SOURCE', None)
+ os.environ.pop('COV_CORE_CONFIG', None)
+ os.environ.pop('COV_CORE_DATAFILE', None)
+ os.environ.pop('COV_CORE_BRANCH', None)
+
+ @staticmethod
+ 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])
+
+ @staticmethod
+ def sep(stream, s, txt):
+ if hasattr(stream, 'sep'):
+ stream.sep(s, txt)
+ else:
+ 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))
+ stream.write(out)
+
+ @_ensure_topdir
+ def summary(self, stream):
+ """Produce coverage reports."""
+ total = None
+
+ if not self.cov_report:
+ with _backup(self.cov, "config"):
+ return self.cov.report(show_missing=True, ignore_errors=True, file=_NullFile)
+
+ # Output coverage section header.
+ if len(self.node_descs) == 1:
+ self.sep(stream, '-', 'coverage: %s' % ''.join(self.node_descs))
+ else:
+ self.sep(stream, '-', 'coverage')
+ for node_desc in sorted(self.node_descs):
+ self.sep(stream, ' ', '%s' % node_desc)
+
+ # Report on any failed workers.
+ if self.failed_workers:
+ self.sep(stream, '-', 'coverage: failed workers')
+ stream.write('The following workers failed to return coverage data, '
+ 'ensure that pytest-cov is installed on these workers.\n')
+ for node in self.failed_workers:
+ stream.write('%s\n' % node.gateway.id)
+
+ # Produce terminal report if wanted.
+ if any(x in self.cov_report for x in ['term', 'term-missing']):
+ options = {
+ 'show_missing': ('term-missing' in self.cov_report) or None,
+ 'ignore_errors': True,
+ 'file': stream,
+ }
+ skip_covered = isinstance(self.cov_report, dict) and 'skip-covered' in self.cov_report.values()
+ options.update({'skip_covered': skip_covered or None})
+ with _backup(self.cov, "config"):
+ total = self.cov.report(**options)
+
+ # Produce annotated source code report if wanted.
+ if 'annotate' in self.cov_report:
+ annotate_dir = self.cov_report['annotate']
+
+ with _backup(self.cov, "config"):
+ self.cov.annotate(ignore_errors=True, directory=annotate_dir)
+ # We need to call Coverage.report here, just to get the total
+ # Coverage.annotate don't return any total and we need it for --cov-fail-under.
+
+ with _backup(self.cov, "config"):
+ total = self.cov.report(ignore_errors=True, file=_NullFile)
+ if annotate_dir:
+ stream.write('Coverage annotated source written to dir %s\n' % annotate_dir)
+ else:
+ stream.write('Coverage annotated source written next to source\n')
+
+ # Produce html report if wanted.
+ if 'html' in self.cov_report:
+ output = self.cov_report['html']
+ with _backup(self.cov, "config"):
+ total = self.cov.html_report(ignore_errors=True, directory=output)
+ stream.write('Coverage HTML written to dir %s\n' % (self.cov.config.html_dir if output is None else output))
+
+ # Produce xml report if wanted.
+ if 'xml' in self.cov_report:
+ output = self.cov_report['xml']
+ with _backup(self.cov, "config"):
+ total = self.cov.xml_report(ignore_errors=True, outfile=output)
+ stream.write('Coverage XML written to file %s\n' % (self.cov.config.xml_output if output is None else output))
+
+ return total
+
+
+class Central(CovController):
+ """Implementation for centralised operation."""
+
+ @_ensure_topdir
+ def start(self):
+ cleanup()
+
+ self.cov = coverage.Coverage(source=self.cov_source,
+ branch=self.cov_branch,
+ data_suffix=True,
+ config_file=self.cov_config)
+ self.combining_cov = coverage.Coverage(source=self.cov_source,
+ branch=self.cov_branch,
+ data_suffix=True,
+ data_file=os.path.abspath(self.cov.config.data_file),
+ config_file=self.cov_config)
+
+ # Erase or load any previous coverage data and start coverage.
+ if not self.cov_append:
+ self.cov.erase()
+ self.cov.start()
+ self.set_env()
+
+ @_ensure_topdir
+ def finish(self):
+ """Stop coverage, save data to file and set the list of coverage objects to report on."""
+
+ self.unset_env()
+ self.cov.stop()
+ self.cov.save()
+
+ self.cov = self.combining_cov
+ self.cov.load()
+ self.cov.combine()
+ self.cov.save()
+
+ node_desc = self.get_node_desc(sys.platform, sys.version_info)
+ self.node_descs.add(node_desc)
+
+
+class DistMaster(CovController):
+ """Implementation for distributed master."""
+
+ @_ensure_topdir
+ def start(self):
+ cleanup()
+
+ # Ensure coverage rc file rsynced if appropriate.
+ if self.cov_config and os.path.exists(self.cov_config):
+ self.config.option.rsyncdir.append(self.cov_config)
+
+ self.cov = coverage.Coverage(source=self.cov_source,
+ branch=self.cov_branch,
+ data_suffix=True,
+ config_file=self.cov_config)
+ self.cov._warn_no_data = False
+ self.cov._warn_unimported_source = False
+ self.cov._warn_preimported_source = False
+ self.combining_cov = coverage.Coverage(source=self.cov_source,
+ branch=self.cov_branch,
+ data_suffix=True,
+ data_file=os.path.abspath(self.cov.config.data_file),
+ config_file=self.cov_config)
+ if not self.cov_append:
+ self.cov.erase()
+ self.cov.start()
+ self.cov.config.paths['source'] = [self.topdir]
+
+ def configure_node(self, node):
+ """Workers need to know if they are collocated and what files have moved."""
+
+ node.workerinput.update({
+ 'cov_master_host': socket.gethostname(),
+ 'cov_master_topdir': self.topdir,
+ 'cov_master_rsync_roots': [str(root) for root in node.nodemanager.roots],
+ })
+
+ def testnodedown(self, node, error):
+ """Collect data file name from worker."""
+
+ # If worker doesn't return any data then it is likely that this
+ # plugin didn't get activated on the worker side.
+ output = getattr(node, 'workeroutput', {})
+ if 'cov_worker_node_id' not in output:
+ self.failed_workers.append(node)
+ return
+
+ # If worker is not collocated then we must save the data file
+ # that it returns to us.
+ if 'cov_worker_data' in output:
+ data_suffix = '%s.%s.%06d.%s' % (
+ socket.gethostname(), os.getpid(),
+ random.randint(0, 999999),
+ output['cov_worker_node_id']
+ )
+
+ cov = coverage.Coverage(source=self.cov_source,
+ branch=self.cov_branch,
+ data_suffix=data_suffix,
+ config_file=self.cov_config)
+ cov.start()
+ if coverage.version_info < (5, 0):
+ data = CoverageData()
+ data.read_fileobj(StringIO(output['cov_worker_data']))
+ cov.data.update(data)
+ else:
+ data = CoverageData(no_disk=True)
+ data.loads(output['cov_worker_data'])
+ cov.get_data().update(data)
+ cov.stop()
+ cov.save()
+ path = output['cov_worker_path']
+ self.cov.config.paths['source'].append(path)
+
+ # Record the worker types that contribute to the data file.
+ rinfo = node.gateway._rinfo()
+ node_desc = self.get_node_desc(rinfo.platform, rinfo.version_info)
+ self.node_descs.add(node_desc)
+
+ @_ensure_topdir
+ def finish(self):
+ """Combines coverage data and sets the list of coverage objects to report on."""
+
+ # Combine all the suffix files into the data file.
+ self.cov.stop()
+ self.cov.save()
+ self.cov = self.combining_cov
+ self.cov.load()
+ self.cov.combine()
+ self.cov.save()
+
+
+class DistWorker(CovController):
+ """Implementation for distributed workers."""
+
+ @_ensure_topdir
+ def start(self):
+
+ cleanup()
+
+ # Determine whether we are collocated with master.
+ self.is_collocated = (socket.gethostname() == self.config.workerinput['cov_master_host'] and
+ self.topdir == self.config.workerinput['cov_master_topdir'])
+
+ # If we are not collocated then rewrite master paths to worker paths.
+ if not self.is_collocated:
+ master_topdir = self.config.workerinput['cov_master_topdir']
+ worker_topdir = self.topdir
+ if self.cov_source is not None:
+ self.cov_source = [source.replace(master_topdir, worker_topdir)
+ for source in self.cov_source]
+ self.cov_config = self.cov_config.replace(master_topdir, worker_topdir)
+
+ # Erase any previous data and start coverage.
+ self.cov = coverage.Coverage(source=self.cov_source,
+ branch=self.cov_branch,
+ data_suffix=True,
+ config_file=self.cov_config)
+ self.cov.start()
+ self.set_env()
+
+ @_ensure_topdir
+ def finish(self):
+ """Stop coverage and send relevant info back to the master."""
+ self.unset_env()
+ self.cov.stop()
+
+ if self.is_collocated:
+ # We don't combine data if we're collocated - we can get
+ # race conditions in the .combine() call (it's not atomic)
+ # The data is going to be combined in the master.
+ self.cov.save()
+
+ # If we are collocated then just inform the master of our
+ # data file to indicate that we have finished.
+ self.config.workeroutput['cov_worker_node_id'] = self.nodeid
+ else:
+ self.cov.combine()
+ self.cov.save()
+ # If we are not collocated then add the current path
+ # and coverage data to the output so we can combine
+ # it on the master node.
+
+ # Send all the data to the master over the channel.
+ if coverage.version_info < (5, 0):
+ buff = StringIO()
+ self.cov.data.write_fileobj(buff)
+ data = buff.getvalue()
+ else:
+ data = self.cov.get_data().dumps()
+
+ self.config.workeroutput.update({
+ 'cov_worker_path': self.topdir,
+ 'cov_worker_node_id': self.nodeid,
+ 'cov_worker_data': data,
+ })
+
+ def summary(self, stream):
+ """Only the master reports so do nothing."""
+
+ pass
--- /dev/null
+"""Coverage plugin for pytest."""
+import argparse
+import os
+import warnings
+
+import coverage
+import pytest
+
+from . import compat
+from . import embed
+
+
+class CoverageError(Exception):
+ """Indicates that our coverage is too low"""
+
+
+def validate_report(arg):
+ file_choices = ['annotate', 'html', 'xml']
+ term_choices = ['term', 'term-missing']
+ term_modifier_choices = ['skip-covered']
+ all_choices = term_choices + file_choices
+ values = arg.split(":", 1)
+ report_type = values[0]
+ if report_type not in all_choices + ['']:
+ msg = 'invalid choice: "{}" (choose from "{}")'.format(arg, all_choices)
+ raise argparse.ArgumentTypeError(msg)
+
+ if len(values) == 1:
+ return report_type, None
+
+ report_modifier = values[1]
+ if report_type in term_choices and report_modifier in term_modifier_choices:
+ return report_type, report_modifier
+
+ if report_type not in file_choices:
+ msg = 'output specifier not supported for: "{}" (choose from "{}")'.format(arg,
+ file_choices)
+ raise argparse.ArgumentTypeError(msg)
+
+ return values
+
+
+def validate_fail_under(num_str):
+ try:
+ return int(num_str)
+ except ValueError:
+ return float(num_str)
+
+
+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')
+ return arg
+
+
+class StoreReport(argparse.Action):
+ def __call__(self, parser, namespace, values, option_string=None):
+ report_type, file = values
+ namespace.cov_report[report_type] = file
+
+
+def pytest_addoption(parser):
+ """Add options to control coverage."""
+
+ group = parser.getgroup(
+ 'cov', 'coverage reporting with distributed testing support')
+ group.addoption('--cov', action='append', default=[], metavar='SOURCE',
+ 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-report', action=StoreReport, default={},
+ metavar='TYPE', type=validate_report,
+ help='Type of report to generate: term, term-missing, '
+ 'annotate, html, xml (multi-allowed). '
+ 'term, term-missing may be followed by ":skip-covered". '
+ 'annotate, html and xml may be followed by ":DEST" '
+ 'where DEST specifies the output location. '
+ 'Use --cov-report= to not generate any output.')
+ group.addoption('--cov-config', action='store', default='.coveragerc',
+ metavar='PATH',
+ help='Config file for coverage. Default: .coveragerc')
+ group.addoption('--no-cov-on-fail', action='store_true', default=False,
+ help='Do not report coverage if test run fails. '
+ 'Default: False')
+ group.addoption('--no-cov', action='store_true', default=False,
+ help='Disable coverage report completely (useful for debuggers). '
+ 'Default: False')
+ group.addoption('--cov-fail-under', action='store', metavar='MIN',
+ type=validate_fail_under,
+ help='Fail if the total coverage is less than MIN.')
+ group.addoption('--cov-append', action='store_true', default=False,
+ help='Do not delete coverage but append to current. '
+ 'Default: False')
+ group.addoption('--cov-branch', action='store_true', default=None,
+ help='Enable branch coverage.')
+ group.addoption('--cov-context', action='store', metavar='CONTEXT',
+ type=validate_context,
+ help='Dynamic contexts to use. "test" for now.')
+
+
+def _prepare_cov_source(cov_source):
+ """
+ Prepare cov_source so that:
+
+ --cov --cov=foobar is equivalent to --cov (cov_source=None)
+ --cov=foo --cov=bar is equivalent to cov_source=['foo', 'bar']
+ """
+ return None if True in cov_source else [path for path in cov_source if path is not True]
+
+
+@pytest.mark.tryfirst
+def pytest_load_initial_conftests(early_config, parser, args):
+ options = early_config.known_args_namespace
+ no_cov = options.no_cov_should_warn = False
+ for arg in args:
+ arg = str(arg)
+ if arg == '--no-cov':
+ no_cov = True
+ elif arg.startswith('--cov') and no_cov:
+ options.no_cov_should_warn = True
+ break
+
+ if early_config.known_args_namespace.cov_source:
+ plugin = CovPlugin(options, early_config.pluginmanager)
+ early_config.pluginmanager.register(plugin, '_cov')
+
+
+class CovPlugin(object):
+ """Use coverage package to produce code coverage reports.
+
+ Delegates all work to a particular implementation based on whether
+ this test process is centralised, a distributed master or a
+ distributed worker.
+ """
+
+ def __init__(self, options, pluginmanager, start=True, no_cov_should_warn=False):
+ """Creates a coverage pytest plugin.
+
+ We read the rc file that coverage uses to get the data file
+ name. This is needed since we give coverage through it's API
+ the data file name.
+ """
+
+ # Our implementation is unknown at this time.
+ self.pid = None
+ self.cov_controller = None
+ self.cov_report = compat.StringIO()
+ self.cov_total = None
+ self.failed = False
+ self._started = False
+ self._start_path = None
+ self._disabled = False
+ self.options = options
+
+ is_dist = (getattr(options, 'numprocesses', False) or
+ getattr(options, 'distload', False) or
+ getattr(options, 'dist', 'no') != 'no')
+ if getattr(options, 'no_cov', False):
+ self._disabled = True
+ return
+
+ if not self.options.cov_report:
+ self.options.cov_report = ['term']
+ elif len(self.options.cov_report) == 1 and '' in self.options.cov_report:
+ self.options.cov_report = {}
+ self.options.cov_source = _prepare_cov_source(self.options.cov_source)
+
+ # import engine lazily here to avoid importing
+ # it for unit tests that don't need it
+ from . import engine
+
+ if is_dist and start:
+ self.start(engine.DistMaster)
+ elif start:
+ self.start(engine.Central)
+
+ # worker is started in pytest hook
+
+ def start(self, controller_cls, config=None, nodeid=None):
+
+ if config is None:
+ # fake config option for engine
+ class Config(object):
+ option = self.options
+
+ config = Config()
+
+ self.cov_controller = controller_cls(
+ self.options.cov_source,
+ self.options.cov_report,
+ self.options.cov_config,
+ self.options.cov_append,
+ self.options.cov_branch,
+ config,
+ nodeid
+ )
+ self.cov_controller.start()
+ self._started = True
+ self._start_path = os.getcwd()
+ cov_config = self.cov_controller.cov.config
+ if self.options.cov_fail_under is None and hasattr(cov_config, 'fail_under'):
+ self.options.cov_fail_under = cov_config.fail_under
+
+ def _is_worker(self, session):
+ return getattr(session.config, 'workerinput', None) is not None
+
+ def pytest_sessionstart(self, session):
+ """At session start determine our implementation and delegate to it."""
+
+ if self.options.no_cov:
+ # Coverage can be disabled because it does not cooperate with debuggers well.
+ self._disabled = True
+ return
+
+ # import engine lazily here to avoid importing
+ # it for unit tests that don't need it
+ from . import engine
+
+ self.pid = os.getpid()
+ if self._is_worker(session):
+ nodeid = (
+ session.config.workerinput.get('workerid', getattr(session, 'nodeid'))
+ )
+ self.start(engine.DistWorker, session.config, nodeid)
+ elif not self._started:
+ self.start(engine.Central)
+
+ if self.options.cov_context == 'test':
+ session.config.pluginmanager.register(TestContextPlugin(self.cov_controller.cov), '_cov_contexts')
+
+ def pytest_configure_node(self, node):
+ """Delegate to our implementation.
+
+ Mark this hook as optional in case xdist is not installed.
+ """
+ if not self._disabled:
+ self.cov_controller.configure_node(node)
+ pytest_configure_node.optionalhook = True
+
+ def pytest_testnodedown(self, node, error):
+ """Delegate to our implementation.
+
+ Mark this hook as optional in case xdist is not installed.
+ """
+ if not self._disabled:
+ self.cov_controller.testnodedown(node, error)
+ pytest_testnodedown.optionalhook = True
+
+ def _should_report(self):
+ return not (self.failed and self.options.no_cov_on_fail)
+
+ def _failed_cov_total(self):
+ cov_fail_under = self.options.cov_fail_under
+ return cov_fail_under is not None and self.cov_total < cov_fail_under
+
+ # we need to wrap pytest_runtestloop. by the time pytest_sessionfinish
+ # runs, it's too late to set testsfailed
+ @compat.hookwrapper
+ def pytest_runtestloop(self, session):
+ yield
+
+ if self._disabled:
+ return
+
+ compat_session = compat.SessionWrapper(session)
+
+ self.failed = bool(compat_session.testsfailed)
+ if self.cov_controller is not None:
+ self.cov_controller.finish()
+
+ if not self._is_worker(session) and self._should_report():
+
+ # import coverage lazily here to avoid importing
+ # it for unit tests that don't need it
+ from coverage.misc import CoverageException
+
+ try:
+ self.cov_total = self.cov_controller.summary(self.cov_report)
+ except CoverageException as exc:
+ 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))
+ self.cov_total = 0
+ assert self.cov_total is not None, 'Test coverage should never be `None`'
+ if self._failed_cov_total():
+ # make sure we get the EXIT_TESTSFAILED exit code
+ compat_session.testsfailed += 1
+
+ def pytest_terminal_summary(self, terminalreporter):
+ if self._disabled:
+ 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))
+ return
+ if self.cov_controller is None:
+ return
+
+ if self.cov_total is None:
+ # we shouldn't report, or report generation failed (error raised above)
+ return
+
+ terminalreporter.write('\n' + self.cov_report.getvalue() + '\n')
+
+ if self.options.cov_fail_under is not None and self.options.cov_fail_under > 0:
+ failed = self.cov_total < self.options.cov_fail_under
+ markup = {'red': True, 'bold': True} if failed else {'green': True}
+ message = (
+ '{fail}Required test coverage of {required}% {reached}. '
+ 'Total coverage: {actual:.2f}%\n'
+ .format(
+ required=self.options.cov_fail_under,
+ actual=self.cov_total,
+ fail="FAIL " if failed else "",
+ reached="not reached" if failed else "reached"
+ )
+ )
+ terminalreporter.write(message, **markup)
+
+ def pytest_runtest_setup(self, item):
+ if os.getpid() != self.pid:
+ # test is run in another process than session, run
+ # coverage manually
+ embed.init()
+
+ def pytest_runtest_teardown(self, item):
+ embed.cleanup()
+
+ @compat.hookwrapper
+ def pytest_runtest_call(self, item):
+ if (item.get_closest_marker('no_cover')
+ or 'no_cover' in getattr(item, 'fixturenames', ())):
+ self.cov_controller.pause()
+ yield
+ self.cov_controller.resume()
+ else:
+ yield
+
+
+class TestContextPlugin(object):
+ def __init__(self, cov):
+ self.cov = cov
+
+ def pytest_runtest_setup(self, item):
+ self.switch_context(item, 'setup')
+
+ def pytest_runtest_teardown(self, item):
+ self.switch_context(item, 'teardown')
+
+ 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)
+ self.cov.switch_context(context)
+
+
+@pytest.fixture
+def no_cover():
+ """A pytest fixture to disable coverage."""
+ pass
+
+
+@pytest.fixture
+def cov(request):
+ """A pytest fixture to provide access to the underlying coverage object."""
+
+ # Check with hasplugin to avoid getplugin exception in older pytest.
+ if request.config.pluginmanager.hasplugin('_cov'):
+ plugin = request.config.pluginmanager.getplugin('_cov')
+ if plugin.cov_controller:
+ return plugin.cov_controller.cov
+ return None
+
+
+def pytest_configure(config):
+ config.addinivalue_line("markers", "no_cover: disable coverage for this test.")
--- /dev/null
+def pytest_configure(config):
+ config.option.runpytest = 'subprocess'
--- /dev/null
+# A test file for test_pytest_cov.py:test_contexts
+
+import unittest
+
+import pytest
+
+
+def test_01():
+ assert 1 == 1 # r1
+
+
+def test_02():
+ assert 2 == 2 # r2
+
+
+class OldStyleTests(unittest.TestCase):
+ items = []
+
+ @classmethod
+ def setUpClass(cls):
+ cls.items.append("hello") # s3
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.items.pop() # t4
+
+ def setUp(self):
+ self.number = 1 # r3 r4
+
+ def tearDown(self):
+ self.number = None # r3 r4
+
+ def test_03(self):
+ assert self.number == 1 # r3
+ assert self.items[0] == "hello" # r3
+
+ def test_04(self):
+ assert self.number == 1 # r4
+ assert self.items[0] == "hello" # r4
+
+
+@pytest.fixture
+def some_data():
+ return [1, 2, 3] # s5 s6
+
+
+def test_05(some_data):
+ assert len(some_data) == 3 # r5
+
+
+@pytest.fixture
+def more_data(some_data):
+ return [2*x for x in some_data] # s6
+
+
+def test_06(some_data, more_data):
+ assert len(some_data) == len(more_data) # r6
+
+
+@pytest.fixture(scope='session')
+def expensive_data():
+ return list(range(10)) # s7
+
+
+def test_07(expensive_data):
+ assert len(expensive_data) == 10 # r7
+
+
+def test_08(expensive_data):
+ assert len(expensive_data) == 10 # r8
+
+
+@pytest.fixture(params=[1, 2, 3])
+def parametrized_number(request):
+ return request.param # s9-1 s9-2 s9-3
+
+
+def test_09(parametrized_number):
+ assert parametrized_number > 0 # r9-1 r9-2 r9-3
+
+
+def test_10():
+ assert 1 == 1 # r10
+
+
+@pytest.mark.parametrize("x, ans", [
+ (1, 101),
+ (2, 202),
+])
+def test_11(x, ans):
+ assert 100 * x + x == ans # r11-1 r11-2
+
+
+@pytest.mark.parametrize("x, ans", [
+ (1, 101),
+ (2, 202),
+], ids=['one', 'two'])
+def test_12(x, ans):
+ assert 100 * x + x == ans # r12-1 r12-2
+
+
+@pytest.mark.parametrize("x", [1, 2])
+@pytest.mark.parametrize("y", [3, 4])
+def test_13(x, y):
+ assert x + y > 0 # r13-1 r13-2 r13-3 r13-4
--- /dev/null
+def do_stuff():
+ a = 1
+ return a
--- /dev/null
+import collections
+import glob
+import os
+import platform
+import re
+import subprocess
+import sys
+from itertools import chain
+
+import coverage
+import py
+import pytest
+import virtualenv
+import xdist
+from fields import Namespace
+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
+
+try:
+ from StringIO import StringIO
+except ImportError:
+ from io import StringIO
+
+coverage, platform # required for skipif mark on test_cov_min_from_coveragerc
+
+max_worker_restart_0 = "--max-worker-restart=0"
+
+SCRIPT = '''
+import sys, helper
+
+def pytest_generate_tests(metafunc):
+ for i in [10]:
+ metafunc.parametrize('p', range(i))
+
+def test_foo(p):
+ x = True
+ helper.do_stuff() # get some coverage in some other completely different location
+ if sys.version_info[0] > 5:
+ assert False
+'''
+
+SCRIPT2 = '''
+#
+
+def test_bar():
+ x = True
+ assert x
+
+'''
+
+
+COVERAGERC_SOURCE = '''\
+[run]
+source = .
+'''
+
+SCRIPT_CHILD = '''
+import sys
+
+idx = int(sys.argv[1])
+
+if idx == 0:
+ foo = "a" # previously there was a "pass" here but Python 3.5 optimizes it away.
+if idx == 1:
+ foo = "b" # previously there was a "pass" here but Python 3.5 optimizes it away.
+'''
+
+SCRIPT_PARENT = '''
+import os
+import subprocess
+import sys
+
+def pytest_generate_tests(metafunc):
+ for i in [2]:
+ metafunc.parametrize('idx', range(i))
+
+def test_foo(idx):
+ out, err = subprocess.Popen(
+ [sys.executable, os.path.join(os.path.dirname(__file__), 'child_script.py'), str(idx)],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE).communicate()
+
+# there is a issue in coverage.py with multiline statements at
+# end of file: https://bitbucket.org/ned/coveragepy/issue/293
+pass
+'''
+
+SCRIPT_PARENT_CHANGE_CWD = '''
+import subprocess
+import sys
+import os
+
+def pytest_generate_tests(metafunc):
+ for i in [2]:
+ metafunc.parametrize('idx', range(i))
+
+def test_foo(idx):
+ os.mkdir("foobar")
+ os.chdir("foobar")
+
+ subprocess.check_call([
+ sys.executable,
+ os.path.join(os.path.dirname(__file__), 'child_script.py'),
+ str(idx)
+ ])
+
+# there is a issue in coverage.py with multiline statements at
+# end of file: https://bitbucket.org/ned/coveragepy/issue/293
+pass
+'''
+
+SCRIPT_PARENT_CHANGE_CWD_IMPORT_CHILD = '''
+import subprocess
+import sys
+import os
+
+def pytest_generate_tests(metafunc):
+ for i in [2]:
+ if metafunc.function is test_foo: metafunc.parametrize('idx', range(i))
+
+def test_foo(idx):
+ os.mkdir("foobar")
+ os.chdir("foobar")
+
+ subprocess.check_call([
+ sys.executable,
+ '-c', 'import sys; sys.argv = ["", str(%s)]; import child_script' % idx
+ ])
+
+# there is a issue in coverage.py with multiline statements at
+# end of file: https://bitbucket.org/ned/coveragepy/issue/293
+pass
+'''
+
+SCRIPT_FUNCARG = '''
+import coverage
+
+def test_foo(cov):
+ assert isinstance(cov, coverage.Coverage)
+'''
+
+SCRIPT_FUNCARG_NOT_ACTIVE = '''
+def test_foo(cov):
+ assert cov is None
+'''
+
+CHILD_SCRIPT_RESULT = '[56] * 100%'
+PARENT_SCRIPT_RESULT = '9 * 100%'
+DEST_DIR = 'cov_dest'
+REPORT_NAME = 'cov.xml'
+
+xdist_params = pytest.mark.parametrize('opts', [
+ '',
+ pytest.param('-n 1', marks=pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"'))
+], ids=['nodist', 'xdist'])
+
+
+@pytest.fixture(scope='session', autouse=True)
+def adjust_sys_path():
+ """Adjust PYTHONPATH during tests to make "helper" importable in SCRIPT."""
+ orig_path = os.environ.get('PYTHONPATH', None)
+ new_path = os.path.dirname(__file__)
+ if orig_path is not None:
+ new_path = os.pathsep.join([new_path, orig_path])
+ os.environ['PYTHONPATH'] = new_path
+
+ yield
+
+ if orig_path is None:
+ del os.environ['PYTHONPATH']
+ else:
+ os.environ['PYTHONPATH'] = orig_path
+
+
+@pytest.fixture(params=[
+ ('branch=true', '--cov-branch', '9 * 85%', '3 * 100%'),
+ ('branch=true', '', '9 * 85%', '3 * 100%'),
+ ('', '--cov-branch', '9 * 85%', '3 * 100%'),
+ ('', '', '9 * 89%', '3 * 100%'),
+], ids=['branch2x', 'branch1c', 'branch1a', 'nobranch'])
+def prop(request):
+ return Namespace(
+ code=SCRIPT,
+ code2=SCRIPT2,
+ conf=request.param[0],
+ fullconf='[run]\n%s\n' % request.param[0],
+ prefixedfullconf='[coverage:run]\n%s\n' % request.param[0],
+ args=request.param[1].split(),
+ result=request.param[2],
+ result2=request.param[3],
+ )
+
+
+def test_central(testdir, prop):
+ script = testdir.makepyfile(prop.code)
+ testdir.tmpdir.join('.coveragerc').write(prop.fullconf)
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ script,
+ *prop.args)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_central* %s *' % prop.result,
+ '*10 passed*'
+ ])
+ assert result.ret == 0
+
+
+def test_annotate(testdir):
+ script = testdir.makepyfile(SCRIPT)
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=annotate',
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'Coverage annotated source written next to source',
+ '*10 passed*',
+ ])
+ assert result.ret == 0
+
+
+def test_annotate_output_dir(testdir):
+ script = testdir.makepyfile(SCRIPT)
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=annotate:' + DEST_DIR,
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'Coverage annotated source written to dir ' + DEST_DIR,
+ '*10 passed*',
+ ])
+ dest_dir = testdir.tmpdir.join(DEST_DIR)
+ assert dest_dir.check(dir=True)
+ assert dest_dir.join(script.basename + ",cover").check()
+ assert result.ret == 0
+
+
+def test_html(testdir):
+ script = testdir.makepyfile(SCRIPT)
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=html',
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'Coverage HTML written to dir htmlcov',
+ '*10 passed*',
+ ])
+ dest_dir = testdir.tmpdir.join('htmlcov')
+ assert dest_dir.check(dir=True)
+ assert dest_dir.join("index.html").check()
+ assert result.ret == 0
+
+
+def test_html_output_dir(testdir):
+ script = testdir.makepyfile(SCRIPT)
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=html:' + DEST_DIR,
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'Coverage HTML written to dir ' + DEST_DIR,
+ '*10 passed*',
+ ])
+ dest_dir = testdir.tmpdir.join(DEST_DIR)
+ assert dest_dir.check(dir=True)
+ assert dest_dir.join("index.html").check()
+ assert result.ret == 0
+
+
+def test_term_report_does_not_interact_with_html_output(testdir):
+ script = testdir.makepyfile(test_funcarg=SCRIPT_FUNCARG)
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing:skip-covered',
+ '--cov-report=html:' + DEST_DIR,
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'Coverage HTML written to dir ' + DEST_DIR,
+ '*1 passed*',
+ ])
+ dest_dir = testdir.tmpdir.join(DEST_DIR)
+ assert dest_dir.check(dir=True)
+ assert sorted(dest_dir.visit("**/*.html")) == [dest_dir.join("index.html"), dest_dir.join("test_funcarg_py.html")]
+ assert dest_dir.join("index.html").check()
+ assert result.ret == 0
+
+
+def test_html_configured_output_dir(testdir):
+ script = testdir.makepyfile(SCRIPT)
+ testdir.tmpdir.join('.coveragerc').write("""
+[html]
+directory = somewhere
+""")
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=html',
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'Coverage HTML written to dir somewhere',
+ '*10 passed*',
+ ])
+ dest_dir = testdir.tmpdir.join('somewhere')
+ assert dest_dir.check(dir=True)
+ assert dest_dir.join("index.html").check()
+ assert result.ret == 0
+
+
+def test_xml_output_dir(testdir):
+ script = testdir.makepyfile(SCRIPT)
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=xml:' + REPORT_NAME,
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'Coverage XML written to file ' + REPORT_NAME,
+ '*10 passed*',
+ ])
+ assert testdir.tmpdir.join(REPORT_NAME).check()
+ assert result.ret == 0
+
+
+def test_term_output_dir(testdir):
+ script = testdir.makepyfile(SCRIPT)
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term:' + DEST_DIR,
+ script)
+
+ result.stderr.fnmatch_lines([
+ '*argument --cov-report: output specifier not supported for: "term:%s"*' % DEST_DIR,
+ ])
+ assert result.ret != 0
+
+
+def test_term_missing_output_dir(testdir):
+ script = testdir.makepyfile(SCRIPT)
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing:' + DEST_DIR,
+ script)
+
+ result.stderr.fnmatch_lines([
+ '*argument --cov-report: output specifier not supported for: '
+ '"term-missing:%s"*' % DEST_DIR,
+ ])
+ assert result.ret != 0
+
+
+def test_cov_min_100(testdir):
+ script = testdir.makepyfile(SCRIPT)
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ '--cov-fail-under=100',
+ script)
+
+ assert result.ret != 0
+ result.stdout.fnmatch_lines([
+ 'FAIL Required test coverage of 100% not reached. Total coverage: *%'
+ ])
+
+
+def test_cov_min_50(testdir):
+ script = testdir.makepyfile(SCRIPT)
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=html',
+ '--cov-report=xml',
+ '--cov-fail-under=50',
+ script)
+
+ assert result.ret == 0
+ result.stdout.fnmatch_lines([
+ 'Required test coverage of 50% reached. Total coverage: *%'
+ ])
+
+
+def test_cov_min_float_value(testdir):
+ script = testdir.makepyfile(SCRIPT)
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ '--cov-fail-under=88.88',
+ script)
+ assert result.ret == 0
+ result.stdout.fnmatch_lines([
+ 'Required test coverage of 88.88% reached. Total coverage: 88.89%'
+ ])
+
+
+def test_cov_min_float_value_not_reached(testdir):
+ script = testdir.makepyfile(SCRIPT)
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ '--cov-fail-under=88.89',
+ script)
+ assert result.ret == 1
+ result.stdout.fnmatch_lines([
+ 'FAIL Required test coverage of 88.89% not reached. Total coverage: 88.89%'
+ ])
+
+
+def test_cov_min_no_report(testdir):
+ script = testdir.makepyfile(SCRIPT)
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=',
+ '--cov-fail-under=50',
+ script)
+
+ assert result.ret == 0
+ result.stdout.fnmatch_lines([
+ 'Required test coverage of 50% reached. Total coverage: *%'
+ ])
+
+
+def test_central_nonspecific(testdir, prop):
+ script = testdir.makepyfile(prop.code)
+ testdir.tmpdir.join('.coveragerc').write(prop.fullconf)
+ result = testdir.runpytest('-v',
+ '--cov',
+ '--cov-report=term-missing',
+ script, *prop.args)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_central_nonspecific* %s *' % prop.result,
+ '*10 passed*'
+ ])
+
+ # multi-module coverage report
+ assert any(line.startswith('TOTAL ') for line in result.stdout.lines)
+
+ assert result.ret == 0
+
+
+def test_cov_min_from_coveragerc(testdir):
+ script = testdir.makepyfile(SCRIPT)
+ testdir.tmpdir.join('.coveragerc').write("""
+[report]
+fail_under = 100
+""")
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ script)
+
+ assert result.ret != 0
+
+
+def test_central_coveragerc(testdir, prop):
+ script = testdir.makepyfile(prop.code)
+ testdir.tmpdir.join('.coveragerc').write(COVERAGERC_SOURCE + prop.conf)
+
+ result = testdir.runpytest('-v',
+ '--cov',
+ '--cov-report=term-missing',
+ script, *prop.args)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_central_coveragerc* %s *' % prop.result,
+ '*10 passed*',
+ ])
+
+ # single-module coverage report
+ assert all(not line.startswith('TOTAL ') for line in result.stdout.lines[-4:])
+
+ assert result.ret == 0
+
+
+@xdist_params
+def test_central_with_path_aliasing(testdir, monkeypatch, opts, prop):
+ mod1 = testdir.mkdir('src').join('mod.py')
+ mod1.write(SCRIPT)
+ mod2 = testdir.mkdir('aliased').join('mod.py')
+ mod2.write(SCRIPT)
+ script = testdir.makepyfile('''
+from mod import *
+''')
+ testdir.tmpdir.join('setup.cfg').write("""
+[coverage:paths]
+source =
+ src
+ aliased
+[coverage:run]
+source = mod
+parallel = true
+%s
+""" % prop.conf)
+
+ monkeypatch.setitem(os.environ, 'PYTHONPATH', os.pathsep.join([os.environ.get('PYTHONPATH', ''), 'aliased']))
+ result = testdir.runpytest('-v', '-s',
+ '--cov',
+ '--cov-report=term-missing',
+ script, *opts.split()+prop.args)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'src[\\/]mod* %s *' % prop.result,
+ '*10 passed*',
+ ])
+
+ # single-module coverage report
+ assert all(not line.startswith('TOTAL ') for line in result.stdout.lines[-4:])
+
+ assert result.ret == 0
+
+
+@xdist_params
+def test_borken_cwd(testdir, monkeypatch, opts):
+ testdir.makepyfile(mod='''
+def foobar(a, b):
+ return a + b
+''')
+
+ script = testdir.makepyfile('''
+import os
+import tempfile
+import pytest
+import mod
+
+@pytest.fixture
+def bad():
+ path = tempfile.mkdtemp('test_borken_cwd')
+ os.chdir(path)
+ yield
+ try:
+ os.rmdir(path)
+ except OSError:
+ pass
+
+def test_foobar(bad):
+ assert mod.foobar(1, 2) == 3
+''')
+ result = testdir.runpytest('-v', '-s',
+ '--cov=mod',
+ '--cov-branch',
+ script, *opts.split())
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ '*mod* 100%',
+ '*1 passed*',
+ ])
+
+ assert result.ret == 0
+
+
+def test_subprocess_with_path_aliasing(testdir, monkeypatch):
+ src = testdir.mkdir('src')
+ src.join('parent_script.py').write(SCRIPT_PARENT)
+ src.join('child_script.py').write(SCRIPT_CHILD)
+ aliased = testdir.mkdir('aliased')
+ parent_script = aliased.join('parent_script.py')
+ parent_script.write(SCRIPT_PARENT)
+ aliased.join('child_script.py').write(SCRIPT_CHILD)
+
+ testdir.tmpdir.join('.coveragerc').write("""
+[paths]
+source =
+ src
+ aliased
+[run]
+source =
+ parent_script
+ child_script
+parallel = true
+""")
+
+ monkeypatch.setitem(os.environ, 'PYTHONPATH', os.pathsep.join([
+ os.environ.get('PYTHONPATH', ''), 'aliased']))
+ result = testdir.runpytest('-v',
+ '--cov',
+ '--cov-report=term-missing',
+ parent_script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'src[\\/]child_script* %s*' % CHILD_SCRIPT_RESULT,
+ 'src[\\/]parent_script* %s*' % PARENT_SCRIPT_RESULT,
+ ])
+ assert result.ret == 0
+
+
+def test_show_missing_coveragerc(testdir, prop):
+ script = testdir.makepyfile(prop.code)
+ testdir.tmpdir.join('.coveragerc').write("""
+[run]
+source = .
+%s
+
+[report]
+show_missing = true
+""" % prop.conf)
+
+ result = testdir.runpytest('-v',
+ '--cov',
+ '--cov-report=term',
+ script, *prop.args)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'Name * Stmts * Miss * Cover * Missing',
+ 'test_show_missing_coveragerc* %s * 11*' % prop.result,
+ '*10 passed*',
+ ])
+
+ # single-module coverage report
+ assert all(not line.startswith('TOTAL ') for line in result.stdout.lines[-4:])
+
+ assert result.ret == 0
+
+
+def test_no_cov_on_fail(testdir):
+ script = testdir.makepyfile('''
+def test_fail():
+ assert False
+
+''')
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ '--no-cov-on-fail',
+ script)
+
+ assert 'coverage: platform' not in result.stdout.str()
+ result.stdout.fnmatch_lines(['*1 failed*'])
+
+
+def test_no_cov(testdir, monkeypatch):
+ script = testdir.makepyfile(SCRIPT)
+ testdir.makeini("""
+ [pytest]
+ addopts=--no-cov
+ """)
+ result = testdir.runpytest('-vvv',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ '-rw',
+ script)
+ result.stdout.fnmatch_lines_random([
+ 'WARNING: Coverage disabled via --no-cov switch!',
+ '*Coverage disabled via --no-cov switch!',
+ ])
+
+
+def test_cov_and_failure_report_on_fail(testdir):
+ script = testdir.makepyfile(SCRIPT + '''
+def test_fail(p):
+ assert False
+
+''')
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-fail-under=100',
+ '--cov-report=html',
+ script)
+
+ result.stdout.fnmatch_lines_random([
+ '*10 failed*',
+ '*coverage: platform*',
+ '*FAIL Required test coverage of 100% not reached*',
+ '*assert False*',
+ ])
+
+
+@pytest.mark.skipif('sys.platform == "win32" or platform.python_implementation() == "PyPy"')
+def test_dist_combine_racecondition(testdir):
+ script = testdir.makepyfile("""
+import pytest
+
+@pytest.mark.parametrize("foo", range(1000))
+def test_foo(foo):
+""" + "\n".join("""
+ if foo == %s:
+ assert True
+""" % i for i in range(1000)))
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ '-n', '5', '-s',
+ script)
+ result.stdout.fnmatch_lines([
+ 'test_dist_combine_racecondition* 0 * 100%*',
+ '*1000 passed*'
+ ])
+
+ for line in chain(result.stdout.lines, result.stderr.lines):
+ assert 'The following workers failed to return coverage data' not in line
+ assert 'INTERNALERROR' not in line
+ assert result.ret == 0
+
+
+@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"')
+def test_dist_collocated(testdir, prop):
+ script = testdir.makepyfile(prop.code)
+ testdir.tmpdir.join('.coveragerc').write(prop.fullconf)
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ '--dist=load',
+ '--tx=2*popen',
+ max_worker_restart_0,
+ script, *prop.args)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_dist_collocated* %s *' % prop.result,
+ '*10 passed*'
+ ])
+ assert result.ret == 0
+
+
+@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"')
+def test_dist_not_collocated(testdir, prop):
+ script = testdir.makepyfile(prop.code)
+ dir1 = testdir.mkdir('dir1')
+ dir2 = testdir.mkdir('dir2')
+ testdir.tmpdir.join('.coveragerc').write('''
+[run]
+%s
+[paths]
+source =
+ .
+ dir1
+ dir2''' % prop.conf)
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ '--dist=load',
+ '--tx=popen//chdir=%s' % dir1,
+ '--tx=popen//chdir=%s' % dir2,
+ '--rsyncdir=%s' % script.basename,
+ '--rsyncdir=.coveragerc',
+ max_worker_restart_0, '-s',
+ script, *prop.args)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_dist_not_collocated* %s *' % prop.result,
+ '*10 passed*'
+ ])
+ assert result.ret == 0
+
+
+@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"')
+def test_dist_not_collocated_coveragerc_source(testdir, prop):
+ script = testdir.makepyfile(prop.code)
+ dir1 = testdir.mkdir('dir1')
+ dir2 = testdir.mkdir('dir2')
+ testdir.tmpdir.join('.coveragerc').write('''
+[run]
+%s
+source = %s
+[paths]
+source =
+ .
+ dir1
+ dir2''' % (prop.conf, script.dirpath()))
+
+ result = testdir.runpytest('-v',
+ '--cov',
+ '--cov-report=term-missing',
+ '--dist=load',
+ '--tx=popen//chdir=%s' % dir1,
+ '--tx=popen//chdir=%s' % dir2,
+ '--rsyncdir=%s' % script.basename,
+ '--rsyncdir=.coveragerc',
+ max_worker_restart_0, '-s',
+ script, *prop.args)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_dist_not_collocated* %s *' % prop.result,
+ '*10 passed*'
+ ])
+ assert result.ret == 0
+
+
+def test_central_subprocess(testdir):
+ scripts = testdir.makepyfile(parent_script=SCRIPT_PARENT,
+ child_script=SCRIPT_CHILD)
+ parent_script = scripts.dirpath().join('parent_script.py')
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % scripts.dirpath(),
+ '--cov-report=term-missing',
+ parent_script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'child_script* %s*' % CHILD_SCRIPT_RESULT,
+ 'parent_script* %s*' % PARENT_SCRIPT_RESULT,
+ ])
+ assert result.ret == 0
+
+
+def test_central_subprocess_change_cwd(testdir):
+ scripts = testdir.makepyfile(parent_script=SCRIPT_PARENT_CHANGE_CWD,
+ child_script=SCRIPT_CHILD)
+ parent_script = scripts.dirpath().join('parent_script.py')
+ testdir.makefile('', coveragerc="""
+[run]
+branch = true
+parallel = true
+""")
+
+ result = testdir.runpytest('-v', '-s',
+ '--cov=%s' % scripts.dirpath(),
+ '--cov-config=coveragerc',
+ '--cov-report=term-missing',
+ parent_script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ '*child_script* %s*' % CHILD_SCRIPT_RESULT,
+ '*parent_script* 100%*',
+ ])
+ assert result.ret == 0
+
+
+def test_central_subprocess_change_cwd_with_pythonpath(testdir, monkeypatch):
+ stuff = testdir.mkdir('stuff')
+ parent_script = stuff.join('parent_script.py')
+ parent_script.write(SCRIPT_PARENT_CHANGE_CWD_IMPORT_CHILD)
+ stuff.join('child_script.py').write(SCRIPT_CHILD)
+ testdir.makefile('', coveragerc="""
+[run]
+parallel = true
+""")
+
+ monkeypatch.setitem(os.environ, 'PYTHONPATH', str(stuff))
+ result = testdir.runpytest('-vv', '-s',
+ '--cov=child_script',
+ '--cov-config=coveragerc',
+ '--cov-report=term-missing',
+ '--cov-branch',
+ parent_script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ '*child_script* %s*' % CHILD_SCRIPT_RESULT,
+ ])
+ assert result.ret == 0
+
+
+def test_central_subprocess_no_subscript(testdir):
+ script = testdir.makepyfile("""
+import subprocess, sys
+
+def test_foo():
+ subprocess.check_call([sys.executable, '-c', 'print("Hello World")'])
+""")
+ testdir.makefile('', coveragerc="""
+[run]
+parallel = true
+""")
+ result = testdir.runpytest('-v',
+ '--cov-config=coveragerc',
+ '--cov=%s' % script.dirpath(),
+ '--cov-branch',
+ script)
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_central_subprocess_no_subscript* * 3 * 0 * 100%*',
+ ])
+ assert result.ret == 0
+
+
+@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"')
+def test_dist_subprocess_collocated(testdir):
+ scripts = testdir.makepyfile(parent_script=SCRIPT_PARENT,
+ child_script=SCRIPT_CHILD)
+ parent_script = scripts.dirpath().join('parent_script.py')
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % scripts.dirpath(),
+ '--cov-report=term-missing',
+ '--dist=load',
+ '--tx=2*popen',
+ max_worker_restart_0,
+ parent_script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'child_script* %s*' % CHILD_SCRIPT_RESULT,
+ 'parent_script* %s*' % PARENT_SCRIPT_RESULT,
+ ])
+ assert result.ret == 0
+
+
+@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"')
+def test_dist_subprocess_not_collocated(testdir, tmpdir):
+ scripts = testdir.makepyfile(parent_script=SCRIPT_PARENT,
+ child_script=SCRIPT_CHILD)
+ parent_script = scripts.dirpath().join('parent_script.py')
+ child_script = scripts.dirpath().join('child_script.py')
+
+ dir1 = tmpdir.mkdir('dir1')
+ dir2 = tmpdir.mkdir('dir2')
+ testdir.tmpdir.join('.coveragerc').write('''
+[paths]
+source =
+ %s
+ */dir1
+ */dir2
+''' % scripts.dirpath())
+ result = testdir.runpytest('-v',
+ '--cov=%s' % scripts.dirpath(),
+ '--dist=load',
+ '--tx=popen//chdir=%s' % dir1,
+ '--tx=popen//chdir=%s' % dir2,
+ '--rsyncdir=%s' % child_script,
+ '--rsyncdir=%s' % parent_script,
+ '--rsyncdir=.coveragerc',
+ max_worker_restart_0,
+ parent_script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'child_script* %s*' % CHILD_SCRIPT_RESULT,
+ 'parent_script* %s*' % PARENT_SCRIPT_RESULT,
+ ])
+ assert result.ret == 0
+
+
+def test_invalid_coverage_source(testdir):
+ script = testdir.makepyfile(SCRIPT)
+ testdir.makeini("""
+ [pytest]
+ console_output_style=classic
+ """)
+ result = testdir.runpytest('-v',
+ '--cov=non_existent_module',
+ '--cov-report=term-missing',
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*10 passed*'
+ ])
+ result.stderr.fnmatch_lines([
+ 'Coverage.py warning: No data was collected.*'
+ ])
+ result.stdout.fnmatch_lines([
+ '*Failed to generate report: No data to report.',
+ ])
+ assert result.ret == 0
+
+ matching_lines = [line for line in result.outlines if '%' in line]
+ assert not matching_lines
+
+
+@pytest.mark.skipif("'dev' in pytest.__version__")
+@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"')
+def test_dist_missing_data(testdir):
+ """Test failure when using a worker without pytest-cov installed."""
+ venv_path = os.path.join(str(testdir.tmpdir), 'venv')
+ virtualenv.cli_run([venv_path])
+ if sys.platform == 'win32':
+ if platform.python_implementation() == "PyPy":
+ exe = os.path.join(venv_path, 'bin', 'python.exe')
+ else:
+ exe = os.path.join(venv_path, 'Scripts', 'python.exe')
+ else:
+ exe = os.path.join(venv_path, 'bin', 'python')
+ subprocess.check_call([
+ exe,
+ '-mpip',
+ 'install',
+ 'py==%s' % py.__version__,
+ 'pytest==%s' % pytest.__version__,
+ 'pytest_xdist==%s' % xdist.__version__
+
+ ])
+ script = testdir.makepyfile(SCRIPT)
+
+ result = testdir.runpytest('-v',
+ '--assert=plain',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ '--dist=load',
+ '--tx=popen//python=%s' % exe,
+ max_worker_restart_0,
+ script)
+ result.stdout.fnmatch_lines([
+ 'The following workers failed to return coverage data, ensure that pytest-cov is installed on these workers.'
+ ])
+
+
+def test_funcarg(testdir):
+ script = testdir.makepyfile(SCRIPT_FUNCARG)
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_funcarg* 3 * 100%*',
+ '*1 passed*'
+ ])
+ assert result.ret == 0
+
+
+def test_funcarg_not_active(testdir):
+ script = testdir.makepyfile(SCRIPT_FUNCARG_NOT_ACTIVE)
+
+ result = testdir.runpytest('-v',
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*1 passed*'
+ ])
+ assert result.ret == 0
+
+
+@pytest.mark.skipif("sys.version_info[0] < 3", reason="no context manager api on Python 2")
+@pytest.mark.skipif('sys.platform == "win32"', reason="multiprocessing support is broken on Windows")
+@pytest.mark.skipif('platform.python_implementation() == "PyPy"', reason="often deadlocks on PyPy")
+@pytest.mark.skipif('sys.version_info[:2] == (3, 8)', reason="deadlocks on Python 3.8, see: https://bugs.python.org/issue38227")
+def test_multiprocessing_pool(testdir):
+ pytest.importorskip('multiprocessing.util')
+
+ script = testdir.makepyfile('''
+import multiprocessing
+
+def target_fn(a):
+ %sse: # pragma: nocover
+ return None
+
+def test_run_target():
+ from pytest_cov.embed import cleanup_on_sigterm
+ cleanup_on_sigterm()
+
+ for i in range(33):
+ with multiprocessing.Pool(3) as p:
+ p.map(target_fn, [i * 3 + j for j in range(3)])
+ p.join()
+''' % ''.join('''if a == %r:
+ return a
+ el''' % i for i in range(99)))
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ script)
+
+ assert "Doesn't seem to be a coverage.py data file" not in result.stdout.str()
+ assert "Doesn't seem to be a coverage.py data file" not in result.stderr.str()
+ assert not testdir.tmpdir.listdir(".coverage.*")
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_multiprocessing_pool* 100%*',
+ '*1 passed*'
+ ])
+ assert result.ret == 0
+
+
+@pytest.mark.skipif('sys.platform == "win32"', reason="multiprocessing support is broken on Windows")
+@pytest.mark.skipif('platform.python_implementation() == "PyPy"', reason="often deadlocks on PyPy")
+@pytest.mark.skipif('sys.version_info[:2] == (3, 8)', reason="deadlocks on Python 3.8, see: https://bugs.python.org/issue38227")
+def test_multiprocessing_pool_terminate(testdir):
+ pytest.importorskip('multiprocessing.util')
+
+ script = testdir.makepyfile('''
+import multiprocessing
+
+def target_fn(a):
+ %sse: # pragma: nocover
+ return None
+
+def test_run_target():
+ from pytest_cov.embed import cleanup_on_sigterm
+ cleanup_on_sigterm()
+
+ for i in range(33):
+ p = multiprocessing.Pool(3)
+ try:
+ p.map(target_fn, [i * 3 + j for j in range(3)])
+ finally:
+ p.terminate()
+ p.join()
+''' % ''.join('''if a == %r:
+ return a
+ el''' % i for i in range(99)))
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ script)
+
+ assert "Doesn't seem to be a coverage.py data file" not in result.stdout.str()
+ assert "Doesn't seem to be a coverage.py data file" not in result.stderr.str()
+ assert not testdir.tmpdir.listdir(".coverage.*")
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_multiprocessing_pool* 100%*',
+ '*1 passed*'
+ ])
+ assert result.ret == 0
+
+
+@pytest.mark.skipif('sys.platform == "win32"', reason="multiprocessing support is broken on Windows")
+@pytest.mark.skipif('sys.version_info[0] > 2 and platform.python_implementation() == "PyPy"', reason="broken on PyPy3")
+def test_multiprocessing_pool_close(testdir):
+ pytest.importorskip('multiprocessing.util')
+
+ script = testdir.makepyfile('''
+import multiprocessing
+
+def target_fn(a):
+ %sse: # pragma: nocover
+ return None
+
+def test_run_target():
+ for i in range(33):
+ p = multiprocessing.Pool(3)
+ try:
+ p.map(target_fn, [i * 3 + j for j in range(3)])
+ finally:
+ p.close()
+ p.join()
+''' % ''.join('''if a == %r:
+ return a
+ el''' % i for i in range(99)))
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ script)
+ assert "Doesn't seem to be a coverage.py data file" not in result.stdout.str()
+ assert "Doesn't seem to be a coverage.py data file" not in result.stderr.str()
+ assert not testdir.tmpdir.listdir(".coverage.*")
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_multiprocessing_pool* 100%*',
+ '*1 passed*'
+ ])
+ assert result.ret == 0
+
+
+@pytest.mark.skipif('sys.platform == "win32"', reason="multiprocessing support is broken on Windows")
+def test_multiprocessing_process(testdir):
+ pytest.importorskip('multiprocessing.util')
+
+ script = testdir.makepyfile('''
+import multiprocessing
+
+def target_fn():
+ a = True
+ return a
+
+def test_run_target():
+ p = multiprocessing.Process(target=target_fn)
+ p.start()
+ p.join()
+''')
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_multiprocessing_process* 8 * 100%*',
+ '*1 passed*'
+ ])
+ assert result.ret == 0
+
+
+@pytest.mark.skipif('sys.platform == "win32"', reason="multiprocessing support is broken on Windows")
+def test_multiprocessing_process_no_source(testdir):
+ pytest.importorskip('multiprocessing.util')
+
+ script = testdir.makepyfile('''
+import multiprocessing
+
+def target_fn():
+ a = True
+ return a
+
+def test_run_target():
+ p = multiprocessing.Process(target=target_fn)
+ p.start()
+ p.join()
+''')
+
+ result = testdir.runpytest('-v',
+ '--cov',
+ '--cov-report=term-missing',
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_multiprocessing_process* 8 * 100%*',
+ '*1 passed*'
+ ])
+ assert result.ret == 0
+
+
+@pytest.mark.skipif('sys.platform == "win32"', reason="multiprocessing support is broken on Windows")
+def test_multiprocessing_process_with_terminate(testdir):
+ pytest.importorskip('multiprocessing.util')
+
+ script = testdir.makepyfile('''
+import multiprocessing
+import time
+from pytest_cov.embed import cleanup_on_sigterm
+cleanup_on_sigterm()
+
+event = multiprocessing.Event()
+
+def target_fn():
+ a = True
+ event.set()
+ time.sleep(5)
+
+def test_run_target():
+ p = multiprocessing.Process(target=target_fn)
+ p.start()
+ time.sleep(0.5)
+ event.wait(1)
+ p.terminate()
+ p.join()
+''')
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_multiprocessing_process* 16 * 100%*',
+ '*1 passed*'
+ ])
+ assert result.ret == 0
+
+
+@pytest.mark.skipif('sys.platform == "win32"', reason="SIGTERM isn't really supported on Windows")
+def test_cleanup_on_sigterm(testdir):
+ script = testdir.makepyfile('''
+import os, signal, subprocess, sys, time
+
+def cleanup(num, frame):
+ print("num == signal.SIGTERM => %s" % (num == signal.SIGTERM))
+ raise Exception()
+
+def test_run():
+ proc = subprocess.Popen([sys.executable, __file__], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ time.sleep(1)
+ proc.terminate()
+ stdout, stderr = proc.communicate()
+ assert not stderr
+ assert stdout == b"""num == signal.SIGTERM => True
+captured Exception()
+"""
+ assert proc.returncode == 0
+
+if __name__ == "__main__":
+ signal.signal(signal.SIGTERM, cleanup)
+
+ from pytest_cov.embed import cleanup_on_sigterm
+ cleanup_on_sigterm()
+
+ try:
+ time.sleep(10)
+ except BaseException as exc:
+ print("captured %r" % exc)
+''')
+
+ result = testdir.runpytest('-vv',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_cleanup_on_sigterm* 26-27',
+ '*1 passed*'
+ ])
+ assert result.ret == 0
+
+
+@pytest.mark.skipif('sys.platform != "win32"')
+@pytest.mark.parametrize('setup', [
+ ('signal.signal(signal.SIGBREAK, signal.SIG_DFL); cleanup_on_signal(signal.SIGBREAK)', '87% 21-22'),
+ ('cleanup_on_signal(signal.SIGBREAK)', '87% 21-22'),
+ ('cleanup()', '73% 19-22'),
+])
+def test_cleanup_on_sigterm_sig_break(testdir, setup):
+ # worth a read: https://stefan.sofa-rockers.org/2013/08/15/handling-sub-process-hierarchies-python-linux-os-x/
+ script = testdir.makepyfile('''
+import os, signal, subprocess, sys, time
+
+def test_run():
+ proc = subprocess.Popen(
+ [sys.executable, __file__],
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+ creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, shell=True
+ )
+ time.sleep(1)
+ proc.send_signal(signal.CTRL_BREAK_EVENT)
+ stdout, stderr = proc.communicate()
+ assert not stderr
+ assert stdout in [b"^C", b"", b"captured IOError(4, 'Interrupted function call')\\n"]
+
+if __name__ == "__main__":
+ from pytest_cov.embed import cleanup_on_signal, cleanup
+ ''' + setup[0] + '''
+
+ try:
+ time.sleep(10)
+ except BaseException as exc:
+ print("captured %r" % exc)
+''')
+
+ result = testdir.runpytest('-vv',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_cleanup_on_sigterm* %s' % setup[1],
+ '*1 passed*'
+ ])
+ assert result.ret == 0
+
+
+@pytest.mark.skipif('sys.platform == "win32"', reason="SIGTERM isn't really supported on Windows")
+@pytest.mark.parametrize('setup', [
+ ('signal.signal(signal.SIGTERM, signal.SIG_DFL); cleanup_on_sigterm()', '88% 18-19'),
+ ('cleanup_on_sigterm()', '88% 18-19'),
+ ('cleanup()', '75% 16-19'),
+])
+def test_cleanup_on_sigterm_sig_dfl(testdir, setup):
+ script = testdir.makepyfile('''
+import os, signal, subprocess, sys, time
+
+def test_run():
+ proc = subprocess.Popen([sys.executable, __file__], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ time.sleep(1)
+ proc.terminate()
+ stdout, stderr = proc.communicate()
+ assert not stderr
+ assert stdout == b""
+ assert proc.returncode in [128 + signal.SIGTERM, -signal.SIGTERM]
+
+if __name__ == "__main__":
+ from pytest_cov.embed import cleanup_on_sigterm, cleanup
+ ''' + setup[0] + '''
+
+ try:
+ time.sleep(10)
+ except BaseException as exc:
+ print("captured %r" % exc)
+''')
+
+ result = testdir.runpytest('-vv',
+ '--assert=plain',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_cleanup_on_sigterm* %s' % setup[1],
+ '*1 passed*'
+ ])
+ assert result.ret == 0
+
+
+@pytest.mark.skipif('sys.platform == "win32"', reason="SIGINT is subtly broken on Windows")
+def test_cleanup_on_sigterm_sig_dfl_sigint(testdir):
+ script = testdir.makepyfile('''
+import os, signal, subprocess, sys, time
+
+def test_run():
+ proc = subprocess.Popen([sys.executable, __file__], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ time.sleep(1)
+ proc.send_signal(signal.SIGINT)
+ stdout, stderr = proc.communicate()
+ assert not stderr
+ assert stdout == b"""captured KeyboardInterrupt()
+"""
+ assert proc.returncode == 0
+
+if __name__ == "__main__":
+ from pytest_cov.embed import cleanup_on_signal
+ cleanup_on_signal(signal.SIGINT)
+
+ try:
+ time.sleep(10)
+ except BaseException as exc:
+ print("captured %r" % exc)
+''')
+
+ result = testdir.runpytest('-vv',
+ '--assert=plain',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_cleanup_on_sigterm* 88% 19-20',
+ '*1 passed*'
+ ])
+ assert result.ret == 0
+
+
+@pytest.mark.skipif('sys.platform == "win32"', reason="fork not available on Windows")
+def test_cleanup_on_sigterm_sig_ign(testdir):
+ script = testdir.makepyfile('''
+import os, signal, subprocess, sys, time
+
+def test_run():
+ proc = subprocess.Popen([sys.executable, __file__], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ time.sleep(1)
+ proc.send_signal(signal.SIGINT)
+ time.sleep(1)
+ proc.terminate()
+ stdout, stderr = proc.communicate()
+ assert not stderr
+ assert stdout == b""
+ # it appears signal handling is buggy on python 2?
+ if sys.version_info == 3: assert proc.returncode in [128 + signal.SIGTERM, -signal.SIGTERM]
+
+if __name__ == "__main__":
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
+
+ from pytest_cov.embed import cleanup_on_signal
+ cleanup_on_signal(signal.SIGINT)
+
+ try:
+ time.sleep(10)
+ except BaseException as exc:
+ print("captured %r" % exc)
+ ''')
+
+ result = testdir.runpytest('-vv',
+ '--assert=plain',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_cleanup_on_sigterm* 89% 23-24',
+ '*1 passed*'
+ ])
+ assert result.ret == 0
+
+
+MODULE = '''
+def func():
+ return 1
+
+'''
+
+CONFTEST = '''
+
+import mod
+mod.func()
+
+'''
+
+BASIC_TEST = '''
+
+def test_basic():
+ x = True
+ assert x
+
+'''
+
+CONF_RESULT = 'mod* 2 * 100%*'
+
+
+def test_cover_conftest(testdir):
+ testdir.makepyfile(mod=MODULE)
+ testdir.makeconftest(CONFTEST)
+ script = testdir.makepyfile(BASIC_TEST)
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ script)
+ assert result.ret == 0
+ result.stdout.fnmatch_lines([CONF_RESULT])
+
+
+@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"')
+def test_cover_looponfail(testdir, monkeypatch):
+ testdir.makepyfile(mod=MODULE)
+ testdir.makeconftest(CONFTEST)
+ script = testdir.makepyfile(BASIC_TEST)
+
+ monkeypatch.setattr(testdir, 'run',
+ lambda *args, **kwargs: _TestProcess(*map(str, args)))
+ with testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--looponfail',
+ script) as process:
+ with dump_on_error(process.read):
+ wait_for_strings(
+ process.read,
+ 30, # 30 seconds
+ 'Stmts Miss Cover'
+ )
+
+
+@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"')
+def test_cover_conftest_dist(testdir):
+ testdir.makepyfile(mod=MODULE)
+ testdir.makeconftest(CONFTEST)
+ script = testdir.makepyfile(BASIC_TEST)
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ '--dist=load',
+ '--tx=2*popen',
+ max_worker_restart_0,
+ script)
+ assert result.ret == 0
+ result.stdout.fnmatch_lines([CONF_RESULT])
+
+
+def test_no_cover_marker(testdir):
+ testdir.makepyfile(mod=MODULE)
+ script = testdir.makepyfile('''
+import pytest
+import mod
+import subprocess
+import sys
+
+@pytest.mark.no_cover
+def test_basic():
+ mod.func()
+ subprocess.check_call([sys.executable, '-c', 'from mod import func; func()'])
+''')
+ result = testdir.runpytest('-v', '-ra', '--strict',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ script)
+ assert result.ret == 0
+ result.stdout.fnmatch_lines(['mod* 2 * 1 * 50% * 2'])
+
+
+def test_no_cover_fixture(testdir):
+ testdir.makepyfile(mod=MODULE)
+ script = testdir.makepyfile('''
+import mod
+import subprocess
+import sys
+
+def test_basic(no_cover):
+ mod.func()
+ subprocess.check_call([sys.executable, '-c', 'from mod import func; func()'])
+''')
+ result = testdir.runpytest('-v', '-ra', '--strict',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ script)
+ assert result.ret == 0
+ result.stdout.fnmatch_lines(['mod* 2 * 1 * 50% * 2'])
+
+
+COVERAGERC = '''
+[report]
+# Regexes for lines to exclude from consideration
+exclude_lines =
+ raise NotImplementedError
+
+'''
+
+EXCLUDED_TEST = '''
+
+def func():
+ raise NotImplementedError
+
+def test_basic():
+ x = True
+ assert x
+
+'''
+
+EXCLUDED_RESULT = '4 * 100%*'
+
+
+def test_coveragerc(testdir):
+ testdir.makefile('', coveragerc=COVERAGERC)
+ script = testdir.makepyfile(EXCLUDED_TEST)
+ result = testdir.runpytest('-v',
+ '--cov-config=coveragerc',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ script)
+ assert result.ret == 0
+ result.stdout.fnmatch_lines(['test_coveragerc* %s' % EXCLUDED_RESULT])
+
+
+@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"')
+def test_coveragerc_dist(testdir):
+ testdir.makefile('', coveragerc=COVERAGERC)
+ script = testdir.makepyfile(EXCLUDED_TEST)
+ result = testdir.runpytest('-v',
+ '--cov-config=coveragerc',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ '-n', '2',
+ max_worker_restart_0,
+ script)
+ assert result.ret == 0
+ result.stdout.fnmatch_lines(
+ ['test_coveragerc_dist* %s' % EXCLUDED_RESULT])
+
+
+SKIP_COVERED_COVERAGERC = '''
+[report]
+skip_covered = True
+
+'''
+
+SKIP_COVERED_TEST = '''
+
+def func():
+ return "full coverage"
+
+def test_basic():
+ assert func() == "full coverage"
+
+'''
+
+SKIP_COVERED_RESULT = '1 file skipped due to complete coverage.'
+
+
+@pytest.mark.parametrize('report_option', [
+ 'term-missing:skip-covered',
+ 'term:skip-covered'])
+def test_skip_covered_cli(testdir, report_option):
+ testdir.makefile('', coveragerc=SKIP_COVERED_COVERAGERC)
+ script = testdir.makepyfile(SKIP_COVERED_TEST)
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=%s' % report_option,
+ script)
+ assert result.ret == 0
+ result.stdout.fnmatch_lines([SKIP_COVERED_RESULT])
+
+
+def test_skip_covered_coveragerc_config(testdir):
+ testdir.makefile('', coveragerc=SKIP_COVERED_COVERAGERC)
+ script = testdir.makepyfile(SKIP_COVERED_TEST)
+ result = testdir.runpytest('-v',
+ '--cov-config=coveragerc',
+ '--cov=%s' % script.dirpath(),
+ script)
+ assert result.ret == 0
+ result.stdout.fnmatch_lines([SKIP_COVERED_RESULT])
+
+
+CLEAR_ENVIRON_TEST = '''
+
+import os
+
+def test_basic():
+ os.environ.clear()
+
+'''
+
+
+def test_clear_environ(testdir):
+ script = testdir.makepyfile(CLEAR_ENVIRON_TEST)
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=term-missing',
+ script)
+ assert result.ret == 0
+
+
+SCRIPT_SIMPLE = '''
+
+def test_foo():
+ assert 1 == 1
+ x = True
+ assert x
+
+'''
+
+SCRIPT_SIMPLE_RESULT = '4 * 100%'
+
+
+@pytest.mark.skipif('sys.platform == "win32"')
+def test_dist_boxed(testdir):
+ script = testdir.makepyfile(SCRIPT_SIMPLE)
+
+ result = testdir.runpytest('-v',
+ '--assert=plain',
+ '--cov=%s' % script.dirpath(),
+ '--boxed',
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_dist_boxed* %s*' % SCRIPT_SIMPLE_RESULT,
+ '*1 passed*'
+ ])
+ assert result.ret == 0
+
+
+@pytest.mark.skipif('sys.platform == "win32"')
+@pytest.mark.skipif('sys.version_info[0] > 2 and platform.python_implementation() == "PyPy"',
+ reason="strange optimization on PyPy3")
+def test_dist_bare_cov(testdir):
+ script = testdir.makepyfile(SCRIPT_SIMPLE)
+
+ result = testdir.runpytest('-v',
+ '--cov',
+ '-n', '1',
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_dist_bare_cov* %s*' % SCRIPT_SIMPLE_RESULT,
+ '*1 passed*'
+ ])
+ assert result.ret == 0
+
+
+def test_not_started_plugin_does_not_fail(testdir):
+ class ns:
+ cov_source = [True]
+ cov_report = ''
+ plugin = pytest_cov.plugin.CovPlugin(ns, None, start=False)
+ plugin.pytest_runtestloop(None)
+ plugin.pytest_terminal_summary(None)
+
+
+def test_default_output_setting(testdir):
+ script = testdir.makepyfile(SCRIPT)
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*coverage*'
+ ])
+ assert result.ret == 0
+
+
+def test_disabled_output(testdir):
+ script = testdir.makepyfile(SCRIPT)
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-report=',
+ script)
+
+ stdout = result.stdout.str()
+ # We don't want the path to the executable to fail the test if we happen
+ # to put the project in a directory with "coverage" in it.
+ stdout = stdout.replace(sys.executable, "<SYS.EXECUTABLE>")
+ assert 'coverage' not in stdout
+ assert result.ret == 0
+
+
+def test_coverage_file(testdir):
+ script = testdir.makepyfile(SCRIPT)
+ data_file_name = 'covdata'
+ os.environ['COVERAGE_FILE'] = data_file_name
+ try:
+ result = testdir.runpytest('-v', '--cov=%s' % script.dirpath(),
+ script)
+ assert result.ret == 0
+ data_file = testdir.tmpdir.join(data_file_name)
+ assert data_file.check()
+ finally:
+ os.environ.pop('COVERAGE_FILE')
+
+
+def test_external_data_file(testdir):
+ script = testdir.makepyfile(SCRIPT)
+ testdir.tmpdir.join('.coveragerc').write("""
+[run]
+data_file = %s
+""" % testdir.tmpdir.join('some/special/place/coverage-data').ensure())
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ script)
+ assert result.ret == 0
+ assert glob.glob(str(testdir.tmpdir.join('some/special/place/coverage-data*')))
+
+
+@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"')
+def test_external_data_file_xdist(testdir):
+ script = testdir.makepyfile(SCRIPT)
+ testdir.tmpdir.join('.coveragerc').write("""
+[run]
+parallel = true
+data_file = %s
+""" % testdir.tmpdir.join('some/special/place/coverage-data').ensure())
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '-n', '1',
+ max_worker_restart_0,
+ script)
+ assert result.ret == 0
+ assert glob.glob(str(testdir.tmpdir.join('some/special/place/coverage-data*')))
+
+
+@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"')
+def test_xdist_no_data_collected(testdir):
+ testdir.makepyfile(target="x = 123")
+ script = testdir.makepyfile("""
+import target
+def test_foobar():
+ assert target.x == 123
+""")
+ result = testdir.runpytest('-v',
+ '--cov=target',
+ '-n', '1',
+ script)
+ assert 'no-data-collected' not in result.stderr.str()
+ assert 'no-data-collected' not in result.stdout.str()
+ assert 'module-not-imported' not in result.stderr.str()
+ assert 'module-not-imported' not in result.stdout.str()
+ assert result.ret == 0
+
+
+def test_external_data_file_negative(testdir):
+ script = testdir.makepyfile(SCRIPT)
+ testdir.tmpdir.join('.coveragerc').write("")
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ script)
+ assert result.ret == 0
+ assert glob.glob(str(testdir.tmpdir.join('.coverage*')))
+
+
+@xdist_params
+def test_append_coverage(testdir, opts, prop):
+ script = testdir.makepyfile(test_1=prop.code)
+ testdir.tmpdir.join('.coveragerc').write(prop.fullconf)
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ script,
+ *opts.split()+prop.args)
+ result.stdout.fnmatch_lines([
+ 'test_1* %s*' % prop.result,
+ ])
+ script2 = testdir.makepyfile(test_2=prop.code2)
+ result = testdir.runpytest('-v',
+ '--cov-append',
+ '--cov=%s' % script2.dirpath(),
+ script2,
+ *opts.split()+prop.args)
+ result.stdout.fnmatch_lines([
+ 'test_1* %s*' % prop.result,
+ 'test_2* %s*' % prop.result2,
+ ])
+
+
+@xdist_params
+def test_do_not_append_coverage(testdir, opts, prop):
+ script = testdir.makepyfile(test_1=prop.code)
+ testdir.tmpdir.join('.coveragerc').write(prop.fullconf)
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ script,
+ *opts.split()+prop.args)
+ result.stdout.fnmatch_lines([
+ 'test_1* %s*' % prop.result,
+ ])
+ script2 = testdir.makepyfile(test_2=prop.code2)
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script2.dirpath(),
+ script2,
+ *opts.split()+prop.args)
+ result.stdout.fnmatch_lines([
+ 'test_1* 0%',
+ 'test_2* %s*' % prop.result2,
+ ])
+
+
+@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"')
+def test_append_coverage_subprocess(testdir):
+ scripts = testdir.makepyfile(parent_script=SCRIPT_PARENT,
+ child_script=SCRIPT_CHILD)
+ parent_script = scripts.dirpath().join('parent_script.py')
+
+ result = testdir.runpytest('-v',
+ '--cov=%s' % scripts.dirpath(),
+ '--cov-append',
+ '--cov-report=term-missing',
+ '--dist=load',
+ '--tx=2*popen',
+ max_worker_restart_0,
+ parent_script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'child_script* %s*' % CHILD_SCRIPT_RESULT,
+ 'parent_script* %s*' % PARENT_SCRIPT_RESULT,
+ ])
+ assert result.ret == 0
+
+
+def test_pth_failure(monkeypatch):
+ with open('src/pytest-cov.pth') as fh:
+ payload = fh.read()
+
+ class SpecificError(Exception):
+ pass
+
+ def bad_init():
+ raise SpecificError()
+
+ buff = StringIO()
+
+ from pytest_cov import embed
+
+ monkeypatch.setattr(embed, 'init', bad_init)
+ monkeypatch.setattr(sys, 'stderr', buff)
+ monkeypatch.setitem(os.environ, 'COV_CORE_SOURCE', 'foobar')
+ exec_(payload)
+ assert buff.getvalue() == '''pytest-cov: Failed to setup subprocess coverage. Environ: {'COV_CORE_SOURCE': 'foobar'} Exception: SpecificError()
+'''
+
+
+def test_double_cov(testdir):
+ script = testdir.makepyfile(SCRIPT_SIMPLE)
+ result = testdir.runpytest('-v',
+ '--assert=plain',
+ '--cov', '--cov=%s' % script.dirpath(),
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_double_cov* %s*' % SCRIPT_SIMPLE_RESULT,
+ '*1 passed*'
+ ])
+ assert result.ret == 0
+
+
+def test_double_cov2(testdir):
+ script = testdir.makepyfile(SCRIPT_SIMPLE)
+ result = testdir.runpytest('-v',
+ '--assert=plain',
+ '--cov', '--cov',
+ script)
+
+ result.stdout.fnmatch_lines([
+ '*- coverage: platform *, python * -*',
+ 'test_double_cov2* %s*' % SCRIPT_SIMPLE_RESULT,
+ '*1 passed*'
+ ])
+ assert result.ret == 0
+
+
+@pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"')
+def test_cov_and_no_cov(testdir):
+ script = testdir.makepyfile(SCRIPT_SIMPLE)
+ result = testdir.runpytest('-v',
+ '--cov', '--no-cov',
+ '-n', '1',
+ '-s',
+ script)
+ assert 'Coverage disabled via --no-cov switch!' not in result.stdout.str()
+ assert 'Coverage disabled via --no-cov switch!' not in result.stderr.str()
+ assert result.ret == 0
+
+
+def find_labels(text, pattern):
+ all_labels = collections.defaultdict(set)
+ lines = text.splitlines()
+ for lineno, line in enumerate(lines, start=1):
+ labels = re.findall(pattern, line)
+ for label in labels:
+ all_labels[label].add(lineno)
+ return all_labels
+
+
+# The contexts and their labels in contextful.py
+EXPECTED_CONTEXTS = {
+ '': 'c0',
+ 'test_contexts.py::test_01|run': 'r1',
+ 'test_contexts.py::test_02|run': 'r2',
+ 'test_contexts.py::OldStyleTests::test_03|setup': 's3',
+ 'test_contexts.py::OldStyleTests::test_03|run': 'r3',
+ 'test_contexts.py::OldStyleTests::test_04|run': 'r4',
+ 'test_contexts.py::OldStyleTests::test_04|teardown': 't4',
+ 'test_contexts.py::test_05|setup': 's5',
+ 'test_contexts.py::test_05|run': 'r5',
+ 'test_contexts.py::test_06|setup': 's6',
+ 'test_contexts.py::test_06|run': 'r6',
+ 'test_contexts.py::test_07|setup': 's7',
+ 'test_contexts.py::test_07|run': 'r7',
+ 'test_contexts.py::test_08|run': 'r8',
+ 'test_contexts.py::test_09[1]|setup': 's9-1',
+ 'test_contexts.py::test_09[1]|run': 'r9-1',
+ 'test_contexts.py::test_09[2]|setup': 's9-2',
+ 'test_contexts.py::test_09[2]|run': 'r9-2',
+ 'test_contexts.py::test_09[3]|setup': 's9-3',
+ 'test_contexts.py::test_09[3]|run': 'r9-3',
+ 'test_contexts.py::test_10|run': 'r10',
+ 'test_contexts.py::test_11[1-101]|run': 'r11-1',
+ 'test_contexts.py::test_11[2-202]|run': 'r11-2',
+ 'test_contexts.py::test_12[one]|run': 'r12-1',
+ 'test_contexts.py::test_12[two]|run': 'r12-2',
+ 'test_contexts.py::test_13[3-1]|run': 'r13-1',
+ 'test_contexts.py::test_13[3-2]|run': 'r13-2',
+ 'test_contexts.py::test_13[4-1]|run': 'r13-3',
+ 'test_contexts.py::test_13[4-2]|run': 'r13-4',
+}
+
+
+@pytest.mark.skipif("coverage.version_info < (5, 0)")
+@xdist_params
+def test_contexts(testdir, opts):
+ with open(os.path.join(os.path.dirname(__file__), "contextful.py")) as f:
+ contextful_tests = f.read()
+ script = testdir.makepyfile(contextful_tests)
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-context=test',
+ script,
+ *opts.split()
+ )
+ assert result.ret == 0
+ result.stdout.fnmatch_lines([
+ 'test_contexts* 100%*',
+ ])
+
+ data = coverage.CoverageData(".coverage")
+ data.read()
+ assert data.measured_contexts() == set(EXPECTED_CONTEXTS)
+ measured = data.measured_files()
+ assert len(measured) == 1
+ test_context_path = list(measured)[0]
+ assert test_context_path.lower() == os.path.abspath("test_contexts.py").lower()
+
+ line_data = find_labels(contextful_tests, r"[crst]\d+(?:-\d+)?")
+ for context, label in EXPECTED_CONTEXTS.items():
+ if context == '':
+ 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)
+
+
+@pytest.mark.skipif("coverage.version_info >= (5, 0)")
+def test_contexts_not_supported(testdir):
+ script = testdir.makepyfile("a = 1")
+ result = testdir.runpytest('-v',
+ '--cov=%s' % script.dirpath(),
+ '--cov-context=test',
+ script,
+ )
+ result.stderr.fnmatch_lines([
+ '*argument --cov-context: Contexts are only supported with coverage.py >= 5.x',
+ ])
+ assert result.ret != 0
+
+
+def test_issue_417(testdir):
+ # https://github.com/pytest-dev/pytest-cov/issues/417
+ whatever = testdir.maketxtfile(whatever="")
+ testdir.inline_genitems(whatever)
--- /dev/null
+; a generative tox configuration, see: https://tox.readthedocs.io/en/latest/config.html#generative-envlist
+
+[tox]
+envlist =
+ check
+ py{27,35,36,37,py,py3}-pytest46-xdist27-coverage{45,52}
+ py{36,37,38,py3}-pytest{46,54}-xdist33-coverage{45,52}
+ py{36,37,38,py3}-pytest{60}-xdist200-coverage{52}
+ docs
+
+[testenv]
+extras = testing
+setenv =
+ PYTHONUNBUFFERED=yes
+
+ # Use env vars for (optional) pinning of deps.
+ pytest46: _DEP_PYTEST=pytest==4.6.10
+ pytest53: _DEP_PYTEST=pytest==5.3.2
+ pytest54: _DEP_PYTEST=pytest==5.4.3
+ pytest60: _DEP_PYTEST=pytest==6.0.1
+
+ xdist27: _DEP_PYTESTXDIST=pytest-xdist==1.27.0
+ xdist29: _DEP_PYTESTXDIST=pytest-xdist==1.29.0
+ xdist31: _DEP_PYTESTXDIST=pytest-xdist==1.31.0
+ xdist32: _DEP_PYTESTXDIST=pytest-xdist==1.32.0
+ xdist33: _DEP_PYTESTXDIST=pytest-xdist==1.33.0
+ xdist34: _DEP_PYTESTXDIST=pytest-xdist==1.34.0
+ xdist200: _DEP_PYTESTXDIST=pytest-xdist==2.0.0
+ xdistdev: _DEP_PYTESTXDIST=git+https://github.com/pytest-dev/pytest-xdist.git#egg=pytest-xdist
+
+ coverage45: _DEP_COVERAGE=coverage==4.5.4
+ coverage50: _DEP_COVERAGE=coverage==5.0.4
+ coverage51: _DEP_COVERAGE=coverage==5.1
+ coverage52: _DEP_COVERAGE=coverage==5.2.1
+ # For testing against a coverage.py working tree.
+ coveragedev: _DEP_COVERAGE=-e{env:COVERAGE_HOME}
+passenv =
+ *
+deps =
+ {env:_DEP_PYTEST:pytest}
+ {env:_DEP_PYTESTXDIST:pytest-xdist}
+ {env:_DEP_COVERAGE:coverage}
+pip_pre = true
+commands =
+ pytest {posargs:-vv}
+
+[testenv:spell]
+setenv =
+ SPELLCHECK=1
+commands =
+ sphinx-build -b spelling docs dist/docs
+skip_install = true
+usedevelop = false
+deps =
+ -r{toxinidir}/docs/requirements.txt
+ sphinxcontrib-spelling
+ pyenchant
+
+[testenv:docs]
+deps =
+ -r{toxinidir}/docs/requirements.txt
+commands =
+ sphinx-build {posargs:-E} -b html docs dist/docs
+
+[testenv:check]
+deps =
+ docutils
+ check-manifest
+ flake8
+ readme-renderer
+ pygments
+ isort
+skip_install = true
+usedevelop = false
+commands =
+ python setup.py check --strict --metadata --restructuredtext
+ check-manifest {toxinidir}
+ flake8 src tests setup.py
+ isort --check-only --diff src tests setup.py