pax_global_header00006660000000000000000000000064135461110050014506gustar00rootroot0000000000000052 comment=33894b550e6445525461ae5dacd3cbbcf381ea41 pytest-cov-2.8.1/000077500000000000000000000000001354611100500136335ustar00rootroot00000000000000pytest-cov-2.8.1/.bumpversion.cfg000066400000000000000000000003141354611100500167410ustar00rootroot00000000000000[bumpversion] current_version = 2.8.1 commit = True tag = True [bumpversion:file:setup.py] [bumpversion:file:README.rst] [bumpversion:file:docs/conf.py] [bumpversion:file:src/pytest_cov/__init__.py] pytest-cov-2.8.1/.cookiecutterrc000066400000000000000000000022141354611100500166600ustar00rootroot00000000000000# Generated by cookiepatcher, a small shim around cookiecutter (pip install cookiepatcher) cookiecutter: appveyor: 'yes' c_extension_cython: 'no' c_extension_optional: 'no' c_extension_support: 'no' codacy: 'yes' codeclimate: 'yes' codecov: 'no' command_line_interface: 'no' command_line_interface_bin_name: pytest-cov coveralls: 'no' distribution_name: pytest-cov email: contact@ionelmc.ro full_name: Ionel Cristian Mărieș github_username: pytest-dev landscape: 'yes' package_name: pytest_cov 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. release_date: '2016-10-10' repo_name: pytest-cov requiresio: 'yes' scrutinizer: 'yes' sphinx_doctest: 'no' sphinx_theme: sphinx-py3doc-enhanced-theme test_matrix_configurator: 'no' test_matrix_separate_coverage: 'no' test_runner: pytest travis: 'yes' version: 2.4.0 website: http://blog.ionelmc.ro year: now pytest-cov-2.8.1/.coveragerc000066400000000000000000000002071354611100500157530ustar00rootroot00000000000000[paths] source = src [run] branch = True source = src parallel = true [report] show_missing = true precision = 2 omit = *migrations* pytest-cov-2.8.1/.editorconfig000066400000000000000000000003271354611100500163120ustar00rootroot00000000000000# see http://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 pytest-cov-2.8.1/.gitignore000066400000000000000000000010531354611100500156220ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs .eggs parts bin var sdist wheelhouse develop-eggs .installed.cfg lib lib64 venv*/ pyvenv*/ # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox .coverage.* nosetests.xml coverage.xml htmlcov # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject .idea *.iml *.komodoproject # Complexity output/*.html output/*/index.html # Sphinx docs/_build .DS_Store *~ .*.sw[po] .build .ve .env .cache .pytest .bootstrap .appveyor.token *.bak pytest-cov-2.8.1/.travis.yml000066400000000000000000000143461354611100500157540ustar00rootroot00000000000000# 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 matrix: fast_finish: true allow_failures: - python: '3.8-dev' jobs: include: - stage: lint env: TOXENV=check - env: TOXENV=docs - stage: tests env: TOXENV=py27-pytest310-xdist27-coverage45 python: '2.7' - env: TOXENV=py27-pytest46-xdist27-coverage45 python: '2.7' - env: TOXENV=py34-pytest310-xdist27-coverage45 python: '3.4' - env: TOXENV=py34-pytest46-xdist27-coverage45 python: '3.4' - env: TOXENV=py35-pytest310-xdist27-coverage45 python: '3.5' - env: TOXENV=py35-pytest46-xdist27-coverage45 python: '3.5' - env: TOXENV=py36-pytest310-xdist27-coverage45 python: '3.6' - env: TOXENV=py36-pytest46-xdist27-coverage45 python: '3.6' - env: TOXENV=py37-pytest310-xdist27-coverage45 python: '3.7' - env: TOXENV=py37-pytest46-xdist27-coverage45 python: '3.7' - env: TOXENV=pypy-pytest310-xdist27-coverage45 python: 'pypy' - env: TOXENV=pypy-pytest46-xdist27-coverage45 python: 'pypy' - env: TOXENV=pypy3-pytest310-xdist27-coverage45 python: 'pypy3' - env: TOXENV=pypy3-pytest46-xdist27-coverage45 python: 'pypy3' - env: TOXENV=py27-pytest310-xdist27-coverage50 python: '2.7' - env: TOXENV=py27-pytest46-xdist27-coverage50 python: '2.7' - env: TOXENV=py35-pytest310-xdist27-coverage50 python: '3.5' - env: TOXENV=py35-pytest46-xdist27-coverage50 python: '3.5' - env: TOXENV=py36-pytest310-xdist27-coverage50 python: '3.6' - env: TOXENV=py36-pytest46-xdist27-coverage50 python: '3.6' - env: TOXENV=py37-pytest310-xdist27-coverage50 python: '3.7' - env: TOXENV=py37-pytest46-xdist27-coverage50 python: '3.7' - env: TOXENV=pypy-pytest310-xdist27-coverage50 python: 'pypy' - env: TOXENV=pypy-pytest46-xdist27-coverage50 python: 'pypy' - env: TOXENV=pypy3-pytest310-xdist27-coverage50 python: 'pypy3' - env: TOXENV=pypy3-pytest46-xdist27-coverage50 python: 'pypy3' - env: TOXENV=py36-pytest46-xdist29-coverage45 python: '3.6' - env: TOXENV=py36-pytest46-xdist29-coverage50 python: '3.6' - env: TOXENV=py36-pytest46-xdist30-coverage45 python: '3.6' - env: TOXENV=py36-pytest46-xdist30-coverage50 python: '3.6' - env: TOXENV=py36-pytest51-xdist29-coverage45 python: '3.6' - env: TOXENV=py36-pytest51-xdist29-coverage50 python: '3.6' - env: TOXENV=py36-pytest51-xdist30-coverage45 python: '3.6' - env: TOXENV=py36-pytest51-xdist30-coverage50 python: '3.6' - env: TOXENV=py36-pytest52-xdist29-coverage45 python: '3.6' - env: TOXENV=py36-pytest52-xdist29-coverage50 python: '3.6' - env: TOXENV=py36-pytest52-xdist30-coverage45 python: '3.6' - env: TOXENV=py36-pytest52-xdist30-coverage50 python: '3.6' - env: TOXENV=py37-pytest46-xdist29-coverage45 python: '3.7' - env: TOXENV=py37-pytest46-xdist29-coverage50 python: '3.7' - env: TOXENV=py37-pytest46-xdist30-coverage45 python: '3.7' - env: TOXENV=py37-pytest46-xdist30-coverage50 python: '3.7' - env: TOXENV=py37-pytest51-xdist29-coverage45 python: '3.7' - env: TOXENV=py37-pytest51-xdist29-coverage50 python: '3.7' - env: TOXENV=py37-pytest51-xdist30-coverage45 python: '3.7' - env: TOXENV=py37-pytest51-xdist30-coverage50 python: '3.7' - env: TOXENV=py37-pytest52-xdist29-coverage45 python: '3.7' - env: TOXENV=py37-pytest52-xdist29-coverage50 python: '3.7' - env: TOXENV=py37-pytest52-xdist30-coverage45 python: '3.7' - env: TOXENV=py37-pytest52-xdist30-coverage50 python: '3.7' - env: TOXENV=py38-pytest46-xdist29-coverage45 python: '3.8-dev' - env: TOXENV=py38-pytest46-xdist29-coverage50 python: '3.8-dev' - env: TOXENV=py38-pytest46-xdist30-coverage45 python: '3.8-dev' - env: TOXENV=py38-pytest46-xdist30-coverage50 python: '3.8-dev' - env: TOXENV=py38-pytest51-xdist29-coverage45 python: '3.8-dev' - env: TOXENV=py38-pytest51-xdist29-coverage50 python: '3.8-dev' - env: TOXENV=py38-pytest51-xdist30-coverage45 python: '3.8-dev' - env: TOXENV=py38-pytest51-xdist30-coverage50 python: '3.8-dev' - env: TOXENV=py38-pytest52-xdist29-coverage45 python: '3.8-dev' - env: TOXENV=py38-pytest52-xdist29-coverage50 python: '3.8-dev' - env: TOXENV=py38-pytest52-xdist30-coverage45 python: '3.8-dev' - env: TOXENV=py38-pytest52-xdist30-coverage50 python: '3.8-dev' - env: TOXENV=pypy3-pytest46-xdist29-coverage45 python: 'pypy3' - env: TOXENV=pypy3-pytest46-xdist29-coverage50 python: 'pypy3' - env: TOXENV=pypy3-pytest46-xdist30-coverage45 python: 'pypy3' - env: TOXENV=pypy3-pytest46-xdist30-coverage50 python: 'pypy3' - env: TOXENV=pypy3-pytest51-xdist29-coverage45 python: 'pypy3' - env: TOXENV=pypy3-pytest51-xdist29-coverage50 python: 'pypy3' - env: TOXENV=pypy3-pytest51-xdist30-coverage45 python: 'pypy3' - env: TOXENV=pypy3-pytest51-xdist30-coverage50 python: 'pypy3' - env: TOXENV=pypy3-pytest52-xdist29-coverage45 python: 'pypy3' - env: TOXENV=pypy3-pytest52-xdist29-coverage50 python: 'pypy3' - env: TOXENV=pypy3-pytest52-xdist30-coverage45 python: 'pypy3' - env: TOXENV=pypy3-pytest52-xdist30-coverage50 python: 'pypy3' - stage: examples python: '3.6' script: cd $TARGET; tox -v env: - TARGET=examples/src-layout - python: '3.6' script: cd $TARGET; tox -v env: - TARGET=examples/adhoc-layout before_install: - python --version - uname -a - lsb_release -a install: - pip install tox - 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 pytest-cov-2.8.1/AUTHORS.rst000066400000000000000000000031431354611100500155130ustar00rootroot00000000000000Authors ======= * 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 pytest-cov-2.8.1/CHANGELOG.rst000066400000000000000000000253211354611100500156570ustar00rootroot00000000000000Changelog ========= 2.8.1 (2019-10-05) ------------------ * Fixed `#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 `__ or `cleanup_on_sigterm `__. See: `#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 `_ * Fixed breakage that occurs when multiple reporting options are used. Contributed by Thomas Grainger in `#338 `_. * Changed internals to use a stub instead of ``os.devnull``. Contributed by Thomas Grainger in `#332 `_. * Added support for Coverage 5.0. Contributed by Ned Batchelder in `#319 `_. * Added support for float values in ``--cov-fail-under``. Contributed by Martín Gaitán in `#311 `_. * Various documentation fixes. Contributed by Juanjo Bazán, Andrew Murray and Albert Tugushev in `#298 `_, `#299 `_ and `#307 `_. * Various testing improvements. Contributed by Ned Batchelder, Daniel Hahler, Ionel Cristian Mărieș and Hugo van Kemenade in `#313 `_, `#314 `_, `#315 `_, `#316 `_, `#325 `_, `#326 `_, `#334 `_ and `#335 `_. * Added the ``--cov-context`` CLI options that enables coverage contexts. Only works with coverage 5.0+. Contributed by Ned Batchelder in `#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 `_. * Various testing and CI improvements. Contributed by Daniel Hahler in `#255 `_, `#266 `_, `#272 `_, `#271 `_ and `#269 `_. * Improved documentation regarding subprocess and multiprocessing. Contributed in `#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 `_ or `#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 `_. * 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 `_ and `#230 `_. * Various test and docs fixes. Contributed by Daniel Hahler in `#224 `_ and `#223 `_. * Fixed the "Module already imported" issue (`#211 `_). Contributed by Daniel Hahler in `#228 `_. 2.6.0 (2018-09-03) ------------------ * Dropped support for Python < 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 `_. * Fixed ``TypeError: 'NoneType' object is not iterable`` in certain xdist configurations. Contributed by Jeremy Bowman in `#213 `_. * Added a ``no_cover`` marker and fixture. Fixes `#78 `_. * Fixed broken ``no_cover`` check when running doctests. Contributed by Terence Honles in `#200 `_. * Fixed various issues with path normalization in reports (when combining coverage data from parallel mode). Fixes `#130 `_. Contributed by Ryan Hiebert & Ionel Cristian Mărieș in `#178 `_. * Report generation failures don't raise exceptions anymore. A warning will be logged instead. Fixes `#161 `_. * Fixed multiprocessing issue on Windows (empty env vars are not passed). Fixes `#165 `_. 2.5.1 (2017-05-11) ------------------ * Fixed xdist breakage (regression in ``2.5.0``). Fixes `#157 `_. * Allow setting custom ``data_file`` name in ``.coveragerc``. Fixes `#145 `_. Contributed by Jannis Leidel & Ionel Cristian Mărieș in `#156 `_. 2.5.0 (2017-05-09) ------------------ * Always show a summary when ``--cov-fail-under`` is used. Contributed by Francis Niu in `PR#141 `_. * Added ``--cov-branch`` option. Fixes `#85 `_. * Improve exception handling in subprocess setup. Fixes `#144 `_. * Fixed handling when ``--cov`` is used multiple times. Fixes `#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 `_. **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 `_. 2.3.1 (2016-08-07) ------------------ * Fixed regression causing spurious errors when xdist was used. See `#124 `_. * Fixed DeprecationWarning about incorrect `addoption` use. Contributed by Florian Bruhin in `PR#127 `_. * Fixed deprecated use of funcarg fixture API. Contributed by Daniel Hahler in `PR#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 `_. * 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 `_. * Fixed bug occurred when bare ``--cov`` parameter was used with xdist. Contributed by Michael Elovskikh in `PR#120 `_. * Add support for ``skip_covered`` and added ``--cov-report=term-skip-covered`` command line options. Contributed by Saurabh Kumar in `PR#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 `_. 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 pytest-cov-2.8.1/CONTRIBUTING.rst000066400000000000000000000052451354611100500163020ustar00rootroot00000000000000============ 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 `_ 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 `_ (look for the "Fork" button). 2. Clone your fork locally:: git clone git@github.com:your_name_here/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, doc builder and spell checker with `tox `_ 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 `_ 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 all the test environments in *parallel* (you need to ``pip install detox``):: detox pytest-cov-2.8.1/LICENSE000066400000000000000000000020571354611100500146440ustar00rootroot00000000000000The 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. pytest-cov-2.8.1/MANIFEST.in000066400000000000000000000010331354611100500153660ustar00rootroot00000000000000graft 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 .isort.cfg include .pylintrc include AUTHORS.rst include CHANGELOG.rst include CONTRIBUTING.rst include LICENSE include README.rst include tox.ini .travis.yml appveyor.yml global-exclude *.py[cod] __pycache__ *.so .coverage pytest-cov-2.8.1/README.rst000066400000000000000000000126211354611100500153240ustar00rootroot00000000000000======== 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://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.python.org/pypi/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.8.1.svg :alt: Commits since latest release :target: https://github.com/pytest-dev/pytest-cov/compare/v2.8.1...master .. |wheel| image:: https://img.shields.io/pypi/wheel/pytest-cov.svg :alt: PyPI Wheel :target: https://pypi.python.org/pypi/pytest-cov .. |supported-versions| image:: https://img.shields.io/pypi/pyversions/pytest-cov.svg :alt: Supported versions :target: https://pypi.python.org/pypi/pytest-cov .. |supported-implementations| image:: https://img.shields.io/pypi/implementation/pytest-cov.svg :alt: Supported implementations :target: https://pypi.python.org/pypi/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. pytest-cov-2.8.1/appveyor.yml000066400000000000000000000050041354611100500162220ustar00rootroot00000000000000# 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-pytest310-xdist27-coverage45,py27-pytest46-xdist27-coverage45,py27-pytest310-xdist27-coverage50,py27-pytest46-xdist27-coverage50' - TOXENV: 'py34-pytest310-xdist27-coverage45,py34-pytest46-xdist27-coverage45' - TOXENV: 'py35-pytest310-xdist27-coverage45,py35-pytest46-xdist27-coverage45,py35-pytest310-xdist27-coverage50,py35-pytest46-xdist27-coverage50' - TOXENV: 'py36-pytest310-xdist27-coverage45,py36-pytest46-xdist27-coverage45,py36-pytest310-xdist27-coverage50,py36-pytest46-xdist27-coverage50,py36-pytest46-xdist29-coverage45,py36-pytest46-xdist29-coverage50,py36-pytest46-xdist30-coverage45,py36-pytest46-xdist30-coverage50,py36-pytest51-xdist29-coverage45,py36-pytest51-xdist29-coverage50,py36-pytest51-xdist30-coverage45,py36-pytest51-xdist30-coverage50,py36-pytest52-xdist29-coverage45,py36-pytest52-xdist29-coverage50,py36-pytest52-xdist30-coverage45,py36-pytest52-xdist30-coverage50' - TOXENV: 'py37-pytest310-xdist27-coverage45,py37-pytest46-xdist27-coverage45,py37-pytest310-xdist27-coverage50,py37-pytest46-xdist27-coverage50,py37-pytest46-xdist29-coverage45,py37-pytest46-xdist29-coverage50,py37-pytest46-xdist30-coverage45,py37-pytest46-xdist30-coverage50,py37-pytest51-xdist29-coverage45,py37-pytest51-xdist29-coverage50,py37-pytest51-xdist30-coverage45,py37-pytest51-xdist30-coverage50,py37-pytest52-xdist29-coverage45,py37-pytest52-xdist29-coverage50,py37-pytest52-xdist30-coverage45,py37-pytest52-xdist30-coverage50' - TOXENV: 'pypy-pytest310-xdist27-coverage45,pypy-pytest46-xdist27-coverage45,pypy-pytest310-xdist27-coverage50,pypy-pytest46-xdist27-coverage50' 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 -U "virtualenv>=16.5.0" - C:\Python37\python -m pip install tox test_script: - 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')) pytest-cov-2.8.1/ci/000077500000000000000000000000001354611100500142265ustar00rootroot00000000000000pytest-cov-2.8.1/ci/bootstrap.py000077500000000000000000000057311354611100500166260ustar00rootroot00000000000000#!/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) pytest-cov-2.8.1/ci/templates/000077500000000000000000000000001354611100500162245ustar00rootroot00000000000000pytest-cov-2.8.1/ci/templates/.travis.yml000066400000000000000000000024741354611100500203440ustar00rootroot00000000000000dist: xenial language: python cache: false env: global: - LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so - SEGFAULT_SIGNALS=all stages: - lint - examples - tests matrix: fast_finish: true allow_failures: - python: '3.8-dev' jobs: 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' {% elif env.startswith("py38-") %} python: '3.8-dev' {% 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.6' script: cd $TARGET; tox -v env: - TARGET=examples/{{ example }}-layout {%- endfor %} before_install: - python --version - uname -a - lsb_release -a install: - pip install tox - 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 pytest-cov-2.8.1/ci/templates/appveyor.yml000066400000000000000000000022021354611100500206100ustar00rootroot00000000000000version: '{branch}-{build}' build: off environment: matrix: - TOXENV: check - TOXENV: '{{ py27_environments|join(",") }}' - TOXENV: '{{ py34_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 -U "virtualenv>=16.5.0" - C:\Python37\python -m pip install tox test_script: - 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')) pytest-cov-2.8.1/docs/000077500000000000000000000000001354611100500145635ustar00rootroot00000000000000pytest-cov-2.8.1/docs/authors.rst000066400000000000000000000000341354611100500167770ustar00rootroot00000000000000.. include:: ../AUTHORS.rst pytest-cov-2.8.1/docs/changelog.rst000066400000000000000000000000361354611100500172430ustar00rootroot00000000000000.. include:: ../CHANGELOG.rst pytest-cov-2.8.1/docs/conf.py000066400000000000000000000025451354611100500160700ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import os 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.8.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 #'), } import sphinx_py3doc_enhanced_theme 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 pytest-cov-2.8.1/docs/config.rst000066400000000000000000000053701354611100500165670ustar00rootroot00000000000000============= 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= --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. pytest-cov-2.8.1/docs/contexts.rst000066400000000000000000000014061354611100500171650ustar00rootroot00000000000000======== 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. pytest-cov-2.8.1/docs/contributing.rst000066400000000000000000000000411354611100500200170ustar00rootroot00000000000000.. include:: ../CONTRIBUTING.rst pytest-cov-2.8.1/docs/debuggers.rst000066400000000000000000000014001354611100500172570ustar00rootroot00000000000000===================== 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! pytest-cov-2.8.1/docs/index.rst000066400000000000000000000006301354611100500164230ustar00rootroot00000000000000Welcome 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` pytest-cov-2.8.1/docs/markers-fixtures.rst000066400000000000000000000014441354611100500206330ustar00rootroot00000000000000==================== 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. pytest-cov-2.8.1/docs/plugins.rst000066400000000000000000000017431354611100500170030ustar00rootroot00000000000000=============== 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 `_ 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 `_ 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 pytest-cov-2.8.1/docs/readme.rst000066400000000000000000000000331354611100500165460ustar00rootroot00000000000000.. include:: ../README.rst pytest-cov-2.8.1/docs/releasing.rst000066400000000000000000000025461354611100500172750ustar00rootroot00000000000000========= 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 `_ and `Travis `_ to give the green builds. #. Check that the docs on `ReadTheDocs `_ are built. #. Make sure you have a clean checkout, run ``git status`` to verify. #. Manually clean temporary files (that are ignored and won't show up in ``git status``):: 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.4 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/* pytest-cov-2.8.1/docs/reporting.rst000066400000000000000000000055431354611100500173350ustar00rootroot00000000000000Reporting ========= 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. pytest-cov-2.8.1/docs/requirements.txt000066400000000000000000000001321354611100500200430ustar00rootroot00000000000000sphinx==1.5.6 # rq.filter: >=1.3,<1.6 sphinx-py3doc-enhanced-theme docutils==0.13.1 -e . pytest-cov-2.8.1/docs/spelling_wordlist.txt000066400000000000000000000001551354611100500210710ustar00rootroot00000000000000builtin builtins classmethod staticmethod classmethods staticmethods args kwargs callstack Changelog Indices pytest-cov-2.8.1/docs/subprocess-support.rst000066400000000000000000000123151354611100500212210ustar00rootroot00000000000000================== 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: .. 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 `_ being roughly: * you need to deliver ``signal.CTRL_BREAK_EVENT`` * it gets delivered to the whole process group, and that can have unforeseen consequences pytest-cov-2.8.1/docs/tox.rst000066400000000000000000000033001354611100500161230ustar00rootroot00000000000000=== Tox === When using `tox `_ 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. pytest-cov-2.8.1/docs/xdist.rst000066400000000000000000000054311354611100500164530ustar00rootroot00000000000000=========================== 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% pytest-cov-2.8.1/examples/000077500000000000000000000000001354611100500154515ustar00rootroot00000000000000pytest-cov-2.8.1/examples/README.rst000066400000000000000000000010341354611100500171360ustar00rootroot00000000000000Simple 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. pytest-cov-2.8.1/examples/adhoc-layout/000077500000000000000000000000001354611100500200425ustar00rootroot00000000000000pytest-cov-2.8.1/examples/adhoc-layout/.coveragerc000066400000000000000000000002501354611100500221600ustar00rootroot00000000000000[paths] source = ../example */site-packages/example [run] branch = true parallel = true source = example . [report] show_missing = true precision = 2 pytest-cov-2.8.1/examples/adhoc-layout/example/000077500000000000000000000000001354611100500214755ustar00rootroot00000000000000pytest-cov-2.8.1/examples/adhoc-layout/example/__init__.py000066400000000000000000000002151354611100500236040ustar00rootroot00000000000000 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 pytest-cov-2.8.1/examples/adhoc-layout/setup.py000066400000000000000000000001731354611100500215550ustar00rootroot00000000000000from setuptools import setup, find_packages setup( name='example', packages=find_packages(include=['example']) ) pytest-cov-2.8.1/examples/adhoc-layout/tests/000077500000000000000000000000001354611100500212045ustar00rootroot00000000000000pytest-cov-2.8.1/examples/adhoc-layout/tests/test_example.py000066400000000000000000000001511354611100500242450ustar00rootroot00000000000000import example def test_add(): assert example.add(1, 1) == 2 assert not example.add(0, 1) == 2 pytest-cov-2.8.1/examples/adhoc-layout/tox.ini000066400000000000000000000011261354611100500213550ustar00rootroot00000000000000[tox] envlist = clean,py27,py36,report [tool:pytest] addopts = --cov-report=term-missing [testenv] commands = pytest --cov --cov-append --cov-config={toxinidir}/.coveragerc {posargs:-vv} deps = pytest pytest-cov depends = {py27,py36}: clean report: py27,py36 # 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 pytest-cov-2.8.1/examples/src-layout/000077500000000000000000000000001354611100500175535ustar00rootroot00000000000000pytest-cov-2.8.1/examples/src-layout/.coveragerc000066400000000000000000000002331354611100500216720ustar00rootroot00000000000000[paths] source = src */site-packages [run] branch = true parallel = true source = example tests [report] show_missing = true precision = 2 pytest-cov-2.8.1/examples/src-layout/setup.py000066400000000000000000000002131354611100500212610ustar00rootroot00000000000000from setuptools import setup, find_packages setup( name='example', packages=find_packages('src'), package_dir={'': 'src'}, ) pytest-cov-2.8.1/examples/src-layout/src/000077500000000000000000000000001354611100500203425ustar00rootroot00000000000000pytest-cov-2.8.1/examples/src-layout/src/example/000077500000000000000000000000001354611100500217755ustar00rootroot00000000000000pytest-cov-2.8.1/examples/src-layout/src/example/__init__.py000066400000000000000000000002151354611100500241040ustar00rootroot00000000000000 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 pytest-cov-2.8.1/examples/src-layout/tests/000077500000000000000000000000001354611100500207155ustar00rootroot00000000000000pytest-cov-2.8.1/examples/src-layout/tests/test_example.py000066400000000000000000000001511354611100500237560ustar00rootroot00000000000000import example def test_add(): assert example.add(1, 1) == 2 assert not example.add(0, 1) == 2 pytest-cov-2.8.1/examples/src-layout/tox.ini000066400000000000000000000007151354611100500210710ustar00rootroot00000000000000[tox] envlist = clean,py27,py36,report [tool:pytest] testpaths = tests addopts = --cov-report=term-missing [testenv] commands = pytest --cov --cov-append {posargs:-vv} deps = pytest pytest-cov depends = {py27,py36}: clean report: py27,py36 [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 pytest-cov-2.8.1/setup.cfg000066400000000000000000000004741354611100500154610ustar00rootroot00000000000000[bdist_wheel] universal = 1 [flake8] max-line-length = 140 [tool:pytest] testpaths = tests python_files = test_*.py addopts = -ra --strict --tb=short -p pytester [isort] force_single_line=True line_length=120 known_first_party=pytest_cov default_section=THIRDPARTY forced_separate=test_pytest_cov pytest-cov-2.8.1/setup.py000066400000000000000000000110561354611100500153500ustar00rootroot00000000000000#!/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): return io.open( join(dirname(__file__), *names), encoding=kwargs.get('encoding', 'utf8') ).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.8.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.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', '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>=3.6', 'coverage>=4.4' ], python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', extras_require={ 'testing': [ 'fields', 'hunter', 'process-tests==2.0.2', 'six', 'virtualenv', ] }, entry_points={ 'pytest11': [ 'pytest_cov = pytest_cov.plugin', ], 'console_scripts': [ ] }, cmdclass={ 'build': BuildWithPTH, 'easy_install': EasyInstallWithPTH, 'install_lib': InstallLibWithPTH, 'develop': DevelopWithPTH, 'genpth': GeneratePTH, }, ) pytest-cov-2.8.1/src/000077500000000000000000000000001354611100500144225ustar00rootroot00000000000000pytest-cov-2.8.1/src/pytest-cov.embed000066400000000000000000000006511354611100500175370ustar00rootroot00000000000000if '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 ) ) pytest-cov-2.8.1/src/pytest-cov.pth000066400000000000000000000005701354611100500172560ustar00rootroot00000000000000import 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')pytest-cov-2.8.1/src/pytest_cov/000077500000000000000000000000001354611100500166215ustar00rootroot00000000000000pytest-cov-2.8.1/src/pytest_cov/__init__.py000066400000000000000000000001351354611100500207310ustar00rootroot00000000000000"""pytest-cov: avoid already-imported warning: PYTEST_DONT_REWRITE.""" __version__ = "2.8.1" pytest-cov-2.8.1/src/pytest_cov/compat.py000066400000000000000000000022441354611100500204600ustar00rootroot00000000000000try: 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) def _attrgetter(attr): """ Return a callable object that fetches attr from its operand. Unlike operator.attrgetter, the returned callable supports an extra two arg form for a default. """ def fn(obj, *args): return getattr(obj, attr, *args) return fn worker = 'slave' # for compatability with pytest-xdist<=1.22.0 workerid = worker + 'id' workerinput = _attrgetter(worker + 'input') workeroutput = _attrgetter(worker + 'output') pytest-cov-2.8.1/src/pytest_cov/embed.py000066400000000000000000000074261354611100500202600ustar00rootroot00000000000000"""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) pytest-cov-2.8.1/src/pytest_cov/engine.py000066400000000000000000000322371354611100500204470ustar00rootroot00000000000000"""Coverage controllers for use by pytest-cov and nose-cov.""" import contextlib import copy import os import random import socket import sys import coverage from coverage.data import CoverageData from .compat import StringIO from .compat import workerinput from .compat import workeroutput 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) 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() def pause(self): self.cov.stop() self.unset_env() def resume(self): self.cov.start() self.set_env() 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) 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.""" def start(self): cleanup() self.cov = coverage.Coverage(source=self.cov_source, branch=self.cov_branch, config_file=self.cov_config) self.combining_cov = coverage.Coverage(source=self.cov_source, branch=self.cov_branch, 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 self.cov_append: self.cov.load() else: self.cov.erase() self.cov.start() self.set_env() 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.""" 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, config_file=self.cov_config) self.combining_cov = coverage.Coverage(source=self.cov_source, branch=self.cov_branch, data_file=os.path.abspath(self.cov.config.data_file), config_file=self.cov_config) if self.cov_append: self.cov.load() else: 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.""" workerinput(node).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 = workeroutput(node, {}) 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) 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.""" def start(self): cleanup() # Determine whether we are collocated with master. self.is_collocated = (socket.gethostname() == workerinput(self.config)['cov_master_host'] and self.topdir == workerinput(self.config)['cov_master_topdir']) # If we are not collocated then rewrite master paths to worker paths. if not self.is_collocated: master_topdir = workerinput(self.config)['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() 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. workeroutput(self.config)['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() workeroutput(self.config).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 pytest-cov-2.8.1/src/pytest_cov/plugin.py000066400000000000000000000322071354611100500204750ustar00rootroot00000000000000"""Coverage plugin for pytest.""" import argparse import os import warnings import coverage import pytest from coverage.misc import CoverageException from . import compat from . import embed from . import engine 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): if early_config.known_args_namespace.cov_source: plugin = CovPlugin(early_config.known_args_namespace, 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): """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._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) 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 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 compat.workerinput(session.config, 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 self.pid = os.getpid() if self._is_worker(session): nodeid = ( compat.workerinput(session.config) .get(compat.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(): 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) if pytest.__version__ >= '3.8': warnings.warn(pytest.PytestWarning(message)) else: session.config.warn(code='COV-2', message=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: message = 'Coverage disabled via --no-cov switch!' terminalreporter.write('WARNING: %s\n' % message, red=True, bold=True) if pytest.__version__ >= '3.8': warnings.warn(pytest.PytestWarning(message)) else: terminalreporter.config.warn(code='COV-1', message=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.") pytest-cov-2.8.1/tests/000077500000000000000000000000001354611100500147755ustar00rootroot00000000000000pytest-cov-2.8.1/tests/conftest.py000066400000000000000000000001111354611100500171650ustar00rootroot00000000000000def pytest_configure(config): config.option.runpytest = 'subprocess' pytest-cov-2.8.1/tests/contextful.py000066400000000000000000000045711354611100500175510ustar00rootroot00000000000000# 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 pytest-cov-2.8.1/tests/helper.py000066400000000000000000000000471354611100500166270ustar00rootroot00000000000000def do_stuff(): a = 1 return a pytest-cov-2.8.1/tests/test_pytest_cov.py000066400000000000000000001725521354611100500206210ustar00rootroot00000000000000import 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 from pytest_cov import compat 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-" + compat.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 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): script = testdir.makepyfile(SCRIPT) result = testdir.runpytest('-vvv', '--cov=%s' % script.dirpath(), '--cov-report=term-missing', '--no-cov', '-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([ '*- coverage: platform *, python * -*', 'test_dist_combine_racecondition* 2002 * 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.create_environment(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) assert result.ret == 0 result.stdout.fnmatch_lines([ '*- coverage: failed 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") 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") 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, "") 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*'))) 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, ]) 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', script) assert result.ret == 0 def find_labels(text, pattern): all_labels = collections.defaultdict(list) lines = text.splitlines() for lineno, line in enumerate(lines, start=1): labels = re.findall(pattern, line) for label in labels: all_labels[label].append(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 = 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 pytest-cov-2.8.1/tox.ini000066400000000000000000000037761354611100500151630ustar00rootroot00000000000000; a generative tox configuration, see: https://tox.readthedocs.io/en/latest/config.html#generative-envlist [tox] envlist = check py{27,34,35,36,37,py,py3}-pytest{310,46}-xdist27-coverage45 py{27,35,36,37,py,py3}-pytest{310,46}-xdist27-coverage50 py{36,37,38,py3}-pytest{46,51,52}-xdist{29,30}-coverage{45,50} docs [testenv] extras = testing setenv = PYTHONUNBUFFERED=yes # Use env vars for (optional) pinning of deps. pytest310: _DEP_PYTEST=pytest==3.10.1 pytest40: _DEP_PYTEST=pytest==4.0.2 pytest41: _DEP_PYTEST=pytest==4.1.1 pytest43: _DEP_PYTEST=pytest==4.3.1 pytest44: _DEP_PYTEST=pytest==4.4.2 pytest45: _DEP_PYTEST=pytest==4.5.0 pytest46: _DEP_PYTEST=pytest==4.6.5 pytest51: _DEP_PYTEST=pytest==5.1.3 pytest52: _DEP_PYTEST=pytest==5.2.0 xdist22: _DEP_PYTESTXDIST=pytest-xdist==1.22.0 xdist27: _DEP_PYTESTXDIST=pytest-xdist==1.27.0 xdist28: _DEP_PYTESTXDIST=pytest-xdist==1.28.0 xdist29: _DEP_PYTESTXDIST=pytest-xdist==1.29.0 xdist30: _DEP_PYTESTXDIST=pytest-xdist==1.30.0 coverage45: _DEP_COVERAGE=coverage==4.5.4 coverage50: _DEP_COVERAGE=coverage==5.0a8 passenv = * deps = {env:_DEP_PYTEST:pytest} {env:_DEP_PYTESTXDIST:pytest-xdist} {env:_DEP_COVERAGE:coverage<5} 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 --recursive src tests setup.py