Imported Upstream version python3-pytest-cov 2.10.1 upstream upstream/2.10.1
authorDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 12 Apr 2021 06:19:03 +0000 (15:19 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 12 Apr 2021 06:19:03 +0000 (15:19 +0900)
72 files changed:
.appveyor.yml [new file with mode: 0644]
.bumpversion.cfg [new file with mode: 0644]
.cookiecutterrc [new file with mode: 0644]
.editorconfig [new file with mode: 0644]
.github/ISSUE_TEMPLATE/bug_report.md [new file with mode: 0644]
.github/ISSUE_TEMPLATE/feature_request.md [new file with mode: 0644]
.github/ISSUE_TEMPLATE/support_request.md [new file with mode: 0644]
.pre-commit-config.yaml [new file with mode: 0644]
.readthedocs.yml [new file with mode: 0644]
.travis.yml [new file with mode: 0644]
AUTHORS.rst [new file with mode: 0644]
CHANGELOG.rst [new file with mode: 0644]
CONTRIBUTING.rst [new file with mode: 0644]
LICENSE [new file with mode: 0644]
MANIFEST.in [new file with mode: 0644]
PKG-INFO [new file with mode: 0644]
README.rst [new file with mode: 0644]
ci/appveyor-with-compiler.cmd [new file with mode: 0644]
ci/bootstrap.py [new file with mode: 0755]
ci/requirements.txt [new file with mode: 0644]
ci/templates/.appveyor.yml [new file with mode: 0644]
ci/templates/.travis.yml [new file with mode: 0644]
docs/authors.rst [new file with mode: 0644]
docs/changelog.rst [new file with mode: 0644]
docs/conf.py [new file with mode: 0644]
docs/config.rst [new file with mode: 0644]
docs/contexts.rst [new file with mode: 0644]
docs/contributing.rst [new file with mode: 0644]
docs/debuggers.rst [new file with mode: 0644]
docs/index.rst [new file with mode: 0644]
docs/markers-fixtures.rst [new file with mode: 0644]
docs/plugins.rst [new file with mode: 0644]
docs/readme.rst [new file with mode: 0644]
docs/releasing.rst [new file with mode: 0644]
docs/reporting.rst [new file with mode: 0644]
docs/requirements.txt [new file with mode: 0644]
docs/spelling_wordlist.txt [new file with mode: 0644]
docs/subprocess-support.rst [new file with mode: 0644]
docs/tox.rst [new file with mode: 0644]
docs/xdist.rst [new file with mode: 0644]
examples/README.rst [new file with mode: 0644]
examples/adhoc-layout/.coveragerc [new file with mode: 0644]
examples/adhoc-layout/example/__init__.py [new file with mode: 0644]
examples/adhoc-layout/setup.py [new file with mode: 0644]
examples/adhoc-layout/tests/test_example.py [new file with mode: 0644]
examples/adhoc-layout/tox.ini [new file with mode: 0644]
examples/src-layout/.coveragerc [new file with mode: 0644]
examples/src-layout/setup.py [new file with mode: 0644]
examples/src-layout/src/example/__init__.py [new file with mode: 0644]
examples/src-layout/tests/test_example.py [new file with mode: 0644]
examples/src-layout/tox.ini [new file with mode: 0644]
setup.cfg [new file with mode: 0644]
setup.py [new file with mode: 0755]
src/pytest-cov.embed [new file with mode: 0644]
src/pytest-cov.pth [new file with mode: 0644]
src/pytest_cov.egg-info/PKG-INFO [new file with mode: 0644]
src/pytest_cov.egg-info/SOURCES.txt [new file with mode: 0644]
src/pytest_cov.egg-info/dependency_links.txt [new file with mode: 0644]
src/pytest_cov.egg-info/entry_points.txt [new file with mode: 0644]
src/pytest_cov.egg-info/not-zip-safe [new file with mode: 0644]
src/pytest_cov.egg-info/requires.txt [new file with mode: 0644]
src/pytest_cov.egg-info/top_level.txt [new file with mode: 0644]
src/pytest_cov/__init__.py [new file with mode: 0644]
src/pytest_cov/compat.py [new file with mode: 0644]
src/pytest_cov/embed.py [new file with mode: 0644]
src/pytest_cov/engine.py [new file with mode: 0644]
src/pytest_cov/plugin.py [new file with mode: 0644]
tests/conftest.py [new file with mode: 0644]
tests/contextful.py [new file with mode: 0644]
tests/helper.py [new file with mode: 0644]
tests/test_pytest_cov.py [new file with mode: 0644]
tox.ini [new file with mode: 0644]

diff --git a/.appveyor.yml b/.appveyor.yml
new file mode 100644 (file)
index 0000000..b5c6654
--- /dev/null
@@ -0,0 +1,33 @@
+# 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'))
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
new file mode 100644 (file)
index 0000000..fc1162d
--- /dev/null
@@ -0,0 +1,21 @@
+[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}'
+
diff --git a/.cookiecutterrc b/.cookiecutterrc
new file mode 100644 (file)
index 0000000..c477a6f
--- /dev/null
@@ -0,0 +1,56 @@
+# 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'
diff --git a/.editorconfig b/.editorconfig
new file mode 100644 (file)
index 0000000..a9c7977
--- /dev/null
@@ -0,0 +1,16 @@
+# 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
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644 (file)
index 0000000..8cdb373
--- /dev/null
@@ -0,0 +1,31 @@
+---
+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
+```
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644 (file)
index 0000000..5e7d56e
--- /dev/null
@@ -0,0 +1,18 @@
+---
+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?
diff --git a/.github/ISSUE_TEMPLATE/support_request.md b/.github/ISSUE_TEMPLATE/support_request.md
new file mode 100644 (file)
index 0000000..7ea731e
--- /dev/null
@@ -0,0 +1,40 @@
+---
+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.
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644 (file)
index 0000000..f994232
--- /dev/null
@@ -0,0 +1,20 @@
+# 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
diff --git a/.readthedocs.yml b/.readthedocs.yml
new file mode 100644 (file)
index 0000000..ac76971
--- /dev/null
@@ -0,0 +1,11 @@
+# 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: .
diff --git a/.travis.yml b/.travis.yml
new file mode 100644 (file)
index 0000000..3c96aad
--- /dev/null
@@ -0,0 +1,115 @@
+# 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
diff --git a/AUTHORS.rst b/AUTHORS.rst
new file mode 100644 (file)
index 0000000..a768868
--- /dev/null
@@ -0,0 +1,41 @@
+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
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
new file mode 100644 (file)
index 0000000..3366645
--- /dev/null
@@ -0,0 +1,247 @@
+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
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
new file mode 100644 (file)
index 0000000..53f5597
--- /dev/null
@@ -0,0 +1,90 @@
+============
+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
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..5b3634b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+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.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644 (file)
index 0000000..6db4611
--- /dev/null
@@ -0,0 +1,26 @@
+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.*
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644 (file)
index 0000000..c036dc8
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,447 @@
+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
diff --git a/README.rst b/README.rst
new file mode 100644 (file)
index 0000000..2b1284d
--- /dev/null
@@ -0,0 +1,167 @@
+========
+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.
diff --git a/ci/appveyor-with-compiler.cmd b/ci/appveyor-with-compiler.cmd
new file mode 100644 (file)
index 0000000..289585f
--- /dev/null
@@ -0,0 +1,23 @@
+:: 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
diff --git a/ci/bootstrap.py b/ci/bootstrap.py
new file mode 100755 (executable)
index 0000000..61747a1
--- /dev/null
@@ -0,0 +1,93 @@
+#!/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)
diff --git a/ci/requirements.txt b/ci/requirements.txt
new file mode 100644 (file)
index 0000000..d7f5177
--- /dev/null
@@ -0,0 +1,4 @@
+virtualenv>=16.6.0
+pip>=19.1.1
+setuptools>=18.0.1
+six>=1.14.0
diff --git a/ci/templates/.appveyor.yml b/ci/templates/.appveyor.yml
new file mode 100644 (file)
index 0000000..92630e1
--- /dev/null
@@ -0,0 +1,32 @@
+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'))
diff --git a/ci/templates/.travis.yml b/ci/templates/.travis.yml
new file mode 100644 (file)
index 0000000..8c7e3b9
--- /dev/null
@@ -0,0 +1,61 @@
+dist: xenial
+language: python
+cache: false
+env:
+  global:
+    - LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so
+    - SEGFAULT_SIGNALS=all
+stages:
+  - lint
+  - examples
+  - tests
+jobs:
+  fast_finish: true
+  allow_failures:
+    - python: '3.8'
+  include:
+    - stage: lint
+      env: TOXENV=check
+    - env: TOXENV=docs
+
+    - stage: tests
+{% for env in tox_environments %}
+    {%+ if not loop.first %}- {% else %}  {% endif -%}
+      env: TOXENV={{ env }}
+    {% if env.startswith("pypy-") %}
+      python: 'pypy'
+    {% elif env.startswith("pypy3-") %}
+      python: 'pypy3'
+    {% else %}
+      python: '{{ "{0[2]}.{0[3]}".format(env) }}'
+    {% endif -%}
+{% endfor %}
+
+    - stage: examples
+{%- for example in ['src', 'adhoc'] %}{{ '' }}
+    {%+ if not loop.first %}- {% else %}  {% endif -%}
+      python: '3.8'
+      script: cd $TARGET; tox -v
+      env:
+          - TARGET=examples/{{ example }}-layout
+{%- endfor %}
+
+before_install:
+  - python --version
+  - uname -a
+  - lsb_release -a
+install:
+  - python -mpip install --progress-bar=off tox -rci/requirements.txt
+  - virtualenv --version
+  - easy_install --version
+  - pip --version
+  - tox --version
+script:
+  - tox -v
+after_failure:
+  - more .tox/log/* | cat
+  - more .tox/*/log/* | cat
+notifications:
+  email:
+    on_success: never
+    on_failure: always
diff --git a/docs/authors.rst b/docs/authors.rst
new file mode 100644 (file)
index 0000000..e122f91
--- /dev/null
@@ -0,0 +1 @@
+.. include:: ../AUTHORS.rst
diff --git a/docs/changelog.rst b/docs/changelog.rst
new file mode 100644 (file)
index 0000000..565b052
--- /dev/null
@@ -0,0 +1 @@
+.. include:: ../CHANGELOG.rst
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644 (file)
index 0000000..353927d
--- /dev/null
@@ -0,0 +1,54 @@
+# -*- 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
diff --git a/docs/config.rst b/docs/config.rst
new file mode 100644 (file)
index 0000000..3e5bf93
--- /dev/null
@@ -0,0 +1,66 @@
+=============
+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.
diff --git a/docs/contexts.rst b/docs/contexts.rst
new file mode 100644 (file)
index 0000000..e5256fc
--- /dev/null
@@ -0,0 +1,18 @@
+========
+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.
diff --git a/docs/contributing.rst b/docs/contributing.rst
new file mode 100644 (file)
index 0000000..e582053
--- /dev/null
@@ -0,0 +1 @@
+.. include:: ../CONTRIBUTING.rst
diff --git a/docs/debuggers.rst b/docs/debuggers.rst
new file mode 100644 (file)
index 0000000..15c8321
--- /dev/null
@@ -0,0 +1,15 @@
+=====================
+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!
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644 (file)
index 0000000..32fb1ca
--- /dev/null
@@ -0,0 +1,29 @@
+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`
diff --git a/docs/markers-fixtures.rst b/docs/markers-fixtures.rst
new file mode 100644 (file)
index 0000000..d0eef23
--- /dev/null
@@ -0,0 +1,43 @@
+====================
+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.
diff --git a/docs/plugins.rst b/docs/plugins.rst
new file mode 100644 (file)
index 0000000..d06c4ff
--- /dev/null
@@ -0,0 +1,24 @@
+===============
+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
diff --git a/docs/readme.rst b/docs/readme.rst
new file mode 100644 (file)
index 0000000..72a3355
--- /dev/null
@@ -0,0 +1 @@
+.. include:: ../README.rst
diff --git a/docs/releasing.rst b/docs/releasing.rst
new file mode 100644 (file)
index 0000000..245dca5
--- /dev/null
@@ -0,0 +1,34 @@
+=========
+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/*
diff --git a/docs/reporting.rst b/docs/reporting.rst
new file mode 100644 (file)
index 0000000..e9e4b06
--- /dev/null
@@ -0,0 +1,74 @@
+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.
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644 (file)
index 0000000..ccec79f
--- /dev/null
@@ -0,0 +1,4 @@
+sphinx==3.0.3
+sphinx-py3doc-enhanced-theme==2.4.0
+docutils==0.16
+-e .
diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt
new file mode 100644 (file)
index 0000000..f95eb78
--- /dev/null
@@ -0,0 +1,11 @@
+builtin
+builtins
+classmethod
+staticmethod
+classmethods
+staticmethods
+args
+kwargs
+callstack
+Changelog
+Indices
diff --git a/docs/subprocess-support.rst b/docs/subprocess-support.rst
new file mode 100644 (file)
index 0000000..331db7d
--- /dev/null
@@ -0,0 +1,174 @@
+==================
+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
diff --git a/docs/tox.rst b/docs/tox.rst
new file mode 100644 (file)
index 0000000..18f9137
--- /dev/null
@@ -0,0 +1,73 @@
+===
+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.
diff --git a/docs/xdist.rst b/docs/xdist.rst
new file mode 100644 (file)
index 0000000..a2da50e
--- /dev/null
@@ -0,0 +1,74 @@
+===========================
+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%
diff --git a/examples/README.rst b/examples/README.rst
new file mode 100644 (file)
index 0000000..a232f2a
--- /dev/null
@@ -0,0 +1,15 @@
+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.
diff --git a/examples/adhoc-layout/.coveragerc b/examples/adhoc-layout/.coveragerc
new file mode 100644 (file)
index 0000000..199d42e
--- /dev/null
@@ -0,0 +1,15 @@
+[paths]
+source =
+    ../example
+    */site-packages/example
+
+[run]
+branch = true
+parallel = true
+source =
+    example
+    .
+
+[report]
+show_missing = true
+precision = 2
diff --git a/examples/adhoc-layout/example/__init__.py b/examples/adhoc-layout/example/__init__.py
new file mode 100644 (file)
index 0000000..18080ac
--- /dev/null
@@ -0,0 +1,13 @@
+
+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
diff --git a/examples/adhoc-layout/setup.py b/examples/adhoc-layout/setup.py
new file mode 100644 (file)
index 0000000..e52b68d
--- /dev/null
@@ -0,0 +1,7 @@
+from setuptools import find_packages
+from setuptools import setup
+
+setup(
+    name='example',
+    packages=find_packages(include=['example'])
+)
diff --git a/examples/adhoc-layout/tests/test_example.py b/examples/adhoc-layout/tests/test_example.py
new file mode 100644 (file)
index 0000000..f4948e6
--- /dev/null
@@ -0,0 +1,6 @@
+import example
+
+
+def test_add():
+    assert example.add(1, 1) == 2
+    assert not example.add(0, 1) == 2
diff --git a/examples/adhoc-layout/tox.ini b/examples/adhoc-layout/tox.ini
new file mode 100644 (file)
index 0000000..a2472d0
--- /dev/null
@@ -0,0 +1,36 @@
+[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
diff --git a/examples/src-layout/.coveragerc b/examples/src-layout/.coveragerc
new file mode 100644 (file)
index 0000000..b4c80de
--- /dev/null
@@ -0,0 +1,15 @@
+[paths]
+source =
+   src
+   */site-packages
+
+[run]
+branch = true
+parallel = true
+source =
+    example
+    tests
+
+[report]
+show_missing = true
+precision = 2
diff --git a/examples/src-layout/setup.py b/examples/src-layout/setup.py
new file mode 100644 (file)
index 0000000..c8ded81
--- /dev/null
@@ -0,0 +1,8 @@
+from setuptools import find_packages
+from setuptools import setup
+
+setup(
+    name='example',
+    packages=find_packages('src'),
+    package_dir={'': 'src'},
+)
diff --git a/examples/src-layout/src/example/__init__.py b/examples/src-layout/src/example/__init__.py
new file mode 100644 (file)
index 0000000..18080ac
--- /dev/null
@@ -0,0 +1,13 @@
+
+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
diff --git a/examples/src-layout/tests/test_example.py b/examples/src-layout/tests/test_example.py
new file mode 100644 (file)
index 0000000..f4948e6
--- /dev/null
@@ -0,0 +1,6 @@
+import example
+
+
+def test_add():
+    assert example.add(1, 1) == 2
+    assert not example.add(0, 1) == 2
diff --git a/examples/src-layout/tox.ini b/examples/src-layout/tox.ini
new file mode 100644 (file)
index 0000000..054b4b8
--- /dev/null
@@ -0,0 +1,34 @@
+[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
diff --git a/setup.cfg b/setup.cfg
new file mode 100644 (file)
index 0000000..25b8349
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,27 @@
+[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
+
diff --git a/setup.py b/setup.py
new file mode 100755 (executable)
index 0000000..6796670
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,154 @@
+#!/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,
+    },
+)
diff --git a/src/pytest-cov.embed b/src/pytest-cov.embed
new file mode 100644 (file)
index 0000000..630a2a7
--- /dev/null
@@ -0,0 +1,13 @@
+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
+            )
+        )
diff --git a/src/pytest-cov.pth b/src/pytest-cov.pth
new file mode 100644 (file)
index 0000000..91f2b7c
--- /dev/null
@@ -0,0 +1 @@
+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
diff --git a/src/pytest_cov.egg-info/PKG-INFO b/src/pytest_cov.egg-info/PKG-INFO
new file mode 100644 (file)
index 0000000..c036dc8
--- /dev/null
@@ -0,0 +1,447 @@
+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
diff --git a/src/pytest_cov.egg-info/SOURCES.txt b/src/pytest_cov.egg-info/SOURCES.txt
new file mode 100644 (file)
index 0000000..e06f275
--- /dev/null
@@ -0,0 +1,72 @@
+.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
diff --git a/src/pytest_cov.egg-info/dependency_links.txt b/src/pytest_cov.egg-info/dependency_links.txt
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/src/pytest_cov.egg-info/entry_points.txt b/src/pytest_cov.egg-info/entry_points.txt
new file mode 100644 (file)
index 0000000..d351f96
--- /dev/null
@@ -0,0 +1,6 @@
+[console_scripts]
+
+
+[pytest11]
+pytest_cov = pytest_cov.plugin
+
diff --git a/src/pytest_cov.egg-info/not-zip-safe b/src/pytest_cov.egg-info/not-zip-safe
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/src/pytest_cov.egg-info/requires.txt b/src/pytest_cov.egg-info/requires.txt
new file mode 100644 (file)
index 0000000..025f7c4
--- /dev/null
@@ -0,0 +1,10 @@
+pytest>=4.6
+coverage>=4.4
+
+[testing]
+fields
+hunter
+process-tests==2.0.2
+six
+pytest-xdist
+virtualenv
diff --git a/src/pytest_cov.egg-info/top_level.txt b/src/pytest_cov.egg-info/top_level.txt
new file mode 100644 (file)
index 0000000..a2fe281
--- /dev/null
@@ -0,0 +1 @@
+pytest_cov
diff --git a/src/pytest_cov/__init__.py b/src/pytest_cov/__init__.py
new file mode 100644 (file)
index 0000000..61668ca
--- /dev/null
@@ -0,0 +1,2 @@
+"""pytest-cov: avoid already-imported warning: PYTEST_DONT_REWRITE."""
+__version__ = "__version__ = '2.10.1'"
diff --git a/src/pytest_cov/compat.py b/src/pytest_cov/compat.py
new file mode 100644 (file)
index 0000000..5b4a0bf
--- /dev/null
@@ -0,0 +1,31 @@
+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)
diff --git a/src/pytest_cov/embed.py b/src/pytest_cov/embed.py
new file mode 100644 (file)
index 0000000..7e081fe
--- /dev/null
@@ -0,0 +1,137 @@
+"""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)
diff --git a/src/pytest_cov/engine.py b/src/pytest_cov/engine.py
new file mode 100644 (file)
index 0000000..eab0656
--- /dev/null
@@ -0,0 +1,401 @@
+"""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
diff --git a/src/pytest_cov/plugin.py b/src/pytest_cov/plugin.py
new file mode 100644 (file)
index 0000000..2d22b30
--- /dev/null
@@ -0,0 +1,380 @@
+"""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.")
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644 (file)
index 0000000..2fb0f9b
--- /dev/null
@@ -0,0 +1,2 @@
+def pytest_configure(config):
+    config.option.runpytest = 'subprocess'
diff --git a/tests/contextful.py b/tests/contextful.py
new file mode 100644 (file)
index 0000000..3527e49
--- /dev/null
@@ -0,0 +1,105 @@
+# 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
diff --git a/tests/helper.py b/tests/helper.py
new file mode 100644 (file)
index 0000000..3e7da4b
--- /dev/null
@@ -0,0 +1,3 @@
+def do_stuff():
+    a = 1
+    return a
diff --git a/tests/test_pytest_cov.py b/tests/test_pytest_cov.py
new file mode 100644 (file)
index 0000000..f0fd6ca
--- /dev/null
@@ -0,0 +1,2115 @@
+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)
diff --git a/tox.ini b/tox.ini
new file mode 100644 (file)
index 0000000..ec08819
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,79 @@
+; 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