pax_global_header00006660000000000000000000000064131051326660014515gustar00rootroot0000000000000052 comment=4eff449bd04d8bf3557deba1e4ee5b24ccba38c2 pytest-cov-2.5.1/000077500000000000000000000000001310513266600136375ustar00rootroot00000000000000pytest-cov-2.5.1/.bumpversion.cfg000066400000000000000000000003141310513266600167450ustar00rootroot00000000000000[bumpversion] current_version = 2.5.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.5.1/.cookiecutterrc000066400000000000000000000022141310513266600166640ustar00rootroot00000000000000# 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.5.1/.coveragerc000066400000000000000000000002071310513266600157570ustar00rootroot00000000000000[paths] source = src [run] branch = True source = src parallel = true [report] show_missing = true precision = 2 omit = *migrations* pytest-cov-2.5.1/.editorconfig000066400000000000000000000003271310513266600163160ustar00rootroot00000000000000# 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.5.1/.gitignore000066400000000000000000000010531310513266600156260ustar00rootroot00000000000000*.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.5.1/.travis.yml000066400000000000000000000245451310513266600157620ustar00rootroot00000000000000language: python sudo: false cache: pip env: global: - LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so - SEGFAULT_SIGNALS=all matrix: - TOXENV=check - TOXENV=docs matrix: include: - python: '2.6' env: - TOXENV=py26-27-37 - python: '2.6' env: - TOXENV=py26-27-40 - python: '2.6' env: - TOXENV=py26-27-41 - python: '2.6' env: - TOXENV=py26-27-42 - python: '2.6' env: - TOXENV=py26-27-43 - python: '2.6' env: - TOXENV=py26-28-37 - python: '2.6' env: - TOXENV=py26-28-40 - python: '2.6' env: - TOXENV=py26-28-41 - python: '2.6' env: - TOXENV=py26-28-42 - python: '2.6' env: - TOXENV=py26-28-43 - python: '2.6' env: - TOXENV=py26-29-37 - python: '2.6' env: - TOXENV=py26-29-40 - python: '2.6' env: - TOXENV=py26-29-41 - python: '2.6' env: - TOXENV=py26-29-42 - python: '2.6' env: - TOXENV=py26-29-43 - python: '2.6' env: - TOXENV=py26-30-37 - python: '2.6' env: - TOXENV=py26-30-40 - python: '2.6' env: - TOXENV=py26-30-41 - python: '2.6' env: - TOXENV=py26-30-42 - python: '2.6' env: - TOXENV=py26-30-43 - python: '2.7' env: - TOXENV=py27-27-37 - python: '2.7' env: - TOXENV=py27-27-40 - python: '2.7' env: - TOXENV=py27-27-41 - python: '2.7' env: - TOXENV=py27-27-42 - python: '2.7' env: - TOXENV=py27-27-43 - python: '2.7' env: - TOXENV=py27-28-37 - python: '2.7' env: - TOXENV=py27-28-40 - python: '2.7' env: - TOXENV=py27-28-41 - python: '2.7' env: - TOXENV=py27-28-42 - python: '2.7' env: - TOXENV=py27-28-43 - python: '2.7' env: - TOXENV=py27-29-37 - python: '2.7' env: - TOXENV=py27-29-40 - python: '2.7' env: - TOXENV=py27-29-41 - python: '2.7' env: - TOXENV=py27-29-42 - python: '2.7' env: - TOXENV=py27-29-43 - python: '2.7' env: - TOXENV=py27-30-37 - python: '2.7' env: - TOXENV=py27-30-40 - python: '2.7' env: - TOXENV=py27-30-41 - python: '2.7' env: - TOXENV=py27-30-42 - python: '2.7' env: - TOXENV=py27-30-43 - python: '3.3' env: - TOXENV=py33-27-37 - python: '3.3' env: - TOXENV=py33-27-40 - python: '3.3' env: - TOXENV=py33-27-41 - python: '3.3' env: - TOXENV=py33-27-42 - python: '3.3' env: - TOXENV=py33-27-43 - python: '3.3' env: - TOXENV=py33-28-37 - python: '3.3' env: - TOXENV=py33-28-40 - python: '3.3' env: - TOXENV=py33-28-41 - python: '3.3' env: - TOXENV=py33-28-42 - python: '3.3' env: - TOXENV=py33-28-43 - python: '3.3' env: - TOXENV=py33-29-37 - python: '3.3' env: - TOXENV=py33-29-40 - python: '3.3' env: - TOXENV=py33-29-41 - python: '3.3' env: - TOXENV=py33-29-42 - python: '3.3' env: - TOXENV=py33-29-43 - python: '3.3' env: - TOXENV=py33-30-37 - python: '3.3' env: - TOXENV=py33-30-40 - python: '3.3' env: - TOXENV=py33-30-41 - python: '3.3' env: - TOXENV=py33-30-42 - python: '3.3' env: - TOXENV=py33-30-43 - python: '3.4' env: - TOXENV=py34-27-37 - python: '3.4' env: - TOXENV=py34-27-40 - python: '3.4' env: - TOXENV=py34-27-41 - python: '3.4' env: - TOXENV=py34-27-42 - python: '3.4' env: - TOXENV=py34-27-43 - python: '3.4' env: - TOXENV=py34-28-37 - python: '3.4' env: - TOXENV=py34-28-40 - python: '3.4' env: - TOXENV=py34-28-41 - python: '3.4' env: - TOXENV=py34-28-42 - python: '3.4' env: - TOXENV=py34-28-43 - python: '3.4' env: - TOXENV=py34-29-37 - python: '3.4' env: - TOXENV=py34-29-40 - python: '3.4' env: - TOXENV=py34-29-41 - python: '3.4' env: - TOXENV=py34-29-42 - python: '3.4' env: - TOXENV=py34-29-43 - python: '3.4' env: - TOXENV=py34-30-37 - python: '3.4' env: - TOXENV=py34-30-40 - python: '3.4' env: - TOXENV=py34-30-41 - python: '3.4' env: - TOXENV=py34-30-42 - python: '3.4' env: - TOXENV=py34-30-43 - python: '3.5' env: - TOXENV=py35-27-37 - python: '3.5' env: - TOXENV=py35-27-40 - python: '3.5' env: - TOXENV=py35-27-41 - python: '3.5' env: - TOXENV=py35-27-42 - python: '3.5' env: - TOXENV=py35-27-43 - python: '3.5' env: - TOXENV=py35-28-37 - python: '3.5' env: - TOXENV=py35-28-40 - python: '3.5' env: - TOXENV=py35-28-41 - python: '3.5' env: - TOXENV=py35-28-42 - python: '3.5' env: - TOXENV=py35-28-43 - python: '3.5' env: - TOXENV=py35-29-37 - python: '3.5' env: - TOXENV=py35-29-40 - python: '3.5' env: - TOXENV=py35-29-41 - python: '3.5' env: - TOXENV=py35-29-42 - python: '3.5' env: - TOXENV=py35-29-43 - python: '3.5' env: - TOXENV=py35-30-37 - python: '3.5' env: - TOXENV=py35-30-40 - python: '3.5' env: - TOXENV=py35-30-41 - python: '3.5' env: - TOXENV=py35-30-42 - python: '3.5' env: - TOXENV=py35-30-43 - python: 'pypy-5.4' env: - TOXENV=pypy-27-37 - python: 'pypy-5.4' env: - TOXENV=pypy-27-40 - python: 'pypy-5.4' env: - TOXENV=pypy-27-41 - python: 'pypy-5.4' env: - TOXENV=pypy-27-42 - python: 'pypy-5.4' env: - TOXENV=pypy-27-43 - python: 'pypy-5.4' env: - TOXENV=pypy-28-37 - python: 'pypy-5.4' env: - TOXENV=pypy-28-40 - python: 'pypy-5.4' env: - TOXENV=pypy-28-41 - python: 'pypy-5.4' env: - TOXENV=pypy-28-42 - python: 'pypy-5.4' env: - TOXENV=pypy-28-43 - python: 'pypy-5.4' env: - TOXENV=pypy-29-37 - python: 'pypy-5.4' env: - TOXENV=pypy-29-40 - python: 'pypy-5.4' env: - TOXENV=pypy-29-41 - python: 'pypy-5.4' env: - TOXENV=pypy-29-42 - python: 'pypy-5.4' env: - TOXENV=pypy-29-43 - python: 'pypy-5.4' env: - TOXENV=pypy-30-37 - python: 'pypy-5.4' env: - TOXENV=pypy-30-40 - python: 'pypy-5.4' env: - TOXENV=pypy-30-41 - python: 'pypy-5.4' env: - TOXENV=pypy-30-42 - python: 'pypy-5.4' env: - TOXENV=pypy-30-43 - python: '2.7' env: - TOXENV=py27-27-43 - python: '2.7' env: - TOXENV=py27-27-44 - python: '2.7' env: - TOXENV=py27-28-43 - python: '2.7' env: - TOXENV=py27-28-44 - python: '2.7' env: - TOXENV=py27-29-43 - python: '2.7' env: - TOXENV=py27-29-44 - python: '2.7' env: - TOXENV=py27-30-43 - python: '2.7' env: - TOXENV=py27-30-44 - python: '3.3' env: - TOXENV=py33-27-43 - python: '3.3' env: - TOXENV=py33-27-44 - python: '3.3' env: - TOXENV=py33-28-43 - python: '3.3' env: - TOXENV=py33-28-44 - python: '3.3' env: - TOXENV=py33-29-43 - python: '3.3' env: - TOXENV=py33-29-44 - python: '3.3' env: - TOXENV=py33-30-43 - python: '3.3' env: - TOXENV=py33-30-44 - python: '3.4' env: - TOXENV=py34-27-43 - python: '3.4' env: - TOXENV=py34-27-44 - python: '3.4' env: - TOXENV=py34-28-43 - python: '3.4' env: - TOXENV=py34-28-44 - python: '3.4' env: - TOXENV=py34-29-43 - python: '3.4' env: - TOXENV=py34-29-44 - python: '3.4' env: - TOXENV=py34-30-43 - python: '3.4' env: - TOXENV=py34-30-44 - python: '3.5' env: - TOXENV=py35-27-43 - python: '3.5' env: - TOXENV=py35-27-44 - python: '3.5' env: - TOXENV=py35-28-43 - python: '3.5' env: - TOXENV=py35-28-44 - python: '3.5' env: - TOXENV=py35-29-43 - python: '3.5' env: - TOXENV=py35-29-44 - python: '3.5' env: - TOXENV=py35-30-43 - python: '3.5' env: - TOXENV=py35-30-44 - python: '3.6' env: - TOXENV=py36-27-43 - python: '3.6' env: - TOXENV=py36-27-44 - python: '3.6' env: - TOXENV=py36-28-43 - python: '3.6' env: - TOXENV=py36-28-44 - python: '3.6' env: - TOXENV=py36-29-43 - python: '3.6' env: - TOXENV=py36-29-44 - python: '3.6' env: - TOXENV=py36-30-43 - python: '3.6' env: - TOXENV=py36-30-44 - python: 'pypy-5.4' env: - TOXENV=pypy-27-43 - python: 'pypy-5.4' env: - TOXENV=pypy-27-44 - python: 'pypy-5.4' env: - TOXENV=pypy-28-43 - python: 'pypy-5.4' env: - TOXENV=pypy-28-44 - python: 'pypy-5.4' env: - TOXENV=pypy-29-43 - python: 'pypy-5.4' env: - TOXENV=pypy-29-44 - python: 'pypy-5.4' env: - TOXENV=pypy-30-43 - python: 'pypy-5.4' env: - TOXENV=pypy-30-44 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.5.1/AUTHORS.rst000066400000000000000000000017641310513266600155260ustar00rootroot00000000000000Authors ======= * 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 pytest-cov-2.5.1/CHANGELOG.rst000066400000000000000000000107251310513266600156650ustar00rootroot00000000000000Changelog ========= 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 wich 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.5.1/CONTRIBUTING.rst000066400000000000000000000052461310513266600163070ustar00rootroot00000000000000============ 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 -- py.test -k test_myfeature To run all the test environments in *parallel* (you need to ``pip install detox``):: detox pytest-cov-2.5.1/LICENSE000066400000000000000000000020571310513266600146500ustar00rootroot00000000000000The 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.5.1/MANIFEST.in000066400000000000000000000005651310513266600154030ustar00rootroot00000000000000graft docs graft example 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 pytest-cov-2.5.1/README.rst000066400000000000000000000303541310513266600153330ustar00rootroot00000000000000======== Overview ======== .. start-badges .. list-table:: :stub-columns: 1 * - docs - |docs| * - tests - | |travis| |appveyor| |requires| * - package - | |version| |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 .. |commits-since| image:: https://img.shields.io/github/commits-since/pytest-dev/pytest-cov/v2.5.1.svg :alt: Commits since latest release :target: https://github.com/pytest-dev/pytest-cov/compare/v2.5.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. It supports centralised testing and distributed testing in both load and each modes. It also supports coverage of subprocesses. All features offered by the coverage package should be available, either through pytest-cov 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 Upgrade ======= `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. Uninstallation ============== 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 ===== Centralised Testing ------------------- Centralised testing will report on the combined coverage of the main process and all of its subprocesses. Running centralised testing:: py.test --cov=myproj 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% Distributed Testing: Load ------------------------- Distributed testing with dist mode set to load will report on the combined coverage of all slaves. The slaves may be spread out over any number of hosts and each slave may be located anywhere on the file system. Each slave will have its subprocesses measured. Running distributed testing with dist mode set to load:: py.test --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:: py.test --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% Distributed Testing: Each ------------------------- Distributed testing with dist mode set to each will report on the combined coverage of all slaves. Since each slave is running all tests this allows generating a combined coverage report for multiple environments. Running distributed testing with dist mode set to each:: py.test --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% Reporting ========= It is possible to generate any combination of the reports for a single test run. The available reports are terminal (with or without missing line numbers shown), HTML, XML and annotated source code. The terminal report without line numbers (default):: py.test --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:: py.test --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:: py.test --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:: py.test --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:: py.test --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:: py.test --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. 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. Coverage Config File ==================== 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:: py.test --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). Limitations =========== For distributed testing the slaves 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 slave. 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. Coverage and debuggers ---------------------- 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! 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.5.1/appveyor.yml000066400000000000000000000047531310513266600162400ustar00rootroot00000000000000version: '{branch}-{build}' build: off cache: - '%LOCALAPPDATA%\pip\Cache' environment: matrix: - TOXENV: check - TOXENV: 'py26-27-37,py26-27-40,py26-27-41,py26-27-42,py26-27-43,py26-28-37,py26-28-40,py26-28-41,py26-28-42,py26-28-43,py26-29-37,py26-29-40,py26-29-41,py26-29-42,py26-29-43,py26-30-37,py26-30-40,py26-30-41,py26-30-42,py26-30-43' - TOXENV: 'py27-27-37,py27-27-40,py27-27-41,py27-27-42,py27-27-43,py27-28-37,py27-28-40,py27-28-41,py27-28-42,py27-28-43,py27-29-37,py27-29-40,py27-29-41,py27-29-42,py27-29-43,py27-30-37,py27-30-40,py27-30-41,py27-30-42,py27-30-43,py27-27-43,py27-27-44,py27-28-43,py27-28-44,py27-29-43,py27-29-44,py27-30-43,py27-30-44' - TOXENV: 'py34-27-37,py34-27-40,py34-27-41,py34-27-42,py34-27-43,py34-28-37,py34-28-40,py34-28-41,py34-28-42,py34-28-43,py34-29-37,py34-29-40,py34-29-41,py34-29-42,py34-29-43,py34-30-37,py34-30-40,py34-30-41,py34-30-42,py34-30-43,py34-27-43,py34-27-44,py34-28-43,py34-28-44,py34-29-43,py34-29-44,py34-30-43,py34-30-44' - TOXENV: 'py35-27-37,py35-27-40,py35-27-41,py35-27-42,py35-27-43,py35-28-37,py35-28-40,py35-28-41,py35-28-42,py35-28-43,py35-29-37,py35-29-40,py35-29-41,py35-29-42,py35-29-43,py35-30-37,py35-30-40,py35-30-41,py35-30-42,py35-30-43,py35-27-43,py35-27-44,py35-28-43,py35-28-44,py35-29-43,py35-29-44,py35-30-43,py35-30-44' - TOXENV: 'pypy-27-37,pypy-27-40,pypy-27-41,pypy-27-42,pypy-27-43,pypy-28-37,pypy-28-40,pypy-28-41,pypy-28-42,pypy-28-43,pypy-29-37,pypy-29-40,pypy-29-41,pypy-29-42,pypy-29-43,pypy-30-37,pypy-30-40,pypy-30-41,pypy-30-42,pypy-30-43,pypy-27-43,pypy-27-44,pypy-28-43,pypy-28-44,pypy-29-43,pypy-29-44,pypy-30-43,pypy-30-44' init: - ps: echo $env:TOXENV - ps: ls C:\Python* install: # install pypy using choco (redirect to a file and write to console in case # choco install returns non-zero, because choco install python.pypy is too # noisy) - choco install python.pypy > pypy-inst.log 2>&1 || (type pypy-inst.log & exit /b 1) - set PATH=C:\tools\pypy\pypy;%PATH% # so tox can find pypy - echo PyPy installed - pypy --version - C:\Python35\python -m pip install tox test_script: - C:\Python35\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.5.1/ci/000077500000000000000000000000001310513266600142325ustar00rootroot00000000000000pytest-cov-2.5.1/ci/bootstrap.py000077500000000000000000000040311310513266600166220ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import absolute_import, print_function, unicode_literals import os import sys from os.path import exists from os.path import join from os.path import dirname from os.path import abspath if __name__ == "__main__": base_path = dirname(dirname(abspath(__file__))) print("Project path: {0}".format(base_path)) 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: subprocess.check_call(["virtualenv", env_path]) except Exception: subprocess.check_call([sys.executable, "-m", "virtualenv", env_path]) print("Installing `jinja2` into bootstrap environment ...") subprocess.check_call([join(bin_path, "pip"), "install", "jinja2"]) activate = join(bin_path, "activate_this.py") exec(compile(open(activate, "rb").read(), activate, "exec"), dict(__file__=activate)) import jinja2 import subprocess 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() for line in subprocess.check_output(['tox', '--listenvs']).splitlines()] tox_environments = [line for line in tox_environments if line not in ['clean', 'report', 'docs', 'check']] template_vars = {'tox_environments': tox_environments} for py_ver in '26 27 33 34 35 py'.split(): template_vars['py%s_environments' % py_ver] = [x for x in tox_environments if x.startswith('py' + py_ver)] for name in os.listdir(join("ci", "templates")): with open(join(base_path, name), "w") as fh: fh.write(jinja.get_template(name).render(**template_vars)) print("Wrote {}".format(name)) print("DONE.") pytest-cov-2.5.1/ci/templates/000077500000000000000000000000001310513266600162305ustar00rootroot00000000000000pytest-cov-2.5.1/ci/templates/.travis.yml000066400000000000000000000013751310513266600203470ustar00rootroot00000000000000language: python sudo: false cache: pip env: global: - LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so - SEGFAULT_SIGNALS=all matrix: - TOXENV=check - TOXENV=docs matrix: include: {%- for env in tox_environments %}{{ '' }} - python: '{{ '{0[0]}-5.4'.format(env.split('-')) if env.startswith('pypy') else '{0[2]}.{0[3]}'.format(env) }}' env: - TOXENV={{ env }} {%- 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.5.1/ci/templates/appveyor.yml000066400000000000000000000023511310513266600206210ustar00rootroot00000000000000version: '{branch}-{build}' build: off cache: - '%LOCALAPPDATA%\pip\Cache' environment: matrix: - TOXENV: check - TOXENV: '{{ py26_environments|join(",") }}' - TOXENV: '{{ py27_environments|join(",") }}' - TOXENV: '{{ py34_environments|join(",") }}' - TOXENV: '{{ py35_environments|join(",") }}' - TOXENV: '{{ pypy_environments|join(",") }}' init: - ps: echo $env:TOXENV - ps: ls C:\Python* install: # install pypy using choco (redirect to a file and write to console in case # choco install returns non-zero, because choco install python.pypy is too # noisy) - choco install python.pypy > pypy-inst.log 2>&1 || (type pypy-inst.log & exit /b 1) - set PATH=C:\tools\pypy\pypy;%PATH% # so tox can find pypy - echo PyPy installed - pypy --version - C:\Python35\python -m pip install tox test_script: - C:\Python35\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.5.1/docs/000077500000000000000000000000001310513266600145675ustar00rootroot00000000000000pytest-cov-2.5.1/docs/authors.rst000066400000000000000000000000341310513266600170030ustar00rootroot00000000000000.. include:: ../AUTHORS.rst pytest-cov-2.5.1/docs/changelog.rst000066400000000000000000000000361310513266600172470ustar00rootroot00000000000000.. include:: ../CHANGELOG.rst pytest-cov-2.5.1/docs/conf.py000066400000000000000000000025471310513266600160760ustar00rootroot00000000000000# -*- 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 = '{0}, {1}'.format(year, author) version = release = '2.5.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.5.1/docs/contributing.rst000066400000000000000000000000411310513266600200230ustar00rootroot00000000000000.. include:: ../CONTRIBUTING.rst pytest-cov-2.5.1/docs/index.rst000066400000000000000000000004441310513266600164320ustar00rootroot00000000000000Welcome to pytest-cov's documentation! ====================================== Contents: .. toctree:: :maxdepth: 2 readme installation contributing releasing authors changelog Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` pytest-cov-2.5.1/docs/installation.rst000066400000000000000000000001321310513266600200160ustar00rootroot00000000000000============ Installation ============ At the command line:: pip install pytest-cov pytest-cov-2.5.1/docs/readme.rst000066400000000000000000000000671310513266600165610ustar00rootroot00000000000000######## Overview ######## .. include:: ../README.rst pytest-cov-2.5.1/docs/releasing.rst000066400000000000000000000025461310513266600173010ustar00rootroot00000000000000========= 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.5.1/docs/requirements.txt000066400000000000000000000000631310513266600200520ustar00rootroot00000000000000sphinx>=1.3,<1.6 sphinx-py3doc-enhanced-theme -e . pytest-cov-2.5.1/docs/spelling_wordlist.txt000066400000000000000000000001551310513266600210750ustar00rootroot00000000000000builtin builtins classmethod staticmethod classmethods staticmethods args kwargs callstack Changelog Indices pytest-cov-2.5.1/example/000077500000000000000000000000001310513266600152725ustar00rootroot00000000000000pytest-cov-2.5.1/example/.coveragerc000066400000000000000000000000241310513266600174070ustar00rootroot00000000000000[run] source = mylibpytest-cov-2.5.1/example/mylib/000077500000000000000000000000001310513266600164065ustar00rootroot00000000000000pytest-cov-2.5.1/example/mylib/__init__.py000066400000000000000000000002151310513266600205150ustar00rootroot00000000000000 import sys PY3 = sys.version_info[0] == 3 if PY3: def add(a, b): return a + b else: def add(a, b): return b + a pytest-cov-2.5.1/example/setup.py000066400000000000000000000001251310513266600170020ustar00rootroot00000000000000 from setuptools import setup, find_packages setup( packages=find_packages() ) pytest-cov-2.5.1/example/tests/000077500000000000000000000000001310513266600164345ustar00rootroot00000000000000pytest-cov-2.5.1/example/tests/test_mylib.py000066400000000000000000000001441310513266600211600ustar00rootroot00000000000000 import mylib def test_add(): assert mylib.add(1, 1) == 2 assert not mylib.add(0, 1) == 2 pytest-cov-2.5.1/example/tox.ini000066400000000000000000000007141310513266600166070ustar00rootroot00000000000000[tox] envlist = cov-init,py27,py34,cov-report [testenv] usedevelop=True setenv = COVERAGE_FILE = .coverage.{envname} commands = py.test --cov --cov-report= {posargs} deps = ../cov-core pytest ../pytest-cov [testenv:cov-init] setenv = COVERAGE_FILE = .coverage deps = coverage commands = coverage erase [testenv:cov-report] setenv = COVERAGE_FILE = .coverage deps = coverage commands = coverage combine coverage report pytest-cov-2.5.1/setup.cfg000066400000000000000000000013471310513266600154650ustar00rootroot00000000000000[bdist_wheel] universal = 1 [flake8] max-line-length = 140 exclude = tests/*,*/migrations/*,*/south_migrations/* [pytest] # using the deprecated name instead of [tool:pytest] cause we test with older pytest versions norecursedirs = .git .tox .env venv dist build south_migrations migrations example python_files = test_*.py *_test.py tests.py addopts = -rxEfsw --strict --ignore=docs/conf.py --ignore=setup.py --ignore=src --ignore=ci --ignore=.eggs --doctest-modules --doctest-glob=\*.rst --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.5.1/setup.py000066400000000000000000000104201310513266600153460ustar00rootroot00000000000000#!/usr/bin/env python # -*- encoding: utf-8 -*- from __future__ import absolute_import, print_function import io from itertools import chain import re from glob import glob from os.path import basename from os.path import dirname from os.path import join from os.path import splitext from distutils.command.build import build from setuptools import Command from setuptools import find_packages from setuptools import setup from setuptools.command.develop import develop from setuptools.command.install_lib import install_lib from setuptools.command.easy_install import easy_install 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.5.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.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', '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>=2.6.0', 'coverage>=3.7.1' ], extras_require={ }, 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.5.1/src/000077500000000000000000000000001310513266600144265ustar00rootroot00000000000000pytest-cov-2.5.1/src/pytest-cov.embed000066400000000000000000000006511310513266600175430ustar00rootroot00000000000000if '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.5.1/src/pytest-cov.pth000066400000000000000000000005701310513266600172620ustar00rootroot00000000000000import 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.5.1/src/pytest_cov/000077500000000000000000000000001310513266600166255ustar00rootroot00000000000000pytest-cov-2.5.1/src/pytest_cov/__init__.py000066400000000000000000000000261310513266600207340ustar00rootroot00000000000000__version__ = "2.5.1" pytest-cov-2.5.1/src/pytest_cov/compat.py000066400000000000000000000013141310513266600204610ustar00rootroot00000000000000try: 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) pytest-cov-2.5.1/src/pytest_cov/embed.py000066400000000000000000000047421310513266600202620ustar00rootroot00000000000000"""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 os import signal active_cov = None def multiprocessing_start(_): cov = init() if cov: multiprocessing.util.Finalize(None, cleanup, args=(cov,), 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: # Import what we need to activate coverage. import coverage # Determine all source roots. if not cov_source: cov_source = None else: cov_source = cov_source.split(os.pathsep) if not cov_config: 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() def cleanup(cov=None): global active_cov _cleanup(cov) if active_cov is not cov: _cleanup(active_cov) active_cov = None multiprocessing_finish = cleanup # in case someone dared to use this internal def _sigterm_handler(*_): cleanup() def cleanup_on_sigterm(): signal.signal(signal.SIGTERM, _sigterm_handler) pytest-cov-2.5.1/src/pytest_cov/engine.py000066400000000000000000000271441310513266600204540ustar00rootroot00000000000000"""Coverage controllers for use by pytest-cov and nose-cov.""" import os import random import socket import sys import coverage from coverage.data import CoverageData from .compat import StringIO 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.node_descs = set() self.failed_slaves = [] self.topdir = os.getcwd() 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'] = '' 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.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 = 0 if not self.cov_report: with open(os.devnull, 'w') as null: total = self.cov.report(show_missing=True, ignore_errors=True, file=null) return total # 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) # 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() if hasattr(coverage, 'version_info') and coverage.version_info[0] >= 4: options.update({'skip_covered': skip_covered or None}) total = self.cov.report(**options) # Produce annotated source code report if wanted. if 'annotate' in self.cov_report: annotate_dir = self.cov_report['annotate'] 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. total = self.cov.report(ignore_errors=True, file=StringIO()) 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: total = self.cov.html_report(ignore_errors=True, directory=self.cov_report['html']) stream.write('Coverage HTML written to dir %s\n' % self.cov.config.html_dir) # Produce xml report if wanted. if 'xml' in self.cov_report: total = self.cov.xml_report(ignore_errors=True, outfile=self.cov_report['xml']) stream.write('Coverage XML written to file %s\n' % self.cov.config.xml_output) # Report on any failed slaves. if self.failed_slaves: self.sep(stream, '-', 'coverage: failed slaves') stream.write('The following slaves failed to return coverage data, ' 'ensure that pytest-cov is installed on these slaves.\n') for node in self.failed_slaves: stream.write('%s\n' % node.gateway.id) return total class Central(CovController): """Implementation for centralised operation.""" def start(self): """Erase any previous coverage data and start coverage.""" self.cov = coverage.coverage(source=self.cov_source, branch=self.cov_branch, config_file=self.cov_config) 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.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): """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) 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): """Slaves need to know if they are collocated and what files have moved.""" node.slaveinput['cov_master_host'] = socket.gethostname() node.slaveinput['cov_master_topdir'] = self.topdir node.slaveinput['cov_master_rsync_roots'] = [str(root) for root in node.nodemanager.roots] def testnodedown(self, node, error): """Collect data file name from slave.""" # If slave doesn't return any data then it is likely that this # plugin didn't get activated on the slave side. if not (hasattr(node, 'slaveoutput') and 'cov_slave_node_id' in node.slaveoutput): self.failed_slaves.append(node) return # If slave is not collocated then we must save the data file # that it returns to us. if 'cov_slave_data' in node.slaveoutput: data_suffix = '%s.%s.%06d.%s' % ( socket.gethostname(), os.getpid(), random.randint(0, 999999), node.slaveoutput['cov_slave_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 hasattr(self.cov.data, 'read_fileobj'): # for coverage 4.0 data = CoverageData() data.read_fileobj(StringIO(node.slaveoutput['cov_slave_data'])) cov.data.update(data) else: cov.data.lines, cov.data.arcs = node.slaveoutput['cov_slave_data'] cov.stop() cov.save() path = node.slaveoutput['cov_slave_path'] self.cov.config.paths['source'].append(path) # Record the slave 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.combine() self.cov.save() class DistSlave(CovController): """Implementation for distributed slaves.""" def start(self): """Determine what data file and suffix to contribute to and start coverage.""" # Determine whether we are collocated with master. self.is_collocated = (socket.gethostname() == self.config.slaveinput['cov_master_host'] and self.topdir == self.config.slaveinput['cov_master_topdir']) # If we are not collocated then rewrite master paths to slave paths. if not self.is_collocated: master_topdir = self.config.slaveinput['cov_master_topdir'] slave_topdir = self.topdir self.cov_source = [source.replace(master_topdir, slave_topdir) for source in self.cov_source] self.cov_config = self.cov_config.replace(master_topdir, slave_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) if self.cov_append: self.cov.load() else: self.cov.erase() 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. self.config.slaveoutput['cov_slave_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. self.config.slaveoutput['cov_slave_path'] = self.topdir self.config.slaveoutput['cov_slave_node_id'] = self.nodeid if hasattr(self.cov.data, 'write_fileobj'): # for coverage 4.0 buff = StringIO() self.cov.data.write_fileobj(buff) self.config.slaveoutput['cov_slave_data'] = buff.getvalue() else: self.config.slaveoutput['cov_slave_data'] = self.cov.data.lines, self.cov.data.arcs def summary(self, stream): """Only the master reports so do nothing.""" pass pytest-cov-2.5.1/src/pytest_cov/plugin.py000066400000000000000000000261571310513266600205100ustar00rootroot00000000000000"""Coverage plugin for pytest.""" import os import pytest import argparse from coverage.misc import CoverageException from . import embed from . import engine from . import compat 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 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='path', nargs='?', const=True, dest='cov_source', help='measure coverage for filesystem path ' '(multi-allowed)') 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=int, 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.') 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') def pytest_configure(config): """Activate coverage plugin if appropriate.""" if config.getvalue('cov_source'): if not config.pluginmanager.hasplugin('_cov'): plugin = CovPlugin(config.option, config.pluginmanager, start=False) 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 slave. """ 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 = 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) # slave 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_slave(self, session): return hasattr(session.config, 'slaveinput') 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.py self._disabled = True return self.pid = os.getpid() if self._is_slave(session): nodeid = session.config.slaveinput.get('slaveid', getattr(session, 'nodeid')) self.start(engine.DistSlave, session.config, nodeid) elif not self._started: self.start(engine.Central) def pytest_configure_node(self, node): """Delegate to our implementation. Mark this hook as optional in case xdist is not installed. """ 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. """ 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_slave(session) and self._should_report(): try: self.cov_total = self.cov_controller.summary(self.cov_report) except CoverageException as exc: raise pytest.UsageError( 'Failed to generate report: %s\n' % exc ) 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: msg = 'Coverage disabled via --no-cov switch!' terminalreporter.write('WARNING: %s\n' % msg, red=True, bold=True) terminalreporter.config.warn(code='COV-U1', message=msg) 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: if self.cov_total < self.options.cov_fail_under: markup = {'red': True, 'bold': True} msg = ( 'FAIL Required test coverage of %d%% not ' 'reached. Total coverage: %.2f%%\n' % (self.options.cov_fail_under, self.cov_total) ) else: markup = {'green': True} msg = ( 'Required test coverage of %d%% ' 'reached. Total coverage: %.2f%%\n' % (self.options.cov_fail_under, self.cov_total) ) terminalreporter.write(msg, **markup) def pytest_runtest_setup(self, item): if os.getpid() != self.pid: # test is run in another process than session, run # coverage manually self.cov = embed.init() def pytest_runtest_teardown(self, item): if self.cov is not None: embed.cleanup(self.cov) self.cov = None @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 pytest-cov-2.5.1/tests/000077500000000000000000000000001310513266600150015ustar00rootroot00000000000000pytest-cov-2.5.1/tests/conftest.py000066400000000000000000000001111310513266600171710ustar00rootroot00000000000000def pytest_configure(config): config.option.runpytest = 'subprocess' pytest-cov-2.5.1/tests/helper.py000066400000000000000000000000471310513266600166330ustar00rootroot00000000000000def do_stuff(): a = 1 return a pytest-cov-2.5.1/tests/test_pytest_cov.py000066400000000000000000001106551310513266600206210ustar00rootroot00000000000000import glob import os import subprocess import sys from distutils.version import StrictVersion from itertools import chain import coverage import py import pytest import virtualenv 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_ from fields import Namespace try: from StringIO import StringIO except ImportError: from io import StringIO import pytest_cov.plugin coverage, StrictVersion # required for skipif mark on test_cov_min_from_coveragerc SCRIPT = ''' import sys, helper def pytest_generate_tests(metafunc): for i in range(10): metafunc.addcall() def test_foo(): 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 subprocess import sys def pytest_generate_tests(metafunc): for i in range(2): metafunc.addcall(funcargs=dict(idx=i)) def test_foo(idx): out, err = subprocess.Popen( [sys.executable, '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 range(2): metafunc.addcall(funcargs=dict(idx=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 range(2): metafunc.addcall(funcargs=dict(idx=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 ''' SCRIPT_FAIL = ''' def test_fail(): assert False ''' CHILD_SCRIPT_RESULT = '[56] * 100%' PARENT_SCRIPT_RESULT = '8 * 100%' DEST_DIR = 'cov_dest' REPORT_NAME = 'cov.xml' xdist = pytest.mark.parametrize('opts', ['', '-n 1'], ids=['nodist', 'xdist']) @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], 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_output_dir(testdir,prop): 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_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) # backport of argparse to py26 doesn't display ArgumentTypeError message result.stderr.fnmatch_lines([ '*argument --cov-report: *', ] if tuple(sys.version_info[:2]) == (2, 6) else [ '*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) # backport of argparse to py26 doesn't display ArgumentTypeError message result.stderr.fnmatch_lines([ '*argument --cov-report: *', ] if tuple(sys.version_info[:2]) == (2, 6) else [ '*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=term-missing', '--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_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 @pytest.mark.skipif('StrictVersion(coverage.__version__) <= StrictVersion("3.8")') 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 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(SCRIPT_FAIL) 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!', '*1 warnings*' if pytest.__version__.startswith('2.7') else 'WCOV-U1*Coverage disabled via --no-cov switch!', ]) def test_cov_and_failure_report_on_fail(testdir): script = testdir.makepyfile(SCRIPT + SCRIPT_FAIL) result = testdir.runpytest('-v', '--cov=%s' % script.dirpath(), '--cov-fail-under=100', script) result.stdout.fnmatch_lines_random([ '*10 failed*', '*coverage: platform*', '*FAIL Required test coverage of 100% not reached*', '*assert False*', ]) 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 slaves failed to return coverage data' not in line assert 'INTERNALERROR' not in line assert result.ret == 0 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-slave-restart=0', script, *prop.args) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', 'test_dist_collocated* %s *' % prop.result, '*10 passed*' ]) assert result.ret == 0 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(prop.fullconf) 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-slave-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 omit = */__init__.py """) 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 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-slave-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_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') result = testdir.runpytest('-v', '--cov=%s' % scripts.dirpath(), '--cov-report=term-missing', '--dist=load', '--tx=popen//chdir=%s' % dir1, '--tx=popen//chdir=%s' % dir2, '--rsyncdir=%s' % child_script, '--rsyncdir=%s' % parent_script, '--max-slave-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) 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.*' ]) if StrictVersion(coverage.__version__) <= StrictVersion("3.8"): # older `coverage report` doesn't error on missing imports assert result.ret == 0 else: # newer `coverage report` errors on missing imports result.stderr.fnmatch_lines([ 'ERROR: 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__") def test_dist_missing_data(testdir): venv_path = os.path.join(str(testdir.tmpdir), 'venv') virtualenv.create_environment(venv_path) if sys.platform == 'win32': exe = os.path.join(venv_path, 'Scripts', 'python.exe') else: exe = os.path.join(venv_path, 'bin', 'python') subprocess.check_call([ exe, '-mpip' if sys.version_info >= (2, 7) else '-mpip.__main__', 'install', 'py==%s' % py.__version__, 'pytest==%s' % pytest.__version__ ]) script = testdir.makepyfile(SCRIPT) result = testdir.runpytest('-v', '--cov=%s' % script.dirpath(), '--cov-report=term-missing', '--dist=load', '--tx=popen//python=%s' % exe, '--max-slave-restart=0', script) result.stdout.fnmatch_lines([ '*- coverage: failed slaves -*' ]) assert result.ret == 0 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 def test_multiprocessing_subprocess(testdir): py.test.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_subprocess* 8 * 100%*', '*1 passed*' ]) assert result.ret == 0 @pytest.mark.skipif('sys.platform == "win32"', reason="multiprocessing don't support clean process temination on Windows") def test_multiprocessing_subprocess_with_terminate(testdir): py.test.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() 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_subprocess* 15 * 100%*', '*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]) def test_cover_looponfail(testdir, monkeypatch): testdir.makepyfile(mod=MODULE) testdir.makeconftest(CONFTEST) script = testdir.makepyfile(BASIC_TEST) monkeypatch.setattr(testdir, 'run', lambda *args: _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' ) 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-slave-restart=0', script) assert result.ret == 0 result.stdout.fnmatch_lines([CONF_RESULT]) 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]) 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-slave-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.skipif('StrictVersion(coverage.__version__) < StrictVersion("4.0")') @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]) @pytest.mark.skipif('StrictVersion(coverage.__version__) < StrictVersion("4.0")') 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', '--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"') 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) assert 'coverage' not in result.stdout.str() 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*'))) 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-slave-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 def xtest_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 def xtest_do_not_append_coverage(testdir, opts, prop): script = testdir.makepyfile(test_1=prop.code) testdir.tmpdir.join('.coveragerc').write("") 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', '--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', '--cov', '--cov', script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', 'test_double_cov2* %s*' % SCRIPT_SIMPLE_RESULT, '*1 passed*' ]) assert result.ret == 0 pytest-cov-2.5.1/tox.ini000066400000000000000000000033051310513266600151530ustar00rootroot00000000000000; a generative tox configuration, see: https://testrun.org/tox/latest/config.html#generative-envlist [tox] envlist = check, {py26,py27,py33,py34,py35,pypy}-{27,28,29,30}-{37,40,41,42,43}, {py27,py33,py34,py35,py36,pypy}-{27,28,29,30}-{43,44}, docs [testenv] basepython = pypy: pypy py26: {env:TOXPYTHON:python2.6} {py27,docs}: {env:TOXPYTHON:python2.7} py33: {env:TOXPYTHON:python3.3} py34: {env:TOXPYTHON:python3.4} py35: {env:TOXPYTHON:python3.5} py36: {env:TOXPYTHON:python3.6} {clean,check,report,extension-coveralls,coveralls,spell}: python3.4 setenv = PYTHONPATH={toxinidir}/tests PYTHONUNBUFFERED=yes passenv = * deps = 27: pytest==2.7.3 28: pytest==2.8.7 29: pytest==2.9.2 30: pytest==3.0.7 pytest-capturelog 37: coverage==3.7.1 40: coverage==4.0.3 41: coverage==4.1 42: coverage==4.2 43: coverage==4.3.4 44: coverage==4.4 virtualenv pytest-xdist==1.15.0 27: pytest-cache==1.0.0 process-tests==1.2.1 six fields pip_pre = true commands = {posargs:py.test -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 skip_install = true usedevelop = false commands = python setup.py check --strict --metadata --restructuredtext check-manifest {toxinidir} flake8 src tests setup.py